//! PoM CLI entry point, parses subcommands and dispatches to handlers or MCP server. use clap::{Parser, Subcommand}; use rmcp::ServiceExt; use tokio::io::{stdin, stdout}; use tracing::info; use tracing_subscriber::{EnvFilter, fmt, prelude::*}; use pom::config::{self, Config}; use pom::db; use pom::error::Result; use pom::tools::PomServer; mod cli; #[derive(Parser)] #[command( name = "pom", version, about = "Peace of Mind: health checks and test orchestration" )] struct Cli { /// Path to config file (default: ~/.config/pom/pom.toml) #[arg(long, global = true)] config: Option, #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { /// Check health of targets Health { /// Target name (omit for all) target: Option, /// Output as JSON #[arg(long)] json: bool, }, /// Run tests on a target via SSH Test { /// Target name target: String, /// Filter tests #[arg(long, short)] filter: Option, /// Output as JSON #[arg(long)] json: bool, }, /// Show status dashboard Status { /// Output as JSON #[arg(long)] json: bool, }, /// Show what version each target is running, and how far behind Versions { /// Output as JSON #[arg(long)] json: bool, }, /// View history History { #[command(subcommand)] kind: cli::HistoryKind, }, /// Prune old records Prune { /// Number of days to keep (default 30) #[arg(long, default_value = "30")] days: i64, }, /// Run DNS and WHOIS checks Dns { /// Target name (omit for all) target: Option, /// Output as JSON #[arg(long)] json: bool, }, /// Run as a daemon, checking health at intervals Serve, /// Show peer mesh status Mesh { /// Output as JSON #[arg(long)] json: bool, }, } #[tokio::main] async fn main() -> Result<()> { // Install the pure-Rust ring crypto provider before any TLS operation. It // verifies signatures for every certificate pom validates: reqwest (built // rustls-no-provider) and the TLS-expiry check both read the process default // via CryptoProvider::get_default. aws-lc-rs is kept out of the dependency // tree entirely (see Cargo.toml), so its C/asm build dep and the aws-lc-sys // advisory surface never reach a build host or the cert-verification path. pom::tls::install_crypto_provider(); let cli = Cli::parse(); let config_path = cli.config.as_deref(); let config = Config::load(config_path)?; match cli.command { None => run_mcp_server(config).await, Some(cmd) => run_cli(cmd, config).await, } } async fn run_mcp_server(config: Config) -> Result<()> { tracing_subscriber::registry() .with(fmt::layer().with_writer(std::io::stderr)) .with(EnvFilter::from_default_env().add_directive("pom=info".parse()?)) .init(); info!("Starting PoM MCP server"); let db_path = config::db_path()?; let pool = db::connect(&db_path).await?; info!("Database ready at {}", db_path.display()); let server = PomServer::new(pool, config); let transport = (stdin(), stdout()); info!("MCP server ready"); let service = server.serve(transport).await?; let quit_reason = service.waiting().await?; info!(?quit_reason, "MCP server shutting down"); Ok(()) } async fn run_cli(cmd: Commands, config: Config) -> Result<()> { let log_level = if matches!(cmd, Commands::Serve) { "pom=info" } else { "pom=warn" }; tracing_subscriber::registry() .with(fmt::layer().with_writer(std::io::stderr)) .with(EnvFilter::from_default_env().add_directive(log_level.parse()?)) .init(); let db_path = config::db_path()?; let pool = db::connect(&db_path).await?; match cmd { Commands::Health { target, json } => { cli::cmd_health(&pool, &config, target.as_deref(), json).await } Commands::Test { target, filter, json, } => cli::cmd_test(&pool, &config, &target, filter.as_deref(), json).await, Commands::Status { json } => cli::cmd_status(&pool, &config, json).await, Commands::Versions { json } => cli::cmd_versions(&pool, &config, json).await, Commands::History { kind } => cli::cmd_history(&pool, kind).await, Commands::Prune { days } => cli::cmd_prune(&pool, days).await, Commands::Dns { target, json } => { cli::cmd_dns(&pool, &config, target.as_deref(), json).await } Commands::Serve => cli::cmd_serve(&pool, &config).await, Commands::Mesh { json } => cli::cmd_mesh(&config, json).await, } }