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#[derive(Parser, Debug, Serialize, JsonSchema)]
15#[command(name = "pd", about = "Palladium engine CLI", version)]
16#[serde(rename_all = "kebab-case")]
17pub struct Cli {
18 #[arg(long, global = true, default_value = "/tmp/palladium.sock")]
20 pub socket: PathBuf,
21
22 #[arg(long, global = true)]
24 pub endpoint: Option<String>,
25
26 #[arg(long, global = true)]
28 pub tls_cert: Option<PathBuf>,
29
30 #[arg(long, global = true)]
32 pub tls_key: Option<PathBuf>,
33
34 #[arg(long, global = true)]
36 pub tls_ca: Option<PathBuf>,
37
38 #[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(commands::start::StartArgs),
51 Stop(commands::stop::StopArgs),
53 Status(commands::status::StatusArgs),
55 #[command(subcommand)]
57 Actor(commands::actor::ActorCommand),
58 #[command(subcommand)]
60 Msg(commands::msg::MsgCommand),
61 Schema,
63 Doctor(commands::doctor::DoctorArgs),
65 Events(commands::events::EventsArgs),
67 #[command(subcommand)]
69 Plugin(commands::plugin::PluginCommand),
70 #[command(subcommand)]
72 Cluster(commands::cluster::ClusterCommand),
73 #[command(subcommand)]
75 Federation(commands::federation::FederationCommand),
76 #[command(subcommand)]
78 Consensus(commands::consensus::ConsensusCommand),
79}
80
81pub type CliResult = Result<(), Box<dyn std::error::Error>>;
82
83pub 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}