cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit 10823fc93b339d601858b416881e3d063c6d6874
parent d8e31b37518205781dc3d80b757e6d977868fd7c
Author: triesap <tyson@radroots.org>
Date:   Wed,  8 Apr 2026 18:32:32 +0000

cli: normalize write surface formatting

Diffstat:
Msrc/cli.rs | 40+++++++++++++++++++++++++---------------
Msrc/render/mod.rs | 42+++++++++++++++++++-----------------------
Msrc/runtime/config.rs | 22+++++++++++-----------
Msrc/runtime/listing.rs | 8++++----
Msrc/runtime/order.rs | 6+++---
Msrc/runtime/signer.rs | 2+-
Mtests/listing.rs | 2+-
Mtests/order.rs | 26++++++++++++++++----------
Mtests/runtime_show.rs | 10++++++++--
9 files changed, 88 insertions(+), 70 deletions(-)

diff --git a/src/cli.rs b/src/cli.rs @@ -913,15 +913,21 @@ mod tests { #[test] fn command_contract_helpers_report_supported_modes() { let config_show = CliArgs::parse_from(["radroots", "config", "show"]); - assert!(config_show - .command - .supports_output_format(OutputFormat::Human)); - assert!(config_show - .command - .supports_output_format(OutputFormat::Json)); - assert!(!config_show - .command - .supports_output_format(OutputFormat::Ndjson)); + assert!( + config_show + .command + .supports_output_format(OutputFormat::Human) + ); + assert!( + config_show + .command + .supports_output_format(OutputFormat::Json) + ); + assert!( + !config_show + .command + .supports_output_format(OutputFormat::Ndjson) + ); assert!(config_show.command.supports_dry_run()); let account_new = CliArgs::parse_from(["radroots", "account", "new"]); @@ -932,14 +938,18 @@ mod tests { assert!(find.command.supports_output_format(OutputFormat::Ndjson)); let sync_watch = CliArgs::parse_from(["radroots", "sync", "watch", "--frames", "1"]); - assert!(sync_watch - .command - .supports_output_format(OutputFormat::Ndjson)); + assert!( + sync_watch + .command + .supports_output_format(OutputFormat::Ndjson) + ); let order_watch = CliArgs::parse_from(["radroots", "order", "watch", "ord_demo"]); - assert!(order_watch - .command - .supports_output_format(OutputFormat::Ndjson)); + assert!( + order_watch + .command + .supports_output_format(OutputFormat::Ndjson) + ); let order_submit = CliArgs::parse_from(["radroots", "order", "submit", "ord_demo"]); assert_eq!(order_submit.command.display_name(), "order submit"); diff --git a/src/render/mod.rs b/src/render/mod.rs @@ -8,8 +8,8 @@ use crate::domain::runtime::{ OrderHistoryView, OrderJobView, OrderListView, OrderNewView, OrderSubmitView, OrderWatchView, RelayListView, RpcSessionsView, RpcStatusView, SyncActionView, SyncStatusView, SyncWatchView, }; -use crate::runtime::config::{OutputConfig, OutputFormat}; use crate::runtime::RuntimeError; +use crate::runtime::config::{OutputConfig, OutputFormat}; const THIN_RULE: &str = "────────────────────────────────────────────────────"; @@ -435,19 +435,11 @@ fn render_ndjson_to(stdout: &mut dyn Write, output: &CommandOutput) -> Result<() } fn yes_no(value: bool) -> &'static str { - if value { - "yes" - } else { - "no" - } + if value { "yes" } else { "no" } } fn present_absent(value: bool) -> &'static str { - if value { - "present" - } else { - "absent" - } + if value { "present" } else { "absent" } } fn render_account_list(stdout: &mut dyn Write, view: &AccountListView) -> Result<(), RuntimeError> { @@ -2108,7 +2100,7 @@ fn human_command_name(view: &CommandView) -> &'static str { #[cfg(test)] mod tests { - use super::{render_human_to, render_ndjson_to, render_table, Table}; + use super::{Table, render_human_to, render_ndjson_to, render_table}; use crate::commands::runtime; use crate::domain::runtime::{ AccountListView, CommandOutput, CommandView, DoctorCheckView, DoctorView, MycStatusView, @@ -2213,20 +2205,22 @@ mod tests { "/workspace/.radroots/config.toml" ); assert_eq!(view.account.selector.as_deref(), Some("acct_demo")); - assert!(view - .account - .store_path - .ends_with(".radroots/data/shared/accounts/store.json")); + assert!( + view.account + .store_path + .ends_with(".radroots/data/shared/accounts/store.json") + ); assert_eq!(view.relay.count, 2); assert_eq!(view.relay.publish_policy, "any"); assert_eq!( view.account.secret_backend.contract_default_backend, "host_vault" ); - assert!(view - .local - .replica_db_path - .ends_with(".radroots/data/apps/cli/replica/replica.sqlite")); + assert!( + view.local + .replica_db_path + .ends_with(".radroots/data/apps/cli/replica/replica.sqlite") + ); } #[test] @@ -2339,9 +2333,11 @@ mod tests { )); let mut buffer = Vec::new(); let error = render_ndjson_to(&mut buffer, &output).expect_err("unsupported ndjson"); - assert!(error - .to_string() - .contains("`config show` does not support --ndjson")); + assert!( + error + .to_string() + .contains("`config show` does not support --ndjson") + ); } #[test] diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -31,12 +31,8 @@ const CLI_HOST_VAULT_POLICY: &str = "desktop"; const CLI_DEFAULT_SECRET_BACKEND: &str = "host_vault"; const CLI_DEFAULT_SECRET_FALLBACK: &str = "encrypted_file"; const CLI_ALLOWED_PROFILES: &[&str] = &[CLI_PROFILE]; -const CLI_ALLOWED_SHARED_SECRET_BACKENDS: &[&str] = &[ - "host_vault", - "encrypted_file", - "memory", - "plaintext_file", -]; +const CLI_ALLOWED_SHARED_SECRET_BACKENDS: &[&str] = + &["host_vault", "encrypted_file", "memory", "plaintext_file"]; const CLI_USES_PROTECTED_STORE: bool = true; const ENV_FILE_PATH: &str = "RADROOTS_ENV_FILE"; const ENV_OUTPUT: &str = "RADROOTS_OUTPUT"; @@ -445,8 +441,10 @@ fn resolve_paths(env: &dyn Environment) -> Result<PathsConfig, RuntimeError> { .map_err(|err| RuntimeError::Config(format!("resolve Radroots path roots: {err}")))?; let app_namespace = RadrootsRuntimeNamespace::app(CLI_APP_NAMESPACE_VALUE) .map_err(|err| RuntimeError::Config(format!("resolve cli namespace: {err}")))?; - let shared_accounts_namespace = RadrootsRuntimeNamespace::shared(SHARED_ACCOUNTS_NAMESPACE_VALUE) - .map_err(|err| RuntimeError::Config(format!("resolve shared accounts namespace: {err}")))?; + let shared_accounts_namespace = + RadrootsRuntimeNamespace::shared(SHARED_ACCOUNTS_NAMESPACE_VALUE).map_err(|err| { + RuntimeError::Config(format!("resolve shared accounts namespace: {err}")) + })?; let shared_identity_namespace = RadrootsRuntimeNamespace::shared(SHARED_IDENTITIES_NAMESPACE_VALUE).map_err(|err| { RuntimeError::Config(format!("resolve shared identities namespace: {err}")) @@ -920,8 +918,7 @@ mod tests { use super::{ AccountConfig, AccountSecretContractConfig, EnvFileValues, Environment, OutputConfig, OutputFormat, PathsConfig, RelayConfigSource, RelayPublishPolicy, RuntimeConfig, - SignerBackend, Verbosity, - parse_env_file_values, + SignerBackend, Verbosity, parse_env_file_values, }; use crate::cli::CliArgs; use clap::Parser; @@ -1334,7 +1331,10 @@ RADROOTS_CLI_LOGGING_STDOUT=false ); assert_eq!(resolved.paths.app_namespace, "apps/cli"); assert_eq!(resolved.paths.shared_accounts_namespace, "shared/accounts"); - assert_eq!(resolved.paths.shared_identities_namespace, "shared/identities"); + assert_eq!( + resolved.paths.shared_identities_namespace, + "shared/identities" + ); assert_eq!( resolved.paths.workspace_config_path, PathBuf::from("/workspaces/radroots-cli/.radroots/config.toml") diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs @@ -7,6 +7,7 @@ use radroots_core::{ RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit, }; +use radroots_events::RadrootsNostrEvent; use radroots_events::kinds::{KIND_LISTING, KIND_LISTING_DRAFT}; use radroots_events::listing::{ RadrootsListing, RadrootsListingAvailability, RadrootsListingBin, @@ -14,10 +15,9 @@ use radroots_events::listing::{ RadrootsListingProduct, RadrootsListingStatus, }; use radroots_events::trade::RadrootsTradeListingValidationError; -use radroots_events::RadrootsNostrEvent; use radroots_events_codec::d_tag::is_d_tag_base64url; use radroots_events_codec::listing::encode::to_wire_parts_with_kind; -use radroots_sql_core::{utils, SqlExecutor, SqliteExecutor}; +use radroots_sql_core::{SqlExecutor, SqliteExecutor, utils}; use radroots_trade::listing::validation::validate_listing_event; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -28,12 +28,12 @@ use crate::domain::runtime::{ ListingMutationEventView, ListingMutationJobView, ListingMutationView, ListingNewView, ListingValidateView, ListingValidationIssueView, SyncFreshnessView, }; +use crate::runtime::RuntimeError; use crate::runtime::accounts; use crate::runtime::config::RuntimeConfig; use crate::runtime::daemon; use crate::runtime::daemon::DaemonRpcError; use crate::runtime::sync::freshness_from_executor; -use crate::runtime::RuntimeError; const DRAFT_KIND: &str = "listing_draft_v1"; const LISTING_SOURCE: &str = "local draft · local first"; @@ -1298,7 +1298,7 @@ fn encode_base64url_no_pad(bytes: [u8; 16]) -> String { #[cfg(test)] mod tests { - use super::{encode_base64url_no_pad, generate_d_tag, ListingDraftDocument, DRAFT_KIND}; + use super::{DRAFT_KIND, ListingDraftDocument, encode_base64url_no_pad, generate_d_tag}; use radroots_events_codec::d_tag::is_d_tag_base64url; #[test] diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -16,10 +16,10 @@ use crate::domain::runtime::{ OrderIssueView, OrderJobView, OrderListView, OrderNewView, OrderSubmitView, OrderSummaryView, OrderWatchFrameView, OrderWatchView, }; +use crate::runtime::RuntimeError; use crate::runtime::accounts; use crate::runtime::config::RuntimeConfig; use crate::runtime::daemon::{self, DaemonRpcError}; -use crate::runtime::RuntimeError; const ORDER_DRAFT_KIND: &str = "order_draft_v1"; const ORDER_SOURCE: &str = "local order drafts · local first"; @@ -1365,8 +1365,8 @@ impl From<OrderGetView> for OrderNewView { #[cfg(test)] mod tests { use super::{ - next_order_id, OrderDraft, OrderDraftDocument, OrderDraftItem, OrderDraftSubmission, - ORDER_DRAFT_KIND, + ORDER_DRAFT_KIND, OrderDraft, OrderDraftDocument, OrderDraftItem, OrderDraftSubmission, + next_order_id, }; #[test] diff --git a/src/runtime/signer.rs b/src/runtime/signer.rs @@ -1,6 +1,6 @@ use crate::domain::runtime::{IdentityPublicView, LocalSignerStatusView, SignerStatusView}; -use crate::runtime::config::{RuntimeConfig, SignerBackend}; use crate::runtime::accounts::SHARED_ACCOUNT_STORE_SOURCE; +use crate::runtime::config::{RuntimeConfig, SignerBackend}; use radroots_nostr_accounts::prelude::RadrootsNostrSelectedAccountStatus; use radroots_nostr_signer::prelude::{ RadrootsNostrLocalSignerAvailability, RadrootsNostrLocalSignerCapability, diff --git a/tests/listing.rs b/tests/listing.rs @@ -10,7 +10,7 @@ use std::time::Duration; use assert_cmd::prelude::*; use radroots_sql_core::{SqlExecutor, SqliteExecutor}; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use tempfile::tempdir; fn data_root(workdir: &Path) -> std::path::PathBuf { diff --git a/tests/order.rs b/tests/order.rs @@ -530,12 +530,16 @@ fn order_submit_persists_submission_metadata_and_reports_job() { assert!(contents.contains("command = \"order.submit\"")); let recorded_requests = requests.lock().expect("requests lock"); - assert!(recorded_requests - .iter() - .any(|request| request.method == "bridge.order.request")); - assert!(recorded_requests - .iter() - .any(|request| { request.auth_header.as_deref() == Some("Bearer test-token") })); + assert!( + recorded_requests + .iter() + .any(|request| request.method == "bridge.order.request") + ); + assert!( + recorded_requests + .iter() + .any(|request| { request.auth_header.as_deref() == Some("Bearer test-token") }) + ); } #[test] @@ -696,8 +700,10 @@ command = "order.submit" assert_eq!(output.status.code(), Some(3)); let json: Value = serde_json::from_slice(output.stdout.as_slice()).expect("cancel json"); assert_eq!(json["state"], "unconfigured"); - assert!(json["reason"] - .as_str() - .expect("cancel reason") - .contains("trade-chain")); + assert!( + json["reason"] + .as_str() + .expect("cancel reason") + .contains("trade-chain") + ); } diff --git a/tests/runtime_show.rs b/tests/runtime_show.rs @@ -106,8 +106,14 @@ fn config_show_json_reports_default_bootstrap_state() { assert_eq!(json["paths"]["profile"], "interactive_user"); assert_eq!(json["paths"]["allowed_profiles"][0], "interactive_user"); assert_eq!(json["paths"]["app_namespace"], "apps/cli"); - assert_eq!(json["paths"]["shared_accounts_namespace"], "shared/accounts"); - assert_eq!(json["paths"]["shared_identities_namespace"], "shared/identities"); + assert_eq!( + json["paths"]["shared_accounts_namespace"], + "shared/accounts" + ); + assert_eq!( + json["paths"]["shared_identities_namespace"], + "shared/identities" + ); assert_eq!( json["paths"]["app_config_path"], config_root(dir.path())