diff --git a/crates/spacesh-cli/src/output.rs b/crates/spacesh-cli/src/output.rs index 10954b2..030c6ce 100644 --- a/crates/spacesh-cli/src/output.rs +++ b/crates/spacesh-cli/src/output.rs @@ -1 +1,85 @@ -pub async fn run(_c: crate::cli::Cli) -> i32 { 0 } +use clap::CommandFactory; +use serde_json::Value; +use crate::cli::{Cli, Sub}; +use crate::{client, mapping}; + +/// Entry point: returns the process exit code. +pub async fn run(cli: Cli) -> i32 { + // Completions are local — no daemon. + if let Sub::Completions { shell } = cli.cmd { + let mut cmd = Cli::command(); + clap_complete::generate(shell, &mut cmd, "spacesh", &mut std::io::stdout()); + return 0; + } + + // notify is best-effort: never fails the caller. + if let Sub::Notify { .. } = &cli.cmd { + let _ = client::notify(mapping::to_cmd(cli.cmd)).await; + return 0; + } + + let is_status = matches!(cli.cmd, Sub::Status); + let cmd = mapping::to_cmd(cli.cmd); + match client::request(cmd).await { + Ok(data) => { + if cli.json { + println!("{}", serde_json::to_string_pretty(&data).unwrap_or_else(|_| "null".into())); + } else if is_status { + print_status(&data); + } else { + print_human(&data); + } + 0 + } + Err(e) => { + if cli.json { + println!("{}", serde_json::json!({ "ok": false, "error": e.to_string() })); + } else { + eprintln!("{e}"); + } + 1 + } + } +} + +/// Human render for non-status commands: surface the salient id, else "ok". +fn print_human(data: &Value) { + if let Some(id) = data.get("workspace_id").and_then(|v| v.as_str()) { + println!("{id}"); + } else if let Some(id) = data.get("surface_id").and_then(|v| v.as_str()) { + println!("{id}"); + } else if let Some(id) = data.get("group_id").and_then(|v| v.as_str()) { + println!("{id}"); + } else if let Some(ids) = data.get("surface_ids").and_then(|v| v.as_array()) { + for id in ids { + if let Some(s) = id.as_str() { println!("{s}"); } + } + } else { + println!("ok"); + } +} + +/// Compact table for `status`. +fn print_status(data: &Value) { + let empty = vec![]; + let workspaces = data.get("workspaces").and_then(|v| v.as_array()).unwrap_or(&empty); + if workspaces.is_empty() { + println!("(no workspaces)"); + return; + } + for w in workspaces { + let name = w.get("name").and_then(|v| v.as_str()).unwrap_or("?"); + let id = w.get("id").and_then(|v| v.as_str()).unwrap_or("?"); + let unread = w.get("unread").and_then(|v| v.as_bool()).unwrap_or(false); + println!("{} ({}){}", name, id, if unread { " *" } else { "" }); + if let Some(surfaces) = w.get("surfaces").and_then(|v| v.as_object()) { + for (sid, sv) in surfaces { + let running = sv.get("running").and_then(|v| v.as_bool()).unwrap_or(false); + let state = sv.get("state").and_then(|v| v.as_str()).unwrap_or("idle"); + let agent = sv.get("spec").and_then(|s| s.get("agent_label")).and_then(|v| v.as_str()).unwrap_or("shell"); + let life = if running { "running" } else { "stopped" }; + println!(" {sid} {agent:<8} {life:<8} {state}"); + } + } + } +}