use crate::api::schema::{EmptyParams, Method, Request, ServerLiveHandoffParams}; pub(super) fn run_server_command(args: &[String]) -> std::io::Result> { let Some(subcommand) = args.first().map(|arg| arg.as_str()) else { return Ok(None); }; match subcommand { "stop" => server_stop(&args[1..]).map(Some), "live-handoff" => server_live_handoff(&args[1..]).map(Some), "--handoff-import" => Ok(None), "reload-config" => server_reload_config(&args[1..]).map(Some), "help" | "--help" | "-h" => { print_server_help(); Ok(Some(0)) } _ => { print_server_help(); Ok(Some(2)) } } } fn server_stop(args: &[String]) -> std::io::Result { if !args.is_empty() { eprintln!("usage: herdr server stop"); return Ok(2); } super::send_ok_request(Method::ServerStop(EmptyParams::default())) } fn server_reload_config(args: &[String]) -> std::io::Result { if !args.is_empty() { eprintln!("usage: herdr server reload-config"); return Ok(2); } super::print_response(&super::send_request(&Request { id: "cli:server:reload-config".into(), method: Method::ServerReloadConfig(EmptyParams::default()), })?) } fn server_live_handoff(args: &[String]) -> std::io::Result { let Some(params) = parse_live_handoff_params(args) else { eprintln!( "usage: herdr server live-handoff [--import-exe ] [--expected-protocol ] [--expected-version ]" ); return Ok(2); }; let response = super::send_request(&Request { id: "cli:server:live-handoff".into(), method: Method::ServerLiveHandoff(params), })?; if response.get("error").is_some() { let rendered = serde_json::to_string(&response).unwrap_or_else(|err| { format!( "{{\"error\":{{\"code\":\"render_failed\",\"message\":\"failed to render error response: {err}\"}}}}" ) }); eprintln!("{rendered}"); return Ok(1); } eprintln!( "live handoff complete; server log: {}", crate::session::data_dir() .join("herdr-server.log") .display() ); Ok(0) } fn parse_live_handoff_params(args: &[String]) -> Option { let mut params = ServerLiveHandoffParams::default(); let mut idx = 0; while idx < args.len() { let arg = &args[idx]; let (flag, value) = if let Some((flag, value)) = arg.split_once('=') { (flag, Some(value.to_string())) } else { let value = args.get(idx + 1).cloned(); idx += 1; (arg.as_str(), value) }; let value = value?; match flag { "--import-exe" => params.import_exe = Some(value), "--expected-protocol" => { params.expected_protocol = Some(value.parse().ok()?); } "--expected-version" => params.expected_version = Some(value), _ => return None, } idx += 1; } Some(params) } fn print_server_help() { eprintln!("herdr server commands:"); eprintln!(" herdr server run as headless server"); eprintln!(" herdr server stop stop the running server via the API socket"); eprintln!(" herdr server live-handoff hand off live panes to a new local server"); eprintln!(" herdr server reload-config reload config.toml in the running server"); } #[cfg(test)] mod tests { use super::*; #[test] fn live_handoff_params_parse_remote_update_fields() { let args = vec![ "--import-exe".to_string(), "/home/me/.local/bin/herdr".to_string(), "--expected-protocol=9".to_string(), "--expected-version".to_string(), "0.6.2".to_string(), ]; let params = parse_live_handoff_params(&args).expect("params"); assert_eq!( params.import_exe.as_deref(), Some("/home/me/.local/bin/herdr") ); assert_eq!(params.expected_protocol, Some(9)); assert_eq!(params.expected_version.as_deref(), Some("0.6.2")); } }