//! 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::{fmt, prelude::*, EnvFilter}; use pom::config::{self, Config}; use pom::db; use pom::error::Result; use pom::tools::PomServer; mod cli; #[derive(Parser)] #[command(name = "pom", 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, }, /// 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 default rustls crypto provider before any TLS operations. // Both aws-lc-rs and ring are in the dependency tree (via reqwest and tokio-rustls), // so rustls can't auto-detect which to use. let _ = tokio_rustls::rustls::crypto::ring::default_provider().install_default(); 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::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, } }