feat(cli): command dispatch, human/--json rendering, status table, completions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 22:17:53 +07:00
parent a9fa1bf77b
commit 3c3a8039f3
+85 -1
View File
@@ -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}");
}
}
}
}