diff --git a/p/src/cli.rs b/p/src/cli.rs index 9462785..c439e23 100644 --- a/p/src/cli.rs +++ b/p/src/cli.rs @@ -75,8 +75,12 @@ pub enum Command { name: Option, }, - /// List registered workers and their reachability status - Workers, + /// List registered workers + Workers { + /// Check reachability of each worker over SSH + #[arg(short, long)] + check: bool, + }, /// Set the default worker Default { diff --git a/p/src/commands/register.rs b/p/src/commands/register.rs index dc097f6..25472c6 100644 --- a/p/src/commands/register.rs +++ b/p/src/commands/register.rs @@ -1,6 +1,43 @@ use anyhow::Result; +use crate::{config, ssh}; + pub fn execute(connection: &str, name: Option<&str>) -> Result<()> { - let _ = (connection, name); - anyhow::bail!("not yet implemented") + let mut cfg = config::load()?; + + let name = match name { + Some(n) => n.to_string(), + None => ssh::hostname_from_connection(connection), + }; + + // Reject duplicates (same name or same connection string). + if cfg.get_worker(&name).is_some() { + anyhow::bail!( + "a worker named '{}' is already registered\n\ + Use 'p default {}' to make it the default, or choose a different name with -n", + name, name + ); + } + + let first = cfg.workers.is_empty(); + cfg.workers.push(config::WorkerConfig { + name: name.clone(), + connection: connection.to_string(), + }); + + // First registered worker becomes the default automatically. + if first { + cfg.default_worker = Some(name.clone()); + } + + config::save(&cfg)?; + + if first { + println!("Registered '{}' and set as default worker.", name); + } else { + println!("Registered '{}'.", name); + println!("Run 'p default {}' to make it the default.", name); + } + + Ok(()) } diff --git a/p/src/commands/set_default.rs b/p/src/commands/set_default.rs index bb49439..fb6b2d6 100644 --- a/p/src/commands/set_default.rs +++ b/p/src/commands/set_default.rs @@ -1,6 +1,25 @@ use anyhow::Result; +use crate::config; + pub fn execute(worker: &str) -> Result<()> { - let _ = worker; - anyhow::bail!("not yet implemented") + let mut cfg = config::load()?; + + if cfg.get_worker(worker).is_none() { + let known: Vec<&str> = cfg.workers.iter().map(|w| w.name.as_str()).collect(); + if known.is_empty() { + anyhow::bail!("no workers registered yet — run 'p register ' first"); + } + anyhow::bail!( + "unknown worker '{}'\n\nKnown workers: {}", + worker, + known.join(", ") + ); + } + + cfg.default_worker = Some(worker.to_string()); + config::save(&cfg)?; + + println!("Default worker set to '{}'.", worker); + Ok(()) } diff --git a/p/src/commands/workers.rs b/p/src/commands/workers.rs index 3a9aa7c..75866dd 100644 --- a/p/src/commands/workers.rs +++ b/p/src/commands/workers.rs @@ -1,5 +1,37 @@ use anyhow::Result; -pub fn execute() -> Result<()> { - anyhow::bail!("not yet implemented") +use crate::{config, ssh}; + +pub fn execute(check: bool) -> Result<()> { + let cfg = config::load()?; + + if cfg.workers.is_empty() { + println!("No workers registered. Run 'p register ' to add one."); + return Ok(()); + } + + let name_w = cfg.workers.iter().map(|w| w.name.len()).max().unwrap_or(0).max(4); + let conn_w = cfg.workers.iter().map(|w| w.connection.len()).max().unwrap_or(0).max(10); + + if check { + println!(" {: Result<()> { cli::Command::Register { connection, name } => { commands::register::execute(&connection, name.as_deref()) } - cli::Command::Workers => commands::workers::execute(), + cli::Command::Workers { check } => commands::workers::execute(check), cli::Command::Default { worker } => commands::set_default::execute(&worker), } } diff --git a/p/src/ssh.rs b/p/src/ssh.rs index 1bafc0a..ffced7f 100644 --- a/p/src/ssh.rs +++ b/p/src/ssh.rs @@ -32,6 +32,24 @@ pub fn hostname_from_connection(conn: &str) -> String { } } +/// Check whether a worker is reachable over SSH (5 s timeout, no auth prompts). +pub fn is_reachable(worker: &WorkerConfig) -> bool { + let mut args = vec![ + "-o".to_string(), "ConnectTimeout=5".to_string(), + "-o".to_string(), "BatchMode=yes".to_string(), + ]; + args.extend(ssh_args(worker)); + args.push("true".to_string()); + + std::process::Command::new("ssh") + .args(&args) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) +} + /// Build the base ssh argument list for a worker (handles custom port). pub fn ssh_args(worker: &WorkerConfig) -> Vec { let (user_host, port) = parse_connection(&worker.connection);