From c22a7a72e100aae7dabcca54a46f4ad2ffdf3ca0 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Fri, 13 Mar 2026 20:07:15 -0400 Subject: [PATCH] cli: proper clap subcommands for daemon + expanded help Convert daemon from hand-rolled string dispatch to proper clap Subcommand enum with typed args. Add custom top-level help that expands nested subcommands (same pattern as bcachefs-tools), so `poc-memory --help` shows full paths like `agent daemon run`. --- poc-memory/src/main.rs | 120 +++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 33 deletions(-) diff --git a/poc-memory/src/main.rs b/poc-memory/src/main.rs index a35b2ca..0778ab2 100644 --- a/poc-memory/src/main.rs +++ b/poc-memory/src/main.rs @@ -408,15 +408,42 @@ enum GraphCmd { }, } +#[derive(Subcommand)] +enum DaemonCmd { + /// Start the daemon (default) + Start, + /// Show daemon status + Status, + /// Show daemon log + Log { + /// Job name to filter by + job: Option, + /// Number of lines to show + #[arg(long, default_value_t = 20)] + lines: usize, + }, + /// Install systemd service + Install, + /// Trigger consolidation via daemon + Consolidate, + /// Run an agent via the daemon + Run { + /// Agent name (e.g. organize, replay, linker) + #[arg(default_value = "replay")] + agent: String, + /// Batch size + #[arg(default_value_t = 1)] + count: usize, + }, + /// Interactive TUI + Tui, +} + #[derive(Subcommand)] enum AgentCmd { /// Background job daemon - Daemon { - /// Subcommand: status, log, install - sub: Option, - /// Additional arguments - args: Vec, - }, + #[command(subcommand)] + Daemon(DaemonCmd), /// Run knowledge agents to convergence #[command(name = "knowledge-loop")] KnowledgeLoop { @@ -596,7 +623,52 @@ enum DigestLevel { Auto, } +/// Print help with subcommands expanded to show nested commands. +fn print_help() { + use clap::CommandFactory; + let cmd = Cli::command(); + + println!("poc-memory - graph-structured memory store"); + println!("usage: poc-memory []\n"); + + for sub in cmd.get_subcommands() { + if sub.get_name() == "help" { continue } + let children: Vec<_> = sub.get_subcommands() + .filter(|c| c.get_name() != "help") + .collect(); + if !children.is_empty() { + for child in &children { + let about = child.get_about().map(|s| s.to_string()).unwrap_or_default(); + let full = format!("{} {}", sub.get_name(), child.get_name()); + // Recurse one more level for daemon subcommands etc. + let grandchildren: Vec<_> = child.get_subcommands() + .filter(|c| c.get_name() != "help") + .collect(); + if !grandchildren.is_empty() { + for gc in grandchildren { + let gc_about = gc.get_about().map(|s| s.to_string()).unwrap_or_default(); + let gc_full = format!("{} {}", full, gc.get_name()); + println!(" {:<34}{gc_about}", gc_full); + } + } else { + println!(" {:<34}{about}", full); + } + } + } else { + let about = sub.get_about().map(|s| s.to_string()).unwrap_or_default(); + println!(" {:<34}{about}", sub.get_name()); + } + } +} + fn main() { + // Handle --help ourselves for expanded subcommand display + let args: Vec = std::env::args().collect(); + if args.len() <= 1 || args.iter().any(|a| a == "--help" || a == "-h") && args.len() == 2 { + print_help(); + return; + } + let cli = Cli::parse(); let result = match cli.command { @@ -660,7 +732,7 @@ fn main() { // Agent Command::Agent(sub) => match sub { - AgentCmd::Daemon { sub, args } => cmd_daemon(sub.as_deref(), &args), + AgentCmd::Daemon(sub) => cmd_daemon(sub), AgentCmd::KnowledgeLoop { max_cycles, batch_size, window, max_depth } => cmd_knowledge_loop(max_cycles, batch_size, window, max_depth), AgentCmd::ConsolidateBatch { count, auto, agent } @@ -2681,33 +2753,15 @@ fn cmd_lookups(date: Option<&str>) -> Result<(), String> { Ok(()) } -fn cmd_daemon(sub: Option<&str>, args: &[String]) -> Result<(), String> { +fn cmd_daemon(sub: DaemonCmd) -> Result<(), String> { match sub { - None => daemon::run_daemon(), - Some("status") => daemon::show_status(), - Some("log") => { - let (job, lines) = match args.first() { - None => (None, 20), - Some(s) => { - if let Ok(n) = s.parse::() { - (None, n) - } else { - let n = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(20); - (Some(s.as_str()), n) - } - } - }; - daemon::show_log(job, lines) - } - Some("install") => daemon::install_service(), - Some("consolidate") => daemon::rpc_consolidate(), - Some("run-agent") | Some("run") => { - let agent = args.first().map(|s| s.as_str()).unwrap_or("replay"); - let count: usize = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(1); - daemon::rpc_run_agent(agent, count) - } - Some("tui") => tui::run_tui(), - Some(other) => Err(format!("unknown daemon subcommand: {}", other)), + DaemonCmd::Start => daemon::run_daemon(), + DaemonCmd::Status => daemon::show_status(), + DaemonCmd::Log { job, lines } => daemon::show_log(job.as_deref(), lines), + DaemonCmd::Install => daemon::install_service(), + DaemonCmd::Consolidate => daemon::rpc_consolidate(), + DaemonCmd::Run { agent, count } => daemon::rpc_run_agent(&agent, count), + DaemonCmd::Tui => tui::run_tui(), } }