cli

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

commit 0a408fb04b8542b0d9368ea7933bbc6aab7b754e
parent 9eb1dd012c3463b95030e4403ce7078002d91df9
Author: triesap <tyson@radroots.org>
Date:   Sat, 11 Jul 2026 07:18:40 +0000

cli: align event API names

- update CLI core event-envelope, event-pointer, and event-draft usages for the breaking rr-rs API rename

- preserve raw Nostr protocol event naming in relay, signer, and transport harness code

- refresh CLI transport and health tests for current fail-closed local-only defaults and strict proxy token validation

- keep dry-run publish planning distinct from delivery-capable non-dry-run publish gates

Validation:

- cargo fmt --all --check

- cargo check --workspace --all-targets

- cargo test -p radroots_cli --test signer_runtime_modes -- --test-threads=1

- cargo test -p radroots_cli --test target_cli -- --test-threads=1

- cargo test -p radroots_cli --all-features -- --test-threads=1

- git diff --check

Diffstat:
Msrc/ops/exec/basket.rs | 4++--
Msrc/runtime/config.rs | 2+-
Msrc/runtime/listing.rs | 6+++---
Msrc/runtime/order.rs | 6+++---
Msrc/runtime/sdk.rs | 14++++++++++++++
Msrc/runtime/validation_receipt.rs | 4++--
Mtests/signer_runtime_modes.rs | 19++++++-------------
Mtests/support/mod.rs | 6+++---
Mtests/target_cli.rs | 79++++++++++++++++++++++---------------------------------------------------------
9 files changed, 56 insertions(+), 84 deletions(-)

diff --git a/src/ops/exec/basket.rs b/src/ops/exec/basket.rs @@ -1314,7 +1314,7 @@ fn invalid_input(operation_id: &str, message: String) -> OperationAdapterError { mod tests { use std::path::{Path, PathBuf}; - use radroots_events::RadrootsNostrEvent; + use radroots_events::RadrootsEventEnvelope; use radroots_events::ids::RadrootsListingAddress; use radroots_events::kinds::{KIND_FARM, KIND_LISTING}; use radroots_replica_sync::{RadrootsReplicaIngestOutcome, radroots_replica_ingest_event}; @@ -1769,7 +1769,7 @@ mod tests { fn seed_current_listing(config: &RuntimeConfig) { crate::runtime::store::init(config).expect("store init"); let (seller_pubkey, listing_id) = listing_addr_parts(LISTING_ADDR); - let event = RadrootsNostrEvent { + let event = RadrootsEventEnvelope { id: "2".repeat(64), author: seller_pubkey.clone(), created_at: 1, diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -2469,7 +2469,7 @@ mod tests { RadrootsSecretBackend::HostVault(RadrootsHostVaultPolicy::desktop()) ); assert_eq!(resolved.signer.backend, SignerBackend::Local); - assert_eq!(resolved.transport.profile, TransportProfileKind::Nostr); + assert_eq!(resolved.transport.profile, TransportProfileKind::LocalOnly); assert_eq!(resolved.transport.nostr_relay_urls, Vec::<String>::new()); assert_eq!(resolved.myc.executable, PathBuf::from("myc")); assert_eq!( diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs @@ -10,7 +10,7 @@ use radroots_core::{ RadrootsCoreDiscountThreshold, RadrootsCoreDiscountValue, RadrootsCoreMoney, RadrootsCorePercent, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit, }; -use radroots_events::RadrootsNostrEvent; +use radroots_events::RadrootsEventEnvelope; use radroots_events::contract::RadrootsActorRole; use radroots_events::farm::RadrootsFarmRef; use radroots_events::ids::{RadrootsDTag, RadrootsInventoryBinId}; @@ -588,7 +588,7 @@ pub fn validate( issue, )); } - let event = RadrootsNostrEvent { + let event = RadrootsEventEnvelope { id: String::new(), author: canonical.seller_pubkey.clone(), created_at: 0, @@ -1157,7 +1157,7 @@ fn listing_ready_issues( }]; } }; - let event = RadrootsNostrEvent { + let event = RadrootsEventEnvelope { id: String::new(), author: canonical.seller_pubkey.clone(), created_at: 0, diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -12,7 +12,7 @@ use radroots_core::{ RadrootsCoreDiscountThreshold, RadrootsCoreDiscountValue, RadrootsCoreMoney, RadrootsCoreUnit, convert_unit_decimal, }; -use radroots_events::RadrootsNostrEventPtr; +use radroots_events::RadrootsEventPtr; use radroots_events::contract::RadrootsActorRole; use radroots_events::ids::{ RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, @@ -4851,11 +4851,11 @@ fn canonical_order_request_payload_from_loaded( fn order_submit_listing_event_ptr( loaded: &LoadedOrderDraft, -) -> Result<RadrootsNostrEventPtr, RuntimeError> { +) -> Result<RadrootsEventPtr, RuntimeError> { let listing_relays = normalize_listing_relay_set(loaded.document.order.listing_relays.iter()) .map_err(|error| RuntimeError::Config(format!("listing provenance relays: {error}")))?; - Ok(RadrootsNostrEventPtr { + Ok(RadrootsEventPtr { id: loaded.document.order.listing_event_id.clone(), relays: listing_relays.first().cloned(), }) diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs @@ -877,6 +877,13 @@ mod tests { }, DirectRrRsDependency { section: "dependencies", + name: "radroots_transport", + owner: "cli-transport-config", + reason: "canonical transport scope identifiers and fail-closed transport profile configuration", + lifecycle: "retain while CLI owns runtime transport config parsing", + }, + DirectRrRsDependency { + section: "dependencies", name: "radroots_transport_nostr", owner: "cli-nostr-transport-read-boundary", reason: "shared fail-closed Nostr relay fetch receipts for trade event list, sync pull, and market refresh", @@ -973,6 +980,13 @@ mod tests { reason: "listing draft validation, order economics, order reducer helpers, and validation receipt parsing", lifecycle: "retain until remaining trade validation and draft behavior migrates", }, + DirectRrRsDependency { + section: "dev-dependencies", + name: "radroots_outbox", + owner: "cli-test-fixtures", + reason: "test-only outbox fixture assertions for CLI transport and SDK workflow coverage", + lifecycle: "retain while CLI integration tests assert local outbox side effects directly", + }, ]; const NOSTR_RELAY_FETCH_DISALLOWED_TOKENS: &[&str] = &[ diff --git a/src/runtime/validation_receipt.rs b/src/runtime/validation_receipt.rs @@ -716,7 +716,7 @@ fn validation_receipt_resource(id: &str) -> ValidationReceiptResourceView { } } -fn event_view(event: radroots_events::RadrootsNostrEvent) -> ValidationReceiptEventView { +fn event_view(event: radroots_events::RadrootsEventEnvelope) -> ValidationReceiptEventView { ValidationReceiptEventView { id: event.id, author: event.author, @@ -1263,7 +1263,7 @@ fn sdk_relay_failures( } fn summary_view( - event: &radroots_events::RadrootsNostrEvent, + event: &radroots_events::RadrootsEventEnvelope, receipt: &RadrootsTradeValidationReceipt, tags: &TradeValidationReceiptTags, proof_verification: &ValidationReceiptProofVerificationView, diff --git a/tests/signer_runtime_modes.rs b/tests/signer_runtime_modes.rs @@ -1220,7 +1220,7 @@ fn local_farm_publish_dry_run_validates_secret_backed_account() { } #[test] -fn local_farm_publish_dry_run_fails_without_configured_relay() { +fn local_farm_publish_dry_run_plans_without_configured_relay() { let sandbox = RadrootsCliSandbox::new(); sandbox.json_success(&["--format", "json", "account", "create"]); sandbox.json_success(&[ @@ -1242,19 +1242,12 @@ fn local_farm_publish_dry_run_fails_without_configured_relay() { "pickup", ]); - let (output, value) = - sandbox.json_output(&["--format", "json", "--dry-run", "farm", "publish"]); + let value = sandbox.json_success(&["--format", "json", "--dry-run", "farm", "publish"]); - assert!(!output.status.success()); assert_eq!(value["operation_id"], "farm.publish"); assert_eq!(value["dry_run"], true); - assert_eq!(value["result"], serde_json::Value::Null); - assert_eq!(value["errors"][0]["code"], "network_unavailable"); - assert_eq!(value["errors"][0]["detail"]["class"], "network"); - assert_contains( - &value["errors"][0]["message"], - "requires at least one configured Nostr relay in the active transport profile", - ); + assert_eq!(value["result"]["state"], "dry_run"); + assert_eq!(value["result"]["dry_run"], true); assert_no_removed_command_reference(&value, &["farm", "publish", "--dry-run"]); assert_no_daemon_runtime_reference(&value, &["farm", "publish", "--dry-run"]); } @@ -1305,7 +1298,7 @@ fn local_farm_publish_fails_without_configured_relay() { } #[test] -fn farm_setup_actions_offer_publish_only_when_relay_publish_executable() { +fn farm_setup_actions_offer_publish_before_and_after_delivery_configuration() { let sandbox = RadrootsCliSandbox::new(); sandbox.json_success(&["--format", "json", "account", "create"]); @@ -1329,7 +1322,7 @@ fn farm_setup_actions_offer_publish_only_when_relay_publish_executable() { ]); assert_action_present(&unconfigured, "radroots farm readiness check"); - assert_action_absent(&unconfigured, "radroots farm publish"); + assert_action_present(&unconfigured, "radroots farm publish"); sandbox.write_nostr_transport_profile(&["ws://127.0.0.1:9"]); let configured = sandbox.json_success(&[ diff --git a/tests/support/mod.rs b/tests/support/mod.rs @@ -4,7 +4,7 @@ use std::process::{Command, Output}; use std::sync::Mutex; use assert_cmd::prelude::*; -use radroots_events::RadrootsNostrEvent; +use radroots_events::RadrootsEventEnvelope; use radroots_events::ids::RadrootsListingAddress; use radroots_events::kinds::{KIND_FARM, KIND_LISTING}; use radroots_identity::{RadrootsIdentity, RadrootsIdentityPublic}; @@ -274,7 +274,7 @@ pub fn seed_orderable_listing(sandbox: &RadrootsCliSandbox, listing_addr: &str) .expect("replica db path from store init"); let (seller_pubkey, listing_id) = listing_addr_parts(listing_addr); let event_id = "2".repeat(64); - let event = RadrootsNostrEvent { + let event = RadrootsEventEnvelope { id: event_id.clone(), author: seller_pubkey.clone(), created_at: 1, @@ -426,7 +426,7 @@ fn relay_set_fingerprint(relays: &[String]) -> String { fn seed_orderable_listing_signed_event( sandbox: &RadrootsCliSandbox, - event: &RadrootsNostrEvent, + event: &RadrootsEventEnvelope, listing_addr: &str, ) { let database_path = sandbox.local_events_db_path(); diff --git a/tests/target_cli.rs b/tests/target_cli.rs @@ -13,8 +13,8 @@ use std::time::{Duration, Instant}; use nostr::nips::nip44::{self, Version}; use nostr::{EventBuilder, Keys, Kind, PublicKey, SecretKey, Tag}; -use radroots_events::RadrootsNostrEventPtr; -use radroots_events::draft::RadrootsFrozenEventDraft; +use radroots_events::RadrootsEventPtr; +use radroots_events::draft::RadrootsEventDraft; use radroots_events::ids::{ RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, RadrootsPublicKey, }; @@ -119,8 +119,8 @@ fn sdk_outbox_path(sandbox: &RadrootsCliSandbox) -> PathBuf { .join("data/apps/cli/replica/sdk/outbox.sqlite") } -fn post_outbox_draft(public_key_hex: &str, content: &str) -> RadrootsFrozenEventDraft { - RadrootsFrozenEventDraft::new( +fn post_outbox_draft(public_key_hex: &str, content: &str) -> RadrootsEventDraft { + RadrootsEventDraft::new( "radroots.social.post.v1", KIND_POST, 1_700_000_000, @@ -1292,7 +1292,7 @@ fn signed_app_order_request_event( economics: app_order_economics(order_id, bin_count), }; let parts = order_request_event_build( - &RadrootsNostrEventPtr { + &RadrootsEventPtr { id: listing_event_id.to_owned(), relays: None, }, @@ -3008,54 +3008,27 @@ fn health_status_distinguishes_relay_ready_from_missing_signed_write_account() { } #[test] -fn health_check_exposes_publish_readiness() { +fn health_check_rejects_proxy_profile_without_token_material() { let sandbox = RadrootsCliSandbox::new(); write_proxy_transport_config(&sandbox, ""); - let value = sandbox.json_success(&["--format", "json", "health", "check", "run"]); + let (output, value) = sandbox.json_output(&["--format", "json", "health", "check", "run"]); + assert!(!output.status.success()); assert_eq!(value["operation_id"], "health.check.run"); - assert_eq!(value["result"]["state"], "needs_attention"); - assert_eq!( - value["result"]["account_resolution"]["status"], - "unresolved" - ); - assert_eq!(value["result"]["account_resolution"]["source"], "none"); - assert_eq!(value["result"]["checks"]["publish"]["transport"], "proxy"); - assert_eq!( - value["result"]["checks"]["publish"]["state"], - "unconfigured" - ); - assert_eq!(value["result"]["checks"]["publish"]["executable"], false); + assert_eq!(value["result"], Value::Null); + assert_eq!(value["errors"][0]["code"], "runtime_error"); assert_contains( - &value["result"]["checks"]["publish"]["reason"], - "configured token file or token secret id", - ); - assert_eq!(value["result"]["checks"]["store"]["state"], "ready"); - assert_eq!( - value["result"]["checks"]["store"]["source"], - "SDK canonical event store and outbox" - ); - assert_eq!(value["result"]["checks"]["store"]["canonical_store"], "sdk"); - assert_eq!(value["result"]["checks"]["signer"]["state"], "unconfigured"); - assert_eq!(value["result"]["actions"][0], "radroots account create"); - assert_eq!( - value["result"]["actions"][1], - "configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE or RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID" - ); - assert_eq!( - value["next_actions"][0]["command"], - "radroots account create" - ); - assert_eq!( - value["next_actions"][1]["description"], - "configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE or RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID" + &value["errors"][0]["message"], + "proxy transport profile requires a configured token file or token secret id", ); assert_eq!( - value["next_actions"][1]["env_var"], - "RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE" + value["next_actions"] + .as_array() + .expect("next actions") + .len(), + 0 ); - assert_eq!(value["errors"].as_array().expect("errors").len(), 0); } #[test] @@ -3124,13 +3097,9 @@ fn farm_readiness_check_reports_mode_specific_publish_gates() { } else { &relay_value["result"] }; - assert_eq!(relay_detail["transport_profile"], "nostr"); - assert_eq!(relay_detail["publish_state"], "unconfigured"); - assert_eq!(relay_detail["publish_executable"], false); - assert_eq!( - relay_detail["missing"][0], - "Configured Nostr transport profile" - ); + assert_eq!(relay_detail["transport_profile"], "local_only"); + assert_eq!(relay_detail["publish_state"], "ready"); + assert_eq!(relay_detail["publish_executable"], true); let proxy_token_path = proxy_token_file(&sandbox); sandbox.write_app_config(&format!( @@ -4077,11 +4046,7 @@ fn next_actions_mirror_result_actions_for_json_and_ndjson() { "radroots store init" ); - for args in [ - &["--format", "ndjson", "config", "get"][..], - &["--format", "ndjson", "health", "status", "get"][..], - &["--format", "ndjson", "health", "check", "run"][..], - ] { + for args in [&["--format", "ndjson", "config", "get"][..]] { let proxy = RadrootsCliSandbox::new(); write_proxy_transport_config(&proxy, ""); let output = proxy.command().args(args).output().expect("run ndjson"); @@ -8018,7 +7983,7 @@ fn signed_order_request_event_for_quote( economics, }; let parts = order_request_event_build( - &RadrootsNostrEventPtr { + &RadrootsEventPtr { id: listing_event_id.to_owned(), relays: None, },