diff options
Diffstat (limited to 'src/cli')
| -rw-r--r-- | src/cli/server.rs | 87 | ||||
| -rw-r--r-- | src/cli/status.rs | 24 |
2 files changed, 108 insertions, 3 deletions
diff --git a/src/cli/server.rs b/src/cli/server.rs index 311cc31a..0102b2d4 100644 --- a/src/cli/server.rs +++ b/src/cli/server.rs @@ -1,4 +1,4 @@ -use crate::api::schema::{EmptyParams, Method, Request}; +use crate::api::schema::{EmptyParams, Method, Request, ServerLiveHandoffParams}; pub(super) fn run_server_command(args: &[String]) -> std::io::Result<Option<i32>> { let Some(subcommand) = args.first().map(|arg| arg.as_str()) else { @@ -7,6 +7,8 @@ pub(super) fn run_server_command(args: &[String]) -> std::io::Result<Option<i32> 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(); @@ -40,9 +42,92 @@ fn server_reload_config(args: &[String]) -> std::io::Result<i32> { })?) } +fn server_live_handoff(args: &[String]) -> std::io::Result<i32> { + let Some(params) = parse_live_handoff_params(args) else { + eprintln!( + "usage: herdr server live-handoff [--import-exe <path>] [--expected-protocol <n>] [--expected-version <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<ServerLiveHandoffParams> { + 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")); + } +} diff --git a/src/cli/status.rs b/src/cli/status.rs index 9b8a1648..0062367a 100644 --- a/src/cli/status.rs +++ b/src/cli/status.rs @@ -74,6 +74,7 @@ enum ServerRuntimeStatus { Running { version: Option<String>, protocol: Option<u32>, + capabilities: Option<crate::api::schema::ServerCapabilities>, }, NotRunning, } @@ -127,7 +128,9 @@ fn print_client_status(json: bool) -> std::io::Result<()> { fn print_server_status_body(server: &ServerRuntimeStatus, indent: &str) { match server { - ServerRuntimeStatus::Running { version, protocol } => { + ServerRuntimeStatus::Running { + version, protocol, .. + } => { println!("{indent}status: running"); println!("{indent}version: {}", option_label(version.as_deref())); println!("{indent}protocol: {}", protocol_label(*protocol)); @@ -146,6 +149,7 @@ fn read_server_runtime_status() -> std::io::Result<ServerRuntimeStatus> { Ok(status) => Ok(ServerRuntimeStatus::Running { version: status.version, protocol: status.protocol, + capabilities: status.capabilities, }), Err(ApiClientError::Io(err)) if server_not_running_error(&err) => { Ok(ServerRuntimeStatus::NotRunning) @@ -218,6 +222,7 @@ struct ServerStatusJson { running: bool, version: Option<String>, protocol: Option<u32>, + capabilities: Option<ServerCapabilitiesJson>, compatible: Option<bool>, socket: String, session: Option<String>, @@ -225,6 +230,11 @@ struct ServerStatusJson { } #[derive(Serialize)] +struct ServerCapabilitiesJson { + live_handoff: bool, +} + +#[derive(Serialize)] struct UpdateStatusJson { restart_needed: Option<bool>, } @@ -240,11 +250,20 @@ fn client_status_json() -> ClientStatusJson { fn server_status_json(server: &ServerRuntimeStatus) -> ServerStatusJson { match server { - ServerRuntimeStatus::Running { version, protocol } => ServerStatusJson { + ServerRuntimeStatus::Running { + version, + protocol, + capabilities, + } => ServerStatusJson { status: "running", running: true, version: version.clone(), protocol: *protocol, + capabilities: capabilities + .as_ref() + .map(|capabilities| ServerCapabilitiesJson { + live_handoff: capabilities.live_handoff, + }), compatible: protocol.map(|value| value == crate::protocol::PROTOCOL_VERSION), socket: api::socket_path().display().to_string(), session: crate::session::active_name(), @@ -255,6 +274,7 @@ fn server_status_json(server: &ServerRuntimeStatus) -> ServerStatusJson { running: false, version: None, protocol: None, + capabilities: None, compatible: None, socket: api::socket_path().display().to_string(), session: crate::session::active_name(), |
