zinc-wallet-cli 0.4.0

Agent-first Bitcoin + Ordinals CLI wallet with account-based taproot ordinals + native segwit payment addresses (optional human mode)
use miette::Diagnostic;
use thiserror::Error;

#[derive(Debug, Error, Diagnostic)]
pub enum AppError {
    #[error("Invalid input: {0}")]
    #[diagnostic(
        code(zinc::invalid_input),
        help("Check your command arguments and try again.")
    )]
    Invalid(String),

    #[error("Configuration error: {0}")]
    #[diagnostic(
        code(zinc::config_error),
        help("Ensure your config file exists and is valid. Run 'zinc setup' to reconfigure.")
    )]
    Config(String),

    #[error("Internal error: {0}")]
    #[diagnostic(code(zinc::internal_error))]
    Internal(String),

    #[error("IO error: {0}")]
    #[diagnostic(code(zinc::io_error))]
    Io(String),

    #[error("Not found: {0}")]
    #[diagnostic(code(zinc::not_found))]
    NotFound(String),

    #[error("Auth error: {0}")]
    #[diagnostic(code(zinc::auth_error))]
    Auth(String),

    #[error("Network error: {0}")]
    #[diagnostic(code(zinc::network_error))]
    Network(String),

    #[error("Insufficient funds: {0}")]
    #[diagnostic(code(zinc::insufficient_funds))]
    InsufficientFunds(String),

    #[error("Policy error: {0}")]
    #[diagnostic(code(zinc::policy_error))]
    Policy(String),

    #[error("Capability error: {0}")]
    #[diagnostic(
        code(zinc::capability_error),
        help("This command requires a 'Seed' profile. Your current profile is 'Watch' only.")
    )]
    Capability(String),
}

impl AppError {
    pub fn tag(&self) -> &str {
        match self {
            Self::Invalid(_) => "invalid",
            Self::Config(_) | Self::Io(_) => "config",
            Self::NotFound(_) => "not_found",
            Self::Auth(_) => "auth",
            Self::Network(_) => "network",
            Self::InsufficientFunds(_) => "insufficient_funds",
            Self::Policy(_) => "policy",
            Self::Capability(_) => "capability",
            Self::Internal(_) => "internal",
        }
    }

    pub fn exit_code(&self) -> i32 {
        match self {
            Self::Invalid(_) => 2,
            Self::Config(_) | Self::Io(_) => 10,
            Self::NotFound(_) => 15,
            Self::Auth(_) => 11,
            Self::Network(_) => 12,
            Self::InsufficientFunds(_) => 13,
            Self::Policy(_) => 14,
            Self::Capability(_) => 16,
            Self::Internal(_) => 1,
        }
    }
}

impl From<std::io::Error> for AppError {
    fn from(err: std::io::Error) -> Self {
        Self::Io(err.to_string())
    }
}

impl From<String> for AppError {
    fn from(msg: String) -> Self {
        Self::Internal(msg)
    }
}