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`.
This commit is contained in:
ProofOfConcept 2026-03-13 20:07:15 -04:00
parent bcf13c564a
commit c22a7a72e1

View file

@ -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<String>,
/// 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<String>,
/// Additional arguments
args: Vec<String>,
},
#[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 <command> [<args>]\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<String> = 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::<usize>() {
(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(),
}
}