commit 407db3816d0c961e63c724a78aba98fd5cba39a3
parent 4cb5dba0d772cb3500d60c18e6b37077ea77cd32
Author: triesap <tyson@radroots.org>
Date: Mon, 27 Apr 2026 08:03:06 +0000
cli: remove legacy command model
Diffstat:
44 files changed, 374 insertions(+), 10461 deletions(-)
diff --git a/src/cli.rs b/src/cli.rs
@@ -1,1126 +0,0 @@
-#![allow(dead_code)]
-
-use clap::{
- ArgAction, Args, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum,
- error::ErrorKind,
-};
-use std::ffi::{OsStr, OsString};
-use std::path::PathBuf;
-
-use crate::runtime::config::OutputFormat;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
-pub enum OutputFormatArg {
- Human,
- Json,
- Ndjson,
-}
-
-impl OutputFormatArg {
- pub fn as_output_format(self) -> OutputFormat {
- match self {
- Self::Human => OutputFormat::Human,
- Self::Json => OutputFormat::Json,
- Self::Ndjson => OutputFormat::Ndjson,
- }
- }
-}
-
-#[derive(Debug, Parser, Clone)]
-#[command(name = "radroots")]
-#[command(version)]
-pub struct CliArgs {
- #[arg(skip)]
- pub output_format: Option<OutputFormatArg>,
- #[arg(long, global = true, action = ArgAction::SetTrue)]
- pub json: bool,
- #[arg(long, global = true, action = ArgAction::SetTrue)]
- pub ndjson: bool,
- #[arg(long = "env-file", global = true)]
- pub env_file: Option<PathBuf>,
- #[arg(long, global = true, action = ArgAction::SetTrue)]
- pub quiet: bool,
- #[arg(long, global = true, action = ArgAction::SetTrue)]
- pub verbose: bool,
- #[arg(long, global = true, action = ArgAction::SetTrue)]
- pub trace: bool,
- #[arg(long = "dry-run", global = true, action = ArgAction::SetTrue)]
- pub dry_run: bool,
- #[arg(long = "no-color", global = true, action = ArgAction::SetTrue)]
- pub no_color: bool,
- #[arg(
- long = "no-input",
- global = true,
- visible_alias = "non-interactive",
- action = ArgAction::SetTrue
- )]
- pub no_input: bool,
- #[arg(long, global = true, action = ArgAction::SetTrue)]
- pub yes: bool,
- #[arg(long, global = true)]
- pub log_filter: Option<String>,
- #[arg(long, global = true)]
- pub log_dir: Option<PathBuf>,
- #[arg(long = "log-stdout", global = true, action = ArgAction::SetTrue)]
- pub log_stdout: bool,
- #[arg(long = "no-log-stdout", global = true, action = ArgAction::SetTrue)]
- pub no_log_stdout: bool,
- #[arg(long, global = true)]
- pub account: Option<String>,
- #[arg(long, global = true)]
- pub identity_path: Option<PathBuf>,
- #[arg(long, global = true)]
- pub signer: Option<String>,
- #[arg(long, global = true)]
- pub relay: Vec<String>,
- #[arg(long, global = true)]
- pub myc_executable: Option<PathBuf>,
- #[arg(long = "myc-status-timeout-ms", global = true)]
- pub myc_status_timeout_ms: Option<u64>,
- #[arg(long = "hyf-enabled", global = true, action = ArgAction::SetTrue)]
- pub hyf_enabled: bool,
- #[arg(long = "no-hyf-enabled", global = true, action = ArgAction::SetTrue)]
- pub no_hyf_enabled: bool,
- #[arg(long = "hyf-executable", global = true)]
- pub hyf_executable: Option<PathBuf>,
- #[command(subcommand)]
- pub command: Command,
-}
-
-impl CliArgs {
- pub fn parse() -> Self {
- Self::try_parse().unwrap_or_else(|error| error.exit())
- }
-
- pub fn try_parse() -> Result<Self, clap::Error> {
- Self::try_parse_from(std::env::args_os())
- }
-
- #[cfg(test)]
- pub fn parse_from<I, T>(itr: I) -> Self
- where
- I: IntoIterator<Item = T>,
- T: Into<OsString> + Clone,
- {
- Self::try_parse_from(itr).unwrap_or_else(|error| error.exit())
- }
-
- pub fn try_parse_from<I, T>(itr: I) -> Result<Self, clap::Error>
- where
- I: IntoIterator<Item = T>,
- T: Into<OsString> + Clone,
- {
- let args = itr.into_iter().map(Into::into).collect::<Vec<_>>();
- let (filtered_args, output_format) = extract_global_output_format(args)?;
- let mut command = Self::build_command();
- let matches = command.try_get_matches_from_mut(filtered_args)?;
- let mut parsed = <Self as FromArgMatches>::from_arg_matches(&matches)?;
- parsed.output_format = output_format;
- Ok(parsed)
- }
-
- pub fn build_command() -> clap::Command {
- <Self as CommandFactory>::command()
- }
-
- fn command_error(message: impl Into<String>, kind: ErrorKind) -> clap::Error {
- let mut command = Self::build_command();
- command.error(kind, message.into())
- }
-}
-
-fn extract_global_output_format(
- args: Vec<OsString>,
-) -> Result<(Vec<OsString>, Option<OutputFormatArg>), clap::Error> {
- let mut iter = args.into_iter();
- let Some(program) = iter.next() else {
- return Ok((Vec::new(), None));
- };
-
- let mut filtered_args = vec![program];
- let mut output_format = None;
- let mut command_tokens = Vec::new();
- let mut skip_known_global_value = false;
-
- while let Some(arg) = iter.next() {
- if skip_known_global_value {
- filtered_args.push(arg);
- skip_known_global_value = false;
- continue;
- }
-
- if let Some((flag, value)) = split_long_option(arg.as_os_str()) {
- if flag == "output" && !matches_local_output_context(command_tokens.as_slice()) {
- output_format = Some(parse_output_format_value(value)?);
- continue;
- }
-
- if matches_known_global_value_option(flag) {
- filtered_args.push(arg);
- continue;
- }
- }
-
- if arg == OsStr::new("--output") {
- if matches_local_output_context(command_tokens.as_slice()) {
- filtered_args.push(arg);
- continue;
- }
-
- let Some(value) = iter.next() else {
- return Err(CliArgs::command_error(
- "`--output` requires a value",
- ErrorKind::InvalidValue,
- ));
- };
- output_format = Some(parse_output_format_value(value.as_os_str())?);
- continue;
- }
-
- if let Some(flag) = long_option_name(arg.as_os_str()) {
- if matches_known_global_value_option(flag) {
- skip_known_global_value = true;
- }
- }
-
- if let Some(token) = arg.to_str() {
- if !token.starts_with('-') {
- command_tokens.push(token.to_owned());
- }
- }
-
- filtered_args.push(arg);
- }
-
- Ok((filtered_args, output_format))
-}
-
-fn parse_output_format_value(value: &OsStr) -> Result<OutputFormatArg, clap::Error> {
- let Some(value) = value.to_str() else {
- return Err(CliArgs::command_error(
- "`--output` must be one of: human, json, ndjson",
- ErrorKind::InvalidUtf8,
- ));
- };
-
- OutputFormatArg::from_str(value, false).map_err(|_| {
- CliArgs::command_error(
- format!("invalid value `{value}` for `--output`; expected one of: human, json, ndjson"),
- ErrorKind::InvalidValue,
- )
- })
-}
-
-fn long_option_name(arg: &OsStr) -> Option<&str> {
- let token = arg.to_str()?;
- token
- .strip_prefix("--")
- .map(|rest| rest.split_once('=').map_or(rest, |(flag, _value)| flag))
-}
-
-fn split_long_option(arg: &OsStr) -> Option<(&str, &OsStr)> {
- let token = arg.to_str()?;
- let (flag, value) = token.strip_prefix("--")?.split_once('=')?;
- Some((flag, OsStr::new(value)))
-}
-
-fn matches_known_global_value_option(flag: &str) -> bool {
- matches!(
- flag,
- "env-file"
- | "log-filter"
- | "log-dir"
- | "account"
- | "identity-path"
- | "signer"
- | "relay"
- | "myc-executable"
- | "hyf-executable"
- )
-}
-
-fn matches_local_output_context(command_tokens: &[String]) -> bool {
- matches!(
- command_tokens,
- [local, export, ..] if local == "local" && export == "export"
- ) || matches!(
- command_tokens,
- [local, backup, ..] if local == "local" && backup == "backup"
- ) || matches!(
- command_tokens,
- [listing, new, ..] if listing == "listing" && new == "new"
- ) || matches!(
- command_tokens,
- [sell, add, ..] if sell == "sell" && add == "add"
- )
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum Command {
- #[command(about = "Create, import, and manage local accounts")]
- Account(AccountArgs),
- #[command(about = "Show effective configuration")]
- Config(ConfigArgs),
- #[command(about = "Check readiness and suggest next steps")]
- Doctor,
- #[command(about = "Set up and publish your farm")]
- Farm(FarmArgs),
- #[command(about = "Advanced search command")]
- Find(FindArgs),
- #[command(about = "Inspect background jobs")]
- Job(JobArgs),
- #[command(about = "Advanced listing commands")]
- Listing(ListingArgs),
- #[command(about = "Manage local market data storage")]
- Local(LocalArgs),
- #[command(about = "Update local market data and search listings")]
- Market(MarketArgs),
- #[command(about = "Show myc status")]
- Myc(MycArgs),
- #[command(about = "Show network posture")]
- Net(NetArgs),
- #[command(about = "Create and manage order requests")]
- Order(OrderArgs),
- #[command(about = "Show relay configuration")]
- Relay(RelayArgs),
- #[command(about = "Show runtime bridge status")]
- Rpc(RpcArgs),
- #[command(about = "Create, check, and publish listings")]
- Sell(SellArgs),
- #[command(about = "Guided first-time setup for sellers and buyers")]
- Setup(SetupArgs),
- Runtime(RuntimeArgs),
- #[command(about = "Show signer readiness")]
- Signer(SignerArgs),
- #[command(about = "Show what is ready and what needs attention")]
- Status,
- #[command(about = "Inspect sync status and watch updates")]
- Sync(SyncArgs),
-}
-
-impl Command {
- pub fn display_name(&self) -> &'static str {
- match self {
- Self::Account(account) => match account.command {
- AccountCommand::New => "account create",
- AccountCommand::Import(_) => "account import",
- AccountCommand::Whoami => "account view",
- AccountCommand::Ls => "account list",
- AccountCommand::Use(_) => "account select",
- AccountCommand::ClearDefault => "account clear-default",
- AccountCommand::Remove(_) => "account remove",
- },
- Self::Config(config) => match config.command {
- ConfigCommand::Show => "config show",
- },
- Self::Doctor => "doctor",
- Self::Farm(farm) => match farm.command {
- FarmCommand::Init(_) => "farm init",
- FarmCommand::Set(_) => "farm set",
- FarmCommand::Publish(_) => "farm publish",
- FarmCommand::Setup(_) => "farm setup",
- FarmCommand::Status(_) => "farm check",
- FarmCommand::Get(_) => "farm show",
- },
- Self::Find(_) => "find",
- Self::Job(job) => match job.command {
- JobCommand::Ls => "job list",
- JobCommand::Get(_) => "job get",
- JobCommand::Watch(_) => "job watch",
- },
- Self::Listing(listing) => match listing.command {
- ListingCommand::New(_) => "listing new",
- ListingCommand::Validate(_) => "listing validate",
- ListingCommand::Get(_) => "listing get",
- ListingCommand::Publish(_) => "listing publish",
- ListingCommand::Update(_) => "listing update",
- ListingCommand::Archive(_) => "listing archive",
- },
- Self::Local(local) => match local.command {
- LocalCommand::Init => "local init",
- LocalCommand::Status => "local status",
- LocalCommand::Export(_) => "local export",
- LocalCommand::Backup(_) => "local backup",
- },
- Self::Market(market) => match market.command {
- MarketCommand::Update => "market update",
- MarketCommand::Search(_) => "market search",
- MarketCommand::View(_) => "market view",
- },
- Self::Myc(myc) => match myc.command {
- MycCommand::Status => "myc status",
- },
- Self::Net(net) => match net.command {
- NetCommand::Status => "net status",
- },
- Self::Order(order) => match order.command {
- OrderCommand::New(_) => "order create",
- OrderCommand::Get(_) => "order view",
- OrderCommand::Ls => "order list",
- OrderCommand::Submit(_) => "order submit",
- OrderCommand::Watch(_) => "order watch",
- OrderCommand::Cancel(_) => "order cancel",
- OrderCommand::History => "order history",
- },
- Self::Relay(relay) => match relay.command {
- RelayCommand::Ls => "relay list",
- },
- Self::Rpc(rpc) => match rpc.command {
- RpcCommand::Status => "rpc status",
- RpcCommand::Sessions => "rpc sessions",
- },
- Self::Sell(sell) => match sell.command {
- SellCommand::Add(_) => "sell add",
- SellCommand::Show(_) => "sell show",
- SellCommand::Check(_) => "sell check",
- SellCommand::Publish(_) => "sell publish",
- SellCommand::Update(_) => "sell update",
- SellCommand::Pause(_) => "sell pause",
- SellCommand::Reprice(_) => "sell reprice",
- SellCommand::Restock(_) => "sell restock",
- },
- Self::Setup(args) => match args.role {
- SetupRoleArg::Seller => "setup seller",
- SetupRoleArg::Buyer => "setup buyer",
- SetupRoleArg::Both => "setup both",
- },
- Self::Runtime(runtime) => match &runtime.command {
- RuntimeCommand::Install(_) => "runtime install",
- RuntimeCommand::Uninstall(_) => "runtime uninstall",
- RuntimeCommand::Status(_) => "runtime status",
- RuntimeCommand::Start(_) => "runtime start",
- RuntimeCommand::Stop(_) => "runtime stop",
- RuntimeCommand::Restart(_) => "runtime restart",
- RuntimeCommand::Logs(_) => "runtime logs",
- RuntimeCommand::Config(runtime_config) => match &runtime_config.command {
- RuntimeConfigCommand::Show(_) => "runtime config show",
- RuntimeConfigCommand::Set(_) => "runtime config set",
- },
- },
- Self::Signer(signer) => match signer.command {
- SignerCommand::Status => "signer status",
- SignerCommand::Session(_) => "signer session",
- },
- Self::Status => "status",
- Self::Sync(sync) => match sync.command {
- SyncCommand::Status => "sync status",
- SyncCommand::Pull => "sync pull",
- SyncCommand::Push => "sync push",
- SyncCommand::Watch(_) => "sync watch",
- },
- }
- }
-
- pub fn supports_output_format(&self, format: OutputFormat) -> bool {
- match format {
- OutputFormat::Human | OutputFormat::Json => true,
- OutputFormat::Ndjson => matches!(
- self,
- Self::Account(AccountArgs {
- command: AccountCommand::Ls,
- }) | Self::Relay(RelayArgs {
- command: RelayCommand::Ls,
- }) | Self::Job(JobArgs {
- command: JobCommand::Ls,
- }) | Self::Job(JobArgs {
- command: JobCommand::Watch(_),
- }) | Self::Rpc(RpcArgs {
- command: RpcCommand::Sessions,
- }) | Self::Order(OrderArgs {
- command: OrderCommand::Ls | OrderCommand::Watch(_) | OrderCommand::History,
- }) | Self::Sync(SyncArgs {
- command: SyncCommand::Watch(_),
- }) | Self::Find(_)
- | Self::Market(MarketArgs {
- command: MarketCommand::Search(_),
- })
- ),
- }
- }
-
- pub fn supports_dry_run(&self) -> bool {
- !matches!(
- self,
- Self::Account(AccountArgs {
- command: AccountCommand::New
- | AccountCommand::Import(_)
- | AccountCommand::Use(_)
- | AccountCommand::ClearDefault
- | AccountCommand::Remove(_),
- }) | Self::Farm(FarmArgs {
- command: FarmCommand::Init(_) | FarmCommand::Set(_) | FarmCommand::Setup(_),
- }) | Self::Local(LocalArgs {
- command: LocalCommand::Init | LocalCommand::Export(_) | LocalCommand::Backup(_),
- }) | Self::Sync(SyncArgs {
- command: SyncCommand::Pull | SyncCommand::Push,
- }) | Self::Listing(ListingArgs {
- command: ListingCommand::New(_),
- }) | Self::Market(MarketArgs {
- command: MarketCommand::Update,
- }) | Self::Order(OrderArgs {
- command: OrderCommand::New(_) | OrderCommand::Cancel(_),
- }) | Self::Sell(SellArgs {
- command: SellCommand::Add(_),
- }) | Self::Setup(_)
- )
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
-pub enum SetupRoleArg {
- Seller,
- Buyer,
- Both,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SetupArgs {
- #[arg(value_enum, default_value = "both")]
- pub role: SetupRoleArg,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct ConfigArgs {
- #[command(subcommand)]
- pub command: ConfigCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum ConfigCommand {
- Show,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct AccountArgs {
- #[command(subcommand)]
- pub command: AccountCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum AccountCommand {
- #[command(
- name = "create",
- visible_alias = "new",
- about = "Create a local account"
- )]
- New,
- #[command(name = "import", about = "Import a watch-only local account")]
- Import(AccountImportArgs),
- #[command(
- name = "view",
- visible_alias = "whoami",
- about = "Show the selected local account"
- )]
- Whoami,
- #[command(name = "list", visible_alias = "ls", about = "List local accounts")]
- Ls,
- #[command(
- name = "select",
- visible_alias = "use",
- about = "Select a local account"
- )]
- Use(AccountUseArgs),
- #[command(name = "clear-default", about = "Clear the stored default account")]
- ClearDefault,
- #[command(name = "remove", about = "Remove a local account")]
- Remove(AccountRemoveArgs),
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct AccountImportArgs {
- pub path: PathBuf,
- #[arg(long, action = ArgAction::SetTrue)]
- pub default: bool,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct AccountUseArgs {
- pub selector: String,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct AccountRemoveArgs {
- pub selector: String,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct MycArgs {
- #[command(subcommand)]
- pub command: MycCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum MycCommand {
- Status,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SignerArgs {
- #[command(subcommand)]
- pub command: SignerCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum SignerCommand {
- Status,
- Session(SignerSessionArgs),
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SignerSessionArgs {
- #[command(subcommand)]
- pub command: SignerSessionCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum SignerSessionCommand {
- List,
- Show {
- session_id: String,
- },
- ConnectBunker {
- url: String,
- },
- ConnectNostrconnect {
- url: String,
- #[arg(long)]
- client_secret_key: String,
- },
- PublicKey {
- session_id: String,
- },
- Authorize {
- session_id: String,
- },
- RequireAuth {
- session_id: String,
- #[arg(long)]
- auth_url: String,
- },
- Close {
- session_id: String,
- },
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct RelayArgs {
- #[command(subcommand)]
- pub command: RelayCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum RelayCommand {
- #[command(name = "list", visible_alias = "ls", about = "List configured relays")]
- Ls,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct FarmArgs {
- #[command(subcommand)]
- pub command: FarmCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum FarmCommand {
- #[command(about = "Create or refresh a farm draft progressively")]
- Init(FarmInitArgs),
- #[command(about = "Set one farm draft field")]
- Set(FarmSetArgs),
- #[command(about = "Publish the current farm draft")]
- Publish(FarmPublishArgs),
- #[command(about = "Create or update a farm draft in one command")]
- Setup(FarmSetupArgs),
- #[command(
- name = "check",
- visible_alias = "status",
- about = "Check farm readiness"
- )]
- Status(FarmScopedArgs),
- #[command(name = "show", visible_alias = "get", about = "Show the farm draft")]
- Get(FarmScopedArgs),
-}
-
-#[derive(Debug, Clone, Args, Default)]
-pub struct FarmPublishArgs {
- #[arg(long, value_enum)]
- pub scope: Option<FarmScopeArg>,
- #[arg(long = "idempotency-key")]
- pub idempotency_key: Option<String>,
- #[arg(long = "signer-session-id")]
- pub signer_session_id: Option<String>,
- #[arg(long = "print-job", action = ArgAction::SetTrue)]
- pub print_job: bool,
- #[arg(long = "print-event", action = ArgAction::SetTrue)]
- pub print_event: bool,
-}
-
-#[derive(Debug, Clone, Args, Default)]
-pub struct FarmScopedArgs {
- #[arg(long, value_enum)]
- pub scope: Option<FarmScopeArg>,
-}
-
-#[derive(Debug, Clone, Args, Default)]
-pub struct FarmInitArgs {
- #[arg(long, value_enum)]
- pub scope: Option<FarmScopeArg>,
- #[arg(long = "farm-d-tag")]
- pub farm_d_tag: Option<String>,
- #[arg(long)]
- pub name: Option<String>,
- #[arg(long = "display-name")]
- pub display_name: Option<String>,
- #[arg(long)]
- pub about: Option<String>,
- #[arg(long)]
- pub website: Option<String>,
- #[arg(long)]
- pub picture: Option<String>,
- #[arg(long)]
- pub banner: Option<String>,
- #[arg(long)]
- pub location: Option<String>,
- #[arg(long)]
- pub city: Option<String>,
- #[arg(long)]
- pub region: Option<String>,
- #[arg(long)]
- pub country: Option<String>,
- #[arg(long = "delivery", visible_alias = "delivery-method")]
- pub delivery_method: Option<String>,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
-pub enum FarmFieldArg {
- Name,
- #[value(name = "display_name", alias = "display-name")]
- DisplayName,
- About,
- Website,
- Picture,
- Banner,
- Location,
- City,
- Region,
- Country,
- Delivery,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct FarmSetArgs {
- #[arg(long, value_enum)]
- pub scope: Option<FarmScopeArg>,
- #[arg(value_enum)]
- pub field: FarmFieldArg,
- #[arg(value_name = "value", num_args = 1..)]
- pub value: Vec<String>,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct FarmSetupArgs {
- #[arg(long, value_enum)]
- pub scope: Option<FarmScopeArg>,
- #[arg(long = "farm-d-tag")]
- pub farm_d_tag: Option<String>,
- #[arg(long)]
- pub name: String,
- #[arg(long = "display-name")]
- pub display_name: Option<String>,
- #[arg(long)]
- pub about: Option<String>,
- #[arg(long)]
- pub website: Option<String>,
- #[arg(long)]
- pub picture: Option<String>,
- #[arg(long)]
- pub banner: Option<String>,
- #[arg(long)]
- pub location: String,
- #[arg(long)]
- pub city: Option<String>,
- #[arg(long)]
- pub region: Option<String>,
- #[arg(long)]
- pub country: Option<String>,
- #[arg(long = "delivery-method", default_value = "pickup")]
- pub delivery_method: String,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
-pub enum FarmScopeArg {
- User,
- Workspace,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct NetArgs {
- #[command(subcommand)]
- pub command: NetCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum NetCommand {
- Status,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct LocalArgs {
- #[command(subcommand)]
- pub command: LocalCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum LocalCommand {
- Init,
- Status,
- Export(LocalExportArgs),
- Backup(LocalBackupArgs),
-}
-
-#[derive(Debug, Clone, Copy, clap::ValueEnum)]
-pub enum LocalExportFormatArg {
- Json,
- Ndjson,
-}
-
-impl LocalExportFormatArg {
- pub fn as_str(self) -> &'static str {
- match self {
- Self::Json => "json",
- Self::Ndjson => "ndjson",
- }
- }
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct LocalExportArgs {
- #[arg(long)]
- pub format: LocalExportFormatArg,
- #[arg(long)]
- pub output: PathBuf,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct LocalBackupArgs {
- #[arg(long)]
- pub output: PathBuf,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SyncArgs {
- #[command(subcommand)]
- pub command: SyncCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum SyncCommand {
- Status,
- Pull,
- Push,
- Watch(SyncWatchArgs),
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SyncWatchArgs {
- #[arg(long)]
- pub frames: usize,
- #[arg(long, default_value_t = 1_000)]
- pub interval_ms: u64,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct FindArgs {
- #[arg(value_name = "query", num_args = 1..)]
- pub query: Vec<String>,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct MarketArgs {
- #[command(subcommand)]
- pub command: MarketCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum MarketCommand {
- #[command(about = "Update local market data")]
- Update,
- #[command(about = "Search listings in local market data")]
- Search(FindArgs),
- #[command(about = "View one published listing")]
- View(RecordKeyArgs),
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct ListingArgs {
- #[command(subcommand)]
- pub command: ListingCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum ListingCommand {
- New(ListingNewArgs),
- Validate(ListingFileArgs),
- Get(RecordKeyArgs),
- Publish(ListingMutationArgs),
- Update(ListingMutationArgs),
- Archive(ListingMutationArgs),
-}
-
-#[derive(Debug, Clone, Args, Default)]
-pub struct ListingNewArgs {
- #[arg(long)]
- pub output: Option<PathBuf>,
- #[arg(long)]
- pub key: Option<String>,
- #[arg(long)]
- pub title: Option<String>,
- #[arg(long)]
- pub category: Option<String>,
- #[arg(long)]
- pub summary: Option<String>,
- #[arg(long = "bin-id")]
- pub bin_id: Option<String>,
- #[arg(long = "quantity-amount")]
- pub quantity_amount: Option<String>,
- #[arg(long = "quantity-unit")]
- pub quantity_unit: Option<String>,
- #[arg(long = "price-amount")]
- pub price_amount: Option<String>,
- #[arg(long = "price-currency")]
- pub price_currency: Option<String>,
- #[arg(long = "price-per-amount")]
- pub price_per_amount: Option<String>,
- #[arg(long = "price-per-unit")]
- pub price_per_unit: Option<String>,
- #[arg(long)]
- pub available: Option<String>,
- #[arg(long)]
- pub label: Option<String>,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct ListingFileArgs {
- pub file: PathBuf,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct ListingMutationArgs {
- pub file: PathBuf,
- #[arg(long)]
- pub idempotency_key: Option<String>,
- #[arg(long = "signer-session-id")]
- pub signer_session_id: Option<String>,
- #[arg(long = "print-job", action = ArgAction::SetTrue)]
- pub print_job: bool,
- #[arg(long = "print-event", action = ArgAction::SetTrue)]
- pub print_event: bool,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct JobArgs {
- #[command(subcommand)]
- pub command: JobCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum JobCommand {
- #[command(name = "list", visible_alias = "ls", about = "List background jobs")]
- Ls,
- #[command(about = "Show one background job")]
- Get(RecordKeyArgs),
- #[command(about = "Watch a background job")]
- Watch(JobWatchArgs),
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct JobWatchArgs {
- pub key: String,
- #[arg(long)]
- pub frames: Option<usize>,
- #[arg(long, default_value_t = 1_000)]
- pub interval_ms: u64,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct RpcArgs {
- #[command(subcommand)]
- pub command: RpcCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum RpcCommand {
- Status,
- Sessions,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct RuntimeArgs {
- #[command(subcommand)]
- pub command: RuntimeCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum RuntimeCommand {
- Install(RuntimeTargetArgs),
- Uninstall(RuntimeTargetArgs),
- Status(RuntimeTargetArgs),
- Start(RuntimeTargetArgs),
- Stop(RuntimeTargetArgs),
- Restart(RuntimeTargetArgs),
- Logs(RuntimeTargetArgs),
- Config(RuntimeConfigArgs),
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct RuntimeTargetArgs {
- pub runtime: String,
- #[arg(long)]
- pub instance: Option<String>,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct RuntimeConfigArgs {
- #[command(subcommand)]
- pub command: RuntimeConfigCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum RuntimeConfigCommand {
- Show(RuntimeTargetArgs),
- Set(RuntimeConfigSetArgs),
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct RuntimeConfigSetArgs {
- #[command(flatten)]
- pub target: RuntimeTargetArgs,
- pub key: String,
- pub value: String,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct OrderArgs {
- #[command(subcommand)]
- pub command: OrderCommand,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum OrderCommand {
- #[command(
- name = "create",
- visible_alias = "new",
- about = "Create a local order draft"
- )]
- New(OrderNewArgs),
- #[command(name = "view", visible_alias = "get", about = "Show one order")]
- Get(RecordKeyArgs),
- #[command(name = "list", visible_alias = "ls", about = "List local orders")]
- Ls,
- #[command(about = "Submit a local order draft")]
- Submit(OrderSubmitArgs),
- #[command(about = "Watch a submitted order")]
- Watch(OrderWatchArgs),
- #[command(about = "Explain durable order cancel availability")]
- Cancel(RecordKeyArgs),
- #[command(about = "Show submitted order history")]
- History,
-}
-
-#[derive(Debug, Clone, Args, Default)]
-pub struct OrderNewArgs {
- #[arg(long)]
- pub listing: Option<String>,
- #[arg(long = "listing-addr")]
- pub listing_addr: Option<String>,
- #[arg(long = "bin")]
- pub bin_id: Option<String>,
- #[arg(long = "qty")]
- pub bin_count: Option<u32>,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct OrderSubmitArgs {
- pub key: String,
- #[arg(long, action = ArgAction::SetTrue)]
- pub watch: bool,
- #[arg(long)]
- pub idempotency_key: Option<String>,
- #[arg(long = "signer-session-id")]
- pub signer_session_id: Option<String>,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct OrderWatchArgs {
- pub key: String,
- #[arg(long)]
- pub frames: Option<usize>,
- #[arg(long, default_value_t = 1_000)]
- pub interval_ms: u64,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct RecordKeyArgs {
- pub key: String,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SellArgs {
- #[command(subcommand)]
- pub command: SellCommand,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SellAddArgs {
- pub product: String,
- #[arg(long)]
- pub file: Option<PathBuf>,
- #[arg(long)]
- pub title: Option<String>,
- #[arg(long)]
- pub category: Option<String>,
- #[arg(long)]
- pub summary: Option<String>,
- #[arg(long = "pack")]
- pub pack: Option<String>,
- #[arg(long = "price")]
- pub price_expr: Option<String>,
- #[arg(long = "stock")]
- pub stock: Option<String>,
-}
-
-#[derive(Debug, Clone, Subcommand)]
-pub enum SellCommand {
- #[command(about = "Create a listing draft")]
- Add(SellAddArgs),
- #[command(about = "Show a local listing draft")]
- Show(SellShowArgs),
- #[command(about = "Check a listing draft")]
- Check(ListingFileArgs),
- #[command(about = "Publish a listing draft")]
- Publish(ListingMutationArgs),
- #[command(about = "Update a published listing from a draft")]
- Update(ListingMutationArgs),
- #[command(about = "Pause a published listing")]
- Pause(ListingMutationArgs),
- #[command(about = "Change the price in a local listing draft")]
- Reprice(SellRepriceArgs),
- #[command(about = "Change the available stock in a local listing draft")]
- Restock(SellRestockArgs),
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SellShowArgs {
- pub file: PathBuf,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SellRepriceArgs {
- pub file: PathBuf,
- pub price_expr: String,
-}
-
-#[derive(Debug, Clone, Args)]
-pub struct SellRestockArgs {
- pub file: PathBuf,
- pub available: String,
-}
diff --git a/src/commands/doctor.rs b/src/commands/doctor.rs
@@ -1,414 +0,0 @@
-use crate::domain::runtime::{
- CommandDisposition, CommandOutput, CommandView, DoctorCheckView, DoctorView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::{RuntimeConfig, SignerBackend};
-use crate::runtime::logging::LoggingState;
-use crate::runtime::provider::{resolve_hyf_provider, resolve_workflow_provider};
-use crate::runtime::signer::resolve_signer_status;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-enum DoctorSeverity {
- Ok,
- Warn,
- ExternalFail,
- InternalFail,
-}
-
-impl DoctorSeverity {
- fn status(self) -> &'static str {
- match self {
- Self::Ok => "ok",
- Self::Warn => "warn",
- Self::ExternalFail | Self::InternalFail => "fail",
- }
- }
-
- fn command_disposition(self) -> CommandDisposition {
- match self {
- Self::Ok => CommandDisposition::Success,
- Self::Warn => CommandDisposition::Unconfigured,
- Self::ExternalFail => CommandDisposition::ExternalUnavailable,
- Self::InternalFail => CommandDisposition::InternalError,
- }
- }
-}
-
-struct EvaluatedCheck {
- severity: DoctorSeverity,
- view: DoctorCheckView,
- action: Option<&'static str>,
-}
-
-pub fn report(
- config: &RuntimeConfig,
- logging: &LoggingState,
-) -> Result<CommandOutput, RuntimeError> {
- let mut checks = Vec::new();
- checks.push(config_check(config));
- let account_resolution = crate::runtime::accounts::resolve_account_resolution(config)?;
- checks.push(account_check(config, &account_resolution)?);
- checks.push(relay_check(config));
-
- let signer = resolve_signer_status(config);
- checks.push(signer_check(&signer));
-
- if matches!(config.signer.backend, SignerBackend::Myc) {
- if let Some(myc) = signer.myc.as_ref() {
- checks.push(myc_check(myc));
- }
- }
-
- checks.push(hyf_check(&resolve_hyf_provider(config)));
- checks.push(workflow_check(&resolve_workflow_provider(config)));
- checks.push(logging_check(config, logging));
- checks.push(binding_check(config));
-
- let severity = checks
- .iter()
- .map(|check| check.severity)
- .max()
- .unwrap_or(DoctorSeverity::Ok);
- let actions = collect_actions(&checks);
- let view = DoctorView {
- ok: severity == DoctorSeverity::Ok,
- state: severity.status().to_owned(),
- account_resolution: crate::runtime::accounts::account_resolution_view(&account_resolution),
- checks: checks.into_iter().map(|check| check.view).collect(),
- source: doctor_source(config),
- actions,
- };
-
- Ok(match severity.command_disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::Doctor(view)),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(CommandView::Doctor(view)),
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::Doctor(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::Doctor(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::Doctor(view))
- }
- })
-}
-
-fn config_check(config: &RuntimeConfig) -> EvaluatedCheck {
- let detail = match (
- config.paths.app_config_path.exists(),
- config
- .paths
- .workspace_config_path
- .as_ref()
- .is_some_and(|path| path.exists()),
- ) {
- (false, false) => "defaults active".to_owned(),
- (true, false) => "app config root present".to_owned(),
- (false, true) => "workspace config root present".to_owned(),
- (true, true) => "app and workspace config roots present".to_owned(),
- };
-
- EvaluatedCheck {
- severity: DoctorSeverity::Ok,
- view: DoctorCheckView {
- name: "config".to_owned(),
- status: "ok".to_owned(),
- detail,
- },
- action: None,
- }
-}
-
-fn account_check(
- config: &RuntimeConfig,
- account_resolution: &crate::runtime::accounts::AccountResolution,
-) -> Result<EvaluatedCheck, RuntimeError> {
- let snapshot = crate::runtime::accounts::snapshot(config)?;
- if snapshot.accounts.is_empty() {
- return Ok(EvaluatedCheck {
- severity: DoctorSeverity::Warn,
- view: DoctorCheckView {
- name: "account".to_owned(),
- status: "warn".to_owned(),
- detail: format!(
- "no local accounts found in {}",
- config.account.store_path.display()
- ),
- },
- action: Some("radroots account new"),
- });
- }
-
- match account_resolution.resolved_account.as_ref() {
- Some(account) => {
- let detail = match account_resolution.source {
- crate::runtime::accounts::AccountResolutionSource::InvocationOverride => {
- match account_resolution.default_account.as_ref() {
- Some(default) if default.record.account_id != account.record.account_id => {
- format!(
- "resolved account {} via invocation override; default account {} remains stored",
- account.record.account_id, default.record.account_id
- )
- }
- Some(default) => format!(
- "resolved account {} via invocation override; default account {} is also stored",
- account.record.account_id, default.record.account_id
- ),
- None => format!(
- "resolved account {} via invocation override; no default account is stored",
- account.record.account_id
- ),
- }
- }
- crate::runtime::accounts::AccountResolutionSource::DefaultAccount => {
- format!(
- "resolved account {} via default account",
- account.record.account_id
- )
- }
- crate::runtime::accounts::AccountResolutionSource::None => {
- format!("resolved account {}", account.record.account_id)
- }
- };
- Ok(EvaluatedCheck {
- severity: DoctorSeverity::Ok,
- view: DoctorCheckView {
- name: "account".to_owned(),
- status: "ok".to_owned(),
- detail,
- },
- action: None,
- })
- }
- None => Ok(EvaluatedCheck {
- severity: DoctorSeverity::Warn,
- view: DoctorCheckView {
- name: "account".to_owned(),
- status: "warn".to_owned(),
- detail: crate::runtime::accounts::unresolved_account_reason(config)?,
- },
- action: Some("radroots account ls"),
- }),
- }
-}
-
-fn signer_check(signer: &crate::domain::runtime::SignerStatusView) -> EvaluatedCheck {
- let (severity, detail, action) = match signer.state.as_str() {
- "ready" => (DoctorSeverity::Ok, format!("{} ready", signer.mode), None),
- "unconfigured" => (
- DoctorSeverity::Warn,
- signer
- .reason
- .clone()
- .unwrap_or_else(|| format!("{} signer is not configured", signer.mode)),
- Some("radroots signer status get"),
- ),
- "degraded" | "unavailable" => (
- DoctorSeverity::ExternalFail,
- signer
- .reason
- .clone()
- .unwrap_or_else(|| format!("{} signer is unavailable", signer.mode)),
- Some("radroots signer status get"),
- ),
- _ => (
- DoctorSeverity::InternalFail,
- signer
- .reason
- .clone()
- .unwrap_or_else(|| format!("{} signer reported an internal error", signer.mode)),
- Some("radroots --format json signer status get"),
- ),
- };
-
- EvaluatedCheck {
- severity,
- view: DoctorCheckView {
- name: "signer".to_owned(),
- status: severity.status().to_owned(),
- detail,
- },
- action,
- }
-}
-
-fn relay_check(config: &RuntimeConfig) -> EvaluatedCheck {
- if config.relay.urls.is_empty() {
- return EvaluatedCheck {
- severity: DoctorSeverity::Warn,
- view: DoctorCheckView {
- name: "relays".to_owned(),
- status: "warn".to_owned(),
- detail: "no relays configured".to_owned(),
- },
- action: Some("radroots relay ls"),
- };
- }
-
- EvaluatedCheck {
- severity: DoctorSeverity::Ok,
- view: DoctorCheckView {
- name: "relays".to_owned(),
- status: "ok".to_owned(),
- detail: format!(
- "{} configured · policy {}",
- config.relay.urls.len(),
- config.relay.publish_policy.as_str()
- ),
- },
- action: None,
- }
-}
-
-fn myc_check(myc: &crate::domain::runtime::MycStatusView) -> EvaluatedCheck {
- let (severity, detail, action) = match myc.state.as_str() {
- "ready" => (
- DoctorSeverity::Ok,
- myc.service_status
- .clone()
- .unwrap_or_else(|| "service ready".to_owned()),
- None,
- ),
- "unconfigured" => (
- DoctorSeverity::Warn,
- myc.reason
- .clone()
- .unwrap_or_else(|| "myc is not configured".to_owned()),
- Some("radroots signer status get"),
- ),
- _ => (
- DoctorSeverity::ExternalFail,
- myc.reason
- .clone()
- .unwrap_or_else(|| "myc is unavailable".to_owned()),
- Some("radroots signer status get"),
- ),
- };
-
- EvaluatedCheck {
- severity,
- view: DoctorCheckView {
- name: "myc".to_owned(),
- status: severity.status().to_owned(),
- detail,
- },
- action,
- }
-}
-
-fn hyf_check(hyf: &crate::runtime::provider::HyfProviderView) -> EvaluatedCheck {
- let (severity, detail) = match hyf.state.as_str() {
- "disabled" => (
- DoctorSeverity::Ok,
- hyf.reason
- .clone()
- .unwrap_or_else(|| "disabled by config".to_owned()),
- ),
- "ready" => (
- DoctorSeverity::Ok,
- hyf.reason
- .clone()
- .unwrap_or_else(|| "healthy · protocol 1 · deterministic available".to_owned()),
- ),
- _ => (
- DoctorSeverity::ExternalFail,
- hyf.reason
- .clone()
- .unwrap_or_else(|| "hyf is unavailable".to_owned()),
- ),
- };
-
- EvaluatedCheck {
- severity,
- view: DoctorCheckView {
- name: "hyf".to_owned(),
- status: severity.status().to_owned(),
- detail,
- },
- action: None,
- }
-}
-
-fn workflow_check(workflow: &crate::runtime::provider::WorkflowProviderView) -> EvaluatedCheck {
- let severity = match workflow.state.as_str() {
- "ready" => DoctorSeverity::Ok,
- "not_configured" | "disabled" | "unavailable" => DoctorSeverity::Warn,
- "unsupported" | "incompatible" => DoctorSeverity::ExternalFail,
- _ => DoctorSeverity::InternalFail,
- };
-
- EvaluatedCheck {
- severity,
- view: DoctorCheckView {
- name: "workflow".to_owned(),
- status: severity.status().to_owned(),
- detail: workflow.detail(),
- },
- action: None,
- }
-}
-
-fn logging_check(config: &RuntimeConfig, logging: &LoggingState) -> EvaluatedCheck {
- let detail = match (config.logging.stdout, logging.current_file.as_ref()) {
- (true, Some(path)) => format!("stdout + file {}", path.display()),
- (true, None) => "stdout only".to_owned(),
- (false, Some(path)) => format!("file {}", path.display()),
- (false, None) => "stdout off · no file sink".to_owned(),
- };
-
- EvaluatedCheck {
- severity: DoctorSeverity::Ok,
- view: DoctorCheckView {
- name: "logging".to_owned(),
- status: "ok".to_owned(),
- detail,
- },
- action: None,
- }
-}
-
-fn binding_check(config: &RuntimeConfig) -> EvaluatedCheck {
- let inspections = config.inspect_capability_bindings();
- let mut configured = 0usize;
- let mut disabled = 0usize;
- let mut not_configured = 0usize;
- for inspection in inspections {
- match inspection.state.as_str() {
- "configured" => configured += 1,
- "disabled" => disabled += 1,
- _ => not_configured += 1,
- }
- }
-
- EvaluatedCheck {
- severity: DoctorSeverity::Ok,
- view: DoctorCheckView {
- name: "bindings".to_owned(),
- status: "ok".to_owned(),
- detail: format!(
- "{configured} configured · {disabled} disabled · {not_configured} not configured"
- ),
- },
- action: None,
- }
-}
-
-fn collect_actions(checks: &[EvaluatedCheck]) -> Vec<String> {
- let mut actions = Vec::new();
- for action in checks.iter().filter_map(|check| check.action) {
- if !actions.iter().any(|existing| existing == action) {
- actions.push(action.to_owned());
- }
- }
- actions
-}
-
-fn doctor_source(config: &RuntimeConfig) -> String {
- let mut sources = vec!["local diagnostics"];
- if matches!(config.signer.backend, SignerBackend::Myc) {
- sources.push("myc status command");
- }
- if config.hyf.enabled {
- sources.push("hyf status control request");
- }
- sources.join(" + ")
-}
diff --git a/src/commands/farm.rs b/src/commands/farm.rs
@@ -1,123 +0,0 @@
-use crate::cli::{FarmInitArgs, FarmPublishArgs, FarmScopedArgs, FarmSetArgs, FarmSetupArgs};
-use crate::domain::runtime::{
- CommandDisposition, CommandOutput, CommandView, FarmGetView, FarmPublishView, FarmSetView,
- FarmSetupView, FarmStatusView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn setup(config: &RuntimeConfig, args: &FarmSetupArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::farm::setup(config, args)?;
- Ok(farm_setup_output(view))
-}
-
-pub fn init(config: &RuntimeConfig, args: &FarmInitArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::farm::init(config, args)?;
- Ok(farm_setup_output(view))
-}
-
-pub fn set(config: &RuntimeConfig, args: &FarmSetArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::farm::set(config, args)?;
- Ok(farm_set_output(view))
-}
-
-pub fn publish(
- config: &RuntimeConfig,
- args: &FarmPublishArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::farm::publish(config, args)?;
- Ok(farm_publish_output(view))
-}
-
-pub fn status(
- config: &RuntimeConfig,
- args: &FarmScopedArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::farm::status(config, args)?;
- Ok(farm_status_output(view))
-}
-
-pub fn get(config: &RuntimeConfig, args: &FarmScopedArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::farm::get(config, args)?;
- Ok(farm_get_output(view))
-}
-
-fn farm_publish_output(view: FarmPublishView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::FarmPublish(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::FarmPublish(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::FarmPublish(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::FarmPublish(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::FarmPublish(view))
- }
- }
-}
-
-fn farm_setup_output(view: FarmSetupView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::FarmSetup(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::FarmSetup(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::FarmSetup(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::FarmSetup(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::FarmSetup(view))
- }
- }
-}
-
-fn farm_set_output(view: FarmSetView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::FarmSet(view)),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(CommandView::FarmSet(view)),
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::FarmSet(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::FarmSet(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::FarmSet(view))
- }
- }
-}
-
-fn farm_status_output(view: FarmStatusView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::FarmStatus(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::FarmStatus(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::FarmStatus(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::FarmStatus(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::FarmStatus(view))
- }
- }
-}
-
-fn farm_get_output(view: FarmGetView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::FarmGet(view)),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(CommandView::FarmGet(view)),
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::FarmGet(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::FarmGet(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::FarmGet(view))
- }
- }
-}
diff --git a/src/commands/find.rs b/src/commands/find.rs
@@ -1,17 +0,0 @@
-use crate::cli::FindArgs;
-use crate::domain::runtime::{CommandDisposition, CommandOutput, CommandView};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn search(config: &RuntimeConfig, args: &FindArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::find::search(config, args)?;
- Ok(match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::Find(view)),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(CommandView::Find(view)),
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::Find(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::Find(view)),
- CommandDisposition::InternalError => CommandOutput::internal_error(CommandView::Find(view)),
- })
-}
diff --git a/src/commands/identity.rs b/src/commands/identity.rs
@@ -1,202 +0,0 @@
-use crate::cli::AccountImportArgs;
-use crate::domain::runtime::{
- AccountClearDefaultView, AccountImportView, AccountListView, AccountNewView, AccountRemoveView,
- AccountSummaryView, AccountUseView, AccountWhoamiView, CommandDisposition, CommandOutput,
- CommandView, IdentityPublicView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::accounts::{
- AccountCreateMode, AccountRecordView, SHARED_ACCOUNT_STORE_SOURCE, account_resolution_view,
- account_summary_view, clear_default_account, create_or_migrate_default_account,
- import_public_identity, remove_account as remove_stored_account, resolve_account_resolution,
- select_account, snapshot, unresolved_account_reason,
-};
-use crate::runtime::config::RuntimeConfig;
-
-pub fn init(config: &RuntimeConfig) -> Result<AccountNewView, RuntimeError> {
- let result = create_or_migrate_default_account(config)?;
- let account = account_summary(&result.account);
- Ok(AccountNewView {
- state: match result.mode {
- AccountCreateMode::Created => "created".to_owned(),
- AccountCreateMode::Migrated => "migrated".to_owned(),
- },
- source: match result.mode {
- AccountCreateMode::Created => SHARED_ACCOUNT_STORE_SOURCE.to_owned(),
- AccountCreateMode::Migrated => "legacy shared identity import · local first".to_owned(),
- },
- public_identity: IdentityPublicView::from_public_identity(
- &result.account.record.public_identity,
- ),
- account,
- actions: vec![
- "radroots account whoami".to_owned(),
- "radroots account ls".to_owned(),
- ],
- })
-}
-
-pub fn show(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let resolution = resolve_account_resolution(config)?;
- let snapshot = snapshot(config)?;
- let view = match resolution.resolved_account.as_ref() {
- Some(account) => AccountWhoamiView {
- state: "ready".to_owned(),
- source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(),
- reason: None,
- account_resolution: account_resolution_view(&resolution),
- public_identity: Some(IdentityPublicView::from_public_identity(
- &account.record.public_identity,
- )),
- actions: Vec::new(),
- },
- None => AccountWhoamiView {
- state: "unconfigured".to_owned(),
- source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(),
- reason: Some(unresolved_account_reason(config)?),
- account_resolution: account_resolution_view(&resolution),
- public_identity: None,
- actions: unresolved_account_actions(snapshot.accounts.is_empty()),
- },
- };
-
- Ok(match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::AccountWhoami(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::AccountWhoami(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::AccountWhoami(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::AccountWhoami(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::AccountWhoami(view))
- }
- })
-}
-
-pub fn import(
- config: &RuntimeConfig,
- args: &AccountImportArgs,
-) -> Result<AccountImportView, RuntimeError> {
- let account = import_public_identity(config, args.path.as_path(), args.default)?;
- let account_view = account_summary(&account);
- Ok(AccountImportView {
- state: "imported".to_owned(),
- source: "shared account store · watch-only import".to_owned(),
- public_identity: IdentityPublicView::from_public_identity(&account.record.public_identity),
- actions: if account.is_default {
- vec![
- "radroots account view".to_owned(),
- "radroots account list".to_owned(),
- ]
- } else {
- vec![
- "radroots account list".to_owned(),
- "radroots account select <selector>".to_owned(),
- ]
- },
- account: account_view,
- })
-}
-
-pub fn list(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let snapshot = snapshot(config)?;
- let accounts = snapshot
- .accounts
- .iter()
- .map(account_summary)
- .collect::<Vec<_>>();
- let actions = if accounts.is_empty() {
- vec![
- "radroots account create".to_owned(),
- "radroots account import <path>".to_owned(),
- ]
- } else {
- Vec::new()
- };
- Ok(CommandOutput::success(CommandView::AccountList(
- AccountListView {
- source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(),
- count: accounts.len(),
- accounts,
- actions,
- },
- )))
-}
-
-pub fn use_account(config: &RuntimeConfig, selector: &str) -> Result<AccountUseView, RuntimeError> {
- let account = select_account(config, selector)?;
- Ok(AccountUseView {
- state: "default".to_owned(),
- source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(),
- default_account_id: account.record.account_id.to_string(),
- account: account_summary(&account),
- })
-}
-
-pub fn clear_default(config: &RuntimeConfig) -> Result<AccountClearDefaultView, RuntimeError> {
- let result = clear_default_account(config)?;
- let cleared_account = result.cleared_account.as_ref().map(account_summary);
- Ok(AccountClearDefaultView {
- state: if cleared_account.is_some() {
- "cleared".to_owned()
- } else {
- "already_clear".to_owned()
- },
- source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(),
- actions: follow_up_account_actions(result.remaining_account_count),
- cleared_account,
- remaining_account_count: result.remaining_account_count,
- })
-}
-
-pub fn remove(config: &RuntimeConfig, selector: &str) -> Result<AccountRemoveView, RuntimeError> {
- let result = remove_stored_account(config, selector)?;
- Ok(AccountRemoveView {
- state: "removed".to_owned(),
- source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(),
- removed_account: account_summary(&result.removed_account),
- default_cleared: result.default_cleared,
- remaining_account_count: result.remaining_account_count,
- actions: if result.default_cleared {
- follow_up_account_actions(result.remaining_account_count)
- } else {
- Vec::new()
- },
- })
-}
-
-fn account_summary(account: &AccountRecordView) -> AccountSummaryView {
- account_summary_view(account)
-}
-
-fn unresolved_account_actions(has_accounts: bool) -> Vec<String> {
- if has_accounts {
- vec![
- "radroots account list".to_owned(),
- "radroots account select <selector>".to_owned(),
- ]
- } else {
- vec![
- "radroots account create".to_owned(),
- "radroots account import <path>".to_owned(),
- ]
- }
-}
-
-fn follow_up_account_actions(remaining_account_count: usize) -> Vec<String> {
- if remaining_account_count == 0 {
- vec![
- "radroots account create".to_owned(),
- "radroots account import <path>".to_owned(),
- ]
- } else {
- vec![
- "radroots account list".to_owned(),
- "radroots account select <selector>".to_owned(),
- ]
- }
-}
diff --git a/src/commands/job.rs b/src/commands/job.rs
@@ -1,16 +0,0 @@
-use crate::cli::JobWatchArgs;
-use crate::domain::runtime::CommandOutput;
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn list(config: &RuntimeConfig) -> CommandOutput {
- crate::runtime::job::list(config)
-}
-
-pub fn get(config: &RuntimeConfig, job_id: &str) -> CommandOutput {
- crate::runtime::job::get(config, job_id)
-}
-
-pub fn watch(config: &RuntimeConfig, args: &JobWatchArgs) -> Result<CommandOutput, RuntimeError> {
- crate::runtime::job::watch(config, args)
-}
diff --git a/src/commands/listing.rs b/src/commands/listing.rs
@@ -1,127 +0,0 @@
-use crate::cli::{ListingFileArgs, ListingMutationArgs, ListingNewArgs, RecordKeyArgs};
-use crate::domain::runtime::{CommandOutput, CommandView};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn new(config: &RuntimeConfig, args: &ListingNewArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::scaffold(config, args)?;
- Ok(match view.disposition() {
- crate::domain::runtime::CommandDisposition::Success => {
- CommandOutput::success(CommandView::ListingNew(view))
- }
- crate::domain::runtime::CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::ListingNew(view))
- }
- crate::domain::runtime::CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::ListingNew(view))
- }
- crate::domain::runtime::CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::ListingNew(view))
- }
- crate::domain::runtime::CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::ListingNew(view))
- }
- })
-}
-
-pub fn validate(
- config: &RuntimeConfig,
- args: &ListingFileArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::validate(config, args)?;
- Ok(CommandOutput::success(CommandView::ListingValidate(view)))
-}
-
-pub fn get(config: &RuntimeConfig, args: &RecordKeyArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::get(config, args)?;
- let output = match view.disposition() {
- crate::domain::runtime::CommandDisposition::Success => {
- CommandOutput::success(CommandView::ListingGet(view))
- }
- crate::domain::runtime::CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::ListingGet(view))
- }
- crate::domain::runtime::CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::ListingGet(view))
- }
- crate::domain::runtime::CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::ListingGet(view))
- }
- crate::domain::runtime::CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::ListingGet(view))
- }
- };
- Ok(output)
-}
-
-pub fn publish(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::publish(config, args)?;
- Ok(match view.disposition() {
- crate::domain::runtime::CommandDisposition::Success => {
- CommandOutput::success(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::ListingMutation(view))
- }
- })
-}
-
-pub fn update(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::update(config, args)?;
- Ok(match view.disposition() {
- crate::domain::runtime::CommandDisposition::Success => {
- CommandOutput::success(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::ListingMutation(view))
- }
- })
-}
-
-pub fn archive(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::archive(config, args)?;
- Ok(match view.disposition() {
- crate::domain::runtime::CommandDisposition::Success => {
- CommandOutput::success(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::ListingMutation(view))
- }
- crate::domain::runtime::CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::ListingMutation(view))
- }
- })
-}
diff --git a/src/commands/local.rs b/src/commands/local.rs
@@ -1,73 +0,0 @@
-use crate::cli::{LocalBackupArgs, LocalExportArgs};
-use crate::domain::runtime::{CommandDisposition, CommandOutput, CommandView};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn init(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- Ok(CommandOutput::success(CommandView::LocalInit(
- crate::runtime::local::init(config)?,
- )))
-}
-
-pub fn status(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::local::status(config)?;
- Ok(match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::LocalStatus(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::LocalStatus(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::LocalStatus(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::LocalStatus(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::LocalStatus(view))
- }
- })
-}
-
-pub fn backup(
- config: &RuntimeConfig,
- args: &LocalBackupArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::local::backup(config, args.output.as_path())?;
- Ok(match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::LocalBackup(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::LocalBackup(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::LocalBackup(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::LocalBackup(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::LocalBackup(view))
- }
- })
-}
-
-pub fn export(
- config: &RuntimeConfig,
- args: &LocalExportArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::local::export(config, args.format, args.output.as_path())?;
- Ok(match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::LocalExport(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::LocalExport(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::LocalExport(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::LocalExport(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::LocalExport(view))
- }
- })
-}
diff --git a/src/commands/market.rs b/src/commands/market.rs
@@ -1,163 +0,0 @@
-use radroots_events::kinds::KIND_LISTING;
-use radroots_events_codec::trade::RadrootsTradeListingAddress;
-
-use crate::cli::{FindArgs, RecordKeyArgs};
-use crate::domain::runtime::{
- CommandDisposition, CommandOutput, CommandView, FindView, ListingGetView, SyncActionView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn update(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let view = market_update_view(crate::runtime::sync::pull(config)?);
- Ok(market_update_output(view))
-}
-
-pub fn search(config: &RuntimeConfig, args: &FindArgs) -> Result<CommandOutput, RuntimeError> {
- let view = market_search_view(crate::runtime::find::search(config, args)?);
- Ok(market_search_output(view))
-}
-
-pub fn view(config: &RuntimeConfig, args: &RecordKeyArgs) -> Result<CommandOutput, RuntimeError> {
- let view = market_view_view(crate::runtime::listing::get(config, args)?);
- Ok(market_view_output(view))
-}
-
-fn market_update_view(mut view: SyncActionView) -> SyncActionView {
- view.actions = match view.state.as_str() {
- "ready" => vec!["radroots market search tomatoes".to_owned()],
- "unavailable" => vec![
- "radroots rpc status".to_owned(),
- "radroots runtime status radrootsd".to_owned(),
- "radroots sync status".to_owned(),
- ],
- "unconfigured" => {
- let mut actions = Vec::new();
- if view.replica_db == "missing" {
- actions.push("radroots local init".to_owned());
- }
- if view.relay_count == 0 {
- actions.push("radroots relay list --relay wss://relay.example.com".to_owned());
- }
- if actions.is_empty() {
- actions.extend(view.actions.clone());
- }
- actions
- }
- _ => view.actions.clone(),
- };
- view
-}
-
-fn market_search_view(mut view: FindView) -> FindView {
- view.actions = match view.state.as_str() {
- "ready" => view
- .results
- .first()
- .map(|result| {
- let mut actions = vec![format!("radroots market view {}", result.product_key)];
- if listing_addr_can_back_order(result.listing_addr.as_deref()) {
- actions.push(format!(
- "radroots order create --listing {}",
- result.product_key
- ));
- }
- actions
- })
- .unwrap_or_default(),
- "empty" => vec![
- "radroots market update".to_owned(),
- "radroots market search eggs".to_owned(),
- ],
- _ => view.actions.clone(),
- };
- view
-}
-
-fn market_view_view(mut view: ListingGetView) -> ListingGetView {
- view.actions = match view.state.as_str() {
- "ready" => {
- let listing_key = view
- .product_key
- .as_deref()
- .unwrap_or(view.lookup.as_str())
- .to_owned();
- if listing_addr_can_back_order(view.listing_addr.as_deref()) {
- vec![format!("radroots order create --listing {listing_key}")]
- } else {
- Vec::new()
- }
- }
- "missing" => vec![
- "radroots market search tomatoes".to_owned(),
- "radroots market update".to_owned(),
- ],
- "unconfigured" => vec![
- "radroots local init".to_owned(),
- "radroots market update".to_owned(),
- ],
- _ => view.actions.clone(),
- };
- view
-}
-
-fn market_update_output(view: SyncActionView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::MarketUpdate(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::MarketUpdate(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::MarketUpdate(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::MarketUpdate(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::MarketUpdate(view))
- }
- }
-}
-
-fn listing_addr_can_back_order(listing_addr: Option<&str>) -> bool {
- let Some(listing_addr) = listing_addr else {
- return false;
- };
- RadrootsTradeListingAddress::parse(listing_addr).is_ok_and(|parsed| parsed.kind == KIND_LISTING)
-}
-
-fn market_search_output(view: FindView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::MarketSearch(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::MarketSearch(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::MarketSearch(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::MarketSearch(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::MarketSearch(view))
- }
- }
-}
-
-fn market_view_output(view: ListingGetView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::MarketView(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::MarketView(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::MarketView(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::MarketView(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::MarketView(view))
- }
- }
-}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
@@ -1,185 +0,0 @@
-pub mod doctor;
-pub mod farm;
-pub mod find;
-pub mod identity;
-pub mod job;
-pub mod listing;
-pub mod local;
-pub mod market;
-pub mod myc;
-pub mod net;
-pub mod order;
-pub mod relay;
-pub mod rpc;
-pub mod runtime;
-pub mod sell;
-pub mod signer;
-pub mod sync;
-pub mod workflow;
-
-use crate::cli::{
- AccountCommand, Command, ConfigCommand, FarmCommand, JobCommand, ListingCommand, LocalCommand,
- MarketCommand, MycCommand, NetCommand, OrderCommand, RelayCommand, RpcCommand, RuntimeCommand,
- RuntimeConfigCommand, SellCommand, SignerCommand, SignerSessionCommand, SyncCommand,
-};
-use crate::domain::runtime::{CommandOutput, CommandView};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-use crate::runtime::logging::LoggingState;
-
-pub fn dispatch(
- command: &Command,
- config: &RuntimeConfig,
- logging: &LoggingState,
-) -> Result<CommandOutput, RuntimeError> {
- match command {
- Command::Account(account) => match &account.command {
- AccountCommand::New => Ok(CommandOutput::success(CommandView::AccountNew(
- identity::init(config)?,
- ))),
- AccountCommand::Import(args) => Ok(CommandOutput::success(CommandView::AccountImport(
- identity::import(config, args)?,
- ))),
- AccountCommand::Whoami => identity::show(config),
- AccountCommand::Ls => identity::list(config),
- AccountCommand::Use(args) => Ok(CommandOutput::success(CommandView::AccountUse(
- identity::use_account(config, args.selector.as_str())?,
- ))),
- AccountCommand::ClearDefault => Ok(CommandOutput::success(
- CommandView::AccountClearDefault(identity::clear_default(config)?),
- )),
- AccountCommand::Remove(args) => Ok(CommandOutput::success(CommandView::AccountRemove(
- identity::remove(config, args.selector.as_str())?,
- ))),
- },
- Command::Myc(myc) => match &myc.command {
- MycCommand::Status => Ok(myc::status(config)),
- },
- Command::Config(config_command) => match &config_command.command {
- ConfigCommand::Show => Ok(CommandOutput::success(CommandView::ConfigShow(
- runtime::show(config, logging)?,
- ))),
- },
- Command::Signer(signer) => match &signer.command {
- SignerCommand::Status => Ok(signer::status(config)),
- SignerCommand::Session(session) => match &session.command {
- SignerSessionCommand::List => Ok(signer::session_list(config)),
- SignerSessionCommand::Show { session_id } => {
- Ok(signer::session_show(config, session_id.as_str()))
- }
- SignerSessionCommand::ConnectBunker { url } => {
- Ok(signer::session_connect_bunker(config, url.as_str()))
- }
- SignerSessionCommand::ConnectNostrconnect {
- url,
- client_secret_key,
- } => Ok(signer::session_connect_nostrconnect(
- config,
- url.as_str(),
- client_secret_key.as_str(),
- )),
- SignerSessionCommand::PublicKey { session_id } => {
- Ok(signer::session_public_key(config, session_id.as_str()))
- }
- SignerSessionCommand::Authorize { session_id } => {
- Ok(signer::session_authorize(config, session_id.as_str()))
- }
- SignerSessionCommand::RequireAuth {
- session_id,
- auth_url,
- } => Ok(signer::session_require_auth(
- config,
- session_id.as_str(),
- auth_url.as_str(),
- )),
- SignerSessionCommand::Close { session_id } => {
- Ok(signer::session_close(config, session_id.as_str()))
- }
- },
- },
- Command::Doctor => doctor::report(config, logging),
- Command::Farm(farm_command) => match &farm_command.command {
- FarmCommand::Init(args) => farm::init(config, args),
- FarmCommand::Set(args) => farm::set(config, args),
- FarmCommand::Publish(args) => farm::publish(config, args),
- FarmCommand::Setup(args) => farm::setup(config, args),
- FarmCommand::Status(args) => farm::status(config, args),
- FarmCommand::Get(args) => farm::get(config, args),
- },
- Command::Find(find_args) => find::search(config, find_args),
- Command::Job(job) => match &job.command {
- JobCommand::Ls => Ok(job::list(config)),
- JobCommand::Get(args) => Ok(job::get(config, args.key.as_str())),
- JobCommand::Watch(args) => job::watch(config, args),
- },
- Command::Listing(listing) => match &listing.command {
- ListingCommand::New(args) => listing::new(config, args),
- ListingCommand::Validate(args) => listing::validate(config, args),
- ListingCommand::Get(args) => listing::get(config, args),
- ListingCommand::Publish(args) => listing::publish(config, args),
- ListingCommand::Update(args) => listing::update(config, args),
- ListingCommand::Archive(args) => listing::archive(config, args),
- },
- Command::Local(local) => match &local.command {
- LocalCommand::Init => local::init(config),
- LocalCommand::Status => local::status(config),
- LocalCommand::Export(args) => local::export(config, args),
- LocalCommand::Backup(args) => local::backup(config, args),
- },
- Command::Market(market) => match &market.command {
- MarketCommand::Update => market::update(config),
- MarketCommand::Search(args) => market::search(config, args),
- MarketCommand::View(args) => market::view(config, args),
- },
- Command::Net(net) => match &net.command {
- NetCommand::Status => net::status(config),
- },
- Command::Order(order) => match &order.command {
- OrderCommand::New(args) => order::new(config, args),
- OrderCommand::Get(args) => order::get(config, args),
- OrderCommand::Ls => order::list(config),
- OrderCommand::Submit(args) => order::submit(config, args),
- OrderCommand::Watch(args) => order::watch(config, args),
- OrderCommand::Cancel(args) => order::cancel(config, args),
- OrderCommand::History => order::history(config),
- },
- Command::Relay(relay) => match &relay.command {
- RelayCommand::Ls => Ok(relay::list(config)),
- },
- Command::Rpc(rpc) => match &rpc.command {
- RpcCommand::Status => Ok(rpc::status(config)),
- RpcCommand::Sessions => Ok(rpc::sessions(config)),
- },
- Command::Sell(sell) => match &sell.command {
- SellCommand::Add(args) => sell::add(config, args),
- SellCommand::Show(args) => sell::show(config, args),
- SellCommand::Check(args) => sell::check(config, args),
- SellCommand::Publish(args) => sell::publish(config, args),
- SellCommand::Update(args) => sell::update(config, args),
- SellCommand::Pause(args) => sell::pause(config, args),
- SellCommand::Reprice(args) => sell::reprice(config, args),
- SellCommand::Restock(args) => sell::restock(config, args),
- },
- Command::Setup(setup) => workflow::setup(config, setup),
- Command::Runtime(runtime_command) => match &runtime_command.command {
- RuntimeCommand::Install(args) => runtime::install(config, args),
- RuntimeCommand::Uninstall(args) => runtime::uninstall(config, args),
- RuntimeCommand::Status(args) => runtime::status(config, args),
- RuntimeCommand::Start(args) => runtime::start(config, args),
- RuntimeCommand::Stop(args) => runtime::stop(config, args),
- RuntimeCommand::Restart(args) => runtime::restart(config, args),
- RuntimeCommand::Logs(args) => runtime::logs(config, args),
- RuntimeCommand::Config(runtime_config) => match &runtime_config.command {
- RuntimeConfigCommand::Show(args) => runtime::config_show(config, logging, args),
- RuntimeConfigCommand::Set(args) => runtime::config_set(config, args),
- },
- },
- Command::Status => workflow::status(config),
- Command::Sync(sync) => match &sync.command {
- SyncCommand::Status => sync::status(config),
- SyncCommand::Pull => sync::pull(config),
- SyncCommand::Push => sync::push(config),
- SyncCommand::Watch(args) => sync::watch(config, args),
- },
- }
-}
diff --git a/src/commands/myc.rs b/src/commands/myc.rs
@@ -1,19 +0,0 @@
-use crate::domain::runtime::{CommandDisposition, CommandOutput, CommandView, MycStatusView};
-use crate::runtime::config::RuntimeConfig;
-
-pub fn status(config: &RuntimeConfig) -> CommandOutput {
- let view: MycStatusView = crate::runtime::myc::resolve_status(&config.myc);
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::MycStatus(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::MycStatus(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::MycStatus(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::MycStatus(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::MycStatus(view))
- }
- }
-}
diff --git a/src/commands/net.rs b/src/commands/net.rs
@@ -1,21 +0,0 @@
-use crate::domain::runtime::{CommandDisposition, CommandOutput, CommandView};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-use crate::runtime::network;
-
-pub fn status(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let view = network::net_status(config)?;
- Ok(match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::NetStatus(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::NetStatus(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::NetStatus(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::NetStatus(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::NetStatus(view))
- }
- })
-}
diff --git a/src/commands/order.rs b/src/commands/order.rs
@@ -1,148 +0,0 @@
-use crate::cli::{OrderNewArgs, OrderSubmitArgs, OrderWatchArgs, RecordKeyArgs};
-use crate::domain::runtime::{
- CommandDisposition, CommandOutput, CommandView, OrderSubmitView, OrderSubmitWatchView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::{
- CapabilityBindingTargetKind, OutputFormat, RuntimeConfig, WRITE_PLANE_TRADE_JSONRPC_CAPABILITY,
-};
-
-pub fn new(config: &RuntimeConfig, args: &OrderNewArgs) -> Result<CommandOutput, RuntimeError> {
- let mut view = crate::runtime::order::scaffold(config, args)?;
- rewrite_order_actions(&mut view.actions);
- Ok(command_output(
- view.disposition(),
- CommandView::OrderNew(view),
- ))
-}
-
-pub fn get(config: &RuntimeConfig, args: &RecordKeyArgs) -> Result<CommandOutput, RuntimeError> {
- let mut view = crate::runtime::order::get(config, args)?;
- rewrite_order_actions(&mut view.actions);
- Ok(command_output(
- view.disposition(),
- CommandView::OrderGet(view),
- ))
-}
-
-pub fn list(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let mut view = crate::runtime::order::list(config)?;
- rewrite_order_actions(&mut view.actions);
- Ok(command_output(
- view.disposition(),
- CommandView::OrderList(view),
- ))
-}
-
-pub fn submit(
- config: &RuntimeConfig,
- args: &OrderSubmitArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let mut view = crate::runtime::order::submit(config, args)?;
- rewrite_order_actions(&mut view.actions);
-
- if args.watch
- && config.output.format == OutputFormat::Human
- && should_watch_submitted_order(&view)
- {
- let watch_config = watch_runtime_config(config);
- let mut watch = crate::runtime::order::watch(
- &watch_config,
- &OrderWatchArgs {
- key: view.order_id.clone(),
- frames: None,
- interval_ms: 1_000,
- },
- )?;
- rewrite_order_actions(&mut watch.actions);
- let combined = OrderSubmitWatchView {
- submit: view,
- watch,
- };
- return Ok(command_output(
- combined.disposition(),
- CommandView::OrderSubmitWatch(combined),
- ));
- }
-
- Ok(command_output(
- view.disposition(),
- CommandView::OrderSubmit(view),
- ))
-}
-
-pub fn watch(config: &RuntimeConfig, args: &OrderWatchArgs) -> Result<CommandOutput, RuntimeError> {
- let mut view = crate::runtime::order::watch(config, args)?;
- rewrite_order_actions(&mut view.actions);
- Ok(command_output(
- view.disposition(),
- CommandView::OrderWatch(view),
- ))
-}
-
-pub fn cancel(config: &RuntimeConfig, args: &RecordKeyArgs) -> Result<CommandOutput, RuntimeError> {
- let mut view = crate::runtime::order::cancel(config, args)?;
- rewrite_order_actions(&mut view.actions);
- Ok(command_output(
- view.disposition(),
- CommandView::OrderCancel(view),
- ))
-}
-
-pub fn history(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let mut view = crate::runtime::order::history(config)?;
- rewrite_order_actions(&mut view.actions);
- Ok(command_output(
- view.disposition(),
- CommandView::OrderHistory(view),
- ))
-}
-
-fn should_watch_submitted_order(view: &OrderSubmitView) -> bool {
- !matches!(
- view.state.as_str(),
- "dry_run" | "error" | "missing" | "unavailable" | "unconfigured"
- ) && view
- .job
- .as_ref()
- .is_some_and(|job| job.job_id.as_str() != "not_submitted")
-}
-
-fn watch_runtime_config(config: &RuntimeConfig) -> RuntimeConfig {
- let mut watch_config = config.clone();
- if let Some(binding) = config.capability_binding(WRITE_PLANE_TRADE_JSONRPC_CAPABILITY) {
- if binding.target_kind == CapabilityBindingTargetKind::ExplicitEndpoint {
- watch_config.rpc.url = binding.target.clone();
- }
- }
- watch_config
-}
-
-fn rewrite_order_actions(actions: &mut Vec<String>) {
- for action in actions {
- *action = rewrite_order_action(action.as_str());
- }
-}
-
-fn rewrite_order_action(action: &str) -> String {
- if action == "radroots order new" {
- return "radroots order create".to_owned();
- }
- if action == "radroots order ls" {
- return "radroots order list".to_owned();
- }
- if let Some(key) = action.strip_prefix("radroots order get ") {
- return format!("radroots order view {key}");
- }
- action.to_owned()
-}
-
-fn command_output(disposition: CommandDisposition, view: CommandView) -> CommandOutput {
- match disposition {
- CommandDisposition::Success => CommandOutput::success(view),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(view),
- CommandDisposition::ExternalUnavailable => CommandOutput::external_unavailable(view),
- CommandDisposition::Unsupported => CommandOutput::unsupported(view),
- CommandDisposition::InternalError => CommandOutput::internal_error(view),
- }
-}
diff --git a/src/commands/relay.rs b/src/commands/relay.rs
@@ -1,20 +0,0 @@
-use crate::domain::runtime::{CommandDisposition, CommandOutput, CommandView};
-use crate::runtime::config::RuntimeConfig;
-use crate::runtime::network;
-
-pub fn list(config: &RuntimeConfig) -> CommandOutput {
- let view = network::relay_list(config);
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::RelayList(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::RelayList(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::RelayList(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::RelayList(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::RelayList(view))
- }
- }
-}
diff --git a/src/commands/rpc.rs b/src/commands/rpc.rs
@@ -1,10 +0,0 @@
-use crate::domain::runtime::CommandOutput;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn status(config: &RuntimeConfig) -> CommandOutput {
- crate::runtime::daemon::status(config)
-}
-
-pub fn sessions(config: &RuntimeConfig) -> CommandOutput {
- crate::runtime::daemon::sessions(config)
-}
diff --git a/src/commands/runtime.rs b/src/commands/runtime.rs
@@ -1,388 +0,0 @@
-use crate::cli::{RuntimeConfigSetArgs, RuntimeTargetArgs};
-use crate::domain::runtime::{
- AccountRuntimeView, AccountSecretRuntimeView, CapabilityBindingRuntimeView, CommandOutput,
- CommandView, ConfigFilesRuntimeView, ConfigShowView, HyfProviderRuntimeView, HyfRuntimeView,
- InteractionRuntimeView, LegacyPathRuntimeView, LocalRuntimeView, LoggingRuntimeView,
- MigrationRuntimeView, MycRuntimeView, OutputRuntimeView, PathsRuntimeView, RelayRuntimeView,
- ResolvedProviderRuntimeView, RpcRuntimeView, SignerRuntimeView, WorkflowRuntimeView,
- WritePlaneRuntimeView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-use crate::runtime::logging::LoggingState;
-use crate::runtime::management::{
- RuntimeCommandAvailability, RuntimeConfigMutationRequest, RuntimeLifecycleAction,
- inspect_action, inspect_config_set, inspect_config_show, inspect_logs, inspect_status,
-};
-use crate::runtime::provider::{
- resolve_capability_providers, resolve_hyf_provider, resolve_workflow_provider,
- resolve_write_plane_provider,
-};
-
-pub fn show(
- config: &RuntimeConfig,
- logging: &LoggingState,
-) -> Result<ConfigShowView, RuntimeError> {
- let secret_backend = crate::runtime::accounts::secret_backend_status(config);
- let write_plane = resolve_write_plane_provider(config);
- let workflow = resolve_workflow_provider(config);
- let hyf_provider = resolve_hyf_provider(config);
- let resolved_providers = resolve_capability_providers(config);
- Ok(ConfigShowView {
- source: "local runtime state".to_owned(),
- output: OutputRuntimeView {
- format: config.output.format.as_str().to_owned(),
- verbosity: config.output.verbosity.as_str().to_owned(),
- color: config.output.color,
- dry_run: config.output.dry_run,
- },
- interaction: InteractionRuntimeView {
- input_enabled: config.interaction.input_enabled,
- assume_yes: config.interaction.assume_yes,
- stdin_tty: config.interaction.stdin_tty,
- stdout_tty: config.interaction.stdout_tty,
- prompts_allowed: config.interaction.prompts_allowed,
- confirmations_allowed: config.interaction.confirmations_allowed,
- },
- config_files: ConfigFilesRuntimeView {
- user_present: config.paths.app_config_path.exists(),
- workspace_present: config
- .paths
- .workspace_config_path
- .as_ref()
- .is_some_and(|path| path.exists()),
- },
- paths: PathsRuntimeView {
- profile: config.paths.profile.clone(),
- profile_source: config.paths.profile_source.clone(),
- allowed_profiles: config.paths.allowed_profiles.clone(),
- root_source: config.paths.root_source.clone(),
- repo_local_root: config
- .paths
- .repo_local_root
- .as_ref()
- .map(|path| path.display().to_string()),
- repo_local_root_source: config.paths.repo_local_root_source.clone(),
- subordinate_path_override_source: config.paths.subordinate_path_override_source.clone(),
- app_namespace: config.paths.app_namespace.clone(),
- shared_accounts_namespace: config.paths.shared_accounts_namespace.clone(),
- shared_identities_namespace: config.paths.shared_identities_namespace.clone(),
- app_config_path: config.paths.app_config_path.display().to_string(),
- workspace_config_enabled: config.paths.workspace_config_path.is_some(),
- workspace_config_path: config
- .paths
- .workspace_config_path
- .as_ref()
- .map(|path| path.display().to_string()),
- app_data_root: config.paths.app_data_root.display().to_string(),
- app_logs_root: config.paths.app_logs_root.display().to_string(),
- shared_accounts_data_root: config.paths.shared_accounts_data_root.display().to_string(),
- shared_accounts_secrets_root: config
- .paths
- .shared_accounts_secrets_root
- .display()
- .to_string(),
- default_identity_path: config.paths.default_identity_path.display().to_string(),
- },
- migration: migration_runtime_view(config),
- logging: LoggingRuntimeView {
- initialized: logging.initialized,
- filter: config.logging.filter.clone(),
- stdout: config.logging.stdout,
- directory: config
- .logging
- .directory
- .as_ref()
- .map(|path| path.display().to_string()),
- current_file: logging
- .current_file
- .as_ref()
- .map(|path| path.display().to_string()),
- },
- account: AccountRuntimeView {
- selector: config.account.selector.clone(),
- store_path: config.account.store_path.display().to_string(),
- secrets_dir: config.account.secrets_dir.display().to_string(),
- identity_path: config.identity.path.display().to_string(),
- secret_backend: AccountSecretRuntimeView {
- contract_default_backend: config.account_secret_contract.default_backend.clone(),
- contract_default_fallback: config.account_secret_contract.default_fallback.clone(),
- allowed_backends: config.account_secret_contract.allowed_backends.clone(),
- host_vault_policy: config.account_secret_contract.host_vault_policy.clone(),
- uses_protected_store: config.account_secret_contract.uses_protected_store,
- configured_primary: secret_backend.configured_primary,
- configured_fallback: secret_backend.configured_fallback,
- state: secret_backend.state,
- active_backend: secret_backend.active_backend,
- used_fallback: secret_backend.used_fallback,
- reason: secret_backend.reason,
- },
- },
- signer: SignerRuntimeView {
- mode: config.signer.backend.as_str().to_owned(),
- },
- relay: RelayRuntimeView {
- count: config.relay.urls.len(),
- urls: config.relay.urls.clone(),
- publish_policy: config.relay.publish_policy.as_str().to_owned(),
- source: config.relay.source.as_str().to_owned(),
- },
- local: LocalRuntimeView {
- root: config.local.root.display().to_string(),
- replica_db_path: config.local.replica_db_path.display().to_string(),
- backups_dir: config.local.backups_dir.display().to_string(),
- exports_dir: config.local.exports_dir.display().to_string(),
- },
- myc: MycRuntimeView {
- executable: config.myc.executable.display().to_string(),
- status_timeout_ms: config.myc.status_timeout_ms,
- },
- write_plane: WritePlaneRuntimeView {
- provider_runtime_id: write_plane.provider_runtime_id,
- binding_model: write_plane.binding_model,
- state: write_plane.state,
- provenance: write_plane.provenance,
- source: write_plane.source,
- target_kind: write_plane.target_kind,
- target: write_plane.target,
- detail: write_plane.detail,
- bridge_auth_configured: write_plane.bridge_auth_configured,
- },
- workflow: WorkflowRuntimeView {
- provider_runtime_id: workflow.provider_runtime_id,
- binding_model: workflow.binding_model,
- state: workflow.state,
- provenance: workflow.provenance,
- source: workflow.source,
- target_kind: workflow.target_kind,
- target: workflow.target,
- hyf_helper_state: workflow.hyf_helper_state,
- hyf_helper_detail: workflow.hyf_helper_detail,
- },
- hyf_provider: HyfProviderRuntimeView {
- provider_runtime_id: hyf_provider.provider_runtime_id,
- binding_model: hyf_provider.binding_model,
- state: hyf_provider.state,
- provenance: hyf_provider.provenance,
- source: hyf_provider.source,
- target_kind: hyf_provider.target_kind,
- target: hyf_provider.target,
- executable: hyf_provider.executable,
- reason: hyf_provider.reason,
- protocol_version: hyf_provider.protocol_version,
- deterministic_available: hyf_provider.deterministic_available,
- },
- hyf: HyfRuntimeView {
- enabled: config.hyf.enabled,
- executable: config.hyf.executable.display().to_string(),
- },
- rpc: RpcRuntimeView {
- url: config.rpc.url.clone(),
- bridge_auth_configured: config.rpc.bridge_bearer_token.is_some(),
- },
- resolved_providers: resolved_providers
- .into_iter()
- .map(|provider| ResolvedProviderRuntimeView {
- capability_id: provider.capability_id,
- provider_runtime_id: provider.provider_runtime_id,
- binding_model: provider.binding_model,
- state: provider.state,
- provenance: provider.provenance,
- source: provider.source,
- target_kind: provider.target_kind,
- target: provider.target,
- })
- .collect(),
- capability_bindings: config
- .inspect_capability_bindings()
- .into_iter()
- .map(|binding| CapabilityBindingRuntimeView {
- capability_id: binding.capability_id,
- provider_runtime_id: binding.provider_runtime_id,
- binding_model: binding.binding_model,
- state: binding.state.as_str().to_owned(),
- source: binding.source,
- target_kind: binding.target_kind,
- target: binding.target,
- managed_account_ref: binding.managed_account_ref,
- signer_session_ref: binding.signer_session_ref,
- })
- .collect(),
- })
-}
-
-pub fn status(
- config: &RuntimeConfig,
- args: &RuntimeTargetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_status(config, args.runtime.as_str(), args.instance.as_deref())?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeStatus(inspection.view),
- ))
-}
-
-pub fn install(
- config: &RuntimeConfig,
- args: &RuntimeTargetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_action(
- config,
- args.runtime.as_str(),
- args.instance.as_deref(),
- RuntimeLifecycleAction::Install,
- )?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeAction(inspection.view),
- ))
-}
-
-pub fn uninstall(
- config: &RuntimeConfig,
- args: &RuntimeTargetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_action(
- config,
- args.runtime.as_str(),
- args.instance.as_deref(),
- RuntimeLifecycleAction::Uninstall,
- )?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeAction(inspection.view),
- ))
-}
-
-pub fn start(
- config: &RuntimeConfig,
- args: &RuntimeTargetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_action(
- config,
- args.runtime.as_str(),
- args.instance.as_deref(),
- RuntimeLifecycleAction::Start,
- )?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeAction(inspection.view),
- ))
-}
-
-pub fn stop(
- config: &RuntimeConfig,
- args: &RuntimeTargetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_action(
- config,
- args.runtime.as_str(),
- args.instance.as_deref(),
- RuntimeLifecycleAction::Stop,
- )?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeAction(inspection.view),
- ))
-}
-
-pub fn restart(
- config: &RuntimeConfig,
- args: &RuntimeTargetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_action(
- config,
- args.runtime.as_str(),
- args.instance.as_deref(),
- RuntimeLifecycleAction::Restart,
- )?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeAction(inspection.view),
- ))
-}
-
-pub fn logs(
- config: &RuntimeConfig,
- args: &RuntimeTargetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_logs(config, args.runtime.as_str(), args.instance.as_deref())?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeLogs(inspection.view),
- ))
-}
-
-pub fn config_show(
- config: &RuntimeConfig,
- _logging: &LoggingState,
- args: &RuntimeTargetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_config_show(config, args.runtime.as_str(), args.instance.as_deref())?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeConfigShow(inspection.view),
- ))
-}
-
-pub fn config_set(
- config: &RuntimeConfig,
- args: &RuntimeConfigSetArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let inspection = inspect_config_set(
- config,
- &RuntimeConfigMutationRequest {
- runtime_id: args.target.runtime.clone(),
- instance_id: args.target.instance.clone(),
- key: args.key.clone(),
- value: args.value.clone(),
- },
- )?;
- Ok(command_output(
- inspection.availability,
- CommandView::RuntimeAction(inspection.view),
- ))
-}
-
-fn migration_runtime_view(config: &RuntimeConfig) -> MigrationRuntimeView {
- let report = &config.migration.report;
- let detected_legacy_paths = report
- .detected_legacy_paths
- .iter()
- .map(|path| LegacyPathRuntimeView {
- id: path.id.clone(),
- description: path.description.clone(),
- path: path.path.display().to_string(),
- destination: path
- .destination
- .as_ref()
- .map(|destination| destination.display().to_string()),
- import_hint: path.import_hint.clone(),
- })
- .collect::<Vec<_>>();
- let actions = if detected_legacy_paths.is_empty() {
- Vec::new()
- } else {
- vec![
- "inspect detected_legacy_paths before writing new local state".to_owned(),
- "perform an explicit export/import or manual copy; startup did not move legacy data"
- .to_owned(),
- ]
- };
- MigrationRuntimeView {
- posture: report.posture.to_owned(),
- state: report.state.to_owned(),
- silent_startup_relocation: report.silent_startup_relocation,
- compatibility_window: report.compatibility_window.to_owned(),
- detected_legacy_paths,
- actions,
- }
-}
-
-fn command_output(availability: RuntimeCommandAvailability, view: CommandView) -> CommandOutput {
- match availability {
- RuntimeCommandAvailability::Success => CommandOutput::success(view),
- RuntimeCommandAvailability::Unconfigured => CommandOutput::unconfigured(view),
- RuntimeCommandAvailability::Unsupported => CommandOutput::unsupported(view),
- }
-}
diff --git a/src/commands/sell.rs b/src/commands/sell.rs
@@ -1,150 +0,0 @@
-use crate::cli::{
- ListingFileArgs, ListingMutationArgs, SellAddArgs, SellRepriceArgs, SellRestockArgs,
- SellShowArgs,
-};
-use crate::domain::runtime::{
- CommandDisposition, CommandOutput, CommandView, SellAddView, SellCheckView,
- SellDraftMutationView, SellMutationView, SellShowView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn add(config: &RuntimeConfig, args: &SellAddArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::sell_add(config, args)?;
- Ok(sell_add_output(view))
-}
-
-pub fn show(config: &RuntimeConfig, args: &SellShowArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::sell_show(config, args)?;
- Ok(sell_show_output(view))
-}
-
-pub fn check(
- config: &RuntimeConfig,
- args: &ListingFileArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::sell_check(config, args)?;
- Ok(sell_check_output(view))
-}
-
-pub fn publish(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::sell_publish(config, args)?;
- Ok(sell_mutation_output(view))
-}
-
-pub fn update(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::sell_update(config, args)?;
- Ok(sell_mutation_output(view))
-}
-
-pub fn pause(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::sell_pause(config, args)?;
- Ok(sell_mutation_output(view))
-}
-
-pub fn reprice(
- config: &RuntimeConfig,
- args: &SellRepriceArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::sell_reprice(config, args)?;
- Ok(sell_draft_mutation_output(view))
-}
-
-pub fn restock(
- config: &RuntimeConfig,
- args: &SellRestockArgs,
-) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::listing::sell_restock(config, args)?;
- Ok(sell_draft_mutation_output(view))
-}
-
-fn sell_add_output(view: SellAddView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::SellAdd(view)),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(CommandView::SellAdd(view)),
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::SellAdd(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::SellAdd(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::SellAdd(view))
- }
- }
-}
-
-fn sell_show_output(view: SellShowView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::SellShow(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::SellShow(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::SellShow(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::SellShow(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::SellShow(view))
- }
- }
-}
-
-fn sell_check_output(view: SellCheckView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::SellCheck(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::SellCheck(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::SellCheck(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::SellCheck(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::SellCheck(view))
- }
- }
-}
-
-fn sell_mutation_output(view: SellMutationView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::SellMutation(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::SellMutation(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::SellMutation(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::SellMutation(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::SellMutation(view))
- }
- }
-}
-
-fn sell_draft_mutation_output(view: SellDraftMutationView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::SellDraftMutation(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::SellDraftMutation(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::SellDraftMutation(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::SellDraftMutation(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::SellDraftMutation(view))
- }
- }
-}
diff --git a/src/commands/signer.rs b/src/commands/signer.rs
@@ -1,163 +0,0 @@
-use crate::domain::runtime::{
- CommandDisposition, CommandOutput, CommandView, SignerSessionActionView, SignerStatusView,
-};
-use crate::runtime::config::RuntimeConfig;
-use crate::runtime::daemon::DaemonRpcError;
-use crate::runtime::signer::resolve_signer_status;
-
-pub fn status(config: &RuntimeConfig) -> CommandOutput {
- let view: SignerStatusView = resolve_signer_status(config);
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::SignerStatus(view)),
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::SignerStatus(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::SignerStatus(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::SignerStatus(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::SignerStatus(view))
- }
- }
-}
-
-pub fn session_list(config: &RuntimeConfig) -> CommandOutput {
- crate::runtime::daemon::signer_sessions(config)
-}
-
-pub fn session_show(config: &RuntimeConfig, session_id: &str) -> CommandOutput {
- session_action_output(
- "show",
- crate::runtime::daemon::signer_session_show(config, session_id),
- )
-}
-
-pub fn session_connect_bunker(config: &RuntimeConfig, url: &str) -> CommandOutput {
- session_action_output(
- "connect_bunker",
- crate::runtime::daemon::signer_session_connect_bunker(config, url),
- )
-}
-
-pub fn session_connect_nostrconnect(
- config: &RuntimeConfig,
- url: &str,
- client_secret_key: &str,
-) -> CommandOutput {
- session_action_output(
- "connect_nostrconnect",
- crate::runtime::daemon::signer_session_connect_nostrconnect(config, url, client_secret_key),
- )
-}
-
-pub fn session_public_key(config: &RuntimeConfig, session_id: &str) -> CommandOutput {
- session_action_output(
- "public_key",
- crate::runtime::daemon::signer_session_public_key(config, session_id),
- )
-}
-
-pub fn session_authorize(config: &RuntimeConfig, session_id: &str) -> CommandOutput {
- session_action_output(
- "authorize",
- crate::runtime::daemon::signer_session_authorize(config, session_id),
- )
-}
-
-pub fn session_require_auth(
- config: &RuntimeConfig,
- session_id: &str,
- auth_url: &str,
-) -> CommandOutput {
- session_action_output(
- "require_auth",
- crate::runtime::daemon::signer_session_require_auth(config, session_id, auth_url),
- )
-}
-
-pub fn session_close(config: &RuntimeConfig, session_id: &str) -> CommandOutput {
- session_action_output(
- "close",
- crate::runtime::daemon::signer_session_close(config, session_id),
- )
-}
-
-fn session_action_output(
- action: &str,
- result: Result<SignerSessionActionView, DaemonRpcError>,
-) -> CommandOutput {
- match result {
- Ok(view) => CommandOutput::success(CommandView::SignerSessionAction(view)),
- Err(error) => {
- let (disposition, view) = session_action_error_view(action, error);
- match disposition {
- CommandDisposition::Unconfigured => {
- CommandOutput::unconfigured(CommandView::SignerSessionAction(view))
- }
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::SignerSessionAction(view))
- }
- CommandDisposition::Unsupported => {
- CommandOutput::unsupported(CommandView::SignerSessionAction(view))
- }
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::SignerSessionAction(view))
- }
- CommandDisposition::Success => {
- CommandOutput::success(CommandView::SignerSessionAction(view))
- }
- }
- }
- }
-}
-
-fn session_action_error_view(
- action: &str,
- error: DaemonRpcError,
-) -> (CommandDisposition, SignerSessionActionView) {
- let (disposition, state, reason) = match error {
- DaemonRpcError::Unconfigured(reason)
- | DaemonRpcError::Unauthorized(reason)
- | DaemonRpcError::MethodUnavailable(reason) => {
- (CommandDisposition::Unconfigured, "unconfigured", reason)
- }
- DaemonRpcError::External(reason) => (
- CommandDisposition::ExternalUnavailable,
- "unavailable",
- reason,
- ),
- DaemonRpcError::InvalidResponse(reason)
- | DaemonRpcError::Remote(reason)
- | DaemonRpcError::UnknownJob(reason) => {
- (CommandDisposition::InternalError, "error", reason)
- }
- };
- (
- disposition,
- SignerSessionActionView {
- action: action.to_owned(),
- state: state.to_owned(),
- source: "daemon signer session rpc · durable write plane".to_owned(),
- session_id: None,
- mode: None,
- remote_signer_pubkey: None,
- client_pubkey: None,
- signer_pubkey: None,
- user_pubkey: None,
- relays: Vec::new(),
- permissions: Vec::new(),
- auth_required: None,
- authorized: None,
- auth_url: None,
- expires_in_secs: None,
- pubkey: None,
- replayed: None,
- required: None,
- closed: None,
- reason: Some(reason),
- },
- )
-}
diff --git a/src/commands/sync.rs b/src/commands/sync.rs
@@ -1,46 +0,0 @@
-use crate::cli::SyncWatchArgs;
-use crate::domain::runtime::{CommandDisposition, CommandOutput, CommandView};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn status(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::sync::status(config)?;
- Ok(output_from_disposition(
- view.disposition(),
- CommandView::SyncStatus(view),
- ))
-}
-
-pub fn pull(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::sync::pull(config)?;
- Ok(output_from_disposition(
- view.disposition(),
- CommandView::SyncPull(view),
- ))
-}
-
-pub fn push(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::sync::push(config)?;
- Ok(output_from_disposition(
- view.disposition(),
- CommandView::SyncPush(view),
- ))
-}
-
-pub fn watch(config: &RuntimeConfig, args: &SyncWatchArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::sync::watch(config, args)?;
- Ok(output_from_disposition(
- view.disposition(),
- CommandView::SyncWatch(view),
- ))
-}
-
-fn output_from_disposition(disposition: CommandDisposition, view: CommandView) -> CommandOutput {
- match disposition {
- CommandDisposition::Success => CommandOutput::success(view),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(view),
- CommandDisposition::ExternalUnavailable => CommandOutput::external_unavailable(view),
- CommandDisposition::Unsupported => CommandOutput::unsupported(view),
- CommandDisposition::InternalError => CommandOutput::internal_error(view),
- }
-}
diff --git a/src/commands/workflow.rs b/src/commands/workflow.rs
@@ -1,44 +0,0 @@
-use crate::cli::SetupArgs;
-use crate::domain::runtime::{
- CommandDisposition, CommandOutput, CommandView, SetupView, StatusView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-
-pub fn setup(config: &RuntimeConfig, args: &SetupArgs) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::workflow::setup(config, args.role)?;
- Ok(setup_output(view))
-}
-
-pub fn status(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> {
- let view = crate::runtime::workflow::status(config)?;
- Ok(status_output(view))
-}
-
-fn setup_output(view: SetupView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::Setup(view)),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(CommandView::Setup(view)),
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::Setup(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::Setup(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::Setup(view))
- }
- }
-}
-
-fn status_output(view: StatusView) -> CommandOutput {
- match view.disposition() {
- CommandDisposition::Success => CommandOutput::success(CommandView::Status(view)),
- CommandDisposition::Unconfigured => CommandOutput::unconfigured(CommandView::Status(view)),
- CommandDisposition::ExternalUnavailable => {
- CommandOutput::external_unavailable(CommandView::Status(view))
- }
- CommandDisposition::Unsupported => CommandOutput::unsupported(CommandView::Status(view)),
- CommandDisposition::InternalError => {
- CommandOutput::internal_error(CommandView::Status(view))
- }
- }
-}
diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs
@@ -8,57 +8,6 @@ use radroots_events::profile::RadrootsProfile;
use radroots_nostr_accounts::prelude::RadrootsNostrAccountRecord;
use serde::Serialize;
-#[derive(Debug, Clone)]
-pub struct CommandOutput {
- disposition: CommandDisposition,
- view: CommandView,
-}
-
-impl CommandOutput {
- pub fn success(view: CommandView) -> Self {
- Self {
- disposition: CommandDisposition::Success,
- view,
- }
- }
-
- pub fn unconfigured(view: CommandView) -> Self {
- Self {
- disposition: CommandDisposition::Unconfigured,
- view,
- }
- }
-
- pub fn external_unavailable(view: CommandView) -> Self {
- Self {
- disposition: CommandDisposition::ExternalUnavailable,
- view,
- }
- }
-
- pub fn unsupported(view: CommandView) -> Self {
- Self {
- disposition: CommandDisposition::Unsupported,
- view,
- }
- }
-
- pub fn internal_error(view: CommandView) -> Self {
- Self {
- disposition: CommandDisposition::InternalError,
- view,
- }
- }
-
- pub fn exit_code(&self) -> ExitCode {
- self.disposition.exit_code()
- }
-
- pub fn view(&self) -> &CommandView {
- &self.view
- }
-}
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandDisposition {
Success,
@@ -80,69 +29,6 @@ impl CommandDisposition {
}
}
-#[derive(Debug, Clone)]
-pub enum CommandView {
- AccountClearDefault(AccountClearDefaultView),
- AccountImport(AccountImportView),
- AccountList(AccountListView),
- AccountNew(AccountNewView),
- AccountRemove(AccountRemoveView),
- AccountUse(AccountUseView),
- AccountWhoami(AccountWhoamiView),
- ConfigShow(ConfigShowView),
- Doctor(DoctorView),
- FarmGet(FarmGetView),
- FarmPublish(FarmPublishView),
- FarmSet(FarmSetView),
- FarmSetup(FarmSetupView),
- FarmStatus(FarmStatusView),
- Find(FindView),
- JobGet(JobGetView),
- JobList(JobListView),
- JobWatch(JobWatchView),
- ListingGet(ListingGetView),
- ListingMutation(ListingMutationView),
- ListingNew(ListingNewView),
- ListingValidate(ListingValidateView),
- LocalBackup(LocalBackupView),
- LocalExport(LocalExportView),
- LocalInit(LocalInitView),
- LocalStatus(LocalStatusView),
- MarketSearch(FindView),
- MarketUpdate(SyncActionView),
- MarketView(ListingGetView),
- MycStatus(MycStatusView),
- NetStatus(NetStatusView),
- OrderCancel(OrderCancelView),
- OrderGet(OrderGetView),
- OrderHistory(OrderHistoryView),
- OrderList(OrderListView),
- OrderNew(OrderNewView),
- OrderSubmit(OrderSubmitView),
- OrderSubmitWatch(OrderSubmitWatchView),
- OrderWatch(OrderWatchView),
- RpcSessions(RpcSessionsView),
- RpcStatus(RpcStatusView),
- RelayList(RelayListView),
- RuntimeAction(RuntimeActionView),
- RuntimeConfigShow(RuntimeManagedConfigView),
- RuntimeLogs(RuntimeLogsView),
- RuntimeStatus(RuntimeStatusView),
- SellAdd(SellAddView),
- SellCheck(SellCheckView),
- SellDraftMutation(SellDraftMutationView),
- SellMutation(SellMutationView),
- SellShow(SellShowView),
- Setup(SetupView),
- SignerSessionAction(SignerSessionActionView),
- SignerStatus(SignerStatusView),
- Status(StatusView),
- SyncPull(SyncActionView),
- SyncPush(SyncActionView),
- SyncStatus(SyncStatusView),
- SyncWatch(SyncWatchView),
-}
-
#[derive(Debug, Clone, Serialize)]
pub struct ConfigShowView {
pub source: String,
diff --git a/src/main.rs b/src/main.rs
@@ -1,6 +1,5 @@
#![forbid(unsafe_code)]
-mod cli;
mod domain;
mod operation_adapter;
mod operation_basket;
@@ -13,6 +12,7 @@ mod operation_registry;
mod operation_runtime;
mod output_contract;
mod runtime;
+mod runtime_args;
mod target_cli;
use std::io::Write;
@@ -20,7 +20,6 @@ use std::process::ExitCode;
use clap::Parser;
-use crate::cli::{CliArgs, Command, ConfigArgs, ConfigCommand, OutputFormatArg};
use crate::operation_adapter::{
OperationAdapter, OperationAdapterError, OperationNetworkMode, OperationOutputFormat,
OperationRequest, OperationRequestPayload, OperationResultPayload, OperationService,
@@ -36,6 +35,7 @@ use crate::operation_runtime::RuntimeOperationService;
use crate::output_contract::OutputEnvelope;
use crate::runtime::config::{RuntimeConfig, SignerBackend};
use crate::runtime::logging::initialize_logging;
+use crate::runtime_args::{RuntimeInvocationArgs, RuntimeOutputFormatArg};
use crate::target_cli::{TargetCliArgs, TargetOutputFormat};
fn main() -> ExitCode {
@@ -52,7 +52,7 @@ fn run() -> Result<ExitCode, runtime::RuntimeError> {
debug_assert!(operation_registry::registry_linkage_is_valid());
debug_assert!(operation_adapter::adapter_registry_linkage_is_valid());
let args = TargetCliArgs::parse();
- let config = RuntimeConfig::from_system(&config_args_from_target(&args)?)?;
+ let config = RuntimeConfig::from_system(&runtime_args_from_target(&args))?;
let logging = initialize_logging(&config.logging)?;
let request =
TargetOperationRequest::from_target_args(&args).map_err(operation_config_error)?;
@@ -64,12 +64,12 @@ fn run() -> Result<ExitCode, runtime::RuntimeError> {
Ok(envelope_exit_code(&envelope))
}
-fn config_args_from_target(args: &TargetCliArgs) -> Result<CliArgs, runtime::RuntimeError> {
- Ok(CliArgs {
+fn runtime_args_from_target(args: &TargetCliArgs) -> RuntimeInvocationArgs {
+ RuntimeInvocationArgs {
output_format: Some(match args.format {
- TargetOutputFormat::Human => OutputFormatArg::Human,
- TargetOutputFormat::Json => OutputFormatArg::Json,
- TargetOutputFormat::Ndjson => OutputFormatArg::Ndjson,
+ TargetOutputFormat::Human => RuntimeOutputFormatArg::Human,
+ TargetOutputFormat::Json => RuntimeOutputFormatArg::Json,
+ TargetOutputFormat::Ndjson => RuntimeOutputFormatArg::Ndjson,
}),
json: false,
ndjson: false,
@@ -94,10 +94,7 @@ fn config_args_from_target(args: &TargetCliArgs) -> Result<CliArgs, runtime::Run
hyf_enabled: false,
no_hyf_enabled: false,
hyf_executable: None,
- command: Command::Config(ConfigArgs {
- command: ConfigCommand::Show,
- }),
- })
+ }
}
fn execute_request(
diff --git a/src/operation_basket.rs b/src/operation_basket.rs
@@ -1,5 +1,3 @@
-#![allow(dead_code)]
-
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
@@ -8,7 +6,6 @@ use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
-use crate::cli::OrderNewArgs;
use crate::domain::runtime::OrderNewView;
use crate::operation_adapter::{
BasketCreateRequest, BasketCreateResult, BasketGetRequest, BasketGetResult,
@@ -20,6 +17,7 @@ use crate::operation_adapter::{
};
use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
+use crate::runtime_args::OrderDraftCreateArgs;
const BASKET_KIND: &str = "basket_v1";
const BASKET_SOURCE: &str = "local baskets - local first";
@@ -373,7 +371,7 @@ impl OperationService<BasketQuoteCreateRequest> for BasketOperationService<'_> {
let order = map_runtime(crate::runtime::order::scaffold(
self.config,
- &OrderNewArgs {
+ &OrderDraftCreateArgs {
listing: item.listing.clone(),
listing_addr: item.listing_addr.clone(),
bin_id: Some(item.bin_id.clone()),
diff --git a/src/operation_core.rs b/src/operation_core.rs
@@ -1,11 +1,8 @@
-#![allow(dead_code)]
-
use std::path::PathBuf;
use serde::Serialize;
use serde_json::{Value, json};
-use crate::cli::LocalExportFormatArg;
use crate::operation_adapter::{
AccountCreateRequest, AccountCreateResult, AccountGetRequest, AccountGetResult,
AccountImportRequest, AccountImportResult, AccountListRequest, AccountListResult,
@@ -27,6 +24,7 @@ use crate::runtime::accounts::{
};
use crate::runtime::config::RuntimeConfig;
use crate::runtime::logging::LoggingState;
+use crate::runtime_args::LocalExportFormatArg;
pub struct CoreOperationService<'a> {
config: &'a RuntimeConfig,
diff --git a/src/operation_farm.rs b/src/operation_farm.rs
@@ -1,11 +1,6 @@
-#![allow(dead_code)]
-
use serde::Serialize;
use serde_json::{Value, json};
-use crate::cli::{
- FarmFieldArg, FarmInitArgs, FarmPublishArgs, FarmScopeArg, FarmScopedArgs, FarmSetArgs,
-};
use crate::operation_adapter::{
FarmCreateRequest, FarmCreateResult, FarmFulfillmentUpdateRequest, FarmFulfillmentUpdateResult,
FarmGetRequest, FarmGetResult, FarmLocationUpdateRequest, FarmLocationUpdateResult,
@@ -16,6 +11,9 @@ use crate::operation_adapter::{
};
use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
+use crate::runtime_args::{
+ FarmCreateArgs, FarmFieldArg, FarmPublishArgs, FarmScopeArg, FarmScopedArgs, FarmUpdateArgs,
+};
pub struct FarmOperationService<'a> {
config: &'a RuntimeConfig,
@@ -34,7 +32,7 @@ impl OperationService<FarmCreateRequest> for FarmOperationService<'_> {
&self,
request: OperationRequest<FarmCreateRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let args = FarmInitArgs {
+ let args = FarmCreateArgs {
scope: scope_input(&request)?,
farm_d_tag: string_input(&request, "farm_d_tag"),
name: string_input(&request, "name"),
@@ -172,7 +170,7 @@ where
R: OperationResultData,
{
let value = required_string(request, "value")?;
- let args = FarmSetArgs {
+ let args = FarmUpdateArgs {
scope: scope_input(request)?,
field,
value: vec![value.clone()],
diff --git a/src/operation_listing.rs b/src/operation_listing.rs
@@ -1,11 +1,8 @@
-#![allow(dead_code)]
-
use std::path::PathBuf;
use serde::Serialize;
use serde_json::{Value, json};
-use crate::cli::{ListingFileArgs, ListingMutationArgs, ListingNewArgs, RecordKeyArgs};
use crate::domain::runtime::{CommandDisposition, ListingMutationView};
use crate::operation_adapter::{
ListingArchiveRequest, ListingArchiveResult, ListingCreateRequest, ListingCreateResult,
@@ -17,6 +14,9 @@ use crate::operation_adapter::{
};
use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
+use crate::runtime_args::{
+ ListingCreateArgs, ListingFileArgs, ListingMutationArgs, RecordLookupArgs,
+};
pub struct ListingOperationService<'a> {
config: &'a RuntimeConfig,
@@ -35,7 +35,7 @@ impl OperationService<ListingCreateRequest> for ListingOperationService<'_> {
&self,
request: OperationRequest<ListingCreateRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let args = ListingNewArgs {
+ let args = ListingCreateArgs {
output: optional_path(&request, "output"),
key: string_input(&request, "key"),
title: string_input(&request, "title"),
@@ -72,7 +72,7 @@ impl OperationService<ListingGetRequest> for ListingOperationService<'_> {
&self,
request: OperationRequest<ListingGetRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let args = RecordKeyArgs {
+ let args = RecordLookupArgs {
key: required_string(&request, "key")?,
};
let view = map_runtime(crate::runtime::listing::get(self.config, &args))?;
diff --git a/src/operation_market.rs b/src/operation_market.rs
@@ -1,11 +1,8 @@
-#![allow(dead_code)]
-
use radroots_events::kinds::KIND_LISTING;
use radroots_events_codec::trade::RadrootsTradeListingAddress;
use serde::Serialize;
use serde_json::{Value, json};
-use crate::cli::{FindArgs, RecordKeyArgs};
use crate::domain::runtime::{FindView, ListingGetView, SyncActionView};
use crate::operation_adapter::{
MarketListingGetRequest, MarketListingGetResult, MarketProductSearchRequest,
@@ -15,6 +12,7 @@ use crate::operation_adapter::{
};
use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
+use crate::runtime_args::{FindQueryArgs, RecordLookupArgs};
pub struct MarketOperationService<'a> {
config: &'a RuntimeConfig,
@@ -53,7 +51,7 @@ impl OperationService<MarketProductSearchRequest> for MarketOperationService<'_>
&self,
request: OperationRequest<MarketProductSearchRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let args = FindArgs {
+ let args = FindQueryArgs {
query: required_query_terms(&request)?,
};
let view = market_product_search_view(map_runtime(crate::runtime::find::search(
@@ -71,7 +69,7 @@ impl OperationService<MarketListingGetRequest> for MarketOperationService<'_> {
&self,
request: OperationRequest<MarketListingGetRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let args = RecordKeyArgs {
+ let args = RecordLookupArgs {
key: required_lookup(&request)?,
};
let view = market_listing_get_view(map_runtime(crate::runtime::listing::get(
diff --git a/src/operation_order.rs b/src/operation_order.rs
@@ -1,9 +1,6 @@
-#![allow(dead_code)]
-
use serde::Serialize;
use serde_json::Value;
-use crate::cli::{OrderSubmitArgs, OrderWatchArgs, RecordKeyArgs};
use crate::domain::runtime::{CommandDisposition, OrderSubmitView};
use crate::operation_adapter::{
OperationAdapterError, OperationRequest, OperationRequestData, OperationRequestPayload,
@@ -13,6 +10,7 @@ use crate::operation_adapter::{
};
use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
+use crate::runtime_args::{OrderSubmitArgs, OrderWatchArgs, RecordLookupArgs};
pub struct OrderOperationService<'a> {
config: &'a RuntimeConfig,
@@ -40,7 +38,6 @@ impl OperationService<OrderSubmitRequest> for OrderOperationService<'_> {
let key = required_order_key(&request)?;
let args = OrderSubmitArgs {
key,
- watch: bool_input(&request, "watch").unwrap_or(false),
idempotency_key: request
.context
.idempotency_key
@@ -68,7 +65,7 @@ impl OperationService<OrderGetRequest> for OrderOperationService<'_> {
&self,
request: OperationRequest<OrderGetRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let args = RecordKeyArgs {
+ let args = RecordLookupArgs {
key: required_order_key(&request)?,
};
let view = map_runtime(crate::runtime::order::get(self.config, &args))?;
@@ -191,13 +188,6 @@ where
.map(str::to_owned)
}
-fn bool_input<P>(request: &OperationRequest<P>, key: &str) -> Option<bool>
-where
- P: OperationRequestPayload + OperationRequestData,
-{
- request.payload.input().get(key).and_then(Value::as_bool)
-}
-
fn usize_input<P>(request: &OperationRequest<P>, key: &str) -> Option<usize>
where
P: OperationRequestPayload + OperationRequestData,
diff --git a/src/operation_runtime.rs b/src/operation_runtime.rs
@@ -1,9 +1,6 @@
-#![allow(dead_code)]
-
use serde::Serialize;
use serde_json::{Value, json};
-use crate::cli::SyncWatchArgs;
use crate::operation_adapter::{
JobGetRequest, JobGetResult, JobListRequest, JobListResult, JobWatchRequest, JobWatchResult,
OperationAdapterError, OperationRequest, OperationRequestData, OperationRequestPayload,
@@ -21,6 +18,7 @@ use crate::runtime::daemon::{self, DaemonRpcError};
use crate::runtime::management::{
RuntimeLifecycleAction, inspect_action, inspect_config_show, inspect_logs, inspect_status,
};
+use crate::runtime_args::SyncWatchArgs;
const DEFAULT_RUNTIME_ID: &str = "radrootsd";
diff --git a/src/render/mod.rs b/src/render/mod.rs
@@ -1,4876 +0,0 @@
-use std::io::{self, Write};
-
-use crate::domain::runtime::{
- AccountClearDefaultView, AccountImportView, AccountListView, AccountRemoveView,
- AccountSummaryView, CommandOutput, CommandView, DoctorCheckView, DoctorView,
- FarmConfigSummaryView, FarmGetView, FarmPublishComponentView, FarmPublishView, FarmSetView,
- FarmSetupView, FarmStatusView, FindView, JobGetView, JobListView, JobWatchView, ListingGetView,
- ListingMutationView, ListingNewView, ListingValidateView, LocalBackupView, LocalExportView,
- LocalInitView, LocalStatusView, NetStatusView, OrderCancelView, OrderDraftItemView,
- OrderGetView, OrderHistoryView, OrderJobView, OrderListView, OrderNewView, OrderSubmitView,
- OrderSubmitWatchView, OrderWatchView, OrderWorkflowView, RelayListView, RpcSessionsView,
- RpcStatusView, RuntimeActionView, RuntimeLogsView, RuntimeManagedConfigView, RuntimeStatusView,
- SellAddView, SellCheckView, SellDraftMutationView, SellMutationView, SellShowView, SetupView,
- SignerWriteKindReadinessView, StatusView, SyncActionView, SyncStatusView, SyncWatchView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::{OutputConfig, OutputFormat, Verbosity};
-
-const THIN_RULE: &str = "────────────────────────────────────────────────────";
-
-pub fn render_output(output: &CommandOutput, config: &OutputConfig) -> Result<(), RuntimeError> {
- match config.format {
- OutputFormat::Human => render_human(output, config),
- OutputFormat::Json => render_json(output),
- OutputFormat::Ndjson => render_ndjson(output),
- }
-}
-
-fn render_human(output: &CommandOutput, config: &OutputConfig) -> Result<(), RuntimeError> {
- let mut stdout = io::stdout().lock();
- render_human_with_config_to(&mut stdout, output, config)
-}
-
-#[cfg(test)]
-fn render_human_to(stdout: &mut dyn Write, output: &CommandOutput) -> Result<(), RuntimeError> {
- render_human_with_config_to(stdout, output, &default_human_output_config())
-}
-
-fn render_human_with_config_to(
- stdout: &mut dyn Write,
- output: &CommandOutput,
- config: &OutputConfig,
-) -> Result<(), RuntimeError> {
- if config.verbosity == Verbosity::Quiet {
- if let Some(quiet) = render_quiet_output(output) {
- writeln!(stdout, "{quiet}")?;
- return Ok(());
- }
- }
-
- let mut buffer = Vec::new();
- render_human_view_to(&mut buffer, output)?;
- let rendered = String::from_utf8(buffer).map_err(|error| {
- RuntimeError::Config(format!("human render output was not utf8: {error}"))
- })?;
- let finalized = finalize_human_output(output, rendered, config)?;
- write!(stdout, "{finalized}")?;
- Ok(())
-}
-
-#[cfg(test)]
-fn default_human_output_config() -> OutputConfig {
- OutputConfig {
- format: OutputFormat::Human,
- verbosity: Verbosity::Normal,
- color: true,
- dry_run: false,
- }
-}
-
-fn render_human_view_to(
- stdout: &mut dyn Write,
- output: &CommandOutput,
-) -> Result<(), RuntimeError> {
- match output.view() {
- CommandView::AccountClearDefault(view) => render_account_clear_default(stdout, view)?,
- CommandView::AccountImport(view) => render_account_import(stdout, view)?,
- CommandView::AccountList(view) => render_account_list(stdout, view)?,
- CommandView::AccountNew(view) => render_account_new(stdout, view)?,
- CommandView::AccountRemove(view) => render_account_remove(stdout, view)?,
- CommandView::AccountUse(view) => render_account_use(stdout, view)?,
- CommandView::AccountWhoami(view) => render_account_whoami(stdout, view)?,
- CommandView::MycStatus(view) => {
- render_myc_status(stdout, view, true)?;
- }
- CommandView::NetStatus(view) => {
- render_net_status(stdout, view)?;
- }
- CommandView::OrderCancel(view) => {
- render_order_cancel(stdout, view)?;
- }
- CommandView::OrderGet(view) => {
- render_order_get(stdout, view)?;
- }
- CommandView::OrderHistory(view) => {
- render_order_history(stdout, view)?;
- }
- CommandView::OrderList(view) => {
- render_order_list(stdout, view)?;
- }
- CommandView::OrderNew(view) => {
- render_order_new(stdout, view)?;
- }
- CommandView::OrderSubmit(view) => {
- render_order_submit(stdout, view)?;
- }
- CommandView::OrderSubmitWatch(view) => {
- render_order_submit_watch(stdout, view)?;
- }
- CommandView::OrderWatch(view) => {
- render_order_watch(stdout, view)?;
- }
- CommandView::RpcSessions(view) => {
- render_rpc_sessions(stdout, view)?;
- }
- CommandView::RpcStatus(view) => {
- render_rpc_status(stdout, view)?;
- }
- CommandView::SignerSessionAction(view) => {
- render_signer_session_action(stdout, view)?;
- }
- CommandView::ConfigShow(view) => {
- render_config_show(stdout, view)?;
- }
- CommandView::Doctor(view) => {
- render_doctor(stdout, view)?;
- }
- CommandView::FarmGet(view) => {
- render_farm_get(stdout, view)?;
- }
- CommandView::FarmPublish(view) => {
- render_farm_publish(stdout, view)?;
- }
- CommandView::FarmSet(view) => {
- render_farm_set(stdout, view)?;
- }
- CommandView::FarmSetup(view) => {
- render_farm_setup(stdout, view)?;
- }
- CommandView::FarmStatus(view) => {
- render_farm_status(stdout, view)?;
- }
- CommandView::Find(view) => {
- render_find(stdout, view)?;
- }
- CommandView::JobGet(view) => {
- render_job_get(stdout, view)?;
- }
- CommandView::JobList(view) => {
- render_job_list(stdout, view)?;
- }
- CommandView::JobWatch(view) => {
- render_job_watch(stdout, view)?;
- }
- CommandView::ListingGet(view) => {
- render_listing_get(stdout, view)?;
- }
- CommandView::ListingMutation(view) => {
- render_listing_mutation(stdout, view)?;
- }
- CommandView::ListingNew(view) => {
- render_listing_new(stdout, view)?;
- }
- CommandView::ListingValidate(view) => {
- render_listing_validate(stdout, view)?;
- }
- CommandView::LocalBackup(view) => {
- render_local_backup(stdout, view)?;
- }
- CommandView::LocalExport(view) => {
- render_local_export(stdout, view)?;
- }
- CommandView::LocalInit(view) => {
- render_local_init(stdout, view)?;
- }
- CommandView::LocalStatus(view) => {
- render_local_status(stdout, view)?;
- }
- CommandView::MarketSearch(view) => {
- render_market_search(stdout, view)?;
- }
- CommandView::MarketUpdate(view) => {
- render_market_update(stdout, view)?;
- }
- CommandView::MarketView(view) => {
- render_market_view(stdout, view)?;
- }
- CommandView::RelayList(view) => {
- render_relay_list(stdout, view)?;
- }
- CommandView::RuntimeAction(view) => {
- render_runtime_action(stdout, view)?;
- }
- CommandView::RuntimeConfigShow(view) => {
- render_runtime_config_show(stdout, view)?;
- }
- CommandView::RuntimeLogs(view) => {
- render_runtime_logs(stdout, view)?;
- }
- CommandView::RuntimeStatus(view) => {
- render_runtime_status(stdout, view)?;
- }
- CommandView::SellAdd(view) => {
- render_sell_add(stdout, view)?;
- }
- CommandView::SellCheck(view) => {
- render_sell_check(stdout, view)?;
- }
- CommandView::SellDraftMutation(view) => {
- render_sell_draft_mutation(stdout, view)?;
- }
- CommandView::SellMutation(view) => {
- render_sell_mutation(stdout, view)?;
- }
- CommandView::SellShow(view) => {
- render_sell_show(stdout, view)?;
- }
- CommandView::Setup(view) => {
- render_setup(stdout, view)?;
- }
- CommandView::SignerStatus(view) => {
- write_context(
- stdout,
- match view.state.as_str() {
- "ready" => "signer · active",
- "unconfigured" => "signer · unconfigured",
- "degraded" => "signer · degraded",
- "unavailable" => "signer · unavailable",
- _ => "signer · error",
- },
- )?;
- let mut signer_rows = vec![
- ("mode", view.mode.as_str()),
- ("status", view.state.as_str()),
- ];
- if let Some(account_id) = &view.signer_account_id {
- signer_rows.push(("signer account id", account_id.as_str()));
- }
- render_pairs(stdout, "signer", signer_rows.as_slice())?;
- if !view.write_kinds.is_empty() {
- writeln!(stdout)?;
- render_signer_write_kinds(stdout, &view.write_kinds)?;
- }
- writeln!(stdout)?;
- render_account_resolution(stdout, &view.account_resolution)?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- writeln!(stdout)?;
- render_signer_binding(stdout, &view.binding)?;
- if let Some(local) = &view.local {
- writeln!(stdout)?;
- render_local_signer(stdout, "local account", local)?;
- }
- if let Some(myc) = &view.myc {
- writeln!(stdout)?;
- render_myc_status(stdout, myc, false)?;
- }
- }
- CommandView::Status(view) => {
- render_status_summary(stdout, view)?;
- }
- CommandView::SyncPull(view) => {
- render_sync_action(stdout, view)?;
- }
- CommandView::SyncPush(view) => {
- render_sync_action(stdout, view)?;
- }
- CommandView::SyncStatus(view) => {
- render_sync_status(stdout, view)?;
- }
- CommandView::SyncWatch(view) => {
- render_sync_watch(stdout, view)?;
- }
- }
- Ok(())
-}
-
-fn render_json(output: &CommandOutput) -> Result<(), RuntimeError> {
- let mut stdout = io::stdout().lock();
- render_json_to(&mut stdout, output)
-}
-
-fn render_json_to(stdout: &mut dyn Write, output: &CommandOutput) -> Result<(), RuntimeError> {
- match output.view() {
- CommandView::AccountClearDefault(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::AccountImport(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::AccountList(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::AccountNew(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::AccountRemove(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::AccountUse(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::AccountWhoami(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::MycStatus(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::NetStatus(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::OrderCancel(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::OrderGet(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::OrderHistory(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::OrderList(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::OrderNew(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::OrderSubmit(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::OrderSubmitWatch(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::OrderWatch(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::RpcSessions(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::RpcStatus(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::ConfigShow(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::Doctor(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::FarmGet(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::FarmPublish(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::FarmSet(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::FarmSetup(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::FarmStatus(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::Find(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::JobGet(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::JobList(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::JobWatch(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::ListingGet(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::ListingMutation(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::ListingNew(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::ListingValidate(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::LocalBackup(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::LocalExport(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::LocalInit(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::LocalStatus(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::MarketSearch(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::MarketUpdate(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::MarketView(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::RelayList(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::RuntimeAction(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::RuntimeConfigShow(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::RuntimeLogs(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::RuntimeStatus(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SellAdd(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SellCheck(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SellDraftMutation(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SellMutation(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SellShow(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::Setup(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SignerSessionAction(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SignerStatus(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::Status(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SyncPull(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SyncPush(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SyncStatus(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- CommandView::SyncWatch(view) => {
- serde_json::to_writer_pretty(&mut *stdout, view)?;
- writeln!(stdout)?;
- }
- }
- Ok(())
-}
-
-fn render_ndjson(output: &CommandOutput) -> Result<(), RuntimeError> {
- let mut stdout = io::stdout().lock();
- render_ndjson_to(&mut stdout, output)
-}
-
-fn render_ndjson_to(stdout: &mut dyn Write, output: &CommandOutput) -> Result<(), RuntimeError> {
- match output.view() {
- CommandView::AccountList(view) => {
- for account in &view.accounts {
- serde_json::to_writer(&mut *stdout, account)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::RelayList(view) => {
- for relay in &view.relays {
- serde_json::to_writer(&mut *stdout, relay)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::Find(view) => {
- for result in &view.results {
- serde_json::to_writer(&mut *stdout, result)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::MarketSearch(view) => {
- for result in &view.results {
- serde_json::to_writer(&mut *stdout, result)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::JobList(view) => {
- for job in &view.jobs {
- serde_json::to_writer(&mut *stdout, job)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::JobWatch(view) => {
- for frame in &view.frames {
- serde_json::to_writer(&mut *stdout, frame)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::OrderHistory(view) => {
- for order in &view.orders {
- serde_json::to_writer(&mut *stdout, order)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::OrderList(view) => {
- for order in &view.orders {
- serde_json::to_writer(&mut *stdout, order)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::OrderWatch(view) => {
- for frame in &view.frames {
- serde_json::to_writer(&mut *stdout, frame)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::RpcSessions(view) => {
- for session in &view.sessions {
- serde_json::to_writer(&mut *stdout, session)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- CommandView::SyncWatch(view) => {
- for frame in &view.frames {
- serde_json::to_writer(&mut *stdout, frame)?;
- writeln!(stdout)?;
- }
- Ok(())
- }
- _ => Err(RuntimeError::Config(format!(
- "`{}` does not support --ndjson",
- human_command_name(output.view())
- ))),
- }
-}
-
-fn yes_no(value: bool) -> &'static str {
- if value { "yes" } else { "no" }
-}
-
-fn render_quiet_output(output: &CommandOutput) -> Option<String> {
- match output.view() {
- CommandView::AccountClearDefault(view) => Some(match &view.cleared_account {
- Some(account) => format!("Default account cleared: {}", account.id),
- None => "No default account configured".to_owned(),
- }),
- CommandView::AccountImport(view) => Some(format!("Account imported: {}", view.account.id)),
- CommandView::AccountNew(view) => Some(format!(
- "{}: {}",
- match view.state.as_str() {
- "migrated" => "Account migrated",
- _ => "Account created",
- },
- view.account.id
- )),
- CommandView::AccountRemove(view) => {
- Some(format!("Account removed: {}", view.removed_account.id))
- }
- CommandView::Find(view) | CommandView::MarketSearch(view) => match view.state.as_str() {
- "ready" if !view.results.is_empty() => Some(
- view.results
- .iter()
- .map(|result| result.product_key.clone())
- .collect::<Vec<_>>()
- .join("\n"),
- ),
- "empty" => Some("No listings found".to_owned()),
- _ => None,
- },
- CommandView::OrderSubmit(view) => match view.state.as_str() {
- "accepted" | "submitted" | "already_submitted" | "deduplicated" => {
- Some(format!("Order submitted: {}", view.order_id))
- }
- _ => None,
- },
- _ => None,
- }
-}
-
-fn finalize_human_output(
- output: &CommandOutput,
- rendered: String,
- config: &OutputConfig,
-) -> Result<String, RuntimeError> {
- let mut cleaned_lines = Vec::new();
- let mut fallback_details = Vec::<(&'static str, String)>::new();
-
- for line in rendered.lines() {
- let trimmed = line.trim_end();
- if trimmed == THIN_RULE {
- continue;
- }
- if let Some(value) = trimmed.trim_start().strip_prefix("workflow source: ") {
- fallback_details.push(("Workflow source", value.to_owned()));
- continue;
- }
- if let Some(value) = trimmed.trim_start().strip_prefix("source: ") {
- fallback_details.push(("Source", value.to_owned()));
- continue;
- }
- if let Some(value) = trimmed.trim_start().strip_prefix("provenance: ") {
- fallback_details.push(("Provenance", value.to_owned()));
- continue;
- }
- if let Some(value) = trimmed.strip_prefix("reason: ") {
- cleaned_lines.push(value.to_owned());
- continue;
- }
- if trimmed == "actions" {
- cleaned_lines.push("Next".to_owned());
- continue;
- }
- cleaned_lines.push(trimmed.to_owned());
- }
-
- let cleaned_lines = collapse_blank_lines(cleaned_lines);
- let mut finalized = cleaned_lines.join("\n");
- if !finalized.is_empty() && !finalized.ends_with('\n') {
- finalized.push('\n');
- }
-
- if matches!(config.verbosity, Verbosity::Verbose | Verbosity::Trace) {
- let mut details = verbose_details(output);
- for fallback in fallback_details {
- if !details.iter().any(|(label, _)| *label == fallback.0) {
- details.push(fallback);
- }
- }
- if !details.is_empty() {
- if !finalized.is_empty() {
- finalized.push('\n');
- }
- finalized.push_str("Details\n");
- finalized.push_str(render_field_rows_string(details.as_slice()).as_str());
- }
- }
-
- if config.verbosity == Verbosity::Trace {
- let mut trace_buffer = Vec::new();
- render_json_to(&mut trace_buffer, output)?;
- let trace_json = String::from_utf8(trace_buffer).map_err(|error| {
- RuntimeError::Config(format!("trace render output was not utf8: {error}"))
- })?;
- if !finalized.is_empty() {
- finalized.push('\n');
- }
- finalized.push_str("Trace\n");
- finalized.push_str(
- render_field_rows_string(&[("Command", human_command_name(output.view()).to_owned())])
- .as_str(),
- );
- for line in trace_json.trim_end().lines() {
- finalized.push_str(" ");
- finalized.push_str(line);
- finalized.push('\n');
- }
- }
-
- Ok(finalized)
-}
-
-fn collapse_blank_lines(lines: Vec<String>) -> Vec<String> {
- let mut collapsed = Vec::new();
- let mut previous_blank = true;
- for line in lines {
- let blank = line.trim().is_empty();
- if blank {
- if previous_blank {
- continue;
- }
- collapsed.push(String::new());
- } else {
- collapsed.push(line);
- }
- previous_blank = blank;
- }
- while collapsed.last().is_some_and(|line| line.trim().is_empty()) {
- collapsed.pop();
- }
- collapsed
-}
-
-fn render_field_rows_string(rows: &[(&str, String)]) -> String {
- let label_width = rows
- .iter()
- .map(|(label, _)| label.len())
- .max()
- .unwrap_or_default();
- let mut rendered = String::new();
- for (label, value) in rows {
- rendered.push_str(
- format!(
- " {label:label_width$} {value}\n",
- label_width = label_width
- )
- .as_str(),
- );
- }
- rendered
-}
-
-fn verbose_details(output: &CommandOutput) -> Vec<(&'static str, String)> {
- match output.view() {
- CommandView::AccountClearDefault(view) => vec![
- ("Source", view.source.clone()),
- (
- "Remaining accounts",
- view.remaining_account_count.to_string(),
- ),
- ],
- CommandView::AccountImport(view) => vec![("Source", view.source.clone())],
- CommandView::AccountList(view) => vec![("Source", view.source.clone())],
- CommandView::AccountNew(view) => vec![("Source", view.source.clone())],
- CommandView::AccountRemove(view) => vec![
- ("Source", view.source.clone()),
- (
- "Remaining accounts",
- view.remaining_account_count.to_string(),
- ),
- ],
- CommandView::AccountUse(view) => vec![("Source", view.source.clone())],
- CommandView::AccountWhoami(view) => vec![("Source", view.source.clone())],
- CommandView::Doctor(view) => vec![("Source", view.source.clone())],
- CommandView::Find(view) | CommandView::MarketSearch(view) => vec![
- ("Source", view.source.clone()),
- ("Freshness", view.freshness.display.clone()),
- ("Relay count", view.relay_count.to_string()),
- ],
- CommandView::ListingGet(view) | CommandView::MarketView(view) => vec![
- ("Source", view.source.clone()),
- ("Freshness", view.provenance.freshness.clone()),
- ("Relay count", view.provenance.relay_count.to_string()),
- ],
- CommandView::OrderSubmit(view) => {
- let mut rows = vec![("Source", view.source.clone())];
- push_row(
- &mut rows,
- "Signer mode",
- view.signer_mode.as_deref().map(str::to_owned),
- );
- push_row(
- &mut rows,
- "Requested session",
- view.requested_signer_session_id
- .as_deref()
- .map(str::to_owned),
- );
- push_row(
- &mut rows,
- "Idempotency key",
- view.idempotency_key.as_deref().map(str::to_owned),
- );
- rows
- }
- CommandView::OrderSubmitWatch(view) => {
- let mut rows = vec![("Source", view.submit.source.clone())];
- push_row(
- &mut rows,
- "Signer mode",
- view.submit.signer_mode.as_deref().map(str::to_owned),
- );
- push_row(
- &mut rows,
- "Requested session",
- view.submit
- .requested_signer_session_id
- .as_deref()
- .map(str::to_owned),
- );
- rows
- }
- CommandView::RelayList(view) => vec![
- ("Source", view.source.clone()),
- ("Relay count", view.count.to_string()),
- ("Publish policy", view.publish_policy.clone()),
- ],
- _ => Vec::new(),
- }
-}
-
-fn present_absent(value: bool) -> &'static str {
- if value { "present" } else { "absent" }
-}
-
-fn render_account_list(stdout: &mut dyn Write, view: &AccountListView) -> Result<(), RuntimeError> {
- if view.accounts.is_empty() {
- writeln!(stdout, "No accounts yet")?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- return Ok(());
- }
-
- writeln!(
- stdout,
- "{} account{}",
- view.count,
- if view.count == 1 { "" } else { "s" }
- )?;
- writeln!(stdout)?;
- for (index, account) in view.accounts.iter().enumerate() {
- writeln!(
- stdout,
- "{}",
- account
- .display_name
- .as_deref()
- .filter(|name| !name.trim().is_empty())
- .unwrap_or(account.id.as_str())
- )?;
- let rows = vec![
- ("Account", account.id.clone()),
- ("Signer", humanize_machine_label(account.signer.as_str())),
- (
- "Default",
- if account.is_default {
- "Yes".to_owned()
- } else {
- "No".to_owned()
- },
- ),
- ];
- render_field_rows(stdout, rows.as_slice())?;
- if index + 1 < view.accounts.len() {
- writeln!(stdout)?;
- }
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_account_import(
- stdout: &mut dyn Write,
- view: &AccountImportView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "Watch-only account imported")?;
- writeln!(stdout)?;
- render_account_section(stdout, &view.account)?;
- writeln!(stdout)?;
- writeln!(stdout, "Identity")?;
- render_field_rows(
- stdout,
- &[("npub", view.public_identity.public_key_npub.clone())],
- )?;
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_account_new(
- stdout: &mut dyn Write,
- view: &crate::domain::runtime::AccountNewView,
-) -> Result<(), RuntimeError> {
- writeln!(
- stdout,
- "{}",
- match view.state.as_str() {
- "migrated" => "Account migrated",
- _ => "Account created",
- }
- )?;
- writeln!(stdout)?;
- render_account_section(stdout, &view.account)?;
- writeln!(stdout)?;
- writeln!(stdout, "Identity")?;
- render_field_rows(
- stdout,
- &[("npub", view.public_identity.public_key_npub.clone())],
- )?;
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_account_use(
- stdout: &mut dyn Write,
- view: &crate::domain::runtime::AccountUseView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "Default account selected")?;
- writeln!(stdout)?;
- render_account_section(stdout, &view.account)
-}
-
-fn render_account_clear_default(
- stdout: &mut dyn Write,
- view: &AccountClearDefaultView,
-) -> Result<(), RuntimeError> {
- writeln!(
- stdout,
- "{}",
- match view.state.as_str() {
- "cleared" => "Default account cleared",
- _ => "No default account configured",
- }
- )?;
- if let Some(account) = &view.cleared_account {
- writeln!(stdout)?;
- render_account_section(stdout, account)?;
- }
- writeln!(stdout)?;
- render_field_rows(
- stdout,
- &[(
- "Remaining accounts",
- view.remaining_account_count.to_string(),
- )],
- )?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_account_remove(
- stdout: &mut dyn Write,
- view: &AccountRemoveView,
-) -> Result<(), RuntimeError> {
- writeln!(
- stdout,
- "{}",
- if view.default_cleared {
- "Default account removed"
- } else {
- "Account removed"
- }
- )?;
- writeln!(stdout)?;
- render_account_section(stdout, &view.removed_account)?;
- writeln!(stdout)?;
- render_field_rows(
- stdout,
- &[(
- "Remaining accounts",
- view.remaining_account_count.to_string(),
- )],
- )?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_account_whoami(
- stdout: &mut dyn Write,
- view: &crate::domain::runtime::AccountWhoamiView,
-) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "ready" => {
- writeln!(stdout, "Resolved account")?;
- writeln!(stdout)?;
- if let Some(account) = &view.account_resolution.resolved_account {
- render_account_section(stdout, account)?;
- }
- writeln!(stdout)?;
- render_account_resolution(stdout, &view.account_resolution)?;
- if let Some(identity) = &view.public_identity {
- writeln!(stdout)?;
- writeln!(stdout, "Identity")?;
- render_field_rows(stdout, &[("npub", identity.public_key_npub.clone())])?;
- }
- }
- _ => {
- writeln!(stdout, "Not ready yet")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- writeln!(stdout)?;
- render_account_resolution(stdout, &view.account_resolution)?;
- writeln!(stdout)?;
- render_item_section(stdout, "Missing", &["Resolved account".to_owned()])?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_account_section(
- stdout: &mut dyn Write,
- account: &AccountSummaryView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "Account")?;
- let mut rows = Vec::<(&str, String)>::new();
- push_row(&mut rows, "Name", account.display_name.clone());
- rows.push(("Account", account.id.clone()));
- rows.push(("Signer", humanize_machine_label(account.signer.as_str())));
- rows.push((
- "Default",
- if account.is_default {
- "Yes".to_owned()
- } else {
- "No".to_owned()
- },
- ));
- render_field_rows(stdout, rows.as_slice())
-}
-
-fn render_account_resolution(
- stdout: &mut dyn Write,
- resolution: &crate::domain::runtime::AccountResolutionView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "Account resolution")?;
- let mut rows = vec![("Source", humanize_machine_label(resolution.source.as_str()))];
- if let Some(account) = &resolution.resolved_account {
- rows.push(("Resolved account", account.id.clone()));
- }
- if let Some(account) = &resolution.default_account {
- rows.push(("Default account", account.id.clone()));
- }
- render_field_rows(stdout, rows.as_slice())
-}
-
-fn render_config_show(
- stdout: &mut dyn Write,
- view: &crate::domain::runtime::ConfigShowView,
-) -> Result<(), RuntimeError> {
- write_context(stdout, "config · effective")?;
- render_pairs(
- stdout,
- "output",
- &[
- ("format", view.output.format.as_str()),
- ("verbosity", view.output.verbosity.as_str()),
- ("color", yes_no(view.output.color)),
- ("dry run", yes_no(view.output.dry_run)),
- ],
- )?;
- render_pairs(
- stdout,
- "interaction",
- &[
- ("input enabled", yes_no(view.interaction.input_enabled)),
- ("assume yes", yes_no(view.interaction.assume_yes)),
- ("stdin tty", yes_no(view.interaction.stdin_tty)),
- ("stdout tty", yes_no(view.interaction.stdout_tty)),
- ("prompts allowed", yes_no(view.interaction.prompts_allowed)),
- (
- "confirmations allowed",
- yes_no(view.interaction.confirmations_allowed),
- ),
- ],
- )?;
- let user_config = format!(
- "{} · {}",
- present_absent(view.config_files.user_present),
- view.paths.app_config_path
- );
- let workspace_config = format!(
- "{} · {}",
- present_absent(view.config_files.workspace_present),
- view.paths
- .workspace_config_path
- .as_deref()
- .unwrap_or("disabled for interactive_user")
- );
- let allowed_profiles = view.paths.allowed_profiles.join(", ");
- render_pairs(
- stdout,
- "runtime roots",
- &[
- ("profile", view.paths.profile.as_str()),
- ("allowed profiles", allowed_profiles.as_str()),
- ("app namespace", view.paths.app_namespace.as_str()),
- (
- "shared accounts namespace",
- view.paths.shared_accounts_namespace.as_str(),
- ),
- (
- "shared identities namespace",
- view.paths.shared_identities_namespace.as_str(),
- ),
- ("app config", user_config.as_str()),
- ("workspace config", workspace_config.as_str()),
- ("app data root", view.paths.app_data_root.as_str()),
- ("app logs root", view.paths.app_logs_root.as_str()),
- (
- "shared accounts data",
- view.paths.shared_accounts_data_root.as_str(),
- ),
- (
- "shared accounts secrets",
- view.paths.shared_accounts_secrets_root.as_str(),
- ),
- (
- "default identity path",
- view.paths.default_identity_path.as_str(),
- ),
- ],
- )?;
-
- let mut logging_rows = vec![
- ("filter", view.logging.filter.as_str()),
- ("stdout", yes_no(view.logging.stdout)),
- ];
- if let Some(directory) = &view.logging.directory {
- logging_rows.push(("directory", directory.as_str()));
- }
- if let Some(current_file) = &view.logging.current_file {
- logging_rows.push(("file", current_file.as_str()));
- }
- render_pairs(stdout, "logging", logging_rows.as_slice())?;
-
- let mut account_rows = vec![
- ("store path", view.account.store_path.as_str()),
- ("secrets dir", view.account.secrets_dir.as_str()),
- (
- "contract default secret backend",
- view.account
- .secret_backend
- .contract_default_backend
- .as_str(),
- ),
- (
- "configured secret backend",
- view.account.secret_backend.configured_primary.as_str(),
- ),
- ("identity path", view.account.identity_path.as_str()),
- ];
- if let Some(fallback) = &view.account.secret_backend.contract_default_fallback {
- account_rows.push(("contract default fallback", fallback.as_str()));
- }
- if let Some(fallback) = &view.account.secret_backend.configured_fallback {
- account_rows.push(("configured secret fallback", fallback.as_str()));
- }
- let allowed_backends = view.account.secret_backend.allowed_backends.join(", ");
- account_rows.push(("allowed secret backends", allowed_backends.as_str()));
- if let Some(policy) = &view.account.secret_backend.host_vault_policy {
- account_rows.push(("host vault policy", policy.as_str()));
- }
- account_rows.push((
- "uses protected store",
- yes_no(view.account.secret_backend.uses_protected_store),
- ));
- account_rows.push(("secret status", view.account.secret_backend.state.as_str()));
- if let Some(active_backend) = &view.account.secret_backend.active_backend {
- account_rows.push(("active secret backend", active_backend.as_str()));
- }
- account_rows.push((
- "used secret fallback",
- yes_no(view.account.secret_backend.used_fallback),
- ));
- if let Some(selector) = &view.account.selector {
- account_rows.insert(0, ("selector", selector.as_str()));
- }
- render_pairs(stdout, "account", account_rows.as_slice())?;
- if let Some(reason) = &view.account.secret_backend.reason {
- writeln!(stdout, "account secret backend reason: {reason}")?;
- }
- render_pairs(stdout, "signer", &[("mode", view.signer.mode.as_str())])?;
- let relay_count = view.relay.count.to_string();
- render_pairs(
- stdout,
- "relay",
- &[
- ("count", relay_count.as_str()),
- ("publish policy", view.relay.publish_policy.as_str()),
- ("source", view.relay.source.as_str()),
- ],
- )?;
- render_pairs(
- stdout,
- "local",
- &[
- ("root", view.local.root.as_str()),
- ("replica db", view.local.replica_db_path.as_str()),
- ("backups dir", view.local.backups_dir.as_str()),
- ("exports dir", view.local.exports_dir.as_str()),
- ],
- )?;
- let myc_status_timeout_ms = view.myc.status_timeout_ms.to_string();
- render_pairs(
- stdout,
- "myc",
- &[
- ("executable", view.myc.executable.as_str()),
- ("status timeout ms", myc_status_timeout_ms.as_str()),
- ],
- )?;
- let write_plane_target = format_runtime_target(
- view.write_plane.target_kind.as_deref(),
- view.write_plane.target.as_deref(),
- );
- render_pairs(
- stdout,
- "write plane",
- &[
- ("provider", view.write_plane.provider_runtime_id.as_str()),
- ("binding model", view.write_plane.binding_model.as_str()),
- ("state", view.write_plane.state.as_str()),
- ("provenance", view.write_plane.provenance.as_str()),
- ("source", view.write_plane.source.as_str()),
- ("target", write_plane_target.as_str()),
- ("detail", view.write_plane.detail.as_str()),
- (
- "bridge auth configured",
- yes_no(view.write_plane.bridge_auth_configured),
- ),
- ],
- )?;
- let workflow_target = format_runtime_target(
- view.workflow.target_kind.as_deref(),
- view.workflow.target.as_deref(),
- );
- render_pairs(
- stdout,
- "workflow",
- &[
- ("provider", view.workflow.provider_runtime_id.as_str()),
- ("binding model", view.workflow.binding_model.as_str()),
- ("state", view.workflow.state.as_str()),
- ("provenance", view.workflow.provenance.as_str()),
- ("source", view.workflow.source.as_str()),
- ("target", workflow_target.as_str()),
- ("hyf helper", view.workflow.hyf_helper_state.as_str()),
- (
- "hyf helper detail",
- view.workflow.hyf_helper_detail.as_str(),
- ),
- ],
- )?;
- render_pairs(
- stdout,
- "hyf",
- &[
- ("enabled", yes_no(view.hyf.enabled)),
- ("executable", view.hyf.executable.as_str()),
- ],
- )?;
- let hyf_provider_target = format_runtime_target(
- view.hyf_provider.target_kind.as_deref(),
- view.hyf_provider.target.as_deref(),
- );
- let mut hyf_provider_rows = vec![
- ("provider", view.hyf_provider.provider_runtime_id.as_str()),
- ("binding model", view.hyf_provider.binding_model.as_str()),
- ("state", view.hyf_provider.state.as_str()),
- ("provenance", view.hyf_provider.provenance.as_str()),
- ("source", view.hyf_provider.source.as_str()),
- ("target", hyf_provider_target.as_str()),
- ("executable", view.hyf_provider.executable.as_str()),
- ];
- if let Some(reason) = &view.hyf_provider.reason {
- hyf_provider_rows.push(("reason", reason.as_str()));
- }
- render_pairs(stdout, "hyf provider", hyf_provider_rows.as_slice())?;
- render_pairs(
- stdout,
- "rpc",
- &[
- ("url", view.rpc.url.as_str()),
- (
- "bridge auth configured",
- yes_no(view.rpc.bridge_auth_configured),
- ),
- ],
- )?;
- writeln!(stdout)?;
- writeln!(stdout, "capability bindings")?;
- let table = Table {
- headers: &["capability", "provider", "state", "target"],
- rows: view
- .capability_bindings
- .iter()
- .map(|binding| {
- vec![
- binding.capability_id.clone(),
- binding.provider_runtime_id.clone(),
- binding.state.clone(),
- format_capability_binding_target(binding),
- ]
- })
- .collect(),
- };
- render_table(stdout, &table)?;
- writeln!(stdout)?;
- writeln!(stdout, "resolved providers")?;
- let resolved_table = Table {
- headers: &["capability", "provider", "state", "provenance", "target"],
- rows: view
- .resolved_providers
- .iter()
- .map(|provider| {
- vec![
- provider.capability_id.clone(),
- provider.provider_runtime_id.clone(),
- provider.state.clone(),
- provider.provenance.clone(),
- format_runtime_target(
- provider.target_kind.as_deref(),
- provider.target.as_deref(),
- ),
- ]
- })
- .collect(),
- };
- render_table(stdout, &resolved_table)?;
- writeln!(stdout, "source: {}", view.source)?;
- Ok(())
-}
-
-fn render_runtime_action(
- stdout: &mut dyn Write,
- view: &RuntimeActionView,
-) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- format!(
- "runtime · {} · {}",
- view.runtime_id,
- view.action.replace('_', " ")
- )
- .as_str(),
- )?;
- render_pairs(
- stdout,
- "runtime",
- &[
- ("runtime", view.runtime_id.as_str()),
- ("instance", view.instance_id.as_str()),
- ("instance source", view.instance_source.as_str()),
- ("group", view.runtime_group.as_str()),
- ("state", view.state.as_str()),
- ("mutates bindings", yes_no(view.mutates_bindings)),
- ],
- )?;
- writeln!(stdout, "detail: {}", view.detail)?;
- if let Some(next_step) = &view.next_step {
- writeln!(stdout, "next step: {next_step}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- Ok(())
-}
-
-fn render_runtime_config_show(
- stdout: &mut dyn Write,
- view: &RuntimeManagedConfigView,
-) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- format!("runtime · {} · config", view.runtime_id).as_str(),
- )?;
- let mut rows = vec![
- ("runtime", view.runtime_id.as_str()),
- ("instance", view.instance_id.as_str()),
- ("instance source", view.instance_source.as_str()),
- ("group", view.runtime_group.as_str()),
- ("state", view.state.as_str()),
- ("config present", yes_no(view.config_present)),
- ];
- if let Some(config_format) = &view.config_format {
- rows.push(("config format", config_format.as_str()));
- }
- if let Some(config_path) = &view.config_path {
- rows.push(("config path", config_path.as_str()));
- }
- if let Some(requires_bootstrap_secret) = view.requires_bootstrap_secret {
- rows.push((
- "requires bootstrap secret",
- yes_no(requires_bootstrap_secret),
- ));
- }
- if let Some(requires_config_bootstrap) = view.requires_config_bootstrap {
- rows.push((
- "requires config bootstrap",
- yes_no(requires_config_bootstrap),
- ));
- }
- if let Some(requires_signer_provider) = view.requires_signer_provider {
- rows.push(("requires signer provider", yes_no(requires_signer_provider)));
- }
- render_pairs(stdout, "runtime config", rows.as_slice())?;
- writeln!(stdout, "detail: {}", view.detail)?;
- writeln!(stdout, "source: {}", view.source)?;
- Ok(())
-}
-
-fn render_runtime_logs(stdout: &mut dyn Write, view: &RuntimeLogsView) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- format!("runtime · {} · logs", view.runtime_id).as_str(),
- )?;
- let mut rows = vec![
- ("runtime", view.runtime_id.as_str()),
- ("instance", view.instance_id.as_str()),
- ("instance source", view.instance_source.as_str()),
- ("group", view.runtime_group.as_str()),
- ("state", view.state.as_str()),
- ("stdout present", yes_no(view.stdout_log_present)),
- ("stderr present", yes_no(view.stderr_log_present)),
- ];
- if let Some(stdout_log_path) = &view.stdout_log_path {
- rows.push(("stdout log", stdout_log_path.as_str()));
- }
- if let Some(stderr_log_path) = &view.stderr_log_path {
- rows.push(("stderr log", stderr_log_path.as_str()));
- }
- render_pairs(stdout, "runtime logs", rows.as_slice())?;
- writeln!(stdout, "detail: {}", view.detail)?;
- writeln!(stdout, "source: {}", view.source)?;
- Ok(())
-}
-
-fn render_runtime_status(
- stdout: &mut dyn Write,
- view: &RuntimeStatusView,
-) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- format!("runtime · {} · status", view.runtime_id).as_str(),
- )?;
- let mut rows = vec![
- ("runtime", view.runtime_id.as_str()),
- ("instance", view.instance_id.as_str()),
- ("instance source", view.instance_source.as_str()),
- ("group", view.runtime_group.as_str()),
- ("posture", view.management_posture.as_str()),
- ("state", view.state.as_str()),
- ("install state", view.install_state.as_str()),
- ("health state", view.health_state.as_str()),
- ("health source", view.health_source.as_str()),
- ("registry", view.registry_path.as_str()),
- ];
- if let Some(mode) = &view.management_mode {
- rows.push(("management mode", mode.as_str()));
- }
- if let Some(service_manager_integration) = view.service_manager_integration {
- rows.push((
- "service manager integration",
- yes_no(service_manager_integration),
- ));
- }
- if let Some(uses_absolute_binary_paths) = view.uses_absolute_binary_paths {
- rows.push((
- "uses absolute binary paths",
- yes_no(uses_absolute_binary_paths),
- ));
- }
- if let Some(preferred_cli_binding) = view.preferred_cli_binding {
- rows.push(("preferred cli binding", yes_no(preferred_cli_binding)));
- }
- render_pairs(stdout, "runtime status", rows.as_slice())?;
- writeln!(stdout, "detail: {}", view.detail)?;
- if let Some(instance_paths) = &view.instance_paths {
- render_pairs(
- stdout,
- "instance paths",
- &[
- ("install dir", instance_paths.install_dir.as_str()),
- ("state dir", instance_paths.state_dir.as_str()),
- ("logs dir", instance_paths.logs_dir.as_str()),
- ("run dir", instance_paths.run_dir.as_str()),
- ("secrets dir", instance_paths.secrets_dir.as_str()),
- ("pid file", instance_paths.pid_file_path.as_str()),
- ("stdout log", instance_paths.stdout_log_path.as_str()),
- ("stderr log", instance_paths.stderr_log_path.as_str()),
- ("metadata", instance_paths.metadata_path.as_str()),
- ],
- )?;
- }
- if let Some(record) = &view.instance_record {
- let mut record_rows = vec![
- ("management mode", record.management_mode.as_str()),
- ("install state", record.install_state.as_str()),
- ("binary path", record.binary_path.as_str()),
- ("config path", record.config_path.as_str()),
- ("logs path", record.logs_path.as_str()),
- ("run path", record.run_path.as_str()),
- ("installed version", record.installed_version.as_str()),
- ];
- if let Some(health_endpoint) = &record.health_endpoint {
- record_rows.push(("health endpoint", health_endpoint.as_str()));
- }
- if let Some(secret_material_ref) = &record.secret_material_ref {
- record_rows.push(("secret material ref", secret_material_ref.as_str()));
- }
- if let Some(last_started_at) = &record.last_started_at {
- record_rows.push(("last started at", last_started_at.as_str()));
- }
- if let Some(last_stopped_at) = &record.last_stopped_at {
- record_rows.push(("last stopped at", last_stopped_at.as_str()));
- }
- if let Some(notes) = &record.notes {
- record_rows.push(("notes", notes.as_str()));
- }
- render_pairs(stdout, "instance record", record_rows.as_slice())?;
- }
- if !view.lifecycle_actions.is_empty() {
- writeln!(
- stdout,
- "lifecycle actions: {}",
- view.lifecycle_actions.join(", ")
- )?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- Ok(())
-}
-
-fn format_capability_binding_target(
- binding: &crate::domain::runtime::CapabilityBindingRuntimeView,
-) -> String {
- let mut rendered =
- format_runtime_target(binding.target_kind.as_deref(), binding.target.as_deref());
- if rendered.is_empty() {
- return rendered;
- }
- if let Some(account_ref) = &binding.managed_account_ref {
- rendered.push_str(format!(" · account {account_ref}").as_str());
- }
- if let Some(session_ref) = &binding.signer_session_ref {
- rendered.push_str(format!(" · session {session_ref}").as_str());
- }
- rendered
-}
-
-fn format_runtime_target(target_kind: Option<&str>, target: Option<&str>) -> String {
- let Some(target) = target else {
- return String::new();
- };
-
- match target_kind {
- Some(kind) => format!("{kind} {target}"),
- None => target.to_owned(),
- }
-}
-
-fn render_doctor(stdout: &mut dyn Write, view: &DoctorView) -> Result<(), RuntimeError> {
- writeln!(stdout, "Readiness check")?;
- let ready = view
- .checks
- .iter()
- .filter(|check| matches!(check.status.as_str(), "ok" | "ready" | "healthy"))
- .map(doctor_item)
- .collect::<Vec<_>>();
- let needs_attention = view
- .checks
- .iter()
- .filter(|check| !matches!(check.status.as_str(), "ok" | "ready" | "healthy"))
- .map(doctor_item)
- .collect::<Vec<_>>();
-
- if !ready.is_empty() || !needs_attention.is_empty() || !view.actions.is_empty() {
- writeln!(stdout)?;
- }
- let mut wrote_section = false;
- if !ready.is_empty() {
- render_item_section(stdout, "Ready", &ready)?;
- wrote_section = true;
- }
- if !needs_attention.is_empty() {
- if wrote_section {
- writeln!(stdout)?;
- }
- render_item_section(stdout, "Needs attention", &needs_attention)?;
- wrote_section = true;
- }
- if !view.actions.is_empty() {
- if wrote_section {
- writeln!(stdout)?;
- }
- render_item_section(stdout, "Next", &view.actions)?;
- }
- writeln!(stdout)?;
- render_account_resolution(stdout, &view.account_resolution)?;
- Ok(())
-}
-
-fn render_find(stdout: &mut dyn Write, view: &FindView) -> Result<(), RuntimeError> {
- render_market_search(stdout, view)
-}
-
-fn render_market_search(stdout: &mut dyn Write, view: &FindView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- writeln!(stdout)?;
- render_item_section(stdout, "Missing", &["Local market data".to_owned()])?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "empty" => {
- writeln!(stdout, "No listings found")?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- _ => {
- writeln!(stdout, "{}", market_search_headline(view))?;
- writeln!(stdout)?;
- for (index, result) in view.results.iter().enumerate() {
- render_market_search_card(stdout, result)?;
- if index + 1 < view.results.len() {
- writeln!(stdout)?;
- }
- }
- if let Some(hyf) = &view.hyf {
- if hyf.rewritten_query.trim() != view.query.trim() {
- writeln!(stdout)?;
- render_item_section(stdout, "Also searched for", &[view.query.clone()])?;
- }
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_market_search_card(
- stdout: &mut dyn Write,
- result: &crate::domain::runtime::FindResultView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "{}", result.title)?;
- let mut rows = vec![("Key", result.product_key.clone())];
- push_row(
- &mut rows,
- "Listing address",
- result
- .listing_addr
- .as_deref()
- .and_then(non_empty_str)
- .map(str::to_owned),
- );
- push_row(
- &mut rows,
- "Place",
- result
- .location_primary
- .as_deref()
- .and_then(non_empty_str)
- .map(str::to_owned),
- );
- push_row(&mut rows, "Offer", quantity_offer_text(&result.available));
- rows.push((
- "Price",
- format_price(
- result.price.amount,
- &result.price.currency,
- result.price.per_amount,
- &result.price.per_unit,
- ),
- ));
- rows.push((
- "Stock",
- format_available(
- result
- .available
- .available_amount
- .unwrap_or(result.available.total_amount),
- result
- .available
- .label
- .as_deref()
- .unwrap_or(result.available.total_unit.as_str()),
- ),
- ));
- render_field_rows(stdout, rows.as_slice())
-}
-
-fn market_search_headline(view: &FindView) -> String {
- let query = view
- .hyf
- .as_ref()
- .map(|hyf| hyf.rewritten_query.as_str())
- .unwrap_or(view.query.as_str());
- format!(
- "{} listing{} for {}",
- view.count,
- if view.count == 1 { "" } else { "s" },
- query
- )
-}
-
-fn render_job_list(stdout: &mut dyn Write, view: &JobListView) -> Result<(), RuntimeError> {
- let context = match view.state.as_str() {
- "ready" => format!(
- "activity · {} job{}",
- view.count,
- if view.count == 1 { "" } else { "s" }
- ),
- "empty" => "activity · no retained jobs".to_owned(),
- "unconfigured" => "activity · jobs unconfigured".to_owned(),
- "unavailable" => "activity · jobs unavailable".to_owned(),
- _ => "activity · jobs error".to_owned(),
- };
- write_context(stdout, context.as_str())?;
- if view.jobs.is_empty() {
- if let Some(reason) = &view.reason {
- writeln!(stdout, "{reason}")?;
- writeln!(stdout)?;
- }
- } else {
- let table = Table {
- headers: &["job", "type", "state", "signer", "session", "updated"],
- rows: view
- .jobs
- .iter()
- .map(|job| {
- let updated_at = job.completed_at_unix.unwrap_or(job.requested_at_unix);
- vec![
- job.id.clone(),
- job.command.clone(),
- job.state.clone(),
- job.signer.clone(),
- job.signer_session_id.clone().unwrap_or_default(),
- crate::runtime::job::format_timestamp(updated_at),
- ]
- })
- .collect(),
- };
- render_table(stdout, &table)?;
- writeln!(stdout)?;
- }
- writeln!(stdout, "rpc url: {}", view.rpc_url)?;
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_job_get(stdout: &mut dyn Write, view: &JobGetView) -> Result<(), RuntimeError> {
- write_context(stdout, format!("activity · {}", view.lookup).as_str())?;
- if let Some(job) = &view.job {
- render_owned_pairs(
- stdout,
- "job",
- &[
- ("id", job.id.clone()),
- ("type", job.command.clone()),
- ("state", job.state.clone()),
- ("signer mode", job.signer.clone()),
- (
- "signer session",
- job.signer_session_id
- .clone()
- .unwrap_or_else(|| "-".to_owned()),
- ),
- (
- "requested",
- crate::runtime::job::format_timestamp(job.requested_at_unix),
- ),
- (
- "completed",
- job.completed_at_unix
- .map(crate::runtime::job::format_timestamp)
- .unwrap_or_else(|| "pending".to_owned()),
- ),
- ("terminal", yes_no(job.terminal).to_owned()),
- (
- "recovered after restart",
- yes_no(job.recovered_after_restart).to_owned(),
- ),
- ("delivery policy", job.delivery_policy.clone()),
- ("relay outcome", job.relay_outcome_summary.clone()),
- ],
- )?;
- if !job.attempt_summaries.is_empty() {
- writeln!(stdout, "attempts")?;
- for attempt in &job.attempt_summaries {
- writeln!(stdout, " {attempt}")?;
- }
- writeln!(stdout)?;
- }
- } else if let Some(reason) = &view.reason {
- writeln!(stdout, "{reason}")?;
- writeln!(stdout)?;
- }
- writeln!(stdout, "rpc url: {}", view.rpc_url)?;
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_job_watch(stdout: &mut dyn Write, view: &JobWatchView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "unavailable" => {
- writeln!(stdout, "Unavailable right now")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "error" => {
- writeln!(stdout, "Could not complete the command")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- _ => {
- writeln!(stdout, "Watching job {}", view.job_id)?;
- if view.frames.is_empty() {
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- } else {
- for frame in &view.frames {
- writeln!(stdout)?;
- writeln!(
- stdout,
- "{}",
- crate::runtime::job::format_clock(frame.observed_at_unix)
- )?;
- let mut rows = vec![
- ("State", humanize_machine_label(frame.state.as_str())),
- ("Summary", frame.summary.clone()),
- ("Signer", humanize_machine_label(frame.signer.as_str())),
- ];
- push_row(&mut rows, "Session", frame.signer_session_id.clone());
- if frame.terminal {
- rows.push(("Terminal", "Yes".to_owned()));
- }
- render_field_rows(stdout, rows.as_slice())?;
- }
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_order_new(stdout: &mut dyn Write, view: &OrderNewView) -> Result<(), RuntimeError> {
- write_context(stdout, "order · draft created")?;
- let mut rows = vec![
- ("order id", view.order_id.as_str()),
- ("file", view.file.as_str()),
- ("ready for submit", yes_no(view.ready_for_submit)),
- ];
- if let Some(listing_lookup) = &view.listing_lookup {
- rows.push(("listing", listing_lookup.as_str()));
- }
- if let Some(listing_addr) = &view.listing_addr {
- rows.push(("listing addr", listing_addr.as_str()));
- }
- if let Some(account_id) = &view.buyer_account_id {
- rows.push(("buyer account", account_id.as_str()));
- }
- if let Some(buyer_pubkey) = &view.buyer_pubkey {
- rows.push(("buyer pubkey", buyer_pubkey.as_str()));
- }
- if let Some(seller_pubkey) = &view.seller_pubkey {
- rows.push(("seller pubkey", seller_pubkey.as_str()));
- }
- render_pairs(stdout, "draft", rows.as_slice())?;
- render_order_items(stdout, &view.items)?;
- render_order_issues(stdout, &view.issues)?;
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_order_get(stdout: &mut dyn Write, view: &OrderGetView) -> Result<(), RuntimeError> {
- let context = match view.state.as_str() {
- "missing" => format!("order · {} missing", view.lookup),
- "submitted" => format!("order · {} submitted", view.lookup),
- "ready" => format!("order · {} ready", view.lookup),
- "draft" => format!("order · {} draft", view.lookup),
- "error" => format!("order · {} error", view.lookup),
- _ => format!("order · {}", view.lookup),
- };
- write_context(stdout, context.as_str())?;
-
- if view.state == "missing" || view.state == "error" {
- if let Some(reason) = &view.reason {
- writeln!(stdout, "{reason}")?;
- writeln!(stdout)?;
- }
- if let Some(file) = &view.file {
- writeln!(stdout, "file: {file}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- return Ok(());
- }
-
- let mut rows = Vec::<(&str, &str)>::new();
- if let Some(order_id) = &view.order_id {
- rows.push(("order id", order_id.as_str()));
- }
- if let Some(file) = &view.file {
- rows.push(("file", file.as_str()));
- }
- rows.push(("ready for submit", yes_no(view.ready_for_submit)));
- if let Some(listing_lookup) = &view.listing_lookup {
- rows.push(("listing", listing_lookup.as_str()));
- }
- if let Some(listing_addr) = &view.listing_addr {
- rows.push(("listing addr", listing_addr.as_str()));
- }
- if let Some(account_id) = &view.buyer_account_id {
- rows.push(("buyer account", account_id.as_str()));
- }
- if let Some(buyer_pubkey) = &view.buyer_pubkey {
- rows.push(("buyer pubkey", buyer_pubkey.as_str()));
- }
- if let Some(seller_pubkey) = &view.seller_pubkey {
- rows.push(("seller pubkey", seller_pubkey.as_str()));
- }
- render_pairs(stdout, "order", rows.as_slice())?;
- if let Some(updated_at_unix) = view.updated_at_unix {
- writeln!(
- stdout,
- "updated: {}",
- crate::runtime::job::format_timestamp(updated_at_unix)
- )?;
- }
- render_order_items(stdout, &view.items)?;
- if let Some(job) = &view.job {
- render_order_job(stdout, job)?;
- }
- if let Some(workflow) = &view.workflow {
- render_order_workflow(stdout, workflow)?;
- }
- render_order_issues(stdout, &view.issues)?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_order_list(stdout: &mut dyn Write, view: &OrderListView) -> Result<(), RuntimeError> {
- let context = match view.state.as_str() {
- "empty" => "orders · no local drafts".to_owned(),
- "degraded" => format!("orders · {} local drafts with issues", view.count),
- _ => format!(
- "orders · {} local draft{}",
- view.count,
- if view.count == 1 { "" } else { "s" }
- ),
- };
- write_context(stdout, context.as_str())?;
- if view.orders.is_empty() {
- writeln!(stdout, "no order drafts found")?;
- writeln!(stdout)?;
- } else {
- let table = Table {
- headers: &["order", "listing", "state", "ready", "job", "updated"],
- rows: view
- .orders
- .iter()
- .map(|order| {
- vec![
- order.id.clone(),
- order
- .listing_lookup
- .clone()
- .or_else(|| order.listing_addr.clone())
- .unwrap_or_default(),
- order.state.clone(),
- yes_no(order.ready_for_submit).to_owned(),
- order
- .job
- .as_ref()
- .map(|job| job.state.clone())
- .unwrap_or_default(),
- crate::runtime::job::format_timestamp(order.updated_at_unix),
- ]
- })
- .collect(),
- };
- render_table(stdout, &table)?;
- writeln!(stdout)?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_order_submit(stdout: &mut dyn Write, view: &OrderSubmitView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "dry_run" => {
- writeln!(stdout, "Dry run only")?;
- writeln!(stdout)?;
- writeln!(stdout, "Order would be submitted.")?;
- writeln!(stdout)?;
- render_order_submit_section(stdout, view)?;
- writeln!(stdout, "Nothing was written.")?;
- }
- "missing" => {
- writeln!(stdout, "Not found")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.issues.is_empty() {
- writeln!(stdout)?;
- writeln!(stdout, "Needs attention")?;
- let rows = view
- .issues
- .iter()
- .map(|issue| (issue.field.as_str(), issue.message.clone()))
- .collect::<Vec<_>>();
- render_field_rows(stdout, rows.as_slice())?;
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "unavailable" => {
- writeln!(stdout, "Unavailable right now")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "error" => {
- writeln!(stdout, "Could not complete the command")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- _ => {
- writeln!(
- stdout,
- "{}",
- match view.state.as_str() {
- "already_submitted" => "Order already submitted",
- "deduplicated" => "Order already in progress",
- _ => "Order submitted",
- }
- )?;
- writeln!(stdout)?;
- render_order_submit_section(stdout, view)?;
- if let Some(job) = &view.job {
- writeln!(stdout)?;
- writeln!(stdout, "Job")?;
- let mut rows = vec![
- ("Job", job.job_id.clone()),
- ("State", humanize_machine_label(job.state.as_str())),
- ];
- push_row(&mut rows, "Event", job.event_id.clone());
- render_field_rows(stdout, rows.as_slice())?;
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_order_submit_section(
- stdout: &mut dyn Write,
- view: &OrderSubmitView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "Order")?;
- let mut rows = vec![("ID", view.order_id.clone())];
- push_row(
- &mut rows,
- "Listing",
- first_present([view.listing_lookup.as_deref(), view.listing_addr.as_deref()]),
- );
- push_row(
- &mut rows,
- "Buyer",
- first_present([
- view.buyer_account_id.as_deref(),
- view.buyer_pubkey.as_deref(),
- ]),
- );
- if !matches!(view.state.as_str(), "dry_run" | "missing" | "unconfigured") {
- rows.push(("State", humanize_machine_label(view.state.as_str())));
- }
- render_field_rows(stdout, rows.as_slice())
-}
-
-fn render_order_submit_watch(
- stdout: &mut dyn Write,
- view: &OrderSubmitWatchView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "{}", order_submit_watch_headline(&view.submit))?;
- writeln!(stdout)?;
-
- writeln!(stdout, "Order")?;
- let mut order_rows = vec![
- ("ID", view.submit.order_id.clone()),
- ("State", humanize_machine_label(view.submit.state.as_str())),
- ];
- push_row(
- &mut order_rows,
- "Listing",
- first_present([
- view.submit.listing_lookup.as_deref(),
- view.submit.listing_addr.as_deref(),
- ]),
- );
- push_row(
- &mut order_rows,
- "Buyer",
- first_present([
- view.submit.buyer_account_id.as_deref(),
- view.submit.buyer_pubkey.as_deref(),
- ]),
- );
- render_field_rows(stdout, order_rows.as_slice())?;
-
- if let Some(job) = &view.submit.job {
- writeln!(stdout, "Job")?;
- let mut job_rows = vec![
- ("Job", job.job_id.clone()),
- ("State", humanize_machine_label(job.state.as_str())),
- ];
- push_row(&mut job_rows, "Event", job.event_id.clone());
- render_field_rows(stdout, job_rows.as_slice())?;
- }
-
- writeln!(stdout, "Watching order {}", view.watch.order_id)?;
-
- if view.watch.frames.is_empty() {
- if let Some(reason) = &view.watch.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- } else {
- for frame in &view.watch.frames {
- writeln!(stdout)?;
- writeln!(
- stdout,
- "{}",
- crate::runtime::job::format_clock(frame.observed_at_unix)
- )?;
- let rows = vec![
- ("State", humanize_machine_label(frame.state.as_str())),
- ("Summary", frame.summary.clone()),
- ];
- render_field_rows(stdout, rows.as_slice())?;
- }
- }
-
- if !view.watch.actions.is_empty() {
- render_item_section(stdout, "Next", &view.watch.actions)?;
- }
- Ok(())
-}
-
-fn render_order_watch(stdout: &mut dyn Write, view: &OrderWatchView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "missing" => {
- writeln!(stdout, "Not found")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "not_submitted" | "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "unavailable" => {
- writeln!(stdout, "Unavailable right now")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "error" => {
- writeln!(stdout, "Could not complete the command")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- _ => {
- writeln!(stdout, "Watching order {}", view.order_id)?;
- if view.frames.is_empty() {
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- } else {
- for frame in &view.frames {
- writeln!(stdout)?;
- writeln!(
- stdout,
- "{}",
- crate::runtime::job::format_clock(frame.observed_at_unix)
- )?;
- let mut rows = vec![
- ("State", humanize_machine_label(frame.state.as_str())),
- ("Summary", frame.summary.clone()),
- ];
- push_row(
- &mut rows,
- "Signer",
- Some(humanize_machine_label(frame.signer_mode.as_str())),
- );
- push_row(&mut rows, "Session", frame.signer_session_id.clone());
- if frame.terminal {
- rows.push(("Terminal", "Yes".to_owned()));
- }
- render_field_rows(stdout, rows.as_slice())?;
- }
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_order_history(
- stdout: &mut dyn Write,
- view: &OrderHistoryView,
-) -> Result<(), RuntimeError> {
- let context = match view.state.as_str() {
- "empty" => "order history · no submitted orders".to_owned(),
- _ => format!(
- "order history · {} submitted order{}",
- view.count,
- if view.count == 1 { "" } else { "s" }
- ),
- };
- write_context(stdout, context.as_str())?;
- if view.orders.is_empty() {
- if let Some(reason) = &view.reason {
- writeln!(stdout, "{reason}")?;
- writeln!(stdout)?;
- }
- } else {
- let table = Table {
- headers: &["order", "listing", "state", "job", "submitted", "updated"],
- rows: view
- .orders
- .iter()
- .map(|order| {
- vec![
- order.id.clone(),
- order
- .listing_lookup
- .clone()
- .or_else(|| order.listing_addr.clone())
- .unwrap_or_default(),
- order.state.clone(),
- order
- .job
- .as_ref()
- .map(|job| job.job_id.clone())
- .unwrap_or_default(),
- order
- .submitted_at_unix
- .map(crate::runtime::job::format_timestamp)
- .unwrap_or_default(),
- crate::runtime::job::format_timestamp(order.updated_at_unix),
- ]
- })
- .collect(),
- };
- render_table(stdout, &table)?;
- writeln!(stdout)?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "note: {reason}")?;
- }
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_order_cancel(stdout: &mut dyn Write, view: &OrderCancelView) -> Result<(), RuntimeError> {
- let context = match view.state.as_str() {
- "missing" => format!("order · {} missing", view.lookup),
- "not_submitted" => format!("order · {} not submitted", view.lookup),
- "unconfigured" => format!("order · {} cancel unavailable", view.lookup),
- "unavailable" => format!("order · {} cancel unavailable", view.lookup),
- "error" => format!("order · {} cancel error", view.lookup),
- _ => format!("order · {} cancel", view.lookup),
- };
- write_context(stdout, context.as_str())?;
- let mut rows = vec![("lookup", view.lookup.as_str())];
- if let Some(order_id) = &view.order_id {
- rows.push(("order id", order_id.as_str()));
- }
- render_pairs(stdout, "order", rows.as_slice())?;
- if let Some(job) = &view.job {
- render_order_job(stdout, job)?;
- }
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_order_items(
- stdout: &mut dyn Write,
- items: &[OrderDraftItemView],
-) -> Result<(), RuntimeError> {
- if items.is_empty() {
- writeln!(stdout, "items: no line items yet")?;
- writeln!(stdout)?;
- return Ok(());
- }
-
- let table = Table {
- headers: &["bin", "qty"],
- rows: items
- .iter()
- .map(|item| vec![item.bin_id.clone(), item.bin_count.to_string()])
- .collect(),
- };
- render_table(stdout, &table)?;
- writeln!(stdout)?;
- Ok(())
-}
-
-fn render_order_job(stdout: &mut dyn Write, job: &OrderJobView) -> Result<(), RuntimeError> {
- let mut rows = vec![
- ("job id", job.job_id.as_str()),
- ("state", job.state.as_str()),
- ];
- if let Some(command) = &job.command {
- rows.push(("command", command.as_str()));
- }
- if let Some(signer_mode) = &job.signer_mode {
- rows.push(("signer mode", signer_mode.as_str()));
- }
- if let Some(signer_session_id) = &job.signer_session_id {
- rows.push(("signer session", signer_session_id.as_str()));
- }
- if let Some(requested_signer_session_id) = &job.requested_signer_session_id {
- rows.push((
- "requested signer session",
- requested_signer_session_id.as_str(),
- ));
- }
- if let Some(event_id) = &job.event_id {
- rows.push(("event id", event_id.as_str()));
- }
- if let Some(event_addr) = &job.event_addr {
- rows.push(("event addr", event_addr.as_str()));
- }
- render_pairs(stdout, "job", rows.as_slice())?;
- if let Some(reason) = &job.reason {
- writeln!(stdout, "job reason: {reason}")?;
- }
- Ok(())
-}
-
-fn render_order_workflow(
- stdout: &mut dyn Write,
- workflow: &OrderWorkflowView,
-) -> Result<(), RuntimeError> {
- let mut rows = vec![
- ("state", workflow.state.as_str()),
- ("order id", workflow.order_id.as_str()),
- ];
- if let Some(listing_addr) = &workflow.listing_addr {
- rows.push(("listing addr", listing_addr.as_str()));
- }
- if let Some(validated_listing_event_id) = &workflow.validated_listing_event_id {
- rows.push((
- "validated listing event",
- validated_listing_event_id.as_str(),
- ));
- }
- if let Some(root_event_id) = &workflow.root_event_id {
- rows.push(("root event id", root_event_id.as_str()));
- }
- if let Some(last_event_id) = &workflow.last_event_id {
- rows.push(("last event id", last_event_id.as_str()));
- }
- render_pairs(stdout, "workflow", rows.as_slice())?;
- if let Some(reason) = &workflow.reason {
- writeln!(stdout, "workflow reason: {reason}")?;
- }
- writeln!(stdout, "workflow source: {}", workflow.source)?;
- Ok(())
-}
-
-fn render_order_issues(
- stdout: &mut dyn Write,
- issues: &[crate::domain::runtime::OrderIssueView],
-) -> Result<(), RuntimeError> {
- if issues.is_empty() {
- return Ok(());
- }
-
- writeln!(stdout, "issues")?;
- for issue in issues {
- writeln!(stdout, " {} {}", issue.field, issue.message)?;
- }
- writeln!(stdout)?;
- Ok(())
-}
-
-fn render_listing_new(stdout: &mut dyn Write, view: &ListingNewView) -> Result<(), RuntimeError> {
- write_context(stdout, "listing · draft created")?;
- let mut rows = vec![
- ("file", view.file.as_str()),
- ("listing id", view.listing_id.as_str()),
- ];
- if let Some(account_id) = &view.selected_account_id {
- rows.push(("account id", account_id.as_str()));
- }
- if let Some(seller_pubkey) = &view.seller_pubkey {
- rows.push(("seller", seller_pubkey.as_str()));
- }
- if let Some(farm_d_tag) = &view.farm_d_tag {
- rows.push(("farm d_tag", farm_d_tag.as_str()));
- }
- if let Some(delivery_method) = &view.delivery_method {
- rows.push(("delivery", delivery_method.as_str()));
- }
- if let Some(location_primary) = &view.location_primary {
- rows.push(("location", location_primary.as_str()));
- }
- render_pairs(stdout, "draft", rows.as_slice())?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_listing_validate(
- stdout: &mut dyn Write,
- view: &ListingValidateView,
-) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- match view.state.as_str() {
- "valid" => "listing · valid",
- _ => "listing · invalid",
- },
- )?;
- let status = if view.valid {
- "ready to publish"
- } else {
- "needs edits"
- };
- let mut rows = vec![("file", view.file.as_str()), ("status", status)];
- if let Some(listing_id) = &view.listing_id {
- rows.push(("listing id", listing_id.as_str()));
- }
- if let Some(seller_pubkey) = &view.seller_pubkey {
- rows.push(("seller", seller_pubkey.as_str()));
- }
- if let Some(farm_d_tag) = &view.farm_d_tag {
- rows.push(("farm d_tag", farm_d_tag.as_str()));
- }
- render_pairs(stdout, "validation", rows.as_slice())?;
- if !view.issues.is_empty() {
- writeln!(stdout, "issues")?;
- for issue in &view.issues {
- match issue.line {
- Some(line) => writeln!(
- stdout,
- " {field} {message} (line {line})",
- field = issue.field,
- message = issue.message
- )?,
- None => writeln!(
- stdout,
- " {field} {message}",
- field = issue.field,
- message = issue.message
- )?,
- }
- }
- writeln!(stdout)?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_listing_get(stdout: &mut dyn Write, view: &ListingGetView) -> Result<(), RuntimeError> {
- render_market_view(stdout, view)
-}
-
-fn render_market_view(stdout: &mut dyn Write, view: &ListingGetView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "missing" => {
- writeln!(stdout, "Not found")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- _ => {
- let headline = view.title.as_deref().unwrap_or("Listing");
- writeln!(stdout, "{headline}")?;
- writeln!(stdout)?;
- let mut rows = Vec::<(&str, String)>::new();
- push_row(
- &mut rows,
- "Key",
- view.product_key
- .as_deref()
- .and_then(non_empty_str)
- .map(str::to_owned),
- );
- push_row(
- &mut rows,
- "Listing address",
- view.listing_addr
- .as_deref()
- .and_then(non_empty_str)
- .map(str::to_owned),
- );
- push_row(
- &mut rows,
- "Category",
- view.category
- .as_deref()
- .and_then(non_empty_str)
- .map(str::to_owned),
- );
- push_row(
- &mut rows,
- "Place",
- view.location_primary
- .as_deref()
- .and_then(non_empty_str)
- .map(str::to_owned),
- );
- if let Some(available) = &view.available {
- push_row(&mut rows, "Offer", quantity_offer_text(available));
- rows.push((
- "Stock",
- format_available(
- available.available_amount.unwrap_or(available.total_amount),
- available
- .label
- .as_deref()
- .unwrap_or(available.total_unit.as_str()),
- ),
- ));
- }
- if let Some(price) = &view.price {
- rows.push((
- "Price",
- format_price(
- price.amount,
- &price.currency,
- price.per_amount,
- &price.per_unit,
- ),
- ));
- }
- render_owned_pairs(stdout, "Listing", rows.as_slice())?;
- let mut wrote_about = false;
- if let Some(description) = &view.description {
- render_item_section(stdout, "About", &[description.clone()])?;
- wrote_about = true;
- }
- if !view.actions.is_empty() {
- if wrote_about {
- writeln!(stdout)?;
- }
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_sell_add(stdout: &mut dyn Write, view: &SellAddView) -> Result<(), RuntimeError> {
- writeln!(stdout, "Listing draft saved")?;
- writeln!(stdout)?;
- writeln!(stdout, "The draft is local until you publish it.")?;
- writeln!(stdout)?;
-
- let mut draft_rows = vec![("File", view.file.clone())];
- push_row(&mut draft_rows, "Listing", view.product_key.clone());
- push_row(&mut draft_rows, "Title", view.title.clone());
- push_row(&mut draft_rows, "Offer", view.offer.clone());
- push_row(&mut draft_rows, "Price", view.price.clone());
- push_row(&mut draft_rows, "Stock", view.stock.clone());
- render_owned_pairs(stdout, "Draft", draft_rows.as_slice())?;
-
- let mut default_rows = Vec::<(&str, String)>::new();
- push_row(&mut default_rows, "Farm", view.farm_name.clone());
- push_row(
- &mut default_rows,
- "Delivery",
- view.delivery_method
- .as_deref()
- .map(humanize_delivery_method),
- );
- push_row(&mut default_rows, "Place", view.location_primary.clone());
- if !default_rows.is_empty() {
- render_owned_pairs(stdout, "Defaults", default_rows.as_slice())?;
- }
-
- if let Some(reason) = &view.reason {
- writeln!(stdout, "{reason}")?;
- writeln!(stdout)?;
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_sell_show(stdout: &mut dyn Write, view: &SellShowView) -> Result<(), RuntimeError> {
- writeln!(stdout, "Listing draft")?;
- writeln!(stdout)?;
-
- let mut draft_rows = vec![("File", view.file.clone())];
- push_row(&mut draft_rows, "Listing", view.product_key.clone());
- push_row(&mut draft_rows, "Title", view.title.clone());
- push_row(&mut draft_rows, "Category", view.category.clone());
- push_row(&mut draft_rows, "Offer", view.offer.clone());
- push_row(&mut draft_rows, "Price", view.price.clone());
- push_row(&mut draft_rows, "Stock", view.stock.clone());
- push_row(
- &mut draft_rows,
- "Delivery",
- view.delivery_method
- .as_deref()
- .map(humanize_delivery_method),
- );
- push_row(&mut draft_rows, "Place", view.location_primary.clone());
- render_owned_pairs(stdout, "Draft", draft_rows.as_slice())?;
-
- if let Some(reason) = &view.reason {
- writeln!(stdout, "{reason}")?;
- writeln!(stdout)?;
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_sell_check(stdout: &mut dyn Write, view: &SellCheckView) -> Result<(), RuntimeError> {
- if view.valid {
- writeln!(stdout, "Draft looks ready")?;
- writeln!(stdout)?;
- let mut draft_rows = vec![("File", view.file.clone())];
- push_row(&mut draft_rows, "Listing", view.product_key.clone());
- push_row(&mut draft_rows, "Seller", view.seller_pubkey.clone());
- push_row(&mut draft_rows, "Farm", view.farm_ref.clone());
- render_owned_pairs(stdout, "Draft", draft_rows.as_slice())?;
- } else {
- writeln!(stdout, "Draft needs changes")?;
- writeln!(stdout)?;
- let rows = view
- .issues
- .iter()
- .map(|issue| (issue.field.as_str(), issue.message.clone()))
- .collect::<Vec<_>>();
- render_field_rows(stdout, rows.as_slice())?;
- }
-
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_sell_mutation(
- stdout: &mut dyn Write,
- view: &SellMutationView,
-) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "dry_run" => {
- writeln!(stdout, "Dry run only")?;
- writeln!(stdout)?;
- writeln!(
- stdout,
- "Listing would be {}.",
- match view.operation.as_str() {
- "publish" => "published",
- "update" => "updated",
- "pause" => "paused",
- _ => "changed",
- }
- )?;
- writeln!(stdout)?;
- let mut rows = vec![("File", view.file.clone())];
- push_row(&mut rows, "Listing", view.product_key.clone());
- rows.push(("Address", view.listing_addr.clone()));
- if view.operation == "publish" {
- push_row(
- &mut rows,
- "Publish mode",
- view.publish_mode.as_deref().map(|mode| {
- if mode == "runtime_bridge" {
- "Runtime bridge".to_owned()
- } else {
- mode.to_owned()
- }
- }),
- );
- }
- render_owned_pairs(stdout, "Listing", rows.as_slice())?;
- writeln!(stdout, "Nothing was written.")?;
- }
- "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "unavailable" => {
- writeln!(stdout, "Unavailable right now")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "error" => {
- writeln!(stdout, "Something went wrong")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- _ => {
- writeln!(
- stdout,
- "{}",
- match view.operation.as_str() {
- "publish" => "Listing published",
- "update" => "Listing updated",
- "pause" => "Listing paused",
- _ => "Listing updated",
- }
- )?;
- writeln!(stdout)?;
- let mut listing_rows = vec![("File", view.file.clone())];
- push_row(&mut listing_rows, "Listing", view.product_key.clone());
- listing_rows.push(("Address", view.listing_addr.clone()));
- if view.operation == "publish" {
- push_row(
- &mut listing_rows,
- "Publish mode",
- view.publish_mode.as_deref().map(|mode| {
- if mode == "runtime_bridge" {
- "Runtime bridge".to_owned()
- } else {
- mode.to_owned()
- }
- }),
- );
- }
- render_owned_pairs(stdout, "Listing", listing_rows.as_slice())?;
-
- let mut job_rows = Vec::<(&str, String)>::new();
- push_row(&mut job_rows, "State", view.job_status.clone());
- push_row(&mut job_rows, "Job", view.job_id.clone());
- push_row(&mut job_rows, "Event", view.event_id.clone());
- if !job_rows.is_empty() {
- render_owned_pairs(stdout, "Job", job_rows.as_slice())?;
- }
-
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_sell_draft_mutation(
- stdout: &mut dyn Write,
- view: &SellDraftMutationView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "Draft updated")?;
- writeln!(stdout)?;
- render_owned_pairs(
- stdout,
- "Changed",
- &[(view.changed_label.as_str(), view.changed_value.clone())],
- )?;
- let mut draft_rows = vec![("File", view.file.clone())];
- push_row(&mut draft_rows, "Listing", view.product_key.clone());
- render_owned_pairs(stdout, "Draft", draft_rows.as_slice())?;
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_listing_mutation(
- stdout: &mut dyn Write,
- view: &ListingMutationView,
-) -> Result<(), RuntimeError> {
- let context = match view.state.as_str() {
- "dry_run" => format!("listing · {} dry run", view.operation),
- "deduplicated" => format!("listing · {} deduplicated", view.operation),
- "published" => format!("listing · {} completed", view.operation),
- "failed" | "unavailable" => format!("listing · {} unavailable", view.operation),
- "unconfigured" => format!("listing · {} unconfigured", view.operation),
- "error" => format!("listing · {} error", view.operation),
- other => format!("listing · {} {other}", view.operation),
- };
- write_context(stdout, context.as_str())?;
-
- let mut rows = vec![
- ("file", view.file.as_str()),
- ("listing id", view.listing_id.as_str()),
- ("event addr", view.listing_addr.as_str()),
- ];
- if let Some(job_id) = &view.job_id {
- rows.push(("job id", job_id.as_str()));
- }
- if let Some(job_status) = &view.job_status {
- rows.push(("status", job_status.as_str()));
- }
- if let Some(event_id) = &view.event_id {
- rows.push(("event id", event_id.as_str()));
- }
- if let Some(signer_mode) = &view.signer_mode {
- rows.push(("signer mode", signer_mode.as_str()));
- }
- if let Some(signer_session_id) = &view.signer_session_id {
- rows.push(("signer session", signer_session_id.as_str()));
- }
- if let Some(requested_signer_session_id) = &view.requested_signer_session_id {
- rows.push((
- "requested signer session",
- requested_signer_session_id.as_str(),
- ));
- }
- render_pairs(stdout, "listing", rows.as_slice())?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
-
- if let Some(job) = &view.job {
- writeln!(stdout)?;
- writeln!(stdout, "job preview")?;
- let job_json = serde_json::to_string_pretty(job)?;
- for line in job_json.lines() {
- writeln!(stdout, " {line}")?;
- }
- }
- if let Some(event) = &view.event {
- writeln!(stdout)?;
- writeln!(stdout, "event preview")?;
- let event_json = serde_json::to_string_pretty(event)?;
- for line in event_json.lines() {
- writeln!(stdout, " {line}")?;
- }
- }
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_relay_list(stdout: &mut dyn Write, view: &RelayListView) -> Result<(), RuntimeError> {
- if view.relays.is_empty() {
- writeln!(stdout, "Not ready yet")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- writeln!(stdout)?;
- render_item_section(stdout, "Missing", &["Relay configuration".to_owned()])?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- return Ok(());
- }
-
- writeln!(
- stdout,
- "{} relay{}",
- view.count,
- if view.count == 1 { "" } else { "s" }
- )?;
- writeln!(stdout)?;
- for (index, relay) in view.relays.iter().enumerate() {
- writeln!(stdout, "{}", relay.url)?;
- let rows = vec![
- (
- "Read",
- if relay.read {
- "Yes".to_owned()
- } else {
- "No".to_owned()
- },
- ),
- (
- "Write",
- if relay.write {
- "Yes".to_owned()
- } else {
- "No".to_owned()
- },
- ),
- ];
- render_field_rows(stdout, rows.as_slice())?;
- if index + 1 < view.relays.len() {
- writeln!(stdout)?;
- }
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
-}
-
-fn render_net_status(stdout: &mut dyn Write, view: &NetStatusView) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- match view.state.as_str() {
- "configured" => "network · configured",
- _ => "network · unconfigured",
- },
- )?;
- let relay_count = view.relay_count.to_string();
- let rows = vec![
- ("status", view.state.as_str()),
- ("session", view.session.as_str()),
- ("relays configured", relay_count.as_str()),
- ("publish policy", view.publish_policy.as_str()),
- ("signer mode", view.signer_mode.as_str()),
- ];
- render_pairs(stdout, "network", rows.as_slice())?;
- writeln!(stdout)?;
- render_account_resolution(stdout, &view.account_resolution)?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_rpc_status(stdout: &mut dyn Write, view: &RpcStatusView) -> Result<(), RuntimeError> {
- write_context(stdout, format!("rpc · {}", view.state).as_str())?;
- let mut rows = vec![("url", view.url.as_str()), ("status", view.state.as_str())];
- if let Some(auth_mode) = &view.auth_mode {
- rows.push(("auth mode", auth_mode.as_str()));
- }
- if let Some(signer_mode) = &view.signer_mode {
- rows.push(("signer mode", signer_mode.as_str()));
- }
- if let Some(default_signer_mode) = &view.default_signer_mode {
- rows.push(("default signer", default_signer_mode.as_str()));
- }
- render_pairs(stdout, "rpc", rows.as_slice())?;
-
- let mut bridge_rows = Vec::<(&str, String)>::new();
- if let Some(enabled) = view.bridge_enabled {
- bridge_rows.push(("bridge enabled", yes_no(enabled).to_owned()));
- }
- if let Some(ready) = view.bridge_ready {
- bridge_rows.push(("bridge ready", yes_no(ready).to_owned()));
- }
- if let Some(relay_count) = view.relay_count {
- bridge_rows.push(("relay count", relay_count.to_string()));
- }
- if let Some(retained_jobs) = view.retained_jobs {
- bridge_rows.push(("retained jobs", retained_jobs.to_string()));
- }
- if let Some(job_status_retention) = view.job_status_retention {
- bridge_rows.push(("job retention", job_status_retention.to_string()));
- }
- if !bridge_rows.is_empty() {
- render_owned_pairs(stdout, "bridge", bridge_rows.as_slice())?;
- }
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_rpc_sessions(stdout: &mut dyn Write, view: &RpcSessionsView) -> Result<(), RuntimeError> {
- let context = match view.state.as_str() {
- "ready" => format!(
- "rpc · {} session{}",
- view.count,
- if view.count == 1 { "" } else { "s" }
- ),
- "empty" => "rpc · no public sessions".to_owned(),
- "unconfigured" => "rpc · sessions unconfigured".to_owned(),
- "unavailable" => "rpc · sessions unavailable".to_owned(),
- _ => "rpc · sessions error".to_owned(),
- };
- write_context(stdout, context.as_str())?;
- if view.sessions.is_empty() {
- if let Some(reason) = &view.reason {
- writeln!(stdout, "{reason}")?;
- writeln!(stdout)?;
- }
- } else {
- let table = Table {
- headers: &[
- "session",
- "role",
- "user",
- "auth",
- "authorized",
- "relays",
- "expires",
- ],
- rows: view
- .sessions
- .iter()
- .map(|session| {
- vec![
- session.session_id.clone(),
- session.role.clone(),
- session
- .user_pubkey
- .clone()
- .unwrap_or_else(|| "n/a".to_owned()),
- yes_no(session.auth_required).to_owned(),
- yes_no(session.authorized).to_owned(),
- session.relay_count.to_string(),
- session
- .expires_in_secs
- .map(|secs| format!("{secs}s"))
- .unwrap_or_else(|| "n/a".to_owned()),
- ]
- })
- .collect(),
- };
- render_table(stdout, &table)?;
- writeln!(stdout)?;
- }
- writeln!(stdout, "rpc url: {}", view.url)?;
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_signer_session_action(
- stdout: &mut dyn Write,
- view: &crate::domain::runtime::SignerSessionActionView,
-) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- format!("signer session · {} · {}", view.action, view.state).as_str(),
- )?;
- let relays = view.relays.join(", ");
- let permissions = view.permissions.join(", ");
- let auth_required = view.auth_required.map(yes_no).unwrap_or("n/a");
- let authorized = view.authorized.map(yes_no).unwrap_or("n/a");
- let replayed = view.replayed.map(yes_no).unwrap_or("n/a");
- let required = view.required.map(yes_no).unwrap_or("n/a");
- let closed = view.closed.map(yes_no).unwrap_or("n/a");
- let expires = view
- .expires_in_secs
- .map(|secs| format!("{secs}s"))
- .unwrap_or_else(|| "n/a".to_owned());
- render_pairs(
- stdout,
- "session",
- &[
- ("id", view.session_id.as_deref().unwrap_or("n/a")),
- ("mode", view.mode.as_deref().unwrap_or("n/a")),
- (
- "remote signer",
- view.remote_signer_pubkey.as_deref().unwrap_or("n/a"),
- ),
- ("client", view.client_pubkey.as_deref().unwrap_or("n/a")),
- (
- "signer pubkey",
- view.signer_pubkey.as_deref().unwrap_or("n/a"),
- ),
- ("user pubkey", view.user_pubkey.as_deref().unwrap_or("n/a")),
- ("pubkey", view.pubkey.as_deref().unwrap_or("n/a")),
- ("auth required", auth_required),
- ("authorized", authorized),
- ("auth url", view.auth_url.as_deref().unwrap_or("n/a")),
- ("expires", expires.as_str()),
- ("replayed", replayed),
- ("required", required),
- ("closed", closed),
- (
- "relays",
- if relays.is_empty() {
- "n/a"
- } else {
- relays.as_str()
- },
- ),
- (
- "permissions",
- if permissions.is_empty() {
- "n/a"
- } else {
- permissions.as_str()
- },
- ),
- ],
- )?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- Ok(())
-}
-
-fn render_sync_status(stdout: &mut dyn Write, view: &SyncStatusView) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- match view.state.as_str() {
- "ready" => "activity · sync status",
- _ => "activity · sync unconfigured",
- },
- )?;
- let relay_count = view.relay_count.to_string();
- let expected = view.queue.expected_count.to_string();
- let pending = view.queue.pending_count.to_string();
- render_pairs(
- stdout,
- "sync",
- &[
- ("status", view.state.as_str()),
- ("freshness", view.freshness.display.as_str()),
- ("pending", pending.as_str()),
- ("expected", expected.as_str()),
- ("relays", relay_count.as_str()),
- ("publish policy", view.publish_policy.as_str()),
- ("replica db", view.replica_db.as_str()),
- ("local root", view.local_root.as_str()),
- ],
- )?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_sync_action(stdout: &mut dyn Write, view: &SyncActionView) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- format!("activity · sync {} {}", view.direction, view.state).as_str(),
- )?;
- let relay_count = view.relay_count.to_string();
- let expected = view.queue.expected_count.to_string();
- let pending = view.queue.pending_count.to_string();
- render_pairs(
- stdout,
- "sync",
- &[
- ("direction", view.direction.as_str()),
- ("status", view.state.as_str()),
- ("freshness", view.freshness.display.as_str()),
- ("pending", pending.as_str()),
- ("expected", expected.as_str()),
- ("relays", relay_count.as_str()),
- ("publish policy", view.publish_policy.as_str()),
- ("replica db", view.replica_db.as_str()),
- ("local root", view.local_root.as_str()),
- ],
- )?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_market_update(stdout: &mut dyn Write, view: &SyncActionView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- let mut missing = Vec::new();
- if view.replica_db == "missing" {
- missing.push("Local market data".to_owned());
- }
- if view.relay_count == 0 {
- missing.push("Relay configuration".to_owned());
- }
- if !missing.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Missing", &missing)?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "unavailable" => {
- writeln!(stdout, "Unavailable right now")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- _ => {
- writeln!(stdout, "Market data updated")?;
- writeln!(stdout)?;
- render_owned_pairs(
- stdout,
- "Local data",
- &[
- ("State", view.state.clone()),
- ("Updated", view.freshness.display.clone()),
- ("Relays", view.relay_count.to_string()),
- ],
- )?;
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_sync_watch(stdout: &mut dyn Write, view: &SyncWatchView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "unavailable" => {
- writeln!(stdout, "Unavailable right now")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- "error" => {
- writeln!(stdout, "Could not complete the command")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- _ => {
- writeln!(stdout, "Watching market sync")?;
- if view.frames.is_empty() {
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- } else {
- for frame in &view.frames {
- writeln!(stdout)?;
- writeln!(
- stdout,
- "{}",
- crate::runtime::job::format_clock(frame.observed_at)
- )?;
- let rows = vec![
- ("State", humanize_machine_label(frame.state.as_str())),
- ("Relays", frame.relay_count.to_string()),
- ("Updated", frame.freshness.display.clone()),
- ("Queue", format!("{} pending", frame.queue.pending_count)),
- ];
- render_field_rows(stdout, rows.as_slice())?;
- }
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- }
- Ok(())
-}
-
-fn render_farm_setup(stdout: &mut dyn Write, view: &FarmSetupView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "unconfigured" => {
- writeln!(stdout, "Not ready yet")?;
- writeln!(stdout)?;
- render_item_section(stdout, "Missing", &["Resolved account".to_owned()])?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
- }
- _ => {
- writeln!(stdout, "Farm draft saved")?;
- if let Some(reason) = &view.reason {
- writeln!(stdout)?;
- writeln!(stdout, "{reason}")?;
- }
- if let Some(config) = &view.config {
- writeln!(stdout)?;
- render_farm_summary(stdout, config)?;
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
- }
- }
-}
-
-fn render_farm_set(stdout: &mut dyn Write, view: &FarmSetView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "unconfigured" => {
- writeln!(stdout, "Farm draft not found")?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
- }
- _ => {
- writeln!(stdout, "Farm updated")?;
- writeln!(stdout)?;
- render_owned_pairs(
- stdout,
- "Changed",
- &[("Field", view.field.clone()), ("Value", view.value.clone())],
- )?;
- if let Some(config) = &view.config {
- render_farm_summary(stdout, config)?;
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
- }
- }
-}
-
-fn render_farm_status(stdout: &mut dyn Write, view: &FarmStatusView) -> Result<(), RuntimeError> {
- match view.state.as_str() {
- "ready" => {
- writeln!(stdout, "Farm ready to publish")?;
- if let Some(config) = &view.config {
- writeln!(stdout)?;
- render_farm_summary(stdout, config)?;
- }
- if !view.actions.is_empty() {
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
- }
- _ => {
- writeln!(stdout, "Farm not ready yet")?;
- if !view.missing.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Missing", &view.missing)?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- Ok(())
- }
- }
-}
-
-fn render_farm_get(stdout: &mut dyn Write, view: &FarmGetView) -> Result<(), RuntimeError> {
- if let Some(document) = &view.document {
- writeln!(stdout, "Farm draft")?;
- writeln!(stdout)?;
- render_farm_document(stdout, document)?;
- } else {
- writeln!(stdout, "Farm draft not found")?;
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- }
- Ok(())
-}
-
-fn render_farm_publish(stdout: &mut dyn Write, view: &FarmPublishView) -> Result<(), RuntimeError> {
- if view.state == "unconfigured" {
- writeln!(stdout, "Not ready yet")?;
- if !view.missing.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Missing", &view.missing)?;
- }
- if !view.actions.is_empty() {
- writeln!(stdout)?;
- render_item_section(stdout, "Next", &view.actions)?;
- }
- return Ok(());
- }
-
- write_context(stdout, format!("farm publish · {}", view.state).as_str())?;
- render_owned_pairs(
- stdout,
- "farm",
- &[
- ("scope", view.scope.clone()),
- ("path", view.path.clone()),
- ("account id", view.selected_account_id.clone()),
- ("account pubkey", view.selected_account_pubkey.clone()),
- ("farm d_tag", view.farm_d_tag.clone()),
- ("dry run", yes_no(view.dry_run).to_owned()),
- ],
- )?;
- render_farm_publish_component(stdout, "profile", &view.profile)?;
- render_farm_publish_component(stdout, "farm record", &view.farm)?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_farm_document(
- stdout: &mut dyn Write,
- document: &crate::domain::runtime::FarmConfigDocumentView,
-) -> Result<(), RuntimeError> {
- let mut rows = Vec::new();
- push_row(
- &mut rows,
- "Name",
- first_present([
- Some(document.profile.name.as_str()),
- Some(document.farm.name.as_str()),
- ]),
- );
- push_row(
- &mut rows,
- "Display name",
- document.profile.display_name.as_deref().map(str::to_owned),
- );
- push_row(
- &mut rows,
- "About",
- first_present([
- document.profile.about.as_deref(),
- document.farm.about.as_deref(),
- ]),
- );
- push_row(
- &mut rows,
- "Website",
- first_present([
- document.profile.website.as_deref(),
- document.farm.website.as_deref(),
- ]),
- );
- push_row(
- &mut rows,
- "Place",
- first_present([
- Some(document.listing_defaults.location.primary.as_str()),
- document
- .farm
- .location
- .as_ref()
- .and_then(|location| location.primary.as_deref()),
- ]),
- );
- push_row(
- &mut rows,
- "City",
- first_present([
- document.listing_defaults.location.city.as_deref(),
- document
- .farm
- .location
- .as_ref()
- .and_then(|location| location.city.as_deref()),
- ]),
- );
- push_row(
- &mut rows,
- "Region",
- first_present([
- document.listing_defaults.location.region.as_deref(),
- document
- .farm
- .location
- .as_ref()
- .and_then(|location| location.region.as_deref()),
- ]),
- );
- push_row(
- &mut rows,
- "Country",
- first_present([
- document.listing_defaults.location.country.as_deref(),
- document
- .farm
- .location
- .as_ref()
- .and_then(|location| location.country.as_deref()),
- ]),
- );
- push_row(
- &mut rows,
- "Delivery",
- non_empty_str(document.listing_defaults.delivery_method.as_str())
- .map(humanize_delivery_method),
- );
- rows.push(("Scope", document.selection.scope.clone()));
- rows.push(("Farm tag", document.selection.farm_d_tag.clone()));
- render_owned_pairs(stdout, "Farm", rows.as_slice())
-}
-
-fn render_farm_publish_component(
- stdout: &mut dyn Write,
- label: &str,
- component: &FarmPublishComponentView,
-) -> Result<(), RuntimeError> {
- let mut rows = vec![
- ("state", component.state.clone()),
- ("rpc method", component.rpc_method.clone()),
- ("event kind", component.event_kind.to_string()),
- ];
- if let Some(job_id) = &component.job_id {
- rows.push(("job id", job_id.clone()));
- }
- if let Some(job_status) = &component.job_status {
- rows.push(("job status", job_status.clone()));
- }
- if let Some(event_id) = &component.event_id {
- rows.push(("event id", event_id.clone()));
- }
- if let Some(event_addr) = &component.event_addr {
- rows.push(("event addr", event_addr.clone()));
- }
- if let Some(reason) = &component.reason {
- rows.push(("reason", reason.clone()));
- }
- render_owned_pairs(stdout, label, rows.as_slice())
-}
-
-fn render_farm_summary(
- stdout: &mut dyn Write,
- config: &FarmConfigSummaryView,
-) -> Result<(), RuntimeError> {
- let mut rows = Vec::new();
- push_row(
- &mut rows,
- "Name",
- non_empty_str(config.name.as_str()).map(str::to_owned),
- );
- rows.push(("Scope", config.scope.clone()));
- push_row(
- &mut rows,
- "Place",
- config
- .location_primary
- .as_deref()
- .and_then(non_empty_str)
- .map(str::to_owned),
- );
- push_row(
- &mut rows,
- "Delivery",
- non_empty_str(config.delivery_method.as_str()).map(humanize_delivery_method),
- );
- render_owned_pairs(stdout, "Farm", rows.as_slice())
-}
-
-fn render_local_init(stdout: &mut dyn Write, view: &LocalInitView) -> Result<(), RuntimeError> {
- write_context(stdout, format!("local · {}", view.state).as_str())?;
- render_pairs(
- stdout,
- "local",
- &[
- ("replica db", view.replica_db.as_str()),
- ("path", view.path.as_str()),
- ("local root", view.local_root.as_str()),
- ("replica db version", view.replica_db_version.as_str()),
- ("backup format version", view.backup_format_version.as_str()),
- ],
- )?;
- writeln!(stdout, "source: {}", view.source)?;
- Ok(())
-}
-
-fn render_local_status(stdout: &mut dyn Write, view: &LocalStatusView) -> Result<(), RuntimeError> {
- write_context(
- stdout,
- match view.state.as_str() {
- "ready" => "local · status",
- _ => "local · unconfigured",
- },
- )?;
- let mut rows = vec![
- ("replica db", view.replica_db.as_str()),
- ("path", view.path.as_str()),
- ("local root", view.local_root.as_str()),
- ];
- if view.state == "ready" {
- rows.push(("replica db version", view.replica_db_version.as_str()));
- rows.push(("backup format version", view.backup_format_version.as_str()));
- rows.push(("schema hash", view.schema_hash.as_str()));
- }
- render_pairs(stdout, "local", rows.as_slice())?;
- if view.state == "ready" {
- let sync_expected = view.sync.expected_count.to_string();
- let sync_pending = view.sync.pending_count.to_string();
- render_pairs(
- stdout,
- "sync",
- &[
- ("expected", sync_expected.as_str()),
- ("pending", sync_pending.as_str()),
- ],
- )?;
- let farms = view.counts.farms.to_string();
- let listings = view.counts.listings.to_string();
- let profiles = view.counts.profiles.to_string();
- let relays = view.counts.relays.to_string();
- let event_states = view.counts.event_states.to_string();
- render_pairs(
- stdout,
- "counts",
- &[
- ("farms", farms.as_str()),
- ("listings", listings.as_str()),
- ("profiles", profiles.as_str()),
- ("relays", relays.as_str()),
- ("event states", event_states.as_str()),
- ],
- )?;
- }
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_setup(stdout: &mut dyn Write, view: &SetupView) -> Result<(), RuntimeError> {
- render_checklist_summary(
- stdout,
- match view.state.as_str() {
- "unconfigured" => "Not ready yet",
- _ => "Setup saved",
- },
- &view.ready,
- &view.needs_attention,
- &view.next,
- )?;
- writeln!(stdout)?;
- render_account_resolution(stdout, &view.account_resolution)
-}
-
-fn render_status_summary(stdout: &mut dyn Write, view: &StatusView) -> Result<(), RuntimeError> {
- render_checklist_summary(
- stdout,
- match view.state.as_str() {
- "unconfigured" => "Not ready yet",
- _ => "Status",
- },
- &view.ready,
- &view.needs_attention,
- &view.next,
- )?;
- writeln!(stdout)?;
- render_account_resolution(stdout, &view.account_resolution)
-}
-
-fn render_checklist_summary(
- stdout: &mut dyn Write,
- headline: &str,
- ready: &[String],
- needs_attention: &[String],
- next: &[String],
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "{headline}")?;
-
- let mut wrote_section = false;
- if !ready.is_empty() || !needs_attention.is_empty() || !next.is_empty() {
- writeln!(stdout)?;
- }
-
- if !ready.is_empty() {
- render_item_section(stdout, "Ready", ready)?;
- wrote_section = true;
- }
-
- if !needs_attention.is_empty() {
- if wrote_section {
- writeln!(stdout)?;
- }
- render_item_section(stdout, "Needs attention", needs_attention)?;
- wrote_section = true;
- }
-
- if !next.is_empty() {
- if wrote_section {
- writeln!(stdout)?;
- }
- render_item_section(stdout, "Next", next)?;
- }
-
- Ok(())
-}
-
-fn render_item_section(
- stdout: &mut dyn Write,
- title: &str,
- items: &[String],
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "{title}")?;
- for item in items {
- writeln!(stdout, " {item}")?;
- }
- Ok(())
-}
-
-fn push_row(rows: &mut Vec<(&'static str, String)>, label: &'static str, value: Option<String>) {
- if let Some(value) = value.filter(|value| !value.trim().is_empty()) {
- rows.push((label, value));
- }
-}
-
-fn first_present<const N: usize>(values: [Option<&str>; N]) -> Option<String> {
- values
- .into_iter()
- .flatten()
- .find_map(|value| non_empty_str(value).map(str::to_owned))
-}
-
-fn non_empty_str(value: &str) -> Option<&str> {
- let trimmed = value.trim();
- if trimmed.is_empty() {
- None
- } else {
- Some(trimmed)
- }
-}
-
-fn humanize_machine_label(value: &str) -> String {
- value
- .split('_')
- .filter(|segment| !segment.is_empty())
- .map(capitalize_ascii_word)
- .collect::<Vec<_>>()
- .join(" ")
-}
-
-fn order_submit_watch_headline(view: &OrderSubmitView) -> &'static str {
- match view.state.as_str() {
- "already_submitted" => "Order already submitted",
- "deduplicated" => "Order already in progress",
- "dry_run" => "Dry run only",
- "error" => "Order submit failed",
- "missing" => "Order draft not found",
- "unavailable" => "Order submit unavailable",
- "unconfigured" => "Not ready yet",
- _ => "Order submitted",
- }
-}
-
-fn humanize_delivery_method(value: &str) -> String {
- value
- .split('_')
- .filter(|segment| !segment.is_empty())
- .map(capitalize_ascii_word)
- .collect::<Vec<_>>()
- .join(" ")
-}
-
-fn capitalize_ascii_word(word: &str) -> String {
- let mut chars = word.chars();
- let Some(first) = chars.next() else {
- return String::new();
- };
- let mut rendered = String::new();
- rendered.push(first.to_ascii_uppercase());
- rendered.push_str(chars.as_str());
- rendered
-}
-
-fn render_local_backup(stdout: &mut dyn Write, view: &LocalBackupView) -> Result<(), RuntimeError> {
- write_context(stdout, format!("local · {}", view.state).as_str())?;
- let size_bytes = view.size_bytes.to_string();
- let mut rows = vec![("file", view.file.as_str())];
- if view.state != "unconfigured" {
- rows.push(("size bytes", size_bytes.as_str()));
- rows.push(("backup format version", view.backup_format_version.as_str()));
- rows.push(("replica db version", view.replica_db_version.as_str()));
- }
- render_pairs(stdout, "backup", rows.as_slice())?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn render_local_export(stdout: &mut dyn Write, view: &LocalExportView) -> Result<(), RuntimeError> {
- write_context(stdout, format!("local · {}", view.state).as_str())?;
- let records = view.records.to_string();
- let mut rows = vec![
- ("format", view.format.as_str()),
- ("file", view.file.as_str()),
- ];
- if view.state != "unconfigured" {
- rows.push(("records", records.as_str()));
- rows.push(("export version", view.export_version.as_str()));
- rows.push(("schema hash", view.schema_hash.as_str()));
- }
- render_pairs(stdout, "export", rows.as_slice())?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- render_actions(stdout, &view.actions)?;
- Ok(())
-}
-
-fn doctor_item(check: &DoctorCheckView) -> String {
- let name = humanize_machine_label(check.name.as_str());
- match non_empty_str(check.detail.as_str()) {
- Some(detail) => format!("{name}: {detail}"),
- None => name,
- }
-}
-
-fn write_context(stdout: &mut dyn Write, line: &str) -> Result<(), RuntimeError> {
- writeln!(stdout, "{line}")?;
- writeln!(stdout)?;
- Ok(())
-}
-
-fn render_actions(stdout: &mut dyn Write, actions: &[String]) -> Result<(), RuntimeError> {
- if actions.is_empty() {
- return Ok(());
- }
- writeln!(stdout)?;
- writeln!(stdout, "Next")?;
- for action in actions {
- writeln!(stdout, " {action}")?;
- }
- Ok(())
-}
-
-fn render_pairs(
- stdout: &mut dyn Write,
- heading: &str,
- rows: &[(&str, &str)],
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "{heading}")?;
- let label_width = rows
- .iter()
- .map(|(label, _)| label.len())
- .max()
- .unwrap_or_default();
- for (label, value) in rows {
- writeln!(stdout, " {label:label_width$} {value}")?;
- }
- writeln!(stdout)?;
- Ok(())
-}
-
-fn render_field_rows(stdout: &mut dyn Write, rows: &[(&str, String)]) -> Result<(), RuntimeError> {
- let label_width = rows
- .iter()
- .map(|(label, _)| label.len())
- .max()
- .unwrap_or_default();
- for (label, value) in rows {
- writeln!(stdout, " {label:label_width$} {value}")?;
- }
- writeln!(stdout)?;
- Ok(())
-}
-
-fn render_owned_pairs(
- stdout: &mut dyn Write,
- heading: &str,
- rows: &[(&str, String)],
-) -> Result<(), RuntimeError> {
- let borrowed = rows
- .iter()
- .map(|(label, value)| (*label, value.as_str()))
- .collect::<Vec<_>>();
- render_pairs(stdout, heading, borrowed.as_slice())
-}
-
-fn render_local_signer(
- stdout: &mut dyn Write,
- heading: &str,
- local: &crate::domain::runtime::LocalSignerStatusView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "{heading}")?;
- writeln!(stdout, " account id: {}", local.account_id)?;
- writeln!(
- stdout,
- " public key hex: {}",
- local.public_identity.public_key_hex
- )?;
- writeln!(
- stdout,
- " public key npub: {}",
- local.public_identity.public_key_npub
- )?;
- writeln!(stdout, " availability: {}", local.availability)?;
- writeln!(stdout, " secret backed: {}", yes_no(local.secret_backed))?;
- writeln!(stdout, " backend: {}", local.backend)?;
- writeln!(stdout, " used fallback: {}", yes_no(local.used_fallback))?;
- Ok(())
-}
-
-fn render_myc_status(
- stdout: &mut dyn Write,
- view: &crate::domain::runtime::MycStatusView,
- standalone: bool,
-) -> Result<(), RuntimeError> {
- if standalone {
- write_context(stdout, format!("myc · {}", view.state).as_str())?;
- }
- let mut rows = vec![
- ("executable", view.executable.as_str()),
- ("status", view.state.as_str()),
- ("ready", yes_no(view.ready)),
- ];
- if let Some(service_status) = &view.service_status {
- rows.push(("service status", service_status.as_str()));
- }
- let remote_session_count = view.remote_session_count.to_string();
- rows.push(("remote session count", remote_session_count.as_str()));
- render_pairs(stdout, "myc", rows.as_slice())?;
- if let Some(reason) = &view.reason {
- writeln!(stdout, "reason: {reason}")?;
- }
- if !view.reasons.is_empty() {
- writeln!(stdout, "reasons: {}", view.reasons.join(" | "))?;
- }
- writeln!(stdout, "source: {}", view.source)?;
- if let Some(local_signer) = &view.local_signer {
- writeln!(stdout)?;
- render_local_signer(stdout, "myc local signer", local_signer)?;
- }
- for session in &view.remote_sessions {
- writeln!(stdout)?;
- render_myc_remote_session(stdout, session)?;
- }
- if let Some(custody) = &view.custody {
- writeln!(stdout)?;
- render_myc_custody_identity(stdout, "myc custody signer", &custody.signer)?;
- render_myc_custody_identity(stdout, "myc custody user", &custody.user)?;
- if let Some(discovery_app) = &custody.discovery_app {
- render_myc_custody_identity(stdout, "myc custody discovery app", discovery_app)?;
- }
- }
- Ok(())
-}
-
-fn render_signer_binding(
- stdout: &mut dyn Write,
- binding: &crate::domain::runtime::SignerBindingStatusView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "signer binding")?;
- writeln!(stdout, " capability: {}", binding.capability_id)?;
- writeln!(stdout, " provider: {}", binding.provider_runtime_id)?;
- writeln!(stdout, " model: {}", binding.binding_model)?;
- writeln!(stdout, " status: {}", binding.state)?;
- writeln!(stdout, " source: {}", binding.source)?;
- if let Some(target_kind) = &binding.target_kind {
- writeln!(stdout, " target kind: {target_kind}")?;
- }
- if let Some(target) = &binding.target {
- writeln!(stdout, " target: {target}")?;
- }
- if let Some(account_ref) = &binding.managed_account_ref {
- writeln!(stdout, " managed account ref: {account_ref}")?;
- }
- if let Some(session_ref) = &binding.signer_session_ref {
- writeln!(stdout, " signer session ref: {session_ref}")?;
- }
- if let Some(session_id) = &binding.resolved_signer_session_id {
- writeln!(stdout, " resolved signer session id: {session_id}")?;
- }
- if let Some(count) = binding.matched_session_count {
- writeln!(stdout, " matched session count: {count}")?;
- }
- if let Some(reason) = &binding.reason {
- writeln!(stdout, " reason: {reason}")?;
- }
- Ok(())
-}
-
-fn render_myc_remote_session(
- stdout: &mut dyn Write,
- session: &crate::domain::runtime::MycRemoteSessionView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "myc remote session {}", session.connection_id)?;
- writeln!(stdout, " signer id: {}", session.signer_identity.id)?;
- writeln!(
- stdout,
- " signer npub: {}",
- session.signer_identity.public_key_npub
- )?;
- writeln!(stdout, " user id: {}", session.user_identity.id)?;
- writeln!(
- stdout,
- " user npub: {}",
- session.user_identity.public_key_npub
- )?;
- writeln!(stdout, " relay count: {}", session.relay_count)?;
- if !session.permissions.is_empty() {
- writeln!(stdout, " permissions: {}", session.permissions.join(", "))?;
- }
- Ok(())
-}
-
-fn render_signer_write_kinds(
- stdout: &mut dyn Write,
- write_kinds: &[SignerWriteKindReadinessView],
-) -> Result<(), RuntimeError> {
- let table = Table {
- headers: &["command", "kind", "ready", "permission"],
- rows: write_kinds
- .iter()
- .map(|kind| {
- vec![
- kind.command.clone(),
- kind.event_kind.to_string(),
- yes_no(kind.ready).to_owned(),
- kind.permission.clone(),
- ]
- })
- .collect(),
- };
- render_table(stdout, &table)?;
- let reasons = write_kinds
- .iter()
- .filter_map(|kind| {
- kind.reason
- .as_ref()
- .map(|reason| (kind.command.as_str(), reason))
- })
- .collect::<Vec<_>>();
- if !reasons.is_empty() {
- writeln!(stdout)?;
- for (command, reason) in reasons {
- writeln!(stdout, "{command}: {reason}")?;
- }
- }
- Ok(())
-}
-
-fn render_myc_custody_identity(
- stdout: &mut dyn Write,
- heading: &str,
- identity: &crate::domain::runtime::MycCustodyIdentityView,
-) -> Result<(), RuntimeError> {
- writeln!(stdout, "{heading}")?;
- writeln!(stdout, " resolved: {}", yes_no(identity.resolved))?;
- if let Some(selected_account_id) = &identity.selected_account_id {
- writeln!(stdout, " selected account id: {selected_account_id}")?;
- }
- if let Some(selected_account_state) = &identity.selected_account_state {
- writeln!(stdout, " selected account state: {selected_account_state}")?;
- }
- if let Some(identity_id) = &identity.identity_id {
- writeln!(stdout, " identity id: {identity_id}")?;
- }
- if let Some(public_key_hex) = &identity.public_key_hex {
- writeln!(stdout, " public key hex: {public_key_hex}")?;
- }
- if let Some(error) = &identity.error {
- writeln!(stdout, " error: {error}")?;
- }
- Ok(())
-}
-
-fn render_table(stdout: &mut dyn Write, table: &Table) -> Result<(), RuntimeError> {
- let mut widths: Vec<usize> = table.headers.iter().map(|header| header.len()).collect();
- for row in &table.rows {
- for (index, cell) in row.iter().enumerate() {
- if let Some(width) = widths.get_mut(index) {
- *width = (*width).max(cell.len());
- }
- }
- }
-
- for (index, header) in table.headers.iter().enumerate() {
- if index > 0 {
- write!(stdout, " ")?;
- }
- write!(stdout, "{header:width$}", width = widths[index])?;
- }
- writeln!(stdout)?;
-
- for row in &table.rows {
- for (index, cell) in row.iter().enumerate() {
- if index > 0 {
- write!(stdout, " ")?;
- }
- write!(stdout, "{cell:width$}", width = widths[index])?;
- }
- writeln!(stdout)?;
- }
-
- Ok(())
-}
-
-fn format_price(amount: f64, currency: &str, per_amount: u32, per_unit: &str) -> String {
- format!(
- "{} {currency}/{} {per_unit}",
- trim_decimal(amount),
- per_amount
- )
-}
-
-fn quantity_offer_text(quantity: &crate::domain::runtime::FindQuantityView) -> Option<String> {
- quantity
- .label
- .as_deref()
- .and_then(non_empty_str)
- .map(str::to_owned)
- .or_else(|| Some(format!("{} {}", quantity.total_amount, quantity.total_unit)))
-}
-
-fn format_available(amount: i64, unit: &str) -> String {
- format!("{amount} {unit}")
-}
-
-fn trim_decimal(value: f64) -> String {
- let formatted = format!("{value:.2}");
- formatted
- .trim_end_matches('0')
- .trim_end_matches('.')
- .to_owned()
-}
-
-struct Table {
- headers: &'static [&'static str],
- rows: Vec<Vec<String>>,
-}
-
-fn human_command_name(view: &CommandView) -> &'static str {
- match view {
- CommandView::AccountClearDefault(_) => "account clear-default",
- CommandView::AccountImport(_) => "account import",
- CommandView::AccountList(_) => "account list",
- CommandView::AccountNew(_) => "account create",
- CommandView::AccountRemove(_) => "account remove",
- CommandView::AccountUse(_) => "account select",
- CommandView::AccountWhoami(_) => "account view",
- CommandView::ConfigShow(_) => "config show",
- CommandView::Doctor(_) => "doctor",
- CommandView::FarmGet(_) => "farm show",
- CommandView::FarmPublish(_) => "farm publish",
- CommandView::FarmSet(_) => "farm set",
- CommandView::FarmSetup(view) => {
- if view.state == "saved" {
- "farm init"
- } else {
- "farm setup"
- }
- }
- CommandView::FarmStatus(_) => "farm check",
- CommandView::Find(_) => "find",
- CommandView::JobGet(_) => "job get",
- CommandView::JobList(_) => "job ls",
- CommandView::JobWatch(_) => "job watch",
- CommandView::ListingGet(_) => "listing get",
- CommandView::ListingMutation(view) => match view.operation.as_str() {
- "publish" => "listing publish",
- "update" => "listing update",
- "archive" => "listing archive",
- _ => "listing publish",
- },
- CommandView::ListingNew(_) => "listing new",
- CommandView::ListingValidate(_) => "listing validate",
- CommandView::LocalBackup(_) => "local backup",
- CommandView::LocalExport(_) => "local export",
- CommandView::LocalInit(_) => "local init",
- CommandView::LocalStatus(_) => "local status",
- CommandView::MarketSearch(_) => "market search",
- CommandView::MarketUpdate(_) => "market update",
- CommandView::MarketView(_) => "market view",
- CommandView::MycStatus(_) => "myc status",
- CommandView::NetStatus(_) => "net status",
- CommandView::OrderCancel(_) => "order cancel",
- CommandView::OrderGet(_) => "order view",
- CommandView::OrderHistory(_) => "order history",
- CommandView::OrderList(_) => "order list",
- CommandView::OrderNew(_) => "order create",
- CommandView::OrderSubmit(_) => "order submit",
- CommandView::OrderSubmitWatch(_) => "order submit --watch",
- CommandView::OrderWatch(_) => "order watch",
- CommandView::RpcSessions(_) => "rpc sessions",
- CommandView::RpcStatus(_) => "rpc status",
- CommandView::RelayList(_) => "relay ls",
- CommandView::RuntimeAction(view) => match view.action.as_str() {
- "install" => "runtime install",
- "uninstall" => "runtime uninstall",
- "start" => "runtime start",
- "stop" => "runtime stop",
- "restart" => "runtime restart",
- "config_set" => "runtime config set",
- _ => "runtime",
- },
- CommandView::RuntimeConfigShow(_) => "runtime config show",
- CommandView::RuntimeLogs(_) => "runtime logs",
- CommandView::RuntimeStatus(_) => "runtime status",
- CommandView::SellAdd(_) => "sell add",
- CommandView::SellCheck(_) => "sell check",
- CommandView::SellDraftMutation(view) => match view.operation.as_str() {
- "reprice" => "sell reprice",
- "restock" => "sell restock",
- _ => "sell",
- },
- CommandView::SellMutation(view) => match view.operation.as_str() {
- "publish" => "sell publish",
- "update" => "sell update",
- "pause" => "sell pause",
- _ => "sell",
- },
- CommandView::SellShow(_) => "sell show",
- CommandView::Setup(_) => "setup",
- CommandView::SignerSessionAction(_) => "signer session",
- CommandView::SignerStatus(_) => "signer status",
- CommandView::Status(_) => "status",
- CommandView::SyncPull(_) => "sync pull",
- CommandView::SyncPush(_) => "sync push",
- CommandView::SyncStatus(_) => "sync status",
- CommandView::SyncWatch(_) => "sync watch",
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::{
- Table, render_human_to, render_human_with_config_to, render_ndjson_to, render_table,
- };
- use crate::commands::runtime;
- use crate::domain::runtime::{
- AccountListView, CommandOutput, CommandView, DoctorCheckView, DoctorView, MycStatusView,
- RelayEntryView, RelayListView,
- };
- use crate::runtime::config::{
- AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig,
- LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat,
- PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig,
- SignerBackend, SignerConfig, Verbosity,
- };
- use crate::runtime::logging::LoggingState;
- use radroots_runtime_paths::RadrootsMigrationReport;
- use radroots_secret_vault::RadrootsSecretBackend;
-
- #[test]
- fn human_render_contains_config_sections() {
- let view = runtime::show(
- &RuntimeConfig {
- output: OutputConfig {
- format: OutputFormat::Human,
- verbosity: Verbosity::Normal,
- color: true,
- dry_run: false,
- },
- interaction: InteractionConfig {
- input_enabled: true,
- assume_yes: false,
- stdin_tty: true,
- stdout_tty: true,
- prompts_allowed: true,
- confirmations_allowed: true,
- },
- paths: PathsConfig {
- profile: "interactive_user".into(),
- profile_source: "default".into(),
- allowed_profiles: vec!["interactive_user".into(), "repo_local".into()],
- root_source: "host_defaults".into(),
- repo_local_root: None,
- repo_local_root_source: None,
- subordinate_path_override_source: "runtime_config".into(),
- app_namespace: "apps/cli".into(),
- shared_accounts_namespace: "shared/accounts".into(),
- shared_identities_namespace: "shared/identities".into(),
- app_config_path: "/home/tester/.radroots/config/apps/cli/config.toml".into(),
- workspace_config_path: None,
- app_data_root: "/home/tester/.radroots/data/apps/cli".into(),
- app_logs_root: "/home/tester/.radroots/logs/apps/cli".into(),
- shared_accounts_data_root: "/home/tester/.radroots/data/shared/accounts".into(),
- shared_accounts_secrets_root: "/home/tester/.radroots/secrets/shared/accounts"
- .into(),
- default_identity_path:
- "/home/tester/.radroots/secrets/shared/identities/default.json".into(),
- },
- migration: MigrationConfig {
- report: RadrootsMigrationReport::empty(),
- },
- logging: LoggingConfig {
- filter: "info".to_owned(),
- directory: None,
- stdout: false,
- },
- account: AccountConfig {
- selector: Some("acct_demo".into()),
- store_path: "/home/tester/.radroots/data/shared/accounts/store.json".into(),
- secrets_dir: "/home/tester/.radroots/secrets/shared/accounts".into(),
- secret_backend: RadrootsSecretBackend::EncryptedFile,
- secret_fallback: None,
- },
- account_secret_contract: AccountSecretContractConfig {
- default_backend: "host_vault".into(),
- default_fallback: Some("encrypted_file".into()),
- allowed_backends: vec!["host_vault".into(), "encrypted_file".into()],
- host_vault_policy: Some("desktop".into()),
- uses_protected_store: true,
- },
- identity: IdentityConfig {
- path: "/home/tester/.radroots/secrets/shared/identities/default.json".into(),
- },
- signer: SignerConfig {
- backend: SignerBackend::Local,
- },
- relay: RelayConfig {
- urls: vec!["wss://relay.one".into(), "wss://relay.two".into()],
- publish_policy: RelayPublishPolicy::Any,
- source: RelayConfigSource::WorkspaceConfig,
- },
- local: LocalConfig {
- root: "/home/tester/.radroots/data/apps/cli/replica".into(),
- replica_db_path: "/home/tester/.radroots/data/apps/cli/replica/replica.sqlite"
- .into(),
- backups_dir: "/home/tester/.radroots/data/apps/cli/replica/backups".into(),
- exports_dir: "/home/tester/.radroots/data/apps/cli/replica/exports".into(),
- },
- myc: MycConfig {
- executable: "myc".into(),
- status_timeout_ms: 2_000,
- },
- hyf: HyfConfig {
- enabled: false,
- executable: "hyfd".into(),
- },
- rpc: RpcConfig {
- url: "http://127.0.0.1:7070".to_owned(),
- bridge_bearer_token: None,
- },
- capability_bindings: Vec::new(),
- },
- &LoggingState {
- initialized: true,
- current_file: None,
- },
- )
- .expect("runtime show");
- assert_eq!(view.output.format, "human");
- assert!(view.interaction.input_enabled);
- assert!(view.interaction.prompts_allowed);
- assert_eq!(view.paths.profile, "interactive_user");
- assert_eq!(view.paths.app_namespace, "apps/cli");
- assert_eq!(view.paths.shared_accounts_namespace, "shared/accounts");
- assert!(!view.paths.workspace_config_enabled);
- assert_eq!(view.paths.workspace_config_path, None);
- assert_eq!(view.account.selector.as_deref(), Some("acct_demo"));
- 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!(!view.hyf.enabled);
- assert_eq!(view.hyf.executable, "hyfd");
- assert_eq!(view.capability_bindings.len(), 4);
- 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")
- );
- }
-
- #[test]
- fn human_render_omits_placeholder_tokens() {
- let output = CommandOutput::success(CommandView::MycStatus(MycStatusView {
- executable: "myc".to_owned(),
- state: "unavailable".to_owned(),
- source: "myc status command · local first".to_owned(),
- service_status: None,
- ready: false,
- reason: None,
- reasons: Vec::new(),
- remote_session_count: 0,
- local_signer: None,
- remote_sessions: Vec::new(),
- custody: None,
- }));
- let mut buffer = Vec::new();
- render_human_to(&mut buffer, &output).expect("render human");
- let rendered = String::from_utf8(buffer).expect("utf8");
- assert!(!rendered.contains("<none>"));
- assert!(!rendered.contains("<unknown>"));
- assert!(!rendered.contains("<disabled>"));
- }
-
- #[test]
- fn ndjson_rejects_singular_views() {
- let output = CommandOutput::success(CommandView::ConfigShow(
- runtime::show(
- &RuntimeConfig {
- output: OutputConfig {
- format: OutputFormat::Ndjson,
- verbosity: Verbosity::Trace,
- color: false,
- dry_run: true,
- },
- interaction: InteractionConfig {
- input_enabled: true,
- assume_yes: false,
- stdin_tty: true,
- stdout_tty: true,
- prompts_allowed: true,
- confirmations_allowed: true,
- },
- paths: PathsConfig {
- profile: "interactive_user".into(),
- profile_source: "default".into(),
- allowed_profiles: vec!["interactive_user".into(), "repo_local".into()],
- root_source: "host_defaults".into(),
- repo_local_root: None,
- repo_local_root_source: None,
- subordinate_path_override_source: "runtime_config".into(),
- app_namespace: "apps/cli".into(),
- shared_accounts_namespace: "shared/accounts".into(),
- shared_identities_namespace: "shared/identities".into(),
- app_config_path: "/home/tester/.radroots/config/apps/cli/config.toml"
- .into(),
- workspace_config_path: None,
- app_data_root: "/home/tester/.radroots/data/apps/cli".into(),
- app_logs_root: "/home/tester/.radroots/logs/apps/cli".into(),
- shared_accounts_data_root: "/home/tester/.radroots/data/shared/accounts"
- .into(),
- shared_accounts_secrets_root:
- "/home/tester/.radroots/secrets/shared/accounts".into(),
- default_identity_path:
- "/home/tester/.radroots/secrets/shared/identities/default.json".into(),
- },
- migration: MigrationConfig {
- report: RadrootsMigrationReport::empty(),
- },
- logging: LoggingConfig {
- filter: "info".to_owned(),
- directory: None,
- stdout: false,
- },
- account: AccountConfig {
- selector: None,
- store_path: "/home/tester/.radroots/data/shared/accounts/store.json".into(),
- secrets_dir: "/home/tester/.radroots/secrets/shared/accounts".into(),
- secret_backend: RadrootsSecretBackend::EncryptedFile,
- secret_fallback: None,
- },
- account_secret_contract: AccountSecretContractConfig {
- default_backend: "host_vault".into(),
- default_fallback: Some("encrypted_file".into()),
- allowed_backends: vec!["host_vault".into(), "encrypted_file".into()],
- host_vault_policy: Some("desktop".into()),
- uses_protected_store: true,
- },
- identity: IdentityConfig {
- path: "/home/tester/.radroots/secrets/shared/identities/default.json"
- .into(),
- },
- signer: SignerConfig {
- backend: SignerBackend::Local,
- },
- relay: RelayConfig {
- urls: Vec::new(),
- publish_policy: RelayPublishPolicy::Any,
- source: RelayConfigSource::Defaults,
- },
- local: LocalConfig {
- root: "/home/tester/.radroots/data/apps/cli/replica".into(),
- replica_db_path:
- "/home/tester/.radroots/data/apps/cli/replica/replica.sqlite".into(),
- backups_dir: "/home/tester/.radroots/data/apps/cli/replica/backups".into(),
- exports_dir: "/home/tester/.radroots/data/apps/cli/replica/exports".into(),
- },
- myc: MycConfig {
- executable: "myc".into(),
- status_timeout_ms: 2_000,
- },
- hyf: HyfConfig {
- enabled: false,
- executable: "hyfd".into(),
- },
- rpc: RpcConfig {
- url: "http://127.0.0.1:7070".to_owned(),
- bridge_bearer_token: None,
- },
- capability_bindings: Vec::new(),
- },
- &LoggingState {
- initialized: true,
- current_file: None,
- },
- )
- .expect("runtime show"),
- ));
- 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")
- );
- }
-
- #[test]
- fn account_list_ndjson_emits_one_json_object_per_account() {
- let output = CommandOutput::success(CommandView::AccountList(AccountListView {
- source: "shared account store · local first".to_owned(),
- count: 2,
- accounts: vec![
- crate::domain::runtime::AccountSummaryView {
- id: "acct_a".to_owned(),
- display_name: Some("Alpha".to_owned()),
- signer: "local".to_owned(),
- is_default: true,
- },
- crate::domain::runtime::AccountSummaryView {
- id: "acct_b".to_owned(),
- display_name: None,
- signer: "local".to_owned(),
- is_default: false,
- },
- ],
- actions: Vec::new(),
- }));
- let mut buffer = Vec::new();
- render_ndjson_to(&mut buffer, &output).expect("render ndjson");
- let rendered = String::from_utf8(buffer).expect("utf8");
- let lines = rendered.lines().collect::<Vec<_>>();
- assert_eq!(lines.len(), 2);
- assert!(lines[0].contains("\"id\":\"acct_a\""));
- assert!(lines[1].contains("\"id\":\"acct_b\""));
- }
-
- #[test]
- fn relay_list_ndjson_emits_one_json_object_per_relay() {
- let output = CommandOutput::success(CommandView::RelayList(RelayListView {
- state: "configured".to_owned(),
- source: "workspace config · local first".to_owned(),
- publish_policy: "any".to_owned(),
- count: 2,
- reason: None,
- relays: vec![
- RelayEntryView {
- url: "wss://relay.one".to_owned(),
- read: true,
- write: true,
- },
- RelayEntryView {
- url: "wss://relay.two".to_owned(),
- read: true,
- write: true,
- },
- ],
- actions: Vec::new(),
- }));
- let mut buffer = Vec::new();
- render_ndjson_to(&mut buffer, &output).expect("render relay ndjson");
- let rendered = String::from_utf8(buffer).expect("utf8");
- let lines = rendered.lines().collect::<Vec<_>>();
- assert_eq!(lines.len(), 2);
- assert!(lines[0].contains("\"url\":\"wss://relay.one\""));
- assert!(lines[1].contains("\"url\":\"wss://relay.two\""));
- }
-
- #[test]
- fn human_render_doctor_uses_readiness_sections() {
- let output = CommandOutput::unconfigured(CommandView::Doctor(DoctorView {
- ok: false,
- state: "warn".to_owned(),
- account_resolution: crate::domain::runtime::AccountResolutionView {
- source: "none".to_owned(),
- resolved_account: None,
- default_account: None,
- },
- checks: vec![
- DoctorCheckView {
- name: "config".to_owned(),
- status: "ok".to_owned(),
- detail: "defaults active".to_owned(),
- },
- DoctorCheckView {
- name: "account".to_owned(),
- status: "warn".to_owned(),
- detail: "no local account in store".to_owned(),
- },
- ],
- source: "local diagnostics".to_owned(),
- actions: vec!["radroots account new".to_owned()],
- }));
- let mut buffer = Vec::new();
- render_human_to(&mut buffer, &output).expect("render human");
- let rendered = String::from_utf8(buffer).expect("utf8");
- assert!(rendered.contains("Readiness check"));
- assert!(rendered.contains("Ready"));
- assert!(rendered.contains("Config: defaults active"));
- assert!(rendered.contains("Needs attention"));
- assert!(rendered.contains("Account: no local account in store"));
- assert!(rendered.contains("Next"));
- assert!(rendered.contains("radroots account new"));
- assert!(!rendered.contains("source: local diagnostics"));
- }
-
- #[test]
- fn human_render_verbose_and_trace_append_diagnostics() {
- let output = CommandOutput::success(CommandView::Doctor(DoctorView {
- ok: true,
- state: "ok".to_owned(),
- account_resolution: crate::domain::runtime::AccountResolutionView {
- source: "default_account".to_owned(),
- resolved_account: None,
- default_account: None,
- },
- checks: vec![DoctorCheckView {
- name: "config".to_owned(),
- status: "ok".to_owned(),
- detail: "defaults active".to_owned(),
- }],
- source: "local diagnostics".to_owned(),
- actions: Vec::new(),
- }));
-
- let mut verbose_buffer = Vec::new();
- render_human_with_config_to(
- &mut verbose_buffer,
- &output,
- &OutputConfig {
- format: OutputFormat::Human,
- verbosity: Verbosity::Verbose,
- color: false,
- dry_run: false,
- },
- )
- .expect("render verbose");
- let verbose_rendered = String::from_utf8(verbose_buffer).expect("utf8");
- assert!(verbose_rendered.contains("Details"));
- assert!(verbose_rendered.contains("Source"));
- assert!(!verbose_rendered.contains("Trace"));
-
- let mut trace_buffer = Vec::new();
- render_human_with_config_to(
- &mut trace_buffer,
- &output,
- &OutputConfig {
- format: OutputFormat::Human,
- verbosity: Verbosity::Trace,
- color: false,
- dry_run: false,
- },
- )
- .expect("render trace");
- let trace_rendered = String::from_utf8(trace_buffer).expect("utf8");
- assert!(trace_rendered.contains("Details"));
- assert!(trace_rendered.contains("Trace"));
- assert!(trace_rendered.contains("\"source\": \"local diagnostics\""));
- }
-
- #[test]
- fn table_renderer_aligns_columns() {
- let table = Table {
- headers: &["item", "status"],
- rows: vec![
- vec!["alpha".to_owned(), "ready".to_owned()],
- vec!["beta-long".to_owned(), "pending".to_owned()],
- ],
- };
- let mut buffer = Vec::new();
- render_table(&mut buffer, &table).expect("render table");
- let rendered = String::from_utf8(buffer).expect("utf8");
- assert!(rendered.contains("item status"));
- assert!(rendered.contains("alpha ready"));
- assert!(rendered.contains("beta-long pending"));
- }
-}
diff --git a/src/runtime/config.rs b/src/runtime/config.rs
@@ -12,10 +12,10 @@ use radroots_secret_vault::{RadrootsHostVaultPolicy, RadrootsSecretBackend};
use serde::Deserialize;
use url::Url;
-use crate::cli::CliArgs;
use crate::runtime::RuntimeError;
pub use crate::runtime::paths::PathsConfig;
use crate::runtime::paths::{ENV_CLI_PATHS_PROFILE, ENV_CLI_PATHS_REPO_LOCAL_ROOT, resolve_paths};
+use crate::runtime_args::RuntimeInvocationArgs;
const DEFAULT_LOG_FILTER: &str = "info";
const DEFAULT_ENV_PATH: &str = ".env";
@@ -470,7 +470,7 @@ impl Environment for SystemEnvironment {
}
impl RuntimeConfig {
- pub fn from_system(args: &CliArgs) -> Result<Self, RuntimeError> {
+ pub fn from_system(args: &RuntimeInvocationArgs) -> Result<Self, RuntimeError> {
let system = SystemEnvironment;
let env_file_path = resolve_env_file_path(args, &system);
let env_file = load_env_file_values(env_file_path.as_deref())?;
@@ -478,7 +478,7 @@ impl RuntimeConfig {
}
fn resolve_with_env_file(
- args: &CliArgs,
+ args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
) -> Result<Self, RuntimeError> {
@@ -904,7 +904,7 @@ fn normalize_binding_ref(value: Option<&str>) -> Option<String> {
}
fn resolve_relay_config(
- args: &CliArgs,
+ args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
user_config: Option<&CliConfigFile>,
@@ -957,7 +957,7 @@ fn resolve_relay_config(
}
fn resolve_signer_config(
- args: &CliArgs,
+ args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
user_config: Option<&CliConfigFile>,
@@ -985,7 +985,7 @@ fn resolve_signer_config(
}
fn resolve_myc_config(
- args: &CliArgs,
+ args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
user_config: Option<&CliConfigFile>,
@@ -1020,7 +1020,7 @@ fn resolve_myc_config(
}
fn resolve_myc_status_timeout_ms(
- args: &CliArgs,
+ args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
user_config: Option<&CliConfigFile>,
@@ -1064,7 +1064,7 @@ fn validate_myc_status_timeout_ms(source: &str, value: u64) -> Result<u64, Runti
}
fn resolve_hyf_enabled(
- args: &CliArgs,
+ args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
user_config: Option<&CliConfigFile>,
@@ -1103,7 +1103,7 @@ fn resolve_hyf_enabled(
}
fn resolve_hyf_executable(
- args: &CliArgs,
+ args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
user_config: Option<&CliConfigFile>,
@@ -1221,7 +1221,7 @@ fn validate_relay_url(value: &str, source: &str) -> Result<String, RuntimeError>
Ok(trimmed.to_owned())
}
-fn resolve_env_file_path(args: &CliArgs, env: &dyn Environment) -> Option<PathBuf> {
+fn resolve_env_file_path(args: &RuntimeInvocationArgs, env: &dyn Environment) -> Option<PathBuf> {
args.env_file
.clone()
.or_else(|| env.var(ENV_FILE_PATH).map(PathBuf::from))
@@ -1232,7 +1232,7 @@ fn resolve_env_file_path(args: &CliArgs, env: &dyn Environment) -> Option<PathBu
}
fn resolve_output_format(
- args: &CliArgs,
+ args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
) -> Result<OutputFormat, RuntimeError> {
@@ -1260,7 +1260,7 @@ fn resolve_output_format(
}
}
-fn resolve_verbosity(args: &CliArgs) -> Result<Verbosity, RuntimeError> {
+fn resolve_verbosity(args: &RuntimeInvocationArgs) -> Result<Verbosity, RuntimeError> {
let selected = [args.quiet, args.verbose, args.trace]
.into_iter()
.filter(|selected| *selected)
@@ -1282,7 +1282,10 @@ fn resolve_verbosity(args: &CliArgs) -> Result<Verbosity, RuntimeError> {
}
}
-fn resolve_interaction_config(args: &CliArgs, env: &dyn Environment) -> InteractionConfig {
+fn resolve_interaction_config(
+ args: &RuntimeInvocationArgs,
+ env: &dyn Environment,
+) -> InteractionConfig {
let stdin_tty = env.stdin_is_tty();
let stdout_tty = env.stdout_is_tty();
let input_enabled = !args.no_input;
@@ -1436,7 +1439,7 @@ fn parse_signer_mode(source: &str, value: String) -> Result<SignerBackend, Runti
}
fn resolve_account_secret_backend(
- _args: &CliArgs,
+ _args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
) -> Result<Option<RadrootsSecretBackend>, RuntimeError> {
@@ -1446,7 +1449,7 @@ fn resolve_account_secret_backend(
}
fn resolve_account_secret_fallback(
- _args: &CliArgs,
+ _args: &RuntimeInvocationArgs,
env: &dyn Environment,
env_file: &EnvFileValues,
) -> Result<Option<Option<RadrootsSecretBackend>>, RuntimeError> {
@@ -1505,7 +1508,7 @@ mod tests {
PathsConfig, RelayConfigSource, RelayPublishPolicy, RuntimeConfig, SignerBackend,
Verbosity, parse_env_file_values,
};
- use crate::cli::CliArgs;
+ use crate::runtime_args::{RuntimeInvocationArgs, RuntimeOutputFormatArg};
use radroots_runtime_paths::{RadrootsHostEnvironment, RadrootsPathResolver, RadrootsPlatform};
use radroots_secret_vault::{RadrootsHostVaultPolicy, RadrootsSecretBackend};
use std::collections::BTreeMap;
@@ -1597,36 +1600,28 @@ mod tests {
}
}
+ fn runtime_args() -> RuntimeInvocationArgs {
+ RuntimeInvocationArgs::default()
+ }
+
#[test]
fn flags_override_environment_values() {
- let args = CliArgs::parse_from([
- "radroots",
- "--output",
- "human",
- "--verbose",
- "--dry-run",
- "--no-color",
- "--log-filter",
- "debug",
- "--log-stdout",
- "--identity-path",
- "custom-identity.json",
- "--signer",
- "local",
- "--relay",
- "wss://relay.one",
- "--relay",
- "wss://relay.two",
- "--myc-executable",
- "bin/myc-cli",
- "--myc-status-timeout-ms",
- "2500",
- "--hyf-enabled",
- "--hyf-executable",
- "bin/hyfd-cli",
- "config",
- "show",
- ]);
+ let args = RuntimeInvocationArgs {
+ output_format: Some(RuntimeOutputFormatArg::Human),
+ verbose: true,
+ dry_run: true,
+ no_color: true,
+ log_filter: Some("debug".to_owned()),
+ log_stdout: true,
+ identity_path: Some(PathBuf::from("custom-identity.json")),
+ signer: Some("local".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),
+ hyf_enabled: true,
+ hyf_executable: Some(PathBuf::from("bin/hyfd-cli")),
+ ..runtime_args()
+ };
let env = MapEnvironment::new(BTreeMap::from([
("RADROOTS_OUTPUT".to_owned(), "human".to_owned()),
("RADROOTS_LOG_FILTER".to_owned(), "trace".to_owned()),
@@ -1746,7 +1741,7 @@ mod tests {
#[test]
fn environment_values_fill_missing_flags() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::from([
("RADROOTS_OUTPUT".to_owned(), "json".to_owned()),
(
@@ -1831,25 +1826,21 @@ mod tests {
#[test]
fn conflicting_boolean_flags_fail() {
- let args = CliArgs::parse_from([
- "radroots",
- "--log-stdout",
- "--no-log-stdout",
- "config",
- "show",
- ]);
+ let args = RuntimeInvocationArgs {
+ log_stdout: true,
+ no_log_stdout: true,
+ ..runtime_args()
+ };
let env = MapEnvironment::new(BTreeMap::new());
let error = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect_err("conflicting flags");
assert!(error.to_string().contains("cannot be used together"));
- let hyf_args = CliArgs::parse_from([
- "radroots",
- "--hyf-enabled",
- "--no-hyf-enabled",
- "config",
- "show",
- ]);
+ let hyf_args = RuntimeInvocationArgs {
+ hyf_enabled: true,
+ no_hyf_enabled: true,
+ ..runtime_args()
+ };
let error =
RuntimeConfig::resolve_with_env_file(&hyf_args, &env, &EnvFileValues::default())
.expect_err("conflicting hyf flags");
@@ -1860,8 +1851,11 @@ mod tests {
fn conflicting_output_and_verbosity_flags_fail() {
let env = MapEnvironment::new(BTreeMap::new());
- let conflicting_output =
- CliArgs::parse_from(["radroots", "--json", "--ndjson", "config", "show"]);
+ let conflicting_output = RuntimeInvocationArgs {
+ json: true,
+ ndjson: true,
+ ..runtime_args()
+ };
let error = RuntimeConfig::resolve_with_env_file(
&conflicting_output,
&env,
@@ -1870,8 +1864,11 @@ mod tests {
.expect_err("conflicting output flags");
assert!(error.to_string().contains("--json and --ndjson"));
- let conflicting_verbosity =
- CliArgs::parse_from(["radroots", "--quiet", "--trace", "config", "show"]);
+ let conflicting_verbosity = RuntimeInvocationArgs {
+ quiet: true,
+ trace: true,
+ ..runtime_args()
+ };
let error = RuntimeConfig::resolve_with_env_file(
&conflicting_verbosity,
&env,
@@ -1884,8 +1881,11 @@ mod tests {
.contains("--quiet, --verbose, and --trace")
);
- let conflicting_aliases =
- CliArgs::parse_from(["radroots", "--output", "json", "--json", "config", "show"]);
+ let conflicting_aliases = RuntimeInvocationArgs {
+ output_format: Some(RuntimeOutputFormatArg::Json),
+ json: true,
+ ..runtime_args()
+ };
let error = RuntimeConfig::resolve_with_env_file(
&conflicting_aliases,
&env,
@@ -1899,8 +1899,11 @@ mod tests {
fn machine_output_rejects_stdout_logging_flags() {
let env = MapEnvironment::new(BTreeMap::new());
- let json_args =
- CliArgs::parse_from(["radroots", "--json", "--log-stdout", "config", "show"]);
+ let json_args = RuntimeInvocationArgs {
+ json: true,
+ log_stdout: true,
+ ..runtime_args()
+ };
let error =
RuntimeConfig::resolve_with_env_file(&json_args, &env, &EnvFileValues::default())
.expect_err("json stdout logging should fail");
@@ -1909,8 +1912,11 @@ mod tests {
assert!(message.contains("json output"));
assert!(message.contains("--no-log-stdout"));
- let ndjson_args =
- CliArgs::parse_from(["radroots", "--ndjson", "--log-stdout", "find", "eggs"]);
+ let ndjson_args = RuntimeInvocationArgs {
+ ndjson: true,
+ log_stdout: true,
+ ..runtime_args()
+ };
let error =
RuntimeConfig::resolve_with_env_file(&ndjson_args, &env, &EnvFileValues::default())
.expect_err("ndjson stdout logging should fail");
@@ -1921,7 +1927,10 @@ mod tests {
#[test]
fn machine_output_rejects_stdout_logging_environment() {
- let json_args = CliArgs::parse_from(["radroots", "--json", "config", "show"]);
+ let json_args = RuntimeInvocationArgs {
+ json: true,
+ ..runtime_args()
+ };
let env = MapEnvironment::new(BTreeMap::from([(
"RADROOTS_CLI_LOGGING_STDOUT".to_owned(),
"true".to_owned(),
@@ -1933,7 +1942,7 @@ mod tests {
assert!(message.contains("RADROOTS_CLI_LOGGING_STDOUT"));
assert!(message.contains("RADROOTS_LOG_STDOUT"));
- let ndjson_env_args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let ndjson_env_args = runtime_args();
let env = MapEnvironment::new(BTreeMap::from([
("RADROOTS_OUTPUT".to_owned(), "ndjson".to_owned()),
("RADROOTS_LOG_STDOUT".to_owned(), "true".to_owned()),
@@ -1946,7 +1955,11 @@ mod tests {
#[test]
fn no_log_stdout_overrides_environment_for_machine_output() {
- let args = CliArgs::parse_from(["radroots", "--json", "--no-log-stdout", "config", "show"]);
+ let args = RuntimeInvocationArgs {
+ json: true,
+ no_log_stdout: true,
+ ..runtime_args()
+ };
let env = MapEnvironment::new(BTreeMap::from([(
"RADROOTS_LOG_STDOUT".to_owned(),
"true".to_owned(),
@@ -1960,7 +1973,7 @@ mod tests {
#[test]
fn invalid_environment_value_fails() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::from([(
"RADROOTS_LOG_STDOUT".to_owned(),
"maybe".to_owned(),
@@ -1977,8 +1990,10 @@ mod tests {
.expect_err("invalid myc timeout");
assert!(error.to_string().contains("RADROOTS_MYC_STATUS_TIMEOUT_MS"));
- let args =
- CliArgs::parse_from(["radroots", "--myc-status-timeout-ms", "0", "config", "show"]);
+ let args = RuntimeInvocationArgs {
+ myc_status_timeout_ms: Some(0),
+ ..runtime_args()
+ };
let env = MapEnvironment::new(BTreeMap::new());
let error = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect_err("zero myc timeout");
@@ -1987,7 +2002,7 @@ mod tests {
#[test]
fn env_file_values_fill_missing_flags() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::new());
let env_file = parse_env_file_values(
r#"
@@ -2035,7 +2050,10 @@ RADROOTS_HYF_EXECUTABLE=bin/hyfd
#[test]
fn explicit_output_flag_overrides_environment_output() {
- let args = CliArgs::parse_from(["radroots", "--output", "ndjson", "find", "eggs"]);
+ let args = RuntimeInvocationArgs {
+ output_format: Some(RuntimeOutputFormatArg::Ndjson),
+ ..runtime_args()
+ };
let env = MapEnvironment::new(BTreeMap::from([(
"RADROOTS_OUTPUT".to_owned(),
"json".to_owned(),
@@ -2048,7 +2066,11 @@ RADROOTS_HYF_EXECUTABLE=bin/hyfd
#[test]
fn interaction_config_reflects_tty_and_flags() {
- let args = CliArgs::parse_from(["radroots", "--no-input", "--yes", "config", "show"]);
+ let args = RuntimeInvocationArgs {
+ no_input: true,
+ yes: true,
+ ..runtime_args()
+ };
let env = MapEnvironment::new(BTreeMap::new()).with_tty(true, true);
let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
@@ -2065,7 +2087,7 @@ RADROOTS_HYF_EXECUTABLE=bin/hyfd
}
);
- let interactive_args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let interactive_args = runtime_args();
let interactive = RuntimeConfig::resolve_with_env_file(
&interactive_args,
&env,
@@ -2087,7 +2109,7 @@ RADROOTS_HYF_EXECUTABLE=bin/hyfd
#[test]
fn process_environment_overrides_env_file_values() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::from([
("RADROOTS_LOG_FILTER".to_owned(), "info".to_owned()),
("RADROOTS_LOG_STDOUT".to_owned(), "true".to_owned()),
@@ -2150,7 +2172,7 @@ RADROOTS_CLI_LOGGING_STDOUT=false
stdin_tty: false,
stdout_tty: false,
};
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect("resolve config");
@@ -2207,7 +2229,7 @@ RADROOTS_CLI_LOGGING_STDOUT=false
stdin_tty: false,
stdout_tty: false,
};
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect("resolve config");
@@ -2262,7 +2284,7 @@ RADROOTS_CLI_LOGGING_STDOUT=false
stdin_tty: false,
stdout_tty: false,
};
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect("resolve config");
@@ -2290,7 +2312,7 @@ RADROOTS_CLI_LOGGING_STDOUT=false
.expect("write user config");
let env = repo_local_env(workspace_root, repo_local_root, user_home, BTreeMap::new());
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect("resolve config");
@@ -2317,7 +2339,7 @@ RADROOTS_CLI_LOGGING_STDOUT=false
user_home,
BTreeMap::from([("RADROOTS_SIGNER".to_owned(), "myc".to_owned())]),
);
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect("resolve config");
@@ -2338,7 +2360,7 @@ RADROOTS_CLI_LOGGING_STDOUT=false
.expect("write workspace config");
let env = repo_local_env(workspace_root, repo_local_root, user_home, BTreeMap::new());
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let error = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect_err("invalid signer mode");
@@ -2401,7 +2423,7 @@ target = "bin/user-hyfd"
stdin_tty: false,
stdout_tty: false,
};
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect("resolve config");
@@ -2463,7 +2485,7 @@ target = "https://rpc.workspace.test/jsonrpc"
stdin_tty: false,
stdout_tty: false,
};
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let error = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect_err("invalid capability binding provider");
@@ -2476,13 +2498,10 @@ target = "https://rpc.workspace.test/jsonrpc"
#[test]
fn invalid_relay_url_fails() {
- let args = CliArgs::parse_from([
- "radroots",
- "--relay",
- "https://not-a-websocket.example.com",
- "relay",
- "ls",
- ]);
+ let args = RuntimeInvocationArgs {
+ relay: vec!["https://not-a-websocket.example.com".to_owned()],
+ ..runtime_args()
+ };
let env = MapEnvironment::new(BTreeMap::new());
let error = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect_err("invalid relay url");
@@ -2491,7 +2510,7 @@ target = "https://rpc.workspace.test/jsonrpc"
#[test]
fn state_roots_are_resolved_from_home_and_workspace() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::new());
let resolved = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default())
.expect("resolve runtime config");
@@ -2527,7 +2546,7 @@ target = "https://rpc.workspace.test/jsonrpc"
#[test]
fn windows_roots_use_native_user_directories() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment {
values: BTreeMap::new(),
current_dir: PathBuf::from(r"C:\workspaces\radroots-cli"),
@@ -2584,7 +2603,7 @@ target = "https://rpc.workspace.test/jsonrpc"
#[test]
fn repo_local_profile_uses_explicit_repo_local_root() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::from([
(
"RADROOTS_CLI_PATHS_PROFILE".to_owned(),
@@ -2649,7 +2668,7 @@ target = "https://rpc.workspace.test/jsonrpc"
#[test]
fn repo_local_profile_requires_explicit_root() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::from([(
"RADROOTS_CLI_PATHS_PROFILE".to_owned(),
"repo_local".to_owned(),
@@ -2666,7 +2685,7 @@ target = "https://rpc.workspace.test/jsonrpc"
#[test]
fn env_file_can_select_repo_local_profile() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::new());
let env_file = parse_env_file_values(
r#"
@@ -2708,7 +2727,7 @@ RADROOTS_CLI_PATHS_REPO_LOCAL_ROOT=.local/radroots/dev
#[test]
fn env_output_accepts_ndjson() {
- let args = CliArgs::parse_from(["radroots", "config", "show"]);
+ let args = runtime_args();
let env = MapEnvironment::new(BTreeMap::from([(
"RADROOTS_OUTPUT".to_owned(),
"ndjson".to_owned(),
diff --git a/src/runtime/daemon.rs b/src/runtime/daemon.rs
@@ -7,26 +7,19 @@ use radroots_events::trade::RadrootsTradeOrder;
use radroots_sdk::{
RadrootsSdkConfig, RadrootsdAuth, SdkPublishError, SdkRadrootsdFarmPublishOptions,
SdkRadrootsdListingPublishOptions, SdkRadrootsdProfilePublishOptions,
- SdkRadrootsdSignerAuthority, SdkRadrootsdSignerSessionConnectRequest,
- SdkRadrootsdSignerSessionHandle, SdkRadrootsdSignerSessionRef, SdkRadrootsdSignerSessionRole,
- SdkRadrootsdSignerSessionView, SdkTransportMode, SignerConfig,
+ SdkRadrootsdSignerAuthority, SdkRadrootsdSignerSessionRef, SdkTransportMode, SignerConfig,
};
use reqwest::blocking::Client;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
-use crate::domain::runtime::{
- CommandOutput, CommandView, JobDetailView, JobSummaryView, RpcSessionView, RpcSessionsView,
- RpcStatusView, SignerSessionActionView,
-};
+use crate::domain::runtime::{JobDetailView, JobSummaryView};
use crate::runtime::config::RuntimeConfig;
use crate::runtime::provider;
-use crate::runtime::signer::{ActorWriteSignerAuthority, configured_myc_signer_authority};
+use crate::runtime::signer::ActorWriteSignerAuthority;
-const RPC_SOURCE: &str = "daemon rpc · durable write plane";
const BRIDGE_SOURCE: &str = "daemon bridge · durable write plane";
-const SIGNER_SESSION_SOURCE: &str = "daemon signer session rpc · durable write plane";
const RPC_TIMEOUT_SECS: u64 = 2;
#[derive(Debug)]
@@ -74,27 +67,6 @@ struct JsonRpcResponseError {
}
#[derive(Debug, Clone, Deserialize)]
-struct BridgeStatusRemote {
- enabled: bool,
- ready: bool,
- auth_mode: String,
- signer_mode: String,
- default_signer_mode: String,
- #[serde(default)]
- supported_signer_modes: Vec<String>,
- available_nip46_signer_sessions: usize,
- relay_count: usize,
- job_status_retention: usize,
- retained_jobs: usize,
- accepted_jobs: usize,
- published_jobs: usize,
- failed_jobs: usize,
- recovered_failed_jobs: usize,
- #[serde(default)]
- methods: Vec<String>,
-}
-
-#[derive(Debug, Clone, Deserialize)]
struct BridgeJobRemote {
job_id: String,
command: String,
@@ -122,17 +94,10 @@ struct BridgeJobRemote {
#[derive(Debug, Clone, Deserialize)]
struct Nip46SessionRemote {
session_id: String,
- role: String,
- client_pubkey: String,
- signer_pubkey: String,
user_pubkey: Option<String>,
#[serde(default)]
- relays: Vec<String>,
- #[serde(default)]
permissions: Vec<String>,
- auth_required: bool,
authorized: bool,
- expires_in_secs: Option<u64>,
#[serde(default)]
signer_authority: Option<ActorWriteSignerAuthority>,
}
@@ -175,376 +140,6 @@ pub struct BridgeOrderRequestResult {
pub event_addr: Option<String>,
}
-pub fn status(config: &RuntimeConfig) -> CommandOutput {
- match bridge_status(config) {
- Ok(status) => CommandOutput::success(CommandView::RpcStatus(RpcStatusView {
- state: if status.ready {
- "ready".to_owned()
- } else {
- "degraded".to_owned()
- },
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- reason: if status.ready {
- None
- } else {
- Some("bridge is reachable but not ready for durable publish traffic".to_owned())
- },
- auth_mode: Some(status.auth_mode),
- signer_mode: Some(status.signer_mode),
- default_signer_mode: Some(status.default_signer_mode),
- supported_signer_modes: status.supported_signer_modes,
- bridge_enabled: Some(status.enabled),
- bridge_ready: Some(status.ready),
- relay_count: Some(status.relay_count),
- available_nip46_signer_sessions: Some(status.available_nip46_signer_sessions),
- job_status_retention: Some(status.job_status_retention),
- retained_jobs: Some(status.retained_jobs),
- accepted_jobs: Some(status.accepted_jobs),
- published_jobs: Some(status.published_jobs),
- failed_jobs: Some(status.failed_jobs),
- recovered_failed_jobs: Some(status.recovered_failed_jobs),
- session_surface_enabled: status
- .methods
- .iter()
- .any(|method| method == "nip46.session.list"),
- methods_count: status.methods.len(),
- actions: if status.ready {
- Vec::new()
- } else {
- vec!["radroots relay list".to_owned()]
- },
- })),
- Err(DaemonRpcError::Unconfigured(reason))
- | Err(DaemonRpcError::Unauthorized(reason))
- | Err(DaemonRpcError::MethodUnavailable(reason)) => {
- CommandOutput::unconfigured(CommandView::RpcStatus(RpcStatusView {
- state: "unconfigured".to_owned(),
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- reason: Some(reason),
- auth_mode: None,
- signer_mode: None,
- default_signer_mode: None,
- supported_signer_modes: Vec::new(),
- bridge_enabled: None,
- bridge_ready: None,
- relay_count: None,
- available_nip46_signer_sessions: None,
- job_status_retention: None,
- retained_jobs: None,
- accepted_jobs: None,
- published_jobs: None,
- failed_jobs: None,
- recovered_failed_jobs: None,
- session_surface_enabled: false,
- methods_count: 0,
- actions: vec![
- "set RADROOTS_RPC_BEARER_TOKEN in .env or your shell".to_owned(),
- "start radrootsd with bridge ingress enabled".to_owned(),
- ],
- }))
- }
- Err(DaemonRpcError::External(reason)) => {
- CommandOutput::external_unavailable(CommandView::RpcStatus(RpcStatusView {
- state: "unavailable".to_owned(),
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- reason: Some(reason),
- auth_mode: None,
- signer_mode: None,
- default_signer_mode: None,
- supported_signer_modes: Vec::new(),
- bridge_enabled: None,
- bridge_ready: None,
- relay_count: None,
- available_nip46_signer_sessions: None,
- job_status_retention: None,
- retained_jobs: None,
- accepted_jobs: None,
- published_jobs: None,
- failed_jobs: None,
- recovered_failed_jobs: None,
- session_surface_enabled: false,
- methods_count: 0,
- actions: vec!["start radrootsd and verify the rpc url".to_owned()],
- }))
- }
- Err(DaemonRpcError::InvalidResponse(reason)) | Err(DaemonRpcError::Remote(reason)) => {
- CommandOutput::internal_error(CommandView::RpcStatus(RpcStatusView {
- state: "error".to_owned(),
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- reason: Some(reason),
- auth_mode: None,
- signer_mode: None,
- default_signer_mode: None,
- supported_signer_modes: Vec::new(),
- bridge_enabled: None,
- bridge_ready: None,
- relay_count: None,
- available_nip46_signer_sessions: None,
- job_status_retention: None,
- retained_jobs: None,
- accepted_jobs: None,
- published_jobs: None,
- failed_jobs: None,
- recovered_failed_jobs: None,
- session_surface_enabled: false,
- methods_count: 0,
- actions: vec!["inspect the daemon rpc response contract".to_owned()],
- }))
- }
- Err(DaemonRpcError::UnknownJob(reason)) => {
- CommandOutput::internal_error(CommandView::RpcStatus(RpcStatusView {
- state: "error".to_owned(),
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- reason: Some(reason),
- auth_mode: None,
- signer_mode: None,
- default_signer_mode: None,
- supported_signer_modes: Vec::new(),
- bridge_enabled: None,
- bridge_ready: None,
- relay_count: None,
- available_nip46_signer_sessions: None,
- job_status_retention: None,
- retained_jobs: None,
- accepted_jobs: None,
- published_jobs: None,
- failed_jobs: None,
- recovered_failed_jobs: None,
- session_surface_enabled: false,
- methods_count: 0,
- actions: Vec::new(),
- }))
- }
- }
-}
-
-pub fn sessions(config: &RuntimeConfig) -> CommandOutput {
- match nip46_sessions(config) {
- Ok(sessions) => {
- let entries = sessions
- .into_iter()
- .map(map_session_view)
- .collect::<Vec<_>>();
- let state = if entries.is_empty() { "empty" } else { "ready" };
- CommandOutput::success(CommandView::RpcSessions(RpcSessionsView {
- state: state.to_owned(),
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- count: entries.len(),
- reason: None,
- sessions: entries,
- actions: Vec::new(),
- }))
- }
- Err(DaemonRpcError::MethodUnavailable(reason)) => {
- CommandOutput::unconfigured(CommandView::RpcSessions(RpcSessionsView {
- state: "unconfigured".to_owned(),
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- sessions: Vec::new(),
- actions: vec!["enable nip46.public_jsonrpc_enabled in radrootsd".to_owned()],
- }))
- }
- Err(DaemonRpcError::External(reason)) => {
- CommandOutput::external_unavailable(CommandView::RpcSessions(RpcSessionsView {
- state: "unavailable".to_owned(),
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- sessions: Vec::new(),
- actions: vec!["start radrootsd and verify the rpc url".to_owned()],
- }))
- }
- Err(DaemonRpcError::Unconfigured(reason))
- | Err(DaemonRpcError::Unauthorized(reason))
- | Err(DaemonRpcError::InvalidResponse(reason))
- | Err(DaemonRpcError::Remote(reason))
- | Err(DaemonRpcError::UnknownJob(reason)) => {
- CommandOutput::internal_error(CommandView::RpcSessions(RpcSessionsView {
- state: "error".to_owned(),
- source: RPC_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- sessions: Vec::new(),
- actions: Vec::new(),
- }))
- }
- }
-}
-
-pub fn signer_sessions(config: &RuntimeConfig) -> CommandOutput {
- match signer_session_views(config) {
- Ok((url, sessions)) => {
- let entries = sessions
- .into_iter()
- .map(map_sdk_session_view)
- .collect::<Vec<_>>();
- let state = if entries.is_empty() { "empty" } else { "ready" };
- CommandOutput::success(CommandView::RpcSessions(RpcSessionsView {
- state: state.to_owned(),
- source: SIGNER_SESSION_SOURCE.to_owned(),
- url,
- count: entries.len(),
- reason: None,
- sessions: entries,
- actions: Vec::new(),
- }))
- }
- Err(DaemonRpcError::External(reason)) => {
- CommandOutput::external_unavailable(CommandView::RpcSessions(RpcSessionsView {
- state: "unavailable".to_owned(),
- source: SIGNER_SESSION_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- sessions: Vec::new(),
- actions: vec![
- "start radrootsd and verify the actor write-plane endpoint".to_owned(),
- ],
- }))
- }
- Err(DaemonRpcError::Unconfigured(reason))
- | Err(DaemonRpcError::Unauthorized(reason))
- | Err(DaemonRpcError::MethodUnavailable(reason)) => {
- CommandOutput::unconfigured(CommandView::RpcSessions(RpcSessionsView {
- state: "unconfigured".to_owned(),
- source: SIGNER_SESSION_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- sessions: Vec::new(),
- actions: vec!["configure the radrootsd actor write-plane binding".to_owned()],
- }))
- }
- Err(DaemonRpcError::InvalidResponse(reason))
- | Err(DaemonRpcError::Remote(reason))
- | Err(DaemonRpcError::UnknownJob(reason)) => {
- CommandOutput::internal_error(CommandView::RpcSessions(RpcSessionsView {
- state: "error".to_owned(),
- source: SIGNER_SESSION_SOURCE.to_owned(),
- url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- sessions: Vec::new(),
- actions: Vec::new(),
- }))
- }
- }
-}
-
-pub fn signer_session_connect_bunker(
- config: &RuntimeConfig,
- url: &str,
-) -> Result<SignerSessionActionView, DaemonRpcError> {
- let mut request = SdkRadrootsdSignerSessionConnectRequest::bunker(url.to_owned());
- if let Some(authority) = configured_myc_signer_authority(config) {
- request = request.with_signer_authority(sdk_signer_authority(&authority));
- }
- signer_session_connect(config, "connect_bunker", &request)
-}
-
-pub fn signer_session_connect_nostrconnect(
- config: &RuntimeConfig,
- url: &str,
- client_secret_key: &str,
-) -> Result<SignerSessionActionView, DaemonRpcError> {
- let mut request = SdkRadrootsdSignerSessionConnectRequest::nostrconnect(
- url.to_owned(),
- client_secret_key.to_owned(),
- );
- if let Some(authority) = configured_myc_signer_authority(config) {
- request = request.with_signer_authority(sdk_signer_authority(&authority));
- }
- signer_session_connect(config, "connect_nostrconnect", &request)
-}
-
-pub fn signer_session_show(
- config: &RuntimeConfig,
- session_id: &str,
-) -> Result<SignerSessionActionView, DaemonRpcError> {
- let sdk = actor_write_sdk_client(config)?;
- let session_ref = SdkRadrootsdSignerSessionRef::from_session_id(session_id.to_owned());
- let session = block_on_sdk(sdk.radrootsd().signer_sessions().status(&session_ref))?
- .map_err(|error| DaemonRpcError::Remote(error.to_string()))?;
- Ok(signer_session_view_action("show", session))
-}
-
-pub fn signer_session_public_key(
- config: &RuntimeConfig,
- session_id: &str,
-) -> Result<SignerSessionActionView, DaemonRpcError> {
- let sdk = actor_write_sdk_client(config)?;
- let session_ref = SdkRadrootsdSignerSessionRef::from_session_id(session_id.to_owned());
- let public_key = block_on_sdk(
- sdk.radrootsd()
- .signer_sessions()
- .get_public_key(&session_ref),
- )?
- .map_err(|error| DaemonRpcError::Remote(error.to_string()))?;
- let mut view = signer_session_action("public_key", "ready");
- view.session_id = Some(session_id.to_owned());
- view.pubkey = Some(public_key.pubkey);
- Ok(view)
-}
-
-pub fn signer_session_authorize(
- config: &RuntimeConfig,
- session_id: &str,
-) -> Result<SignerSessionActionView, DaemonRpcError> {
- let sdk = actor_write_sdk_client(config)?;
- let session_ref = SdkRadrootsdSignerSessionRef::from_session_id(session_id.to_owned());
- let result = block_on_sdk(sdk.radrootsd().signer_sessions().authorize(&session_ref))?
- .map_err(|error| DaemonRpcError::Remote(error.to_string()))?;
- let mut view = signer_session_action("authorize", "ready");
- view.session_id = Some(session_id.to_owned());
- view.authorized = Some(result.authorized);
- view.replayed = Some(result.replayed);
- Ok(view)
-}
-
-pub fn signer_session_require_auth(
- config: &RuntimeConfig,
- session_id: &str,
- auth_url: &str,
-) -> Result<SignerSessionActionView, DaemonRpcError> {
- let sdk = actor_write_sdk_client(config)?;
- let session_ref = SdkRadrootsdSignerSessionRef::from_session_id(session_id.to_owned());
- let result = block_on_sdk(
- sdk.radrootsd()
- .signer_sessions()
- .require_auth(&session_ref, auth_url),
- )?
- .map_err(|error| DaemonRpcError::Remote(error.to_string()))?;
- let mut view = signer_session_action("require_auth", "ready");
- view.session_id = Some(session_id.to_owned());
- view.required = Some(result.required);
- view.auth_url = Some(auth_url.to_owned());
- Ok(view)
-}
-
-pub fn signer_session_close(
- config: &RuntimeConfig,
- session_id: &str,
-) -> Result<SignerSessionActionView, DaemonRpcError> {
- let sdk = actor_write_sdk_client(config)?;
- let session_ref = SdkRadrootsdSignerSessionRef::from_session_id(session_id.to_owned());
- let result = block_on_sdk(sdk.radrootsd().signer_sessions().close(&session_ref))?
- .map_err(|error| DaemonRpcError::Remote(error.to_string()))?;
- let mut view = signer_session_action("close", "ready");
- view.session_id = Some(session_id.to_owned());
- view.closed = Some(result.closed);
- Ok(view)
-}
-
pub fn bridge_job_list(config: &RuntimeConfig) -> Result<Vec<JobSummaryView>, DaemonRpcError> {
bridge_jobs(config).map(|jobs| jobs.into_iter().map(map_job_summary_view).collect())
}
@@ -698,15 +293,6 @@ pub fn bridge_order_request(
map_order_request_receipt(receipt, idempotency_key)
}
-fn bridge_status(config: &RuntimeConfig) -> Result<BridgeStatusRemote, DaemonRpcError> {
- call(
- &default_target(config),
- "bridge.status",
- None,
- RpcAuthMode::BridgeBearer,
- )
-}
-
fn bridge_jobs(config: &RuntimeConfig) -> Result<Vec<BridgeJobRemote>, DaemonRpcError> {
call(
&default_target(config),
@@ -728,10 +314,6 @@ fn bridge_job_status(
)
}
-fn nip46_sessions(config: &RuntimeConfig) -> Result<Vec<Nip46SessionRemote>, DaemonRpcError> {
- nip46_sessions_with_target(&default_target(config))
-}
-
fn nip46_sessions_with_target(
target: &RpcTarget,
) -> Result<Vec<Nip46SessionRemote>, DaemonRpcError> {
@@ -826,83 +408,6 @@ fn sdk_signer_authority(value: &ActorWriteSignerAuthority) -> SdkRadrootsdSigner
}
}
-fn signer_session_views(
- config: &RuntimeConfig,
-) -> Result<(String, Vec<SdkRadrootsdSignerSessionView>), DaemonRpcError> {
- let target = actor_write_target(config)?;
- let sdk = radrootsd_sdk_client(&target)?;
- let sessions = block_on_sdk(sdk.radrootsd().signer_sessions().list())?
- .map_err(|error| DaemonRpcError::Remote(error.to_string()))?;
- Ok((target.url, sessions))
-}
-
-fn signer_session_connect(
- config: &RuntimeConfig,
- action: &str,
- request: &SdkRadrootsdSignerSessionConnectRequest,
-) -> Result<SignerSessionActionView, DaemonRpcError> {
- let sdk = actor_write_sdk_client(config)?;
- let handle = block_on_sdk(sdk.radrootsd().signer_sessions().connect(request))?
- .map_err(|error| DaemonRpcError::Remote(error.to_string()))?;
- Ok(signer_session_handle_action(action, handle))
-}
-
-fn signer_session_action(action: &str, state: &str) -> SignerSessionActionView {
- SignerSessionActionView {
- action: action.to_owned(),
- state: state.to_owned(),
- source: SIGNER_SESSION_SOURCE.to_owned(),
- session_id: None,
- mode: None,
- remote_signer_pubkey: None,
- client_pubkey: None,
- signer_pubkey: None,
- user_pubkey: None,
- relays: Vec::new(),
- permissions: Vec::new(),
- auth_required: None,
- authorized: None,
- auth_url: None,
- expires_in_secs: None,
- pubkey: None,
- replayed: None,
- required: None,
- closed: None,
- reason: None,
- }
-}
-
-fn signer_session_handle_action(
- action: &str,
- handle: SdkRadrootsdSignerSessionHandle,
-) -> SignerSessionActionView {
- let mut view = signer_session_action(action, "ready");
- view.session_id = Some(handle.session().session_id().to_owned());
- view.mode = Some(format!("{:?}", handle.mode()));
- view.remote_signer_pubkey = Some(handle.remote_signer_pubkey().to_owned());
- view.client_pubkey = Some(handle.client_pubkey().to_owned());
- view.relays = handle.relays().to_vec();
- view
-}
-
-fn signer_session_view_action(
- action: &str,
- session: SdkRadrootsdSignerSessionView,
-) -> SignerSessionActionView {
- let mut view = signer_session_action(action, "ready");
- view.session_id = Some(session.session().session_id().to_owned());
- view.signer_pubkey = Some(session.signer_pubkey);
- view.user_pubkey = session.user_pubkey;
- view.client_pubkey = Some(session.client_pubkey);
- view.relays = session.relays;
- view.permissions = session.permissions;
- view.auth_required = Some(session.auth_required);
- view.authorized = Some(session.authorized);
- view.auth_url = session.auth_url;
- view.expires_in_secs = session.expires_in_secs;
- view
-}
-
fn map_sdk_publish_error(error: SdkPublishError) -> DaemonRpcError {
match error {
SdkPublishError::Config(err) => DaemonRpcError::Unconfigured(err.to_string()),
@@ -1322,43 +827,6 @@ fn map_job_detail_view(job: BridgeJobRemote) -> JobDetailView {
}
}
-fn map_session_view(session: Nip46SessionRemote) -> RpcSessionView {
- RpcSessionView {
- session_id: session.session_id,
- role: session.role,
- client_pubkey: session.client_pubkey,
- signer_pubkey: session.signer_pubkey,
- user_pubkey: session.user_pubkey,
- relay_count: session.relays.len(),
- permissions_count: session.permissions.len(),
- auth_required: session.auth_required,
- authorized: session.authorized,
- expires_in_secs: session.expires_in_secs,
- }
-}
-
-fn map_sdk_session_view(session: SdkRadrootsdSignerSessionView) -> RpcSessionView {
- RpcSessionView {
- session_id: session.session().session_id().to_owned(),
- role: sdk_session_role(session.role).to_owned(),
- client_pubkey: session.client_pubkey,
- signer_pubkey: session.signer_pubkey,
- user_pubkey: session.user_pubkey,
- relay_count: session.relays.len(),
- permissions_count: session.permissions.len(),
- auth_required: session.auth_required,
- authorized: session.authorized,
- expires_in_secs: session.expires_in_secs,
- }
-}
-
-fn sdk_session_role(role: SdkRadrootsdSignerSessionRole) -> &'static str {
- match role {
- SdkRadrootsdSignerSessionRole::InboundLocalSigner => "inbound_local_signer",
- SdkRadrootsdSignerSessionRole::OutboundRemoteSigner => "outbound_remote_signer",
- }
-}
-
pub fn bridge_source() -> &'static str {
BRIDGE_SOURCE
}
diff --git a/src/runtime/farm.rs b/src/runtime/farm.rs
@@ -9,10 +9,6 @@ use radroots_events_codec::d_tag::is_d_tag_base64url;
use radroots_events_codec::farm::encode::to_wire_parts_with_kind;
use radroots_events_codec::profile::encode::to_wire_parts_with_profile_type;
-use crate::cli::{
- FarmFieldArg, FarmInitArgs, FarmPublishArgs, FarmScopeArg, FarmScopedArgs, FarmSetArgs,
- FarmSetupArgs,
-};
use crate::domain::runtime::{
FarmConfigDocumentView, FarmConfigSummaryView, FarmGetView, FarmListingDefaultsView,
FarmPublicationView, FarmPublishComponentView, FarmPublishEventView, FarmPublishJobView,
@@ -27,12 +23,15 @@ use crate::runtime::farm_config::{
FarmMissingField, FarmPublicationStatus, ResolvedFarmConfig, SUPPORTED_FARM_CONFIG_VERSION,
};
use crate::runtime::signer::{ActorWriteBindingError, resolve_actor_write_authority};
+use crate::runtime_args::{
+ FarmCreateArgs, FarmFieldArg, FarmPublishArgs, FarmScopeArg, FarmScopedArgs, FarmUpdateArgs,
+};
const FARM_CONFIG_SOURCE: &str = "farm config · local first";
static D_TAG_COUNTER: AtomicU64 = AtomicU64::new(0);
-pub fn init(config: &RuntimeConfig, args: &FarmInitArgs) -> Result<FarmSetupView, RuntimeError> {
+pub fn init(config: &RuntimeConfig, args: &FarmCreateArgs) -> Result<FarmSetupView, RuntimeError> {
let scope = scope_from_arg(args.scope);
let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
let Some(selected_account) = selected_account_for_draft(config)? else {
@@ -51,26 +50,7 @@ pub fn init(config: &RuntimeConfig, args: &FarmInitArgs) -> Result<FarmSetupView
)
}
-pub fn setup(config: &RuntimeConfig, args: &FarmSetupArgs) -> Result<FarmSetupView, RuntimeError> {
- let scope = scope_from_arg(args.scope);
- let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
- let Some(selected_account) = selected_account_for_draft(config)? else {
- return Ok(missing_selected_account_setup_view());
- };
- let existing = farm_config::load(config, Some(resolved_scope))?;
- let document = setup_document(args, resolved_scope, &selected_account, existing.as_ref())?;
- save_draft_view(
- "configured",
- resolved_scope,
- &selected_account,
- &document,
- None,
- farm_setup_actions(&document),
- config,
- )
-}
-
-pub fn set(config: &RuntimeConfig, args: &FarmSetArgs) -> Result<FarmSetView, RuntimeError> {
+pub fn set(config: &RuntimeConfig, args: &FarmUpdateArgs) -> Result<FarmSetView, RuntimeError> {
let scope = scope_from_arg(args.scope);
let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
let path = farm_config::config_path(&config.paths, resolved_scope)?;
@@ -989,7 +969,7 @@ fn init_document(
scope: FarmConfigScope,
account: &AccountRecordView,
existing: Option<&ResolvedFarmConfig>,
- args: &FarmInitArgs,
+ args: &FarmCreateArgs,
) -> Result<FarmConfigDocument, RuntimeError> {
let existing_document = existing.map(|resolved| &resolved.document);
let farm_d_tag = match args.farm_d_tag.as_deref() {
@@ -1275,123 +1255,6 @@ fn publication_for_document(
.unwrap_or_default()
}
-fn setup_document(
- args: &FarmSetupArgs,
- scope: FarmConfigScope,
- account: &AccountRecordView,
- existing: Option<&ResolvedFarmConfig>,
-) -> Result<FarmConfigDocument, RuntimeError> {
- let existing_document = existing.map(|resolved| &resolved.document);
- let name = required_text(args.name.as_str(), "farm.name")?;
- let location_primary = required_text(args.location.as_str(), "farm.location.primary")?;
- let delivery_method = required_text(
- args.delivery_method.as_str(),
- "listing_defaults.delivery_method",
- )?;
- let farm_d_tag = match args.farm_d_tag.as_deref() {
- Some(value) => required_d_tag(value, "farm_d_tag")?,
- None => existing_document
- .map(|document| document.farm.d_tag.clone())
- .unwrap_or_else(generate_d_tag),
- };
- if !is_d_tag_base64url(farm_d_tag.as_str()) {
- return Err(RuntimeError::Config(
- "farm_d_tag must be a 22-character base64url identifier".to_owned(),
- ));
- }
-
- let about = optional_arg_or_existing(
- args.about.as_ref(),
- existing_document.and_then(|document| document.profile.about.as_ref()),
- );
- let website = optional_arg_or_existing(
- args.website.as_ref(),
- existing_document.and_then(|document| document.profile.website.as_ref()),
- );
- let picture = optional_arg_or_existing(
- args.picture.as_ref(),
- existing_document.and_then(|document| document.profile.picture.as_ref()),
- );
- let banner = optional_arg_or_existing(
- args.banner.as_ref(),
- existing_document.and_then(|document| document.profile.banner.as_ref()),
- );
- let display_name = optional_arg_or_existing(
- args.display_name.as_ref(),
- existing_document.and_then(|document| document.profile.display_name.as_ref()),
- )
- .or_else(|| Some(name.clone()));
- let city = optional_arg_or_existing(
- args.city.as_ref(),
- existing_document
- .and_then(|document| document.farm.location.as_ref())
- .and_then(|location| location.city.as_ref()),
- );
- let region = optional_arg_or_existing(
- args.region.as_ref(),
- existing_document
- .and_then(|document| document.farm.location.as_ref())
- .and_then(|location| location.region.as_ref()),
- );
- let country = optional_arg_or_existing(
- args.country.as_ref(),
- existing_document
- .and_then(|document| document.farm.location.as_ref())
- .and_then(|location| location.country.as_ref()),
- );
- let publication = publication_for_document(existing_document, account, farm_d_tag.as_str());
-
- Ok(FarmConfigDocument {
- version: SUPPORTED_FARM_CONFIG_VERSION,
- selection: FarmConfigSelection {
- scope,
- account: account.record.account_id.to_string(),
- farm_d_tag: farm_d_tag.clone(),
- },
- profile: RadrootsProfile {
- name: name.clone(),
- display_name,
- nip05: None,
- about: about.clone(),
- website: website.clone(),
- picture: picture.clone(),
- banner: banner.clone(),
- lud06: None,
- lud16: None,
- bot: None,
- },
- farm: RadrootsFarm {
- d_tag: farm_d_tag,
- name,
- about,
- website,
- picture,
- banner,
- location: Some(RadrootsFarmLocation {
- primary: Some(location_primary.clone()),
- city: city.clone(),
- region: region.clone(),
- country: country.clone(),
- gcs: None,
- }),
- tags: None,
- },
- listing_defaults: FarmListingDefaults {
- delivery_method,
- location: RadrootsListingLocation {
- primary: location_primary,
- city,
- region,
- country,
- lat: None,
- lng: None,
- geohash: None,
- },
- },
- publication,
- })
-}
-
fn configured_account(
config: &RuntimeConfig,
account_id: &str,
diff --git a/src/runtime/find.rs b/src/runtime/find.rs
@@ -1,7 +1,6 @@
use radroots_replica_db::ReplicaSql;
use radroots_sql_core::SqliteExecutor;
-use crate::cli::FindArgs;
use crate::domain::runtime::{
FindHyfView, FindPriceView, FindQuantityView, FindResultHyfView, FindResultProvenanceView,
FindResultView, FindView, SyncFreshnessView,
@@ -10,6 +9,7 @@ use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
use crate::runtime::hyf::{self, HyfQueryRewriteRequest, HyfRequestContext};
use crate::runtime::sync::freshness_from_executor;
+use crate::runtime_args::FindQueryArgs;
const FIND_SOURCE: &str = "local replica · local first";
const FIND_HYF_SOURCE: &str = "hyf query_rewrite · local first";
@@ -40,7 +40,7 @@ impl AppliedQueryRewrite {
}
}
-pub fn search(config: &RuntimeConfig, args: &FindArgs) -> Result<FindView, RuntimeError> {
+pub fn search(config: &RuntimeConfig, args: &FindQueryArgs) -> Result<FindView, RuntimeError> {
let query = args.query.join(" ");
if !config.local.replica_db_path.exists() {
return Ok(FindView {
diff --git a/src/runtime/job.rs b/src/runtime/job.rs
@@ -1,278 +0,0 @@
-use std::thread;
-use std::time::Duration;
-
-use chrono::{DateTime, Utc};
-
-use crate::cli::JobWatchArgs;
-use crate::domain::runtime::{
- CommandOutput, CommandView, JobGetView, JobListView, JobWatchFrameView, JobWatchView,
-};
-use crate::runtime::RuntimeError;
-use crate::runtime::config::RuntimeConfig;
-use crate::runtime::daemon::{self, DaemonRpcError};
-
-pub fn list(config: &RuntimeConfig) -> CommandOutput {
- match daemon::bridge_job_list(config) {
- Ok(jobs) => CommandOutput::success(CommandView::JobList(JobListView {
- state: if jobs.is_empty() {
- "empty".to_owned()
- } else {
- "ready".to_owned()
- },
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- count: jobs.len(),
- reason: None,
- jobs,
- actions: Vec::new(),
- })),
- Err(error) => error_job_list_view(config, error),
- }
-}
-
-pub fn get(config: &RuntimeConfig, job_id: &str) -> CommandOutput {
- match daemon::bridge_job(config, job_id) {
- Ok(Some(job)) => CommandOutput::success(CommandView::JobGet(JobGetView {
- state: "ready".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- lookup: job_id.to_owned(),
- reason: None,
- job: Some(job),
- actions: Vec::new(),
- })),
- Ok(None) => CommandOutput::success(CommandView::JobGet(JobGetView {
- state: "missing".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- lookup: job_id.to_owned(),
- reason: Some(format!("job `{job_id}` was not found in radrootsd")),
- job: None,
- actions: vec!["radroots job ls".to_owned()],
- })),
- Err(error) => error_job_get_view(config, job_id, error),
- }
-}
-
-pub fn watch(config: &RuntimeConfig, args: &JobWatchArgs) -> Result<CommandOutput, RuntimeError> {
- if args.frames == Some(0) {
- return Err(RuntimeError::Config(
- "--frames must be greater than zero when provided".to_owned(),
- ));
- }
-
- let mut frames = Vec::new();
- let max_frames = args.frames.unwrap_or(usize::MAX);
- loop {
- match daemon::bridge_job(config, args.key.as_str()) {
- Ok(Some(job)) => {
- frames.push(JobWatchFrameView {
- sequence: frames.len() + 1,
- observed_at_unix: job.completed_at_unix.unwrap_or(job.requested_at_unix),
- state: job.state.clone(),
- terminal: job.terminal,
- signer: job.signer.clone(),
- signer_session_id: job.signer_session_id.clone(),
- summary: job.relay_outcome_summary.clone(),
- });
- if job.terminal || frames.len() >= max_frames {
- let state = if job.terminal {
- job.state
- } else {
- "watching".to_owned()
- };
- return Ok(CommandOutput::success(CommandView::JobWatch(
- JobWatchView {
- state,
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- job_id: args.key.clone(),
- interval_ms: args.interval_ms,
- reason: None,
- frames,
- actions: Vec::new(),
- },
- )));
- }
- }
- Ok(None) => {
- return Ok(CommandOutput::success(CommandView::JobWatch(
- JobWatchView {
- state: "missing".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- job_id: args.key.clone(),
- interval_ms: args.interval_ms,
- reason: Some(format!("job `{}` was not found in radrootsd", args.key)),
- frames,
- actions: vec!["radroots job ls".to_owned()],
- },
- )));
- }
- Err(error) => {
- return Ok(error_job_watch_view(config, args, frames, error));
- }
- }
-
- thread::sleep(Duration::from_millis(args.interval_ms));
- }
-}
-
-pub fn format_timestamp(unix: u64) -> String {
- DateTime::<Utc>::from_timestamp(unix as i64, 0)
- .map(|value| value.format("%Y-%m-%d %H:%M:%S UTC").to_string())
- .unwrap_or_else(|| unix.to_string())
-}
-
-pub fn format_clock(unix: u64) -> String {
- DateTime::<Utc>::from_timestamp(unix as i64, 0)
- .map(|value| value.format("%H:%M:%S").to_string())
- .unwrap_or_else(|| unix.to_string())
-}
-
-fn error_job_list_view(config: &RuntimeConfig, error: DaemonRpcError) -> CommandOutput {
- match error {
- DaemonRpcError::Unconfigured(reason)
- | DaemonRpcError::Unauthorized(reason)
- | DaemonRpcError::MethodUnavailable(reason) => {
- CommandOutput::unconfigured(CommandView::JobList(JobListView {
- state: "unconfigured".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- jobs: Vec::new(),
- actions: vec![
- "set RADROOTS_RPC_BEARER_TOKEN in .env or your shell".to_owned(),
- "start radrootsd with bridge ingress enabled".to_owned(),
- ],
- }))
- }
- DaemonRpcError::External(reason) => {
- CommandOutput::external_unavailable(CommandView::JobList(JobListView {
- state: "unavailable".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- jobs: Vec::new(),
- actions: vec!["start radrootsd and verify the rpc url".to_owned()],
- }))
- }
- DaemonRpcError::InvalidResponse(reason)
- | DaemonRpcError::Remote(reason)
- | DaemonRpcError::UnknownJob(reason) => {
- CommandOutput::internal_error(CommandView::JobList(JobListView {
- state: "error".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- count: 0,
- reason: Some(reason),
- jobs: Vec::new(),
- actions: Vec::new(),
- }))
- }
- }
-}
-
-fn error_job_get_view(
- config: &RuntimeConfig,
- job_id: &str,
- error: DaemonRpcError,
-) -> CommandOutput {
- match error {
- DaemonRpcError::Unconfigured(reason)
- | DaemonRpcError::Unauthorized(reason)
- | DaemonRpcError::MethodUnavailable(reason) => {
- CommandOutput::unconfigured(CommandView::JobGet(JobGetView {
- state: "unconfigured".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- lookup: job_id.to_owned(),
- reason: Some(reason),
- job: None,
- actions: vec![
- "set RADROOTS_RPC_BEARER_TOKEN in .env or your shell".to_owned(),
- "start radrootsd with bridge ingress enabled".to_owned(),
- ],
- }))
- }
- DaemonRpcError::External(reason) => {
- CommandOutput::external_unavailable(CommandView::JobGet(JobGetView {
- state: "unavailable".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- lookup: job_id.to_owned(),
- reason: Some(reason),
- job: None,
- actions: vec!["start radrootsd and verify the rpc url".to_owned()],
- }))
- }
- DaemonRpcError::InvalidResponse(reason)
- | DaemonRpcError::Remote(reason)
- | DaemonRpcError::UnknownJob(reason) => {
- CommandOutput::internal_error(CommandView::JobGet(JobGetView {
- state: "error".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- lookup: job_id.to_owned(),
- reason: Some(reason),
- job: None,
- actions: Vec::new(),
- }))
- }
- }
-}
-
-fn error_job_watch_view(
- config: &RuntimeConfig,
- args: &JobWatchArgs,
- frames: Vec<JobWatchFrameView>,
- error: DaemonRpcError,
-) -> CommandOutput {
- match error {
- DaemonRpcError::Unconfigured(reason)
- | DaemonRpcError::Unauthorized(reason)
- | DaemonRpcError::MethodUnavailable(reason) => {
- CommandOutput::unconfigured(CommandView::JobWatch(JobWatchView {
- state: "unconfigured".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- job_id: args.key.clone(),
- interval_ms: args.interval_ms,
- reason: Some(reason),
- frames,
- actions: vec![
- "set RADROOTS_RPC_BEARER_TOKEN in .env or your shell".to_owned(),
- "start radrootsd with bridge ingress enabled".to_owned(),
- ],
- }))
- }
- DaemonRpcError::External(reason) => {
- CommandOutput::external_unavailable(CommandView::JobWatch(JobWatchView {
- state: "unavailable".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- job_id: args.key.clone(),
- interval_ms: args.interval_ms,
- reason: Some(reason),
- frames,
- actions: vec!["start radrootsd and verify the rpc url".to_owned()],
- }))
- }
- DaemonRpcError::InvalidResponse(reason)
- | DaemonRpcError::Remote(reason)
- | DaemonRpcError::UnknownJob(reason) => {
- CommandOutput::internal_error(CommandView::JobWatch(JobWatchView {
- state: "error".to_owned(),
- source: daemon::bridge_source().to_owned(),
- rpc_url: config.rpc.url.clone(),
- job_id: args.key.clone(),
- interval_ms: args.interval_ms,
- reason: Some(reason),
- frames,
- actions: Vec::new(),
- }))
- }
- }
-}
diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs
@@ -25,15 +25,10 @@ use radroots_trade::listing::publish::validate_listing_for_seller;
use radroots_trade::listing::validation::validate_listing_event;
use serde::{Deserialize, Serialize};
-use crate::cli::{
- ListingFileArgs, ListingMutationArgs, ListingNewArgs, RecordKeyArgs, SellAddArgs,
- SellRepriceArgs, SellRestockArgs, SellShowArgs,
-};
use crate::domain::runtime::{
FindPriceView, FindQuantityView, FindResultProvenanceView, ListingGetView,
ListingMutationEventView, ListingMutationJobView, ListingMutationView, ListingNewView,
- ListingValidateView, ListingValidationIssueView, SellAddView, SellCheckView,
- SellDraftMutationView, SellMutationView, SellShowView, SyncFreshnessView,
+ ListingValidateView, ListingValidationIssueView, SyncFreshnessView,
};
use crate::runtime::RuntimeError;
use crate::runtime::accounts;
@@ -43,6 +38,9 @@ use crate::runtime::daemon::DaemonRpcError;
use crate::runtime::farm_config;
use crate::runtime::signer::{ActorWriteBindingError, resolve_actor_write_authority};
use crate::runtime::sync::freshness_from_executor;
+use crate::runtime_args::{
+ ListingCreateArgs, ListingFileArgs, ListingMutationArgs, RecordLookupArgs,
+};
const DRAFT_KIND: &str = "listing_draft_v1";
const LISTING_SOURCE: &str = "local draft · local first";
@@ -157,33 +155,6 @@ struct ListingAuthoringDefaults {
}
#[derive(Debug, Clone)]
-struct DraftSummary {
- product_key: Option<String>,
- title: Option<String>,
- category: Option<String>,
- offer: Option<String>,
- price: Option<String>,
- stock: Option<String>,
- delivery_method: Option<String>,
- location_primary: Option<String>,
-}
-
-#[derive(Debug, Clone)]
-struct ParsedQuantityExpr {
- amount: String,
- unit: String,
- label: String,
-}
-
-#[derive(Debug, Clone)]
-struct ParsedPriceExpr {
- amount: String,
- currency: String,
- per_amount: String,
- per_unit: String,
-}
-
-#[derive(Debug, Clone)]
struct CanonicalListingDraft {
listing_id: String,
seller_pubkey: String,
@@ -210,7 +181,7 @@ impl ListingMutationOperation {
pub fn scaffold(
config: &RuntimeConfig,
- args: &ListingNewArgs,
+ args: &ListingCreateArgs,
) -> Result<ListingNewView, RuntimeError> {
let (draft, defaults) = build_listing_draft(config, args)?;
let output_path = default_listing_output_path(args.output.as_ref(), &draft.listing.d_tag)?;
@@ -242,209 +213,9 @@ pub fn scaffold(
})
}
-pub fn sell_add(config: &RuntimeConfig, args: &SellAddArgs) -> Result<SellAddView, RuntimeError> {
- let listing_args = listing_args_from_sell_add(args)?;
- let (draft, defaults) = build_listing_draft(config, &listing_args)?;
- let output_path = listing_args
- .output
- .clone()
- .expect("sell add always sets an explicit output path");
- write_listing_draft(&output_path, &draft, false)?;
-
- let summary = summarize_draft(&draft);
- let mut actions = vec![format!(
- "radroots listing validate {}",
- output_path.display()
- )];
- if defaults.selected_account_pubkey.is_some() && defaults.selected_farm_d_tag.is_some() {
- actions.push(format!(
- "radroots listing publish {}",
- output_path.display()
- ));
- }
- if defaults.selected_account_pubkey.is_none() {
- actions.push("radroots account create".to_owned());
- }
- if let Some(action) = &defaults.farm_next_action {
- actions.push(action.clone());
- }
-
- Ok(SellAddView {
- state: "draft_saved".to_owned(),
- source: LISTING_SOURCE.to_owned(),
- file: output_path.display().to_string(),
- product_key: summary.product_key,
- title: summary.title,
- offer: summary.offer,
- price: summary.price,
- stock: summary.stock,
- farm_name: defaults.farm_name,
- delivery_method: summary.delivery_method,
- location_primary: summary.location_primary,
- reason: defaults.farm_reason,
- actions,
- })
-}
-
-pub fn sell_show(
- _config: &RuntimeConfig,
- args: &SellShowArgs,
-) -> Result<SellShowView, RuntimeError> {
- let draft = read_listing_draft(&args.file)?;
- let summary = summarize_draft(&draft);
- Ok(SellShowView {
- state: "ready".to_owned(),
- source: LISTING_SOURCE.to_owned(),
- file: args.file.display().to_string(),
- product_key: summary.product_key,
- title: summary.title,
- category: summary.category,
- offer: summary.offer,
- price: summary.price,
- stock: summary.stock,
- delivery_method: summary.delivery_method,
- location_primary: summary.location_primary,
- reason: None,
- actions: vec![
- format!("radroots listing validate {}", args.file.display()),
- format!("radroots listing publish {}", args.file.display()),
- ],
- })
-}
-
-pub fn sell_reprice(
- _config: &RuntimeConfig,
- args: &SellRepriceArgs,
-) -> Result<SellDraftMutationView, RuntimeError> {
- let mut draft = read_listing_draft(&args.file)?;
- let parsed = parse_price_expr(args.price_expr.as_str())?;
- draft.primary_bin.price_amount = parsed.amount;
- draft.primary_bin.price_currency = parsed.currency;
- draft.primary_bin.price_per_amount = parsed.per_amount;
- draft.primary_bin.price_per_unit = parsed.per_unit;
- write_listing_draft(&args.file, &draft, true)?;
-
- let summary = summarize_draft(&draft);
- Ok(SellDraftMutationView {
- state: "updated".to_owned(),
- operation: "reprice".to_owned(),
- source: LISTING_SOURCE.to_owned(),
- file: args.file.display().to_string(),
- product_key: summary.product_key,
- changed_label: "Price".to_owned(),
- changed_value: summary
- .price
- .unwrap_or_else(|| args.price_expr.trim().to_owned()),
- actions: vec![
- format!("radroots listing validate {}", args.file.display()),
- format!("radroots listing update {}", args.file.display()),
- ],
- })
-}
-
-pub fn sell_restock(
- _config: &RuntimeConfig,
- args: &SellRestockArgs,
-) -> Result<SellDraftMutationView, RuntimeError> {
- let mut draft = read_listing_draft(&args.file)?;
- parse_decimal_string(args.available.as_str(), "`sell restock <available>`")?;
- draft.inventory.available = args.available.trim().to_owned();
- write_listing_draft(&args.file, &draft, true)?;
-
- let summary = summarize_draft(&draft);
- Ok(SellDraftMutationView {
- state: "updated".to_owned(),
- operation: "restock".to_owned(),
- source: LISTING_SOURCE.to_owned(),
- file: args.file.display().to_string(),
- product_key: summary.product_key,
- changed_label: "Stock".to_owned(),
- changed_value: summary
- .stock
- .unwrap_or_else(|| format!("{} available", args.available.trim())),
- actions: vec![
- format!("radroots listing validate {}", args.file.display()),
- format!("radroots listing update {}", args.file.display()),
- ],
- })
-}
-
-pub fn sell_check(
- config: &RuntimeConfig,
- args: &ListingFileArgs,
-) -> Result<SellCheckView, RuntimeError> {
- let view = validate(config, args)?;
- let summary = read_listing_draft(&args.file)
- .ok()
- .map(|draft| summarize_draft(&draft));
- let actions = if view.valid {
- vec![format!("radroots listing publish {}", args.file.display())]
- } else {
- vec![
- format!("edit {}", args.file.display()),
- format!("radroots listing validate {}", args.file.display()),
- "Edit the draft file and run the command again".to_owned(),
- ]
- };
-
- Ok(SellCheckView {
- state: if view.valid {
- "ready".to_owned()
- } else {
- "invalid".to_owned()
- },
- source: view.source,
- file: view.file,
- valid: view.valid,
- product_key: summary
- .as_ref()
- .and_then(|summary| summary.product_key.clone()),
- seller_pubkey: view.seller_pubkey,
- farm_ref: view.farm_d_tag,
- issues: view.issues,
- actions,
- })
-}
-
-pub fn sell_publish(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<SellMutationView, RuntimeError> {
- let view = publish(config, args)?;
- Ok(sell_mutation_from_listing(
- view,
- args.file.as_path(),
- "publish",
- ))
-}
-
-pub fn sell_update(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<SellMutationView, RuntimeError> {
- let view = update(config, args)?;
- Ok(sell_mutation_from_listing(
- view,
- args.file.as_path(),
- "update",
- ))
-}
-
-pub fn sell_pause(
- config: &RuntimeConfig,
- args: &ListingMutationArgs,
-) -> Result<SellMutationView, RuntimeError> {
- let view = archive(config, args)?;
- Ok(sell_mutation_from_listing(
- view,
- args.file.as_path(),
- "pause",
- ))
-}
-
fn build_listing_draft(
config: &RuntimeConfig,
- args: &ListingNewArgs,
+ args: &ListingCreateArgs,
) -> Result<(ListingDraftDocument, ListingAuthoringDefaults), RuntimeError> {
let defaults = authoring_defaults(config)?;
let quantity_unit = args.quantity_unit.clone().unwrap_or_else(|| "g".to_owned());
@@ -509,52 +280,6 @@ fn build_listing_draft(
Ok((draft, defaults))
}
-fn listing_args_from_sell_add(args: &SellAddArgs) -> Result<ListingNewArgs, RuntimeError> {
- let product_key = slugify_ascii(args.product.as_str());
- if product_key.is_empty() {
- return Err(RuntimeError::Config(
- "`sell add <product>` requires at least one ASCII letter or digit".to_owned(),
- ));
- }
-
- let title = args
- .title
- .clone()
- .unwrap_or_else(|| title_case_ascii(args.product.as_str()));
- let category = args.category.clone().unwrap_or_else(|| title.clone());
- let pack = args.pack.as_deref().map(parse_quantity_expr).transpose()?;
- let price = args
- .price_expr
- .as_deref()
- .map(parse_price_expr)
- .transpose()?;
- let output = Some(match &args.file {
- Some(path) => path.clone(),
- None => std::path::PathBuf::from(format!("listing-{product_key}.toml")),
- });
-
- Ok(ListingNewArgs {
- output,
- key: Some(product_key),
- title: Some(title.clone()),
- category: Some(category),
- summary: Some(
- args.summary
- .clone()
- .unwrap_or_else(|| format!("Listing for {title}")),
- ),
- bin_id: None,
- quantity_amount: pack.as_ref().map(|pack| pack.amount.clone()),
- quantity_unit: pack.as_ref().map(|pack| pack.unit.clone()),
- price_amount: price.as_ref().map(|price| price.amount.clone()),
- price_currency: price.as_ref().map(|price| price.currency.clone()),
- price_per_amount: price.as_ref().map(|price| price.per_amount.clone()),
- price_per_unit: price.as_ref().map(|price| price.per_unit.clone()),
- available: args.stock.clone(),
- label: pack.as_ref().map(|pack| pack.label.clone()),
- })
-}
-
fn default_listing_output_path(
explicit: Option<&std::path::PathBuf>,
listing_id: &str,
@@ -583,268 +308,6 @@ fn write_listing_draft(
Ok(())
}
-fn read_listing_draft(path: &Path) -> Result<ListingDraftDocument, RuntimeError> {
- let contents = fs::read_to_string(path)?;
- toml::from_str::<ListingDraftDocument>(&contents).map_err(|error| {
- RuntimeError::Config(format!(
- "failed to parse listing draft {}: {error}",
- path.display()
- ))
- })
-}
-
-fn successful_sell_mutation_actions(operation: &str, product_key: Option<&str>) -> Vec<String> {
- match operation {
- "publish" => {
- let mut actions = Vec::new();
- if let Some(product_key) = product_key {
- actions.push(format!("radroots market listing get {product_key}"));
- actions.push(format!("radroots listing create --key {product_key}"));
- }
- actions
- }
- "update" => product_key
- .map(|product_key| vec![format!("radroots market listing get {product_key}")])
- .unwrap_or_default(),
- "pause" => product_key
- .map(|product_key| vec![format!("radroots listing create --key {product_key}")])
- .unwrap_or_default(),
- _ => Vec::new(),
- }
-}
-
-fn summarize_draft(draft: &ListingDraftDocument) -> DraftSummary {
- DraftSummary {
- product_key: non_empty(draft.product.key.clone()),
- title: non_empty(draft.product.title.clone()),
- category: non_empty(draft.product.category.clone()),
- offer: draft_offer_text(draft),
- price: draft_price_text(draft),
- stock: draft_stock_text(draft),
- delivery_method: non_empty(draft.delivery.method.clone()),
- location_primary: non_empty(draft.location.primary.clone()),
- }
-}
-
-fn sell_mutation_from_listing(
- view: ListingMutationView,
- file: &Path,
- operation: &str,
-) -> SellMutationView {
- let summary = read_listing_draft(file)
- .ok()
- .map(|draft| summarize_draft(&draft));
- let product_key = summary
- .as_ref()
- .and_then(|summary| summary.product_key.clone());
- let actions = match view.state.as_str() {
- "published" | "deduplicated" => {
- successful_sell_mutation_actions(operation, product_key.as_deref())
- }
- _ => view.actions,
- };
-
- SellMutationView {
- state: view.state,
- operation: operation.to_owned(),
- source: view.source,
- file: view.file,
- product_key,
- listing_addr: view.listing_addr,
- dry_run: view.dry_run,
- deduplicated: view.deduplicated,
- publish_mode: Some("runtime_bridge".to_owned()),
- job_id: view.job_id,
- job_status: view.job_status,
- event_id: view.event_id,
- reason: view.reason,
- actions,
- }
-}
-
-fn draft_offer_text(draft: &ListingDraftDocument) -> Option<String> {
- non_empty(draft.primary_bin.label.clone()).or_else(|| {
- let amount = draft.primary_bin.quantity_amount.trim();
- let unit = draft.primary_bin.quantity_unit.trim();
- if amount.is_empty() || unit.is_empty() {
- None
- } else {
- Some(format!("{} {}", trim_decimal_string(amount), unit))
- }
- })
-}
-
-fn draft_price_text(draft: &ListingDraftDocument) -> Option<String> {
- let amount = non_empty(draft.primary_bin.price_amount.clone())?;
- let currency = non_empty(draft.primary_bin.price_currency.clone())?;
- let per_amount = non_empty(draft.primary_bin.price_per_amount.clone())?;
- let per_unit = non_empty(draft.primary_bin.price_per_unit.clone())?;
- let denominator = if per_unit == "each"
- && numeric_strings_equal(
- per_amount.as_str(),
- draft.primary_bin.quantity_amount.trim(),
- )
- && !draft.primary_bin.label.trim().is_empty()
- {
- draft.primary_bin.label.trim().to_owned()
- } else if per_amount == "1" {
- per_unit.to_owned()
- } else {
- format!("{} {}", trim_decimal_string(&per_amount), per_unit)
- };
- Some(format!(
- "{} {}/{}",
- trim_decimal_string(&amount),
- currency.to_ascii_uppercase(),
- denominator
- ))
-}
-
-fn draft_stock_text(draft: &ListingDraftDocument) -> Option<String> {
- non_empty(draft.inventory.available.clone())
- .map(|available| format!("{} available", trim_decimal_string(&available)))
-}
-
-fn parse_quantity_expr(expr: &str) -> Result<ParsedQuantityExpr, RuntimeError> {
- let trimmed = expr.trim();
- if trimmed.is_empty() {
- return Err(RuntimeError::Config(
- "quantity expression must not be empty".to_owned(),
- ));
- }
- if trimmed.eq_ignore_ascii_case("dozen") {
- return Ok(ParsedQuantityExpr {
- amount: "12".to_owned(),
- unit: "each".to_owned(),
- label: "dozen".to_owned(),
- });
- }
-
- let parts = trimmed.split_whitespace().collect::<Vec<_>>();
- if parts.is_empty() {
- return Err(RuntimeError::Config(
- "quantity expression must not be empty".to_owned(),
- ));
- }
-
- let (amount, unit) = if parse_decimal_string(parts[0], "quantity amount").is_ok() {
- let Some(unit) = parts.get(1) else {
- return Err(RuntimeError::Config(
- "quantity expression must include a unit, for example `1 kg`".to_owned(),
- ));
- };
- (parts[0].trim().to_owned(), unit.trim().to_ascii_lowercase())
- } else {
- ("1".to_owned(), parts[0].trim().to_ascii_lowercase())
- };
-
- unit.parse::<RadrootsCoreUnit>().map_err(|_| {
- RuntimeError::Config(format!(
- "quantity expression uses unsupported unit `{unit}`"
- ))
- })?;
-
- Ok(ParsedQuantityExpr {
- amount,
- unit,
- label: trimmed.to_owned(),
- })
-}
-
-fn parse_price_expr(expr: &str) -> Result<ParsedPriceExpr, RuntimeError> {
- let trimmed = expr.trim();
- if trimmed.is_empty() {
- return Err(RuntimeError::Config(
- "price expression must not be empty".to_owned(),
- ));
- }
-
- let segments = trimmed.split_whitespace().collect::<Vec<_>>();
- if segments.len() < 2 {
- return Err(RuntimeError::Config(
- "price expression must look like `10 USD/kg`".to_owned(),
- ));
- }
-
- parse_decimal_string(segments[0], "price amount")?;
- let remainder = segments[1..].join(" ");
- let Some((currency, per_expr)) = remainder.split_once('/') else {
- return Err(RuntimeError::Config(
- "price expression must include a `/`, for example `10 USD/kg`".to_owned(),
- ));
- };
- let per = parse_quantity_expr(per_expr)?;
- RadrootsCoreCurrency::from_str_upper(currency.trim().to_ascii_uppercase().as_str()).map_err(
- |_| {
- RuntimeError::Config(format!(
- "price expression uses unsupported currency `{}`",
- currency.trim()
- ))
- },
- )?;
-
- Ok(ParsedPriceExpr {
- amount: segments[0].trim().to_owned(),
- currency: currency.trim().to_ascii_uppercase(),
- per_amount: per.amount,
- per_unit: per.unit,
- })
-}
-
-fn parse_decimal_string(value: &str, label: &str) -> Result<RadrootsCoreDecimal, RuntimeError> {
- value
- .trim()
- .parse::<RadrootsCoreDecimal>()
- .map_err(|_| RuntimeError::Config(format!("{label} must be a valid decimal value")))
-}
-
-fn slugify_ascii(value: &str) -> String {
- let mut slug = String::new();
- let mut last_was_dash = false;
- for ch in value.chars() {
- if ch.is_ascii_alphanumeric() {
- slug.push(ch.to_ascii_lowercase());
- last_was_dash = false;
- } else if !slug.is_empty() && !last_was_dash {
- slug.push('-');
- last_was_dash = true;
- }
- }
- slug.trim_matches('-').to_owned()
-}
-
-fn title_case_ascii(value: &str) -> String {
- value
- .split(|ch: char| !ch.is_ascii_alphanumeric())
- .filter(|segment| !segment.is_empty())
- .map(capitalize_ascii_word)
- .collect::<Vec<_>>()
- .join(" ")
-}
-
-fn capitalize_ascii_word(word: &str) -> String {
- let mut chars = word.chars();
- let Some(first) = chars.next() else {
- return String::new();
- };
- let mut rendered = String::new();
- rendered.push(first.to_ascii_uppercase());
- rendered.push_str(chars.as_str());
- rendered
-}
-
-fn numeric_strings_equal(lhs: &str, rhs: &str) -> bool {
- trim_decimal_string(lhs) == trim_decimal_string(rhs)
-}
-
-fn trim_decimal_string(value: &str) -> String {
- if let Ok(parsed) = value.trim().parse::<RadrootsCoreDecimal>() {
- parsed.to_string()
- } else {
- value.trim().to_owned()
- }
-}
-
pub fn validate(
config: &RuntimeConfig,
args: &ListingFileArgs,
@@ -930,7 +393,10 @@ pub fn validate(
}
}
-pub fn get(config: &RuntimeConfig, args: &RecordKeyArgs) -> Result<ListingGetView, RuntimeError> {
+pub fn get(
+ config: &RuntimeConfig,
+ args: &RecordLookupArgs,
+) -> Result<ListingGetView, RuntimeError> {
let freshness = if config.local.replica_db_path.exists() {
let executor = SqliteExecutor::open(&config.local.replica_db_path)?;
freshness_from_executor(&executor)?
diff --git a/src/runtime/local.rs b/src/runtime/local.rs
@@ -8,13 +8,13 @@ use radroots_replica_sync::radroots_replica_sync_status;
use radroots_sql_core::SqliteExecutor;
use serde_json::json;
-use crate::cli::LocalExportFormatArg;
use crate::domain::runtime::{
LocalBackupView, LocalExportView, LocalInitView, LocalReplicaCountsView, LocalReplicaSyncView,
LocalStatusView,
};
use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
+use crate::runtime_args::LocalExportFormatArg;
const LOCAL_SOURCE: &str = "local replica · local first";
diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs
@@ -1,5 +1,3 @@
-#![allow(dead_code)]
-
pub mod accounts;
pub mod config;
pub mod daemon;
@@ -8,7 +6,6 @@ pub mod farm;
pub mod farm_config;
pub mod find;
pub mod hyf;
-pub mod job;
pub mod listing;
pub mod local;
pub mod logging;
@@ -20,7 +17,6 @@ pub mod paths;
pub mod provider;
pub mod signer;
pub mod sync;
-pub mod workflow;
use std::process::ExitCode;
diff --git a/src/runtime/network.rs b/src/runtime/network.rs
@@ -1,6 +1,4 @@
-use crate::domain::runtime::{NetStatusView, RelayEntryView, RelayListView};
-use crate::runtime::RuntimeError;
-use crate::runtime::accounts;
+use crate::domain::runtime::{RelayEntryView, RelayListView};
use crate::runtime::config::RuntimeConfig;
pub fn relay_list(config: &RuntimeConfig) -> RelayListView {
@@ -35,33 +33,6 @@ pub fn relay_list(config: &RuntimeConfig) -> RelayListView {
}
}
-pub fn net_status(config: &RuntimeConfig) -> Result<NetStatusView, RuntimeError> {
- let account_resolution = accounts::resolve_account_resolution(config)?;
- let relay_count = config.relay.urls.len();
- let configured = relay_count > 0;
-
- Ok(NetStatusView {
- state: if configured {
- "configured".to_owned()
- } else {
- "unconfigured".to_owned()
- },
- source: config.relay.source.as_str().to_owned(),
- session: if configured {
- "not_started".to_owned()
- } else {
- "not_configured".to_owned()
- },
- relay_count,
- publish_policy: config.relay.publish_policy.as_str().to_owned(),
- signer_mode: config.signer.backend.as_str().to_owned(),
- account_resolution: accounts::account_resolution_view(&account_resolution),
- reason: (!configured)
- .then_some("no relays are configured for this operator session".to_owned()),
- actions: relay_actions(config),
- })
-}
-
fn relay_actions(config: &RuntimeConfig) -> Vec<String> {
if config.relay.urls.is_empty() {
vec!["radroots --relay wss://relay.example.com relay list".to_owned()]
diff --git a/src/runtime/order.rs b/src/runtime/order.rs
@@ -23,7 +23,6 @@ use rhi::rhi::{Rhi, start_subscriber};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
-use crate::cli::{OrderNewArgs, OrderSubmitArgs, OrderWatchArgs, RecordKeyArgs};
use crate::domain::runtime::{
OrderCancelView, OrderDraftItemView, OrderGetView, OrderHistoryEntryView, OrderHistoryView,
OrderIssueView, OrderJobView, OrderListView, OrderNewView, OrderSubmitView, OrderSummaryView,
@@ -36,6 +35,9 @@ use crate::runtime::config::{
};
use crate::runtime::daemon::{self, DaemonRpcError};
use crate::runtime::signer::{ActorWriteBindingError, resolve_actor_write_authority};
+use crate::runtime_args::{
+ OrderDraftCreateArgs, OrderSubmitArgs, OrderWatchArgs, RecordLookupArgs,
+};
const ORDER_DRAFT_KIND: &str = "order_draft_v1";
const ORDER_SOURCE: &str = "local order drafts · local first";
@@ -152,7 +154,10 @@ struct ResolvedOrderListing {
seller_pubkey: String,
}
-pub fn scaffold(config: &RuntimeConfig, args: &OrderNewArgs) -> Result<OrderNewView, RuntimeError> {
+pub fn scaffold(
+ config: &RuntimeConfig,
+ args: &OrderDraftCreateArgs,
+) -> Result<OrderNewView, RuntimeError> {
validate_scaffold_args(args)?;
let listing_lookup = normalize_optional(args.listing.as_deref());
@@ -226,7 +231,7 @@ pub fn scaffold(config: &RuntimeConfig, args: &OrderNewArgs) -> Result<OrderNewV
Ok(view)
}
-pub fn get(config: &RuntimeConfig, args: &RecordKeyArgs) -> Result<OrderGetView, RuntimeError> {
+pub fn get(config: &RuntimeConfig, args: &RecordLookupArgs) -> Result<OrderGetView, RuntimeError> {
let lookup = args.key.clone();
let file = draft_lookup_path(config, lookup.as_str());
if !file.exists() {
@@ -821,7 +826,7 @@ pub fn history(config: &RuntimeConfig) -> Result<OrderHistoryView, RuntimeError>
pub fn cancel(
config: &RuntimeConfig,
- args: &RecordKeyArgs,
+ args: &RecordLookupArgs,
) -> Result<OrderCancelView, RuntimeError> {
let file = draft_lookup_path(config, args.key.as_str());
if !file.exists() {
@@ -887,7 +892,7 @@ pub fn cancel(
})
}
-fn validate_scaffold_args(args: &OrderNewArgs) -> Result<(), RuntimeError> {
+fn validate_scaffold_args(args: &OrderDraftCreateArgs) -> Result<(), RuntimeError> {
match (normalize_optional(args.bin_id.as_deref()), args.bin_count) {
(None, Some(_)) => Err(RuntimeError::Config(
"`--qty` requires `--bin` when creating an order draft".to_owned(),
diff --git a/src/runtime/signer.rs b/src/runtime/signer.rs
@@ -107,27 +107,6 @@ pub fn resolve_actor_write_authority(
}))
}
-pub fn configured_myc_signer_authority(
- config: &RuntimeConfig,
-) -> Option<ActorWriteSignerAuthority> {
- let binding = config.capability_binding(SIGNER_REMOTE_NIP46_CAPABILITY)?;
- if binding.provider_runtime_id != SIGNER_BINDING_PROVIDER_RUNTIME_ID {
- return None;
- }
- if !matches!(
- binding.target_kind,
- CapabilityBindingTargetKind::ManagedInstance
- ) || binding.target != "default"
- {
- return None;
- }
- Some(ActorWriteSignerAuthority {
- provider_runtime_id: SIGNER_BINDING_PROVIDER_RUNTIME_ID.to_owned(),
- account_identity_id: binding.managed_account_ref.clone()?,
- provider_signer_session_id: binding.signer_session_ref.clone(),
- })
-}
-
fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView {
let (account_resolution, resolved_account_id) =
match crate::runtime::accounts::resolve_account_resolution(config) {
diff --git a/src/runtime/sync.rs b/src/runtime/sync.rs
@@ -5,13 +5,13 @@ use radroots_replica_db::ReplicaSql;
use radroots_replica_sync::radroots_replica_sync_status;
use radroots_sql_core::SqliteExecutor;
-use crate::cli::SyncWatchArgs;
use crate::domain::runtime::{
SyncActionView, SyncFreshnessView, SyncQueueView, SyncStatusView, SyncWatchFrameView,
SyncWatchView,
};
use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
+use crate::runtime_args::SyncWatchArgs;
const SYNC_SOURCE: &str = "local replica · local first";
const RELAY_SETUP_ACTION: &str = "radroots --relay wss://relay.example.com relay list";
diff --git a/src/runtime/workflow.rs b/src/runtime/workflow.rs
@@ -1,300 +0,0 @@
-use crate::cli::{FarmScopedArgs, SetupRoleArg};
-use crate::domain::runtime::{SetupView, StatusView};
-use crate::runtime::RuntimeError;
-use crate::runtime::accounts::{self};
-use crate::runtime::config::RuntimeConfig;
-use crate::runtime::{farm, local};
-
-const WORKFLOW_SOURCE: &str = "workflow summary · local first";
-const RELAY_SETUP_ACTION: &str = "radroots relay list --relay wss://relay.example.com";
-
-pub fn setup(config: &RuntimeConfig, role: SetupRoleArg) -> Result<SetupView, RuntimeError> {
- let account_resolution = accounts::resolve_account_resolution(config)?;
- let local_status = ensure_local_status(config)?;
- let farm = inspect_farm(config)?;
- let relay_configured = relay_configured(config);
- let relay_count = config.relay.urls.len();
-
- let mut state = "saved";
- let mut ready = Vec::new();
- let mut needs_attention = Vec::new();
- let mut next = Vec::new();
-
- if account_resolution.resolved_account.is_some() {
- ready.push("Resolved account".to_owned());
- } else {
- state = "unconfigured";
- needs_attention.push("Resolved account".to_owned());
- push_unresolved_account_actions(config, &mut next, Some(role))?;
- }
-
- if local_status.state == "ready" {
- ready.push("Local market data".to_owned());
- } else {
- state = "unconfigured";
- needs_attention.push("Local market data".to_owned());
- }
-
- if relay_configured {
- ready.push("Relay configuration".to_owned());
- } else {
- needs_attention.push("Relay configuration".to_owned());
- }
-
- if account_resolution.resolved_account.is_some() {
- match role {
- SetupRoleArg::Seller | SetupRoleArg::Both => {
- apply_farm_attention(&mut ready, &mut needs_attention, &mut next, &farm);
- push_next(&mut next, farm.primary_next_action.as_deref());
- }
- SetupRoleArg::Buyer => {}
- }
-
- match role {
- SetupRoleArg::Buyer | SetupRoleArg::Both if relay_configured => {
- push_next(&mut next, Some("radroots market product search tomatoes"));
- }
- _ => {}
- }
-
- if !relay_configured {
- push_next(&mut next, Some(RELAY_SETUP_ACTION));
- }
-
- push_next(&mut next, Some("radroots health status get"));
- }
-
- Ok(SetupView {
- state: state.to_owned(),
- source: WORKFLOW_SOURCE.to_owned(),
- role: role_name(role).to_owned(),
- account_resolution: accounts::account_resolution_view(&account_resolution),
- local_state: local_status.state,
- local_root: local_status.local_root,
- relay_state: relay_state(config).to_owned(),
- relay_count,
- farm_state: farm.state.to_owned(),
- ready,
- needs_attention,
- next,
- })
-}
-
-pub fn status(config: &RuntimeConfig) -> Result<StatusView, RuntimeError> {
- let account_resolution = accounts::resolve_account_resolution(config)?;
- let local_status = local::status(config)?;
- let farm = inspect_farm(config)?;
- let relay_configured = relay_configured(config);
- let relay_count = config.relay.urls.len();
-
- let mut ready = Vec::new();
- let mut needs_attention = Vec::new();
- let mut next = Vec::new();
- let mut state = "ready";
-
- if account_resolution.resolved_account.is_some() {
- ready.push("Resolved account".to_owned());
- } else {
- state = "unconfigured";
- needs_attention.push("Resolved account".to_owned());
- push_unresolved_account_actions(config, &mut next, None)?;
- }
-
- if local_status.state == "ready" {
- ready.push("Local market data".to_owned());
- } else {
- state = "unconfigured";
- needs_attention.push("Local market data".to_owned());
- }
-
- if relay_configured {
- ready.push("Relay configuration".to_owned());
- } else {
- state = "unconfigured";
- needs_attention.push("Relay configuration".to_owned());
- }
-
- if state == "ready" {
- apply_farm_attention(&mut ready, &mut needs_attention, &mut next, &farm);
-
- if relay_configured {
- match farm.state {
- "draft" | "published" => {
- push_next(&mut next, Some("radroots listing create --key tomatoes"))
- }
- "missing" => push_next(&mut next, Some("radroots market product search tomatoes")),
- _ => {}
- }
- }
- } else if account_resolution.resolved_account.is_some() {
- push_next(&mut next, Some("radroots basket create"));
- push_next(&mut next, Some("radroots farm create"));
- if account_resolution.resolved_account.is_some()
- && local_status.state == "ready"
- && !relay_configured
- {
- next.clear();
- push_next(&mut next, Some(RELAY_SETUP_ACTION));
- push_next(&mut next, Some("radroots health status get"));
- }
- }
-
- Ok(StatusView {
- state: state.to_owned(),
- source: WORKFLOW_SOURCE.to_owned(),
- account_resolution: accounts::account_resolution_view(&account_resolution),
- local_state: local_status.state,
- local_root: local_status.local_root,
- relay_state: relay_state(config).to_owned(),
- relay_count,
- farm_state: farm.state.to_owned(),
- ready,
- needs_attention,
- next,
- })
-}
-
-fn ensure_local_status(
- config: &RuntimeConfig,
-) -> Result<crate::domain::runtime::LocalStatusView, RuntimeError> {
- let _ = local::init(config)?;
- local::status(config)
-}
-
-#[derive(Debug, Clone)]
-struct FarmWorkflowState {
- state: &'static str,
- primary_next_action: Option<String>,
-}
-
-fn inspect_farm(config: &RuntimeConfig) -> Result<FarmWorkflowState, RuntimeError> {
- let view = farm::status(config, &FarmScopedArgs::default())?;
- if !view.config_present {
- return Ok(FarmWorkflowState {
- state: "missing",
- primary_next_action: view.actions.into_iter().next(),
- });
- }
-
- if view.account_state != "ready" {
- return Ok(FarmWorkflowState {
- state: "account_missing",
- primary_next_action: view.actions.into_iter().next(),
- });
- }
-
- let Some(config_summary) = view.config else {
- return Ok(FarmWorkflowState {
- state: "missing",
- primary_next_action: view.actions.into_iter().next(),
- });
- };
-
- let published = config_summary.publication.profile_state == "published"
- && config_summary.publication.farm_state == "published";
-
- Ok(FarmWorkflowState {
- state: if published { "published" } else { "draft" },
- primary_next_action: (!published).then(|| "radroots farm publish".to_owned()),
- })
-}
-
-fn apply_farm_attention(
- ready: &mut Vec<String>,
- needs_attention: &mut Vec<String>,
- next: &mut Vec<String>,
- farm: &FarmWorkflowState,
-) {
- match farm.state {
- "missing" => {
- needs_attention.push("Farm draft".to_owned());
- }
- "draft" => {
- needs_attention.push("Farm not yet published".to_owned());
- push_next(next, Some("radroots farm publish"));
- }
- "published" => {
- ready.push("Farm published".to_owned());
- }
- "account_missing" => {
- needs_attention.push("Farm draft account not available locally".to_owned());
- }
- _ => {}
- }
-}
-
-fn relay_configured(config: &RuntimeConfig) -> bool {
- !config.relay.urls.is_empty()
-}
-
-fn relay_state(config: &RuntimeConfig) -> &'static str {
- if relay_configured(config) {
- "configured"
- } else {
- "unconfigured"
- }
-}
-
-fn role_name(role: SetupRoleArg) -> &'static str {
- match role {
- SetupRoleArg::Seller => "seller",
- SetupRoleArg::Buyer => "buyer",
- SetupRoleArg::Both => "both",
- }
-}
-
-fn push_unresolved_account_actions(
- config: &RuntimeConfig,
- next: &mut Vec<String>,
- setup_role: Option<SetupRoleArg>,
-) -> Result<(), RuntimeError> {
- match unresolved_account_resolution_state(config)? {
- UnresolvedAccountResolutionState::NoAccounts => {
- push_next(next, Some("radroots account create"));
- }
- UnresolvedAccountResolutionState::AccountsExistWithoutDefault => {
- push_next(next, Some("radroots account list"));
- push_next(next, Some("radroots account select <selector>"));
- }
- }
-
- if let Some(role) = setup_role {
- push_next(next, Some(setup_command(role)));
- }
-
- Ok(())
-}
-
-fn unresolved_account_resolution_state(
- config: &RuntimeConfig,
-) -> Result<UnresolvedAccountResolutionState, RuntimeError> {
- let snapshot = accounts::snapshot(config)?;
- Ok(if snapshot.accounts.is_empty() {
- UnresolvedAccountResolutionState::NoAccounts
- } else {
- UnresolvedAccountResolutionState::AccountsExistWithoutDefault
- })
-}
-
-fn setup_command(role: SetupRoleArg) -> &'static str {
- match role {
- SetupRoleArg::Seller => "radroots farm create",
- SetupRoleArg::Buyer => "radroots basket create",
- SetupRoleArg::Both => "radroots workspace init",
- }
-}
-
-fn push_next(next: &mut Vec<String>, command: Option<&str>) {
- let Some(command) = command else {
- return;
- };
- if !next.iter().any(|existing| existing == command) {
- next.push(command.to_owned());
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum UnresolvedAccountResolutionState {
- NoAccounts,
- AccountsExistWithoutDefault,
-}
diff --git a/src/runtime_args.rs b/src/runtime_args.rs
@@ -0,0 +1,192 @@
+use std::path::PathBuf;
+
+use crate::runtime::config::OutputFormat;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum RuntimeOutputFormatArg {
+ Human,
+ Json,
+ Ndjson,
+}
+
+impl RuntimeOutputFormatArg {
+ pub fn as_output_format(self) -> OutputFormat {
+ match self {
+ Self::Human => OutputFormat::Human,
+ Self::Json => OutputFormat::Json,
+ Self::Ndjson => OutputFormat::Ndjson,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct RuntimeInvocationArgs {
+ pub output_format: Option<RuntimeOutputFormatArg>,
+ pub json: bool,
+ pub ndjson: bool,
+ pub env_file: Option<PathBuf>,
+ pub quiet: bool,
+ pub verbose: bool,
+ pub trace: bool,
+ pub dry_run: bool,
+ pub no_color: bool,
+ pub no_input: bool,
+ pub yes: bool,
+ pub log_filter: Option<String>,
+ pub log_dir: Option<PathBuf>,
+ pub log_stdout: bool,
+ pub no_log_stdout: bool,
+ pub account: Option<String>,
+ pub identity_path: Option<PathBuf>,
+ pub signer: Option<String>,
+ pub relay: Vec<String>,
+ pub myc_executable: Option<PathBuf>,
+ pub myc_status_timeout_ms: Option<u64>,
+ pub hyf_enabled: bool,
+ pub no_hyf_enabled: bool,
+ pub hyf_executable: Option<PathBuf>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum LocalExportFormatArg {
+ Json,
+ Ndjson,
+}
+
+impl LocalExportFormatArg {
+ pub fn as_str(self) -> &'static str {
+ match self {
+ Self::Json => "json",
+ Self::Ndjson => "ndjson",
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct SyncWatchArgs {
+ pub frames: usize,
+ pub interval_ms: u64,
+}
+
+#[derive(Debug, Clone)]
+pub struct FindQueryArgs {
+ pub query: Vec<String>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FarmScopeArg {
+ User,
+ Workspace,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FarmFieldArg {
+ Name,
+ DisplayName,
+ About,
+ Website,
+ Picture,
+ Banner,
+ Location,
+ City,
+ Region,
+ Country,
+ Delivery,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct FarmScopedArgs {
+ pub scope: Option<FarmScopeArg>,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct FarmCreateArgs {
+ pub scope: Option<FarmScopeArg>,
+ pub farm_d_tag: Option<String>,
+ pub name: Option<String>,
+ pub display_name: Option<String>,
+ pub about: Option<String>,
+ pub website: Option<String>,
+ pub picture: Option<String>,
+ pub banner: Option<String>,
+ pub location: Option<String>,
+ pub city: Option<String>,
+ pub region: Option<String>,
+ pub country: Option<String>,
+ pub delivery_method: Option<String>,
+}
+
+#[derive(Debug, Clone)]
+pub struct FarmUpdateArgs {
+ pub scope: Option<FarmScopeArg>,
+ pub field: FarmFieldArg,
+ pub value: Vec<String>,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct FarmPublishArgs {
+ pub scope: Option<FarmScopeArg>,
+ pub idempotency_key: Option<String>,
+ pub signer_session_id: Option<String>,
+ pub print_job: bool,
+ pub print_event: bool,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct ListingCreateArgs {
+ pub output: Option<PathBuf>,
+ pub key: Option<String>,
+ pub title: Option<String>,
+ pub category: Option<String>,
+ pub summary: Option<String>,
+ pub bin_id: Option<String>,
+ pub quantity_amount: Option<String>,
+ pub quantity_unit: Option<String>,
+ pub price_amount: Option<String>,
+ pub price_currency: Option<String>,
+ pub price_per_amount: Option<String>,
+ pub price_per_unit: Option<String>,
+ pub available: Option<String>,
+ pub label: Option<String>,
+}
+
+#[derive(Debug, Clone)]
+pub struct ListingFileArgs {
+ pub file: PathBuf,
+}
+
+#[derive(Debug, Clone)]
+pub struct ListingMutationArgs {
+ pub file: PathBuf,
+ pub idempotency_key: Option<String>,
+ pub signer_session_id: Option<String>,
+ pub print_job: bool,
+ pub print_event: bool,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct OrderDraftCreateArgs {
+ pub listing: Option<String>,
+ pub listing_addr: Option<String>,
+ pub bin_id: Option<String>,
+ pub bin_count: Option<u32>,
+}
+
+#[derive(Debug, Clone)]
+pub struct OrderSubmitArgs {
+ pub key: String,
+ pub idempotency_key: Option<String>,
+ pub signer_session_id: Option<String>,
+}
+
+#[derive(Debug, Clone)]
+pub struct OrderWatchArgs {
+ pub key: String,
+ pub frames: Option<usize>,
+ pub interval_ms: u64,
+}
+
+#[derive(Debug, Clone)]
+pub struct RecordLookupArgs {
+ pub key: String,
+}