cli

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

commit 649e1b21038ce4a069996dc33b3981cd0afe3147
parent 488476f70d1b3d18bfacf609dd51d47b63f6519d
Author: triesap <tyson@radroots.org>
Date:   Thu,  7 May 2026 02:44:53 +0000

cli: add publish mode resolution

- add the target publish mode flag and invocation field
- resolve publish mode from flag env config and default
- accept only nostr_relay and radrootsd values
- cover publish mode precedence and invalid config sources

Diffstat:
Msrc/main.rs | 1+
Msrc/operation_basket.rs | 8++++++--
Msrc/operation_core.rs | 8++++++--
Msrc/operation_farm.rs | 8++++++--
Msrc/operation_listing.rs | 8++++++--
Msrc/operation_market.rs | 8++++++--
Msrc/operation_order.rs | 8++++++--
Msrc/operation_runtime.rs | 8++++++--
Msrc/runtime/config.rs | 273++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/runtime/order.rs | 8++++++--
Msrc/runtime/provider.rs | 9+++++++--
Msrc/runtime_args.rs | 1+
Msrc/target_cli.rs | 18++++++++++++++++++
13 files changed, 346 insertions(+), 20 deletions(-)

diff --git a/src/main.rs b/src/main.rs @@ -102,6 +102,7 @@ fn runtime_args_from_target(args: &TargetCliArgs) -> RuntimeInvocationArgs { account: args.account_id.clone(), identity_path: None, signer: None, + publish_mode: args.publish_mode.map(|mode| mode.as_str().to_owned()), relay: args.relay.clone(), myc_executable: None, myc_status_timeout_ms: None, diff --git a/src/operation_basket.rs b/src/operation_basket.rs @@ -1074,8 +1074,8 @@ mod tests { use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; const LISTING_ADDR: &str = "30402:1111111111111111111111111111111111111111111111111111111111111111:AAAAAAAAAAAAAAAAAAAAAg"; @@ -1385,6 +1385,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: Vec::new(), publish_policy: RelayPublishPolicy::Any, diff --git a/src/operation_core.rs b/src/operation_core.rs @@ -725,8 +725,8 @@ mod tests { use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; use crate::runtime::logging::LoggingState; @@ -926,6 +926,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: Vec::new(), publish_policy: RelayPublishPolicy::Any, diff --git a/src/operation_farm.rs b/src/operation_farm.rs @@ -336,8 +336,8 @@ mod tests { use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; #[test] @@ -474,6 +474,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: Vec::new(), publish_policy: RelayPublishPolicy::Any, diff --git a/src/operation_listing.rs b/src/operation_listing.rs @@ -360,8 +360,8 @@ mod tests { use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; #[test] @@ -499,6 +499,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: Vec::new(), publish_policy: RelayPublishPolicy::Any, diff --git a/src/operation_market.rs b/src/operation_market.rs @@ -269,8 +269,8 @@ mod tests { use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; const LISTING_ADDR: &str = "30402:1111111111111111111111111111111111111111111111111111111111111111:AAAAAAAAAAAAAAAAAAAAAg"; @@ -577,6 +577,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: Vec::new(), publish_policy: RelayPublishPolicy::Any, diff --git a/src/operation_order.rs b/src/operation_order.rs @@ -1300,8 +1300,8 @@ mod tests { use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; #[test] @@ -1799,6 +1799,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: Vec::new(), publish_policy: RelayPublishPolicy::Any, diff --git a/src/operation_runtime.rs b/src/operation_runtime.rs @@ -145,8 +145,8 @@ mod tests { use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; #[test] @@ -264,6 +264,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: relays, publish_policy: RelayPublishPolicy::Any, diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -45,6 +45,7 @@ const ENV_ACCOUNT_SECRET_BACKEND: &str = "RADROOTS_ACCOUNT_SECRET_BACKEND"; const ENV_ACCOUNT_SECRET_FALLBACK: &str = "RADROOTS_ACCOUNT_SECRET_FALLBACK"; const ENV_IDENTITY_PATH: &str = "RADROOTS_IDENTITY_PATH"; const ENV_SIGNER: &str = "RADROOTS_SIGNER"; +const ENV_PUBLISH_MODE: &str = "RADROOTS_PUBLISH_MODE"; const ENV_RELAYS: &str = "RADROOTS_RELAYS"; const ENV_MYC_EXECUTABLE: &str = "RADROOTS_MYC_EXECUTABLE"; const ENV_MYC_STATUS_TIMEOUT_MS: &str = "RADROOTS_MYC_STATUS_TIMEOUT_MS"; @@ -67,6 +68,7 @@ const SUPPORTED_ENV_FILE_KEYS: &[&str] = &[ ENV_ACCOUNT_SECRET_FALLBACK, ENV_IDENTITY_PATH, ENV_SIGNER, + ENV_PUBLISH_MODE, ENV_RELAYS, ENV_MYC_EXECUTABLE, ENV_MYC_STATUS_TIMEOUT_MS, @@ -181,6 +183,36 @@ pub struct SignerConfig { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PublishMode { + NostrRelay, + Radrootsd, +} + +impl PublishMode { + pub fn as_str(self) -> &'static str { + match self { + Self::NostrRelay => "nostr_relay", + Self::Radrootsd => "radrootsd", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PublishModeSource { + Flags, + Environment, + UserConfig, + WorkspaceConfig, + Defaults, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublishConfig { + pub mode: PublishMode, + pub source: PublishModeSource, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RelayPublishPolicy { Any, } @@ -324,6 +356,7 @@ pub struct RuntimeConfig { pub account_secret_contract: AccountSecretContractConfig, pub identity: IdentityConfig, pub signer: SignerConfig, + pub publish: PublishConfig, pub relay: RelayConfig, pub local: LocalConfig, pub myc: MycConfig, @@ -349,6 +382,7 @@ impl EnvFileValues { #[derive(Debug, Default, Deserialize)] struct CliConfigFile { relay: Option<RelayFileConfig>, + publish: Option<PublishFileConfig>, signer: Option<SignerFileConfig>, myc: Option<MycFileConfig>, hyf: Option<HyfFileConfig>, @@ -363,6 +397,11 @@ struct RelayFileConfig { } #[derive(Debug, Default, Deserialize)] +struct PublishFileConfig { + mode: Option<String>, +} + +#[derive(Debug, Default, Deserialize)] struct RpcFileConfig { url: Option<String>, } @@ -557,6 +596,13 @@ impl RuntimeConfig { app_config.as_ref(), workspace_config.as_ref(), )?, + publish: resolve_publish_config( + args, + env, + env_file, + app_config.as_ref(), + workspace_config.as_ref(), + )?, relay: resolve_relay_config( args, env, @@ -967,6 +1013,53 @@ fn resolve_signer_config( Ok(SignerConfig { backend }) } +fn resolve_publish_config( + args: &RuntimeInvocationArgs, + env: &dyn Environment, + env_file: &EnvFileValues, + user_config: Option<&CliConfigFile>, + workspace_config: Option<&CliConfigFile>, +) -> Result<PublishConfig, RuntimeError> { + if let Some(value) = args.publish_mode.clone() { + return Ok(PublishConfig { + mode: parse_publish_mode("--publish-mode", value)?, + source: PublishModeSource::Flags, + }); + } + + if let Some((key, value)) = env_value_entry(env, env_file, &[ENV_PUBLISH_MODE]) { + return Ok(PublishConfig { + mode: parse_publish_mode(key.as_str(), value)?, + source: PublishModeSource::Environment, + }); + } + + if let Some(value) = user_config + .and_then(|config| config.publish.as_ref()) + .and_then(|publish| publish.mode.clone()) + { + return Ok(PublishConfig { + mode: parse_publish_mode("user config [publish].mode", value)?, + source: PublishModeSource::UserConfig, + }); + } + + if let Some(value) = workspace_config + .and_then(|config| config.publish.as_ref()) + .and_then(|publish| publish.mode.clone()) + { + return Ok(PublishConfig { + mode: parse_publish_mode("workspace config [publish].mode", value)?, + source: PublishModeSource::WorkspaceConfig, + }); + } + + Ok(PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }) +} + fn resolve_myc_config( args: &RuntimeInvocationArgs, env: &dyn Environment, @@ -1421,6 +1514,18 @@ fn parse_signer_mode(source: &str, value: String) -> Result<SignerBackend, Runti } } +fn parse_publish_mode(source: &str, value: String) -> Result<PublishMode, RuntimeError> { + match value.trim().to_ascii_lowercase().as_str() { + "nostr_relay" => Ok(PublishMode::NostrRelay), + "radrootsd" => Ok(PublishMode::Radrootsd), + other => Err(RuntimeError::Config(format!( + "{source} must be `{}` or `{}`, got `{other}`", + PublishMode::NostrRelay.as_str(), + PublishMode::Radrootsd.as_str() + ))), + } +} + fn resolve_account_secret_backend( _args: &RuntimeInvocationArgs, env: &dyn Environment, @@ -1488,8 +1593,8 @@ mod tests { AccountConfig, AccountSecretContractConfig, CapabilityBindingConfig, CapabilityBindingSource, CapabilityBindingTargetKind, EnvFileValues, Environment, HyfConfig, INFERENCE_HYF_STDIO_CAPABILITY, InteractionConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfigSource, RelayPublishPolicy, RuntimeConfig, SignerBackend, - Verbosity, parse_env_file_values, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfigSource, + RelayPublishPolicy, RuntimeConfig, SignerBackend, Verbosity, parse_env_file_values, }; use crate::runtime_args::{RuntimeInvocationArgs, RuntimeOutputFormatArg}; use radroots_runtime_paths::{RadrootsHostEnvironment, RadrootsPathResolver, RadrootsPlatform}; @@ -1598,6 +1703,7 @@ mod tests { log_stdout: true, identity_path: Some(PathBuf::from("custom-identity.json")), signer: Some("local".to_owned()), + publish_mode: Some("nostr_relay".to_owned()), relay: vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()], myc_executable: Some(PathBuf::from("bin/myc-cli")), myc_status_timeout_ms: Some(2500), @@ -1614,6 +1720,7 @@ mod tests { "env-identity.json".to_owned(), ), ("RADROOTS_SIGNER".to_owned(), "myc".to_owned()), + ("RADROOTS_PUBLISH_MODE".to_owned(), "radrootsd".to_owned()), ("RADROOTS_RELAYS".to_owned(), "wss://relay.env".to_owned()), ("RADROOTS_MYC_EXECUTABLE".to_owned(), "env-myc".to_owned()), ( @@ -1706,6 +1813,13 @@ mod tests { ); assert_eq!(resolved.signer.backend, SignerBackend::Local); assert_eq!( + resolved.publish, + PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Flags, + } + ); + assert_eq!( resolved.relay.urls, vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()] ); @@ -1739,6 +1853,7 @@ mod tests { "state/identity.json".to_owned(), ), ("RADROOTS_SIGNER".to_owned(), "myc".to_owned()), + ("RADROOTS_PUBLISH_MODE".to_owned(), "radrootsd".to_owned()), ( "RADROOTS_RELAYS".to_owned(), "wss://relay.one,wss://relay.two".to_owned(), @@ -1792,6 +1907,13 @@ mod tests { assert_eq!(resolved.identity.path, PathBuf::from("state/identity.json")); assert_eq!(resolved.signer.backend, SignerBackend::Myc); assert_eq!( + resolved.publish, + PublishConfig { + mode: PublishMode::Radrootsd, + source: PublishModeSource::Environment, + } + ); + assert_eq!( resolved.relay.urls, vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()] ); @@ -1973,6 +2095,16 @@ mod tests { .expect_err("invalid myc timeout"); assert!(error.to_string().contains("RADROOTS_MYC_STATUS_TIMEOUT_MS")); + let env = MapEnvironment::new(BTreeMap::from([( + "RADROOTS_PUBLISH_MODE".to_owned(), + "relay".to_owned(), + )])); + let error = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default()) + .expect_err("invalid publish mode"); + assert!(error.to_string().contains("RADROOTS_PUBLISH_MODE")); + assert!(error.to_string().contains("nostr_relay")); + assert!(error.to_string().contains("radrootsd")); + let args = RuntimeInvocationArgs { myc_status_timeout_ms: Some(0), ..runtime_args() @@ -1996,6 +2128,7 @@ RADROOTS_CLI_LOGGING_STDOUT=false RADROOTS_ACCOUNT=acct_env_file RADROOTS_IDENTITY_PATH=state/identity.json RADROOTS_SIGNER=myc +RADROOTS_PUBLISH_MODE=radrootsd RADROOTS_RELAYS=wss://relay.env-file RADROOTS_MYC_EXECUTABLE=bin/myc RADROOTS_MYC_STATUS_TIMEOUT_MS=4500 @@ -2018,6 +2151,13 @@ RADROOTS_HYF_EXECUTABLE=bin/hyfd assert_eq!(resolved.account.selector.as_deref(), Some("acct_env_file")); assert_eq!(resolved.identity.path, PathBuf::from("state/identity.json")); assert_eq!(resolved.signer.backend, SignerBackend::Myc); + assert_eq!( + resolved.publish, + PublishConfig { + mode: PublishMode::Radrootsd, + source: PublishModeSource::Environment, + } + ); assert_eq!(resolved.relay.urls, vec!["wss://relay.env-file".to_owned()]); assert_eq!(resolved.relay.source, RelayConfigSource::Environment); assert_eq!(resolved.myc.executable, PathBuf::from("bin/myc")); @@ -2171,6 +2311,112 @@ RADROOTS_CLI_LOGGING_STDOUT=false } #[test] + fn publish_mode_precedence_tracks_source() { + let temp = tempdir().expect("tempdir"); + let workspace_root = temp.path().join("workspace"); + let repo_local_root = workspace_root.join("infra/local/runtime/radroots"); + let app_config_dir = repo_local_root.join("config/apps/cli"); + let user_home = temp.path().join("home"); + fs::create_dir_all(&repo_local_root).expect("workspace config dir"); + fs::create_dir_all(&app_config_dir).expect("app config dir"); + fs::write( + repo_local_root.join("config.toml"), + "[publish]\nmode = \"radrootsd\"\n", + ) + .expect("write workspace config"); + fs::write( + app_config_dir.join("config.toml"), + "[publish]\nmode = \"nostr_relay\"\n", + ) + .expect("write user config"); + + let env = repo_local_env( + workspace_root.clone(), + repo_local_root.clone(), + user_home.clone(), + BTreeMap::from([("RADROOTS_PUBLISH_MODE".to_owned(), "radrootsd".to_owned())]), + ); + let args = RuntimeInvocationArgs { + publish_mode: Some("nostr_relay".to_owned()), + ..runtime_args() + }; + let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default()) + .expect("resolve flag publish mode"); + assert_eq!( + resolved.publish, + PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Flags, + } + ); + + let env = repo_local_env( + workspace_root.clone(), + repo_local_root.clone(), + user_home.clone(), + BTreeMap::from([("RADROOTS_PUBLISH_MODE".to_owned(), "radrootsd".to_owned())]), + ); + let resolved = + RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default()) + .expect("resolve environment publish mode"); + assert_eq!( + resolved.publish, + PublishConfig { + mode: PublishMode::Radrootsd, + source: PublishModeSource::Environment, + } + ); + + let env = repo_local_env( + workspace_root.clone(), + repo_local_root.clone(), + user_home.clone(), + BTreeMap::new(), + ); + let resolved = + RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default()) + .expect("resolve user publish mode"); + assert_eq!( + resolved.publish, + PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::UserConfig, + } + ); + + fs::remove_file(app_config_dir.join("config.toml")).expect("remove user config"); + let env = repo_local_env( + workspace_root.clone(), + repo_local_root.clone(), + user_home.clone(), + BTreeMap::new(), + ); + let resolved = + RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default()) + .expect("resolve workspace publish mode"); + assert_eq!( + resolved.publish, + PublishConfig { + mode: PublishMode::Radrootsd, + source: PublishModeSource::WorkspaceConfig, + } + ); + + fs::remove_file(repo_local_root.join("config.toml")).expect("remove workspace config"); + let env = repo_local_env(workspace_root, repo_local_root, user_home, BTreeMap::new()); + let resolved = + RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default()) + .expect("resolve default publish mode"); + assert_eq!( + resolved.publish, + PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + } + ); + } + + #[test] fn user_hyf_config_overrides_workspace_hyf_config() { let temp = tempdir().expect("tempdir"); let workspace_root = temp.path().join("workspace"); @@ -2353,6 +2599,29 @@ RADROOTS_CLI_LOGGING_STDOUT=false } #[test] + fn invalid_publish_config_reports_config_source() { + let temp = tempdir().expect("tempdir"); + let workspace_root = temp.path().join("workspace"); + let repo_local_root = workspace_root.join("infra/local/runtime/radroots"); + let user_home = temp.path().join("home"); + fs::create_dir_all(&repo_local_root).expect("workspace config dir"); + fs::write( + repo_local_root.join("config.toml"), + "[publish]\nmode = \"nostr\"\n", + ) + .expect("write workspace config"); + + let env = repo_local_env(workspace_root, repo_local_root, user_home, BTreeMap::new()); + let error = + RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default()) + .expect_err("invalid publish mode"); + let message = error.to_string(); + assert!(message.contains("workspace config [publish].mode")); + assert!(message.contains("nostr_relay")); + assert!(message.contains("radrootsd")); + } + + #[test] fn user_capability_binding_overrides_workspace_binding() { let temp = tempdir().expect("tempdir"); let workspace_root = temp.path().join("workspace"); diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -10352,8 +10352,8 @@ mod tests { use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, + PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; use crate::runtime::direct_relay::DirectRelayFetchReceipt; use crate::runtime_args::{ @@ -15929,6 +15929,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: Vec::new(), publish_policy: RelayPublishPolicy::Any, diff --git a/src/runtime/provider.rs b/src/runtime/provider.rs @@ -249,8 +249,9 @@ mod tests { AccountConfig, AccountSecretContractConfig, CapabilityBindingConfig, CapabilityBindingSource, CapabilityBindingTargetKind, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, - OutputFormat, PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, - RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + OutputFormat, PathsConfig, PublishConfig, PublishMode, PublishModeSource, RelayConfig, + RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, + SignerConfig, Verbosity, }; fn sample_config(bindings: Vec<CapabilityBindingConfig>, hyf_enabled: bool) -> RuntimeConfig { @@ -316,6 +317,10 @@ mod tests { signer: SignerConfig { backend: SignerBackend::Local, }, + publish: PublishConfig { + mode: PublishMode::NostrRelay, + source: PublishModeSource::Defaults, + }, relay: RelayConfig { urls: Vec::new(), publish_policy: RelayPublishPolicy::Any, diff --git a/src/runtime_args.rs b/src/runtime_args.rs @@ -39,6 +39,7 @@ pub struct RuntimeInvocationArgs { pub account: Option<String>, pub identity_path: Option<PathBuf>, pub signer: Option<String>, + pub publish_mode: Option<String>, pub relay: Vec<String>, pub myc_executable: Option<PathBuf>, pub myc_status_timeout_ms: Option<u64>, diff --git a/src/target_cli.rs b/src/target_cli.rs @@ -11,6 +11,22 @@ pub enum TargetOutputFormat { Ndjson, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +pub enum TargetPublishMode { + #[value(name = "nostr_relay")] + NostrRelay, + Radrootsd, +} + +impl TargetPublishMode { + pub fn as_str(self) -> &'static str { + match self { + Self::NostrRelay => "nostr_relay", + Self::Radrootsd => "radrootsd", + } + } +} + #[derive(Debug, Parser, Clone)] #[command(name = "radroots", disable_help_subcommand = true)] pub struct TargetCliArgs { @@ -20,6 +36,8 @@ pub struct TargetCliArgs { pub account_id: Option<String>, #[arg(long = "relay", global = true)] pub relay: Vec<String>, + #[arg(long = "publish-mode", global = true, value_enum)] + pub publish_mode: Option<TargetPublishMode>, #[arg(long = "offline", global = true, action = ArgAction::SetTrue, conflicts_with = "online")] pub offline: bool, #[arg(long = "online", global = true, action = ArgAction::SetTrue, conflicts_with = "offline")]