cli

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

commit f8f2c6c96506ce573a7003810495c3201089e23b
parent 3d10d708a66ca2eec9b7cb881289d17dae5e913c
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Apr 2026 06:08:29 +0000

cli: repair public global flags

- remove deferred target parser globals
- carry account-id into the output actor
- drop signer-session-id from public write context
- extend target cli coverage for rejected flags

Diffstat:
Msrc/operation_adapter.rs | 28+++++++++++-----------------
Msrc/operation_farm.rs | 6+-----
Msrc/operation_listing.rs | 8++------
Msrc/operation_order.rs | 6+-----
Msrc/runtime/daemon.rs | 4++--
Msrc/target_cli.rs | 24+++++++++---------------
Mtests/target_cli.rs | 25+++++++++++++++++++++++++
7 files changed, 51 insertions(+), 50 deletions(-)

diff --git a/src/operation_adapter.rs b/src/operation_adapter.rs @@ -7,8 +7,8 @@ use serde_json::{Map, Value}; use crate::operation_registry::{OPERATION_REGISTRY, OperationSpec, get_operation}; use crate::output_contract::{ - CliExitCode, EnvelopeContext, NextAction, OUTPUT_SCHEMA_VERSION, OutputEnvelope, OutputError, - OutputWarning, + CliExitCode, EnvelopeActor, EnvelopeContext, NextAction, OUTPUT_SCHEMA_VERSION, OutputEnvelope, + OutputError, OutputWarning, }; use crate::target_cli::{TargetCliArgs, TargetOutputFormat}; @@ -64,9 +64,6 @@ impl Default for OperationInputMode { pub struct OperationContext { pub output_format: OperationOutputFormat, pub account_id: Option<String>, - pub farm_id: Option<String>, - pub profile: Option<String>, - pub signer_session_id: Option<String>, pub relays: Vec<String>, pub network_mode: OperationNetworkMode, pub dry_run: bool, @@ -85,9 +82,6 @@ impl OperationContext { Self { output_format: OperationOutputFormat::from(args.format), account_id: args.account_id.clone(), - farm_id: args.farm_id.clone(), - profile: args.profile.clone(), - signer_session_id: args.signer_session_id.clone(), relays: args.relay.clone(), network_mode: if args.offline { OperationNetworkMode::Offline @@ -116,6 +110,10 @@ impl OperationContext { let mut context = EnvelopeContext::new(request_id, self.dry_run); context.correlation_id = self.correlation_id.clone(); context.idempotency_key = self.idempotency_key.clone(); + context.actor = self.account_id.as_ref().map(|account_id| EnvelopeActor { + account_id: account_id.clone(), + role: "account".to_owned(), + }); context } } @@ -771,12 +769,6 @@ mod tests { "json", "--account-id", "acct_test", - "--farm-id", - "farm_test", - "--profile", - "repo_local", - "--signer-session-id", - "sess_test", "--relay", "wss://relay.one", "--online", @@ -803,9 +795,6 @@ mod tests { assert_eq!(context.output_format, OperationOutputFormat::Json); assert_eq!(context.account_id.as_deref(), Some("acct_test")); - assert_eq!(context.farm_id.as_deref(), Some("farm_test")); - assert_eq!(context.profile.as_deref(), Some("repo_local")); - assert_eq!(context.signer_session_id.as_deref(), Some("sess_test")); assert_eq!(context.relays, vec!["wss://relay.one".to_owned()]); assert_eq!(context.network_mode, OperationNetworkMode::Online); assert!(context.dry_run); @@ -817,6 +806,11 @@ mod tests { assert!(context.verbose); assert!(context.trace); assert!(!context.color); + + let envelope_context = context.envelope_context("req_test"); + let actor = envelope_context.actor.expect("account actor"); + assert_eq!(actor.account_id, "acct_test"); + assert_eq!(actor.role, "account"); } #[test] diff --git a/src/operation_farm.rs b/src/operation_farm.rs @@ -140,11 +140,7 @@ impl OperationService<FarmPublishRequest> for FarmOperationService<'_> { .idempotency_key .clone() .or_else(|| string_input(&request, "idempotency_key")), - signer_session_id: request - .context - .signer_session_id - .clone() - .or_else(|| string_input(&request, "signer_session_id")), + signer_session_id: string_input(&request, "signer_session_id"), print_job: bool_input(&request, "print_job").unwrap_or(false), print_event: bool_input(&request, "print_event").unwrap_or(false), }; diff --git a/src/operation_listing.rs b/src/operation_listing.rs @@ -176,11 +176,7 @@ where .idempotency_key .clone() .or_else(|| string_input(request, "idempotency_key")), - signer_session_id: request - .context - .signer_session_id - .clone() - .or_else(|| string_input(request, "signer_session_id")), + signer_session_id: string_input(request, "signer_session_id"), print_job: bool_input(request, "print_job").unwrap_or(false), print_event: bool_input(request, "print_event").unwrap_or(false), }) @@ -198,7 +194,7 @@ where "action": action, "file": optional_path(request, "file").map(|path| path.display().to_string()), "idempotency_key": request.context.idempotency_key, - "signer_session_id": request.context.signer_session_id, + "signer_session_id": string_input(request, "signer_session_id"), })) } diff --git a/src/operation_order.rs b/src/operation_order.rs @@ -46,11 +46,7 @@ impl OperationService<OrderSubmitRequest> for OrderOperationService<'_> { .idempotency_key .clone() .or_else(|| string_input(&request, "idempotency_key")), - signer_session_id: request - .context - .signer_session_id - .clone() - .or_else(|| string_input(&request, "signer_session_id")), + signer_session_id: string_input(&request, "signer_session_id"), }; let mut config = self.config.clone(); if request.context.dry_run { diff --git a/src/runtime/daemon.rs b/src/runtime/daemon.rs @@ -1078,10 +1078,10 @@ pub fn resolve_signer_session_id( match matches.len() { 1 => Ok(matches.pop().expect("exactly one signer session")), 0 => Err(DaemonRpcError::Unconfigured(format!( - "no authorized signer session matched {actor_role} pubkey `{actor_pubkey}` for sign_event:{event_kind}; connect a signer session or pass --signer-session-id" + "no authorized signer session matched {actor_role} pubkey `{actor_pubkey}` for sign_event:{event_kind}; configure exactly one signer session" ))), _ => Err(DaemonRpcError::Unconfigured(format!( - "multiple authorized signer sessions matched {actor_role} pubkey `{actor_pubkey}` for sign_event:{event_kind}; pass --signer-session-id" + "multiple authorized signer sessions matched {actor_role} pubkey `{actor_pubkey}` for sign_event:{event_kind}; configure exactly one signer session" ))), } } diff --git a/src/target_cli.rs b/src/target_cli.rs @@ -18,12 +18,6 @@ pub struct TargetCliArgs { pub format: TargetOutputFormat, #[arg(long = "account-id", global = true)] pub account_id: Option<String>, - #[arg(long = "farm-id", global = true)] - pub farm_id: Option<String>, - #[arg(long = "profile", global = true)] - pub profile: Option<String>, - #[arg(long = "signer-session-id", global = true)] - pub signer_session_id: Option<String>, #[arg(long = "relay", global = true)] pub relay: Vec<String>, #[arg(long = "offline", global = true, action = ArgAction::SetTrue, conflicts_with = "online")] @@ -791,12 +785,6 @@ mod tests { "ndjson", "--account-id", "acct_test", - "--farm-id", - "farm_test", - "--profile", - "repo_local", - "--signer-session-id", - "sess_test", "--relay", "wss://relay.one", "--relay", @@ -819,9 +807,6 @@ mod tests { assert_eq!(parsed.format, TargetOutputFormat::Ndjson); assert_eq!(parsed.account_id.as_deref(), Some("acct_test")); - assert_eq!(parsed.farm_id.as_deref(), Some("farm_test")); - assert_eq!(parsed.profile.as_deref(), Some("repo_local")); - assert_eq!(parsed.signer_session_id.as_deref(), Some("sess_test")); assert_eq!( parsed.relay, vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()] @@ -846,6 +831,15 @@ mod tests { vec!["radroots", "--yes", "config", "get"], vec!["radroots", "--non-interactive", "config", "get"], vec!["radroots", "--signer", "myc", "config", "get"], + vec!["radroots", "--farm-id", "farm_test", "config", "get"], + vec!["radroots", "--profile", "repo_local", "config", "get"], + vec![ + "radroots", + "--signer-session-id", + "sess_test", + "config", + "get", + ], ]; for args in rejected { diff --git a/tests/target_cli.rs b/tests/target_cli.rs @@ -67,6 +67,9 @@ fn removed_global_flags_are_rejected_publicly() { ["--yes", "workspace", "get"].as_slice(), ["--non-interactive", "workspace", "get"].as_slice(), ["--signer", "myc", "workspace", "get"].as_slice(), + ["--farm-id", "farm_test", "workspace", "get"].as_slice(), + ["--profile", "repo_local", "workspace", "get"].as_slice(), + ["--signer-session-id", "session_test", "workspace", "get"].as_slice(), ] { let output = radroots().args(args).output().expect("run removed flag"); @@ -94,6 +97,28 @@ fn removed_command_families_are_rejected_publicly() { } #[test] +fn account_id_global_populates_envelope_actor() { + let output = radroots() + .args([ + "--format", + "json", + "--account-id", + "acct_test", + "workspace", + "get", + ]) + .output() + .expect("run workspace get"); + + assert!(output.status.success()); + let value: Value = serde_json::from_slice(&output.stdout).expect("json envelope"); + + assert_eq!(value["operation_id"], "workspace.get"); + assert_eq!(value["actor"]["account_id"], "acct_test"); + assert_eq!(value["actor"]["role"], "account"); +} + +#[test] fn target_command_outputs_standard_json_envelope() { let output = radroots() .args(["--format", "json", "workspace", "get"])