Skip to main content

palladium_cli/
lib.rs

1#![forbid(unsafe_code)]
2
3pub mod client;
4pub mod commands;
5pub mod output;
6
7use std::path::PathBuf;
8
9use clap::{Parser, Subcommand};
10use schemars::JsonSchema;
11use serde::Serialize;
12
13/// The `pd` CLI: control and inspect a running Palladium engine.
14#[derive(Parser, Debug, Serialize, JsonSchema)]
15#[command(name = "pd", about = "Palladium engine CLI", version)]
16#[serde(rename_all = "kebab-case")]
17pub struct Cli {
18    /// Unix socket path for the engine control plane.
19    #[arg(long, global = true, default_value = "/tmp/palladium.sock")]
20    pub socket: PathBuf,
21
22    /// Control plane endpoint (unix://, tcp://, quic://). Overrides --socket.
23    #[arg(long, global = true)]
24    pub endpoint: Option<String>,
25
26    /// mTLS client certificate (required for tcp/quic endpoints).
27    #[arg(long, global = true)]
28    pub tls_cert: Option<PathBuf>,
29
30    /// mTLS client private key (required for tcp/quic endpoints).
31    #[arg(long, global = true)]
32    pub tls_key: Option<PathBuf>,
33
34    /// mTLS CA certificate (optional for tcp/quic endpoints).
35    #[arg(long, global = true)]
36    pub tls_ca: Option<PathBuf>,
37
38    /// TLS SNI hostname override (optional for tcp/quic endpoints).
39    #[arg(long, global = true)]
40    pub tls_sni: Option<String>,
41
42    #[command(subcommand)]
43    pub command: Command,
44}
45
46#[derive(Subcommand, Debug, Serialize, JsonSchema)]
47#[serde(rename_all = "kebab-case")]
48pub enum Command {
49    /// Start the Palladium engine.
50    Start(commands::start::StartArgs),
51    /// Stop a running engine gracefully.
52    Stop(commands::stop::StopArgs),
53    /// Show engine status.
54    Status(commands::status::StatusArgs),
55    /// Manage actors.
56    #[command(subcommand)]
57    Actor(commands::actor::ActorCommand),
58    /// Send and inspect messages.
59    #[command(subcommand)]
60    Msg(commands::msg::MsgCommand),
61    /// Export the CLI schema as JSON.
62    Schema,
63    /// High-density system health snapshot.
64    Doctor(commands::doctor::DoctorArgs),
65    /// Stream system events.
66    Events(commands::events::EventsArgs),
67    /// Manage plugins.
68    #[command(subcommand)]
69    Plugin(commands::plugin::PluginCommand),
70    /// Manage clusters.
71    #[command(subcommand)]
72    Cluster(commands::cluster::ClusterCommand),
73    /// Manage federation policy.
74    #[command(subcommand)]
75    Federation(commands::federation::FederationCommand),
76    /// Manage consensus groups.
77    #[command(subcommand)]
78    Consensus(commands::consensus::ConsensusCommand),
79}
80
81pub type CliResult = Result<(), Box<dyn std::error::Error>>;
82
83/// Dispatch a parsed CLI invocation.
84pub fn run(cli: Cli) -> CliResult {
85    let endpoint = resolve_endpoint(&cli)?;
86    match cli.command {
87        Command::Start(args) => commands::start::run(&args, &cli.socket),
88        Command::Stop(args) => commands::stop::run(&args, &endpoint),
89        Command::Status(args) => commands::status::run(&args, &endpoint),
90        Command::Actor(cmd) => commands::actor::run(cmd, &endpoint),
91        Command::Msg(cmd) => commands::msg::run(cmd, &endpoint),
92        Command::Doctor(args) => commands::doctor::run(&args, &endpoint),
93        Command::Events(args) => commands::events::run(&args, &endpoint),
94        Command::Plugin(cmd) => commands::plugin::run(cmd, &endpoint),
95        Command::Federation(cmd) => commands::federation::run(cmd, &endpoint),
96        Command::Consensus(cmd) => commands::consensus::run(cmd, &endpoint),
97        Command::Schema => commands::schema::run(),
98        Command::Cluster(cmd) => commands::cluster::run(cmd, &endpoint),
99    }
100}
101
102fn resolve_endpoint(cli: &Cli) -> Result<client::Endpoint, client::CliError> {
103    if let Some(endpoint) = &cli.endpoint {
104        return parse_endpoint(endpoint, cli);
105    }
106    Ok(client::Endpoint::Unix(cli.socket.clone()))
107}
108
109fn parse_endpoint(endpoint: &str, cli: &Cli) -> Result<client::Endpoint, client::CliError> {
110    let (scheme, rest) = endpoint
111        .split_once("://")
112        .ok_or_else(|| client::CliError::Protocol("invalid endpoint format".to_string()))?;
113
114    match scheme {
115        "unix" => Ok(client::Endpoint::Unix(PathBuf::from(rest))),
116        "tcp" | "quic" => {
117            let (host, port_str) = rest.split_once(':').ok_or_else(|| {
118                client::CliError::Protocol("missing host:port in endpoint".to_string())
119            })?;
120            let port: u16 = port_str
121                .parse()
122                .map_err(|_| client::CliError::Protocol("invalid port in endpoint".to_string()))?;
123
124            let cert = cli.tls_cert.clone().ok_or_else(|| {
125                client::CliError::Protocol("missing --tls-cert for network endpoint".to_string())
126            })?;
127            let key = cli.tls_key.clone().ok_or_else(|| {
128                client::CliError::Protocol("missing --tls-key for network endpoint".to_string())
129            })?;
130
131            let tls = client::TlsClientConfig {
132                cert,
133                key,
134                ca: cli.tls_ca.clone(),
135                sni: cli.tls_sni.clone(),
136            };
137
138            if scheme == "tcp" {
139                Ok(client::Endpoint::Tcp {
140                    host: host.to_string(),
141                    port,
142                    tls,
143                })
144            } else {
145                Ok(client::Endpoint::Quic {
146                    host: host.to_string(),
147                    port,
148                    tls,
149                })
150            }
151        }
152        _ => Err(client::CliError::Protocol(
153            "unsupported endpoint scheme".to_string(),
154        )),
155    }
156}