main: replace giant match block with Run trait dispatch

Each subcommand enum (Command, NodeCmd, JournalCmd, GraphCmd,
CursorCmd, DaemonCmd, AgentCmd, AdminCmd) now implements a Run
trait. main() becomes `cli.command.run()`.

Standalone dispatch functions (cmd_cursor, cmd_daemon,
cmd_experience_mine) inlined into their enum's Run impl.
No functional changes.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
ProofOfConcept 2026-03-26 18:00:23 -04:00
parent 3e410347a2
commit 41fcec58f0

View file

@ -767,6 +767,203 @@ fn print_help() {
} }
} }
// ── Dispatch ─────────────────────────────────────────────────────────
trait Run {
fn run(self) -> Result<(), String>;
}
impl Run for Command {
fn run(self) -> Result<(), String> {
match self {
Self::Search { query, pipeline, expand, full, debug, fuzzy, content }
=> cli::misc::cmd_search(&query, &pipeline, expand, full, debug, fuzzy, content),
Self::Render { key } => cli::node::cmd_render(&key),
Self::Write { key } => cli::node::cmd_write(&key),
Self::Edit { key } => cli::node::cmd_edit(&key),
Self::History { full, key } => cli::node::cmd_history(&key, full),
Self::Tail { n, full } => cli::journal::cmd_tail(n, full),
Self::Status => cli::misc::cmd_status(),
Self::Query { expr } => cli::misc::cmd_query(&expr),
Self::Used { key } => cli::node::cmd_used(&key),
Self::Wrong { key, context } => cli::node::cmd_wrong(&key, &context),
Self::NotRelevant { key } => cli::node::cmd_not_relevant(&key),
Self::NotUseful { key } => cli::node::cmd_not_useful(&key),
Self::WeightSet { key, weight } => cli::node::cmd_weight_set(&key, weight),
Self::Gap { description } => cli::node::cmd_gap(&description),
Self::Node(sub) => sub.run(),
Self::Journal(sub) => sub.run(),
Self::GraphCmd(sub) => sub.run(),
Self::Cursor(sub) => sub.run(),
Self::Agent(sub) => sub.run(),
Self::Admin(sub) => sub.run(),
}
}
}
impl Run for NodeCmd {
fn run(self) -> Result<(), String> {
match self {
Self::Delete { key } => cli::node::cmd_node_delete(&key),
Self::Rename { old_key, new_key } => cli::node::cmd_node_rename(&old_key, &new_key),
Self::List { pattern } => cli::node::cmd_list_keys(pattern.as_deref()),
Self::Edges => cli::node::cmd_list_edges(),
Self::Dump => cli::node::cmd_dump_json(),
}
}
}
impl Run for JournalCmd {
fn run(self) -> Result<(), String> {
match self {
Self::Write { text } => cli::journal::cmd_journal_write(&text),
Self::Tail { n, full, level } => cli::journal::cmd_journal_tail(n, full, level),
Self::Enrich { jsonl_path, entry_text, grep_line }
=> cli::agent::cmd_journal_enrich(&jsonl_path, &entry_text, grep_line),
}
}
}
impl Run for GraphCmd {
fn run(self) -> Result<(), String> {
match self {
Self::Link { key } => cli::graph::cmd_link(&key),
Self::LinkAdd { source, target, reason }
=> cli::graph::cmd_link_add(&source, &target, &reason),
Self::LinkSet { source, target, strength }
=> cli::graph::cmd_link_set(&source, &target, strength),
Self::LinkImpact { source, target } => cli::graph::cmd_link_impact(&source, &target),
Self::LinkAudit { apply } => cli::graph::cmd_link_audit(apply),
Self::LinkOrphans { min_degree, links_per, sim_threshold }
=> cli::graph::cmd_link_orphans(min_degree, links_per, sim_threshold),
Self::TriangleClose { min_degree, sim_threshold, max_per_hub }
=> cli::graph::cmd_triangle_close(min_degree, sim_threshold, max_per_hub),
Self::CapDegree { max_degree } => cli::graph::cmd_cap_degree(max_degree),
Self::NormalizeStrengths { apply } => cli::graph::cmd_normalize_strengths(apply),
Self::Differentiate { key, apply } => cli::graph::cmd_differentiate(key.as_deref(), apply),
Self::Trace { key } => cli::graph::cmd_trace(&key),
Self::Interference { threshold } => cli::graph::cmd_interference(threshold),
Self::Communities { top_n, min_size } => cli::graph::cmd_communities(top_n, min_size),
Self::Overview => cli::graph::cmd_graph(),
Self::Spectral { k } => cli::graph::cmd_spectral(k),
Self::SpectralSave { k } => cli::graph::cmd_spectral_save(k),
Self::SpectralNeighbors { key, n } => cli::graph::cmd_spectral_neighbors(&key, n),
Self::SpectralPositions { n } => cli::graph::cmd_spectral_positions(n),
Self::SpectralSuggest { n } => cli::graph::cmd_spectral_suggest(n),
Self::Organize { term, threshold, key_only, anchor }
=> cli::graph::cmd_organize(&term, threshold, key_only, anchor),
}
}
}
impl Run for CursorCmd {
fn run(self) -> Result<(), String> {
match self {
Self::Show => {
let store = store::Store::load()?;
cursor::show(&store)
}
Self::Set { key } => {
if key.is_empty() { return Err("cursor set requires a key".into()); }
let key = key.join(" ");
let store = store::Store::load()?;
let bare = store::strip_md_suffix(&key);
if !store.nodes.contains_key(&bare) {
return Err(format!("Node not found: {}", bare));
}
cursor::set(&bare)?;
cursor::show(&store)
}
Self::Forward => { let s = store::Store::load()?; cursor::move_temporal(&s, true) }
Self::Back => { let s = store::Store::load()?; cursor::move_temporal(&s, false) }
Self::Up => { let s = store::Store::load()?; cursor::move_up(&s) }
Self::Down => { let s = store::Store::load()?; cursor::move_down(&s) }
Self::Clear => cursor::clear(),
}
}
}
impl Run for DaemonCmd {
fn run(self) -> Result<(), String> {
match self {
Self::Start => daemon::run_daemon(),
Self::Status => daemon::show_status(),
Self::Log { job, task, lines } => {
if let Some(ref task_name) = task {
daemon::show_task_log(task_name, lines)
} else {
daemon::show_log(job.as_deref(), lines)
}
}
Self::Install => daemon::install_service(),
Self::Consolidate => daemon::rpc_consolidate(),
Self::Run { agent, count } => daemon::rpc_run_agent(&agent, count),
Self::Tui => tui::run_tui(),
Self::ReloadConfig => {
match daemon::send_rpc_pub("reload-config") {
Some(resp) => { eprintln!("{}", resp.trim()); Ok(()) }
None => Err("daemon not running".into()),
}
}
}
}
}
impl Run for AgentCmd {
fn run(self) -> Result<(), String> {
match self {
Self::Daemon(sub) => sub.run(),
Self::KnowledgeLoop { max_cycles, batch_size, window, max_depth }
=> cli::agent::cmd_knowledge_loop(max_cycles, batch_size, window, max_depth),
Self::ConsolidateBatch { count, auto, agent }
=> cli::agent::cmd_consolidate_batch(count, auto, agent),
Self::ConsolidateSession => cli::agent::cmd_consolidate_session(),
Self::ConsolidateFull => cli::agent::cmd_consolidate_full(),
Self::ApplyAgent { all } => cmd_apply_agent(all),
Self::ApplyConsolidation { apply, report }
=> cli::agent::cmd_apply_consolidation(apply, report.as_deref()),
Self::Digest { level } => cmd_digest(level),
Self::DigestLinks { apply } => cli::agent::cmd_digest_links(apply),
Self::ExperienceMine { .. }
=> Err("experience-mine has been removed — use the observation agent instead.".into()),
Self::FactMine { path, batch, dry_run, output, min_messages }
=> cli::agent::cmd_fact_mine(&path, batch, dry_run, output.as_deref(), min_messages),
Self::FactMineStore { path } => cli::agent::cmd_fact_mine_store(&path),
Self::Run { agent, count, target, query, dry_run, local, state_dir }
=> cli::agent::cmd_run_agent(&agent, count, &target, query.as_deref(), dry_run, local, state_dir.as_deref()),
Self::ReplayQueue { count } => cli::agent::cmd_replay_queue(count),
Self::Evaluate { matchups, model, dry_run }
=> cli::agent::cmd_evaluate_agents(matchups, &model, dry_run),
}
}
}
impl Run for AdminCmd {
fn run(self) -> Result<(), String> {
match self {
Self::Init => cli::admin::cmd_init(),
Self::Health => cli::admin::cmd_health(),
Self::Fsck => cli::admin::cmd_fsck(),
Self::Dedup { apply } => cli::admin::cmd_dedup(apply),
Self::BulkRename { from, to, apply } => cli::admin::cmd_bulk_rename(&from, &to, apply),
Self::DailyCheck => cli::admin::cmd_daily_check(),
Self::Import { files } => cli::admin::cmd_import(&files),
Self::Export { files, all } => cli::admin::cmd_export(&files, all),
Self::LoadContext { stats } => cli::misc::cmd_load_context(stats),
Self::Log => cli::misc::cmd_log(),
Self::Params => cli::misc::cmd_params(),
Self::LookupBump { keys } => cli::node::cmd_lookup_bump(&keys),
Self::Lookups { date } => cli::node::cmd_lookups(date.as_deref()),
Self::MigrateTranscriptProgress => {
let mut store = store::Store::load()?;
let count = store.migrate_transcript_progress()?;
println!("Migrated {} transcript segment markers", count);
Ok(())
}
}
}
}
fn main() { fn main() {
// Handle --help ourselves for expanded subcommand display // Handle --help ourselves for expanded subcommand display
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
@ -777,126 +974,7 @@ fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let result = match cli.command { if let Err(e) = cli.command.run() {
// Core
Command::Search { query, pipeline, expand, full, debug, fuzzy, content }
=> cli::misc::cmd_search(&query, &pipeline, expand, full, debug, fuzzy, content),
Command::Render { key } => cli::node::cmd_render(&key),
Command::Write { key } => cli::node::cmd_write(&key),
Command::Edit { key } => cli::node::cmd_edit(&key),
Command::History { full, key } => cli::node::cmd_history(&key, full),
Command::Tail { n, full } => cli::journal::cmd_tail(n, full),
Command::Status => cli::misc::cmd_status(),
Command::Query { expr } => cli::misc::cmd_query(&expr),
Command::Used { key } => cli::node::cmd_used(&key),
Command::Wrong { key, context } => cli::node::cmd_wrong(&key, &context),
Command::NotRelevant { key } => cli::node::cmd_not_relevant(&key),
Command::NotUseful { key } => cli::node::cmd_not_useful(&key),
Command::WeightSet { key, weight } => cli::node::cmd_weight_set(&key, weight),
Command::Gap { description } => cli::node::cmd_gap(&description),
// Node
Command::Node(sub) => match sub {
NodeCmd::Delete { key } => cli::node::cmd_node_delete(&key),
NodeCmd::Rename { old_key, new_key } => cli::node::cmd_node_rename(&old_key, &new_key),
NodeCmd::List { pattern } => cli::node::cmd_list_keys(pattern.as_deref()),
NodeCmd::Edges => cli::node::cmd_list_edges(),
NodeCmd::Dump => cli::node::cmd_dump_json(),
},
// Journal
Command::Journal(sub) => match sub {
JournalCmd::Write { text } => cli::journal::cmd_journal_write(&text),
JournalCmd::Tail { n, full, level } => cli::journal::cmd_journal_tail(n, full, level),
JournalCmd::Enrich { jsonl_path, entry_text, grep_line }
=> cli::agent::cmd_journal_enrich(&jsonl_path, &entry_text, grep_line),
},
// Graph
Command::GraphCmd(sub) => match sub {
GraphCmd::Link { key } => cli::graph::cmd_link(&key),
GraphCmd::LinkAdd { source, target, reason }
=> cli::graph::cmd_link_add(&source, &target, &reason),
GraphCmd::LinkSet { source, target, strength }
=> cli::graph::cmd_link_set(&source, &target, strength),
GraphCmd::LinkImpact { source, target }
=> cli::graph::cmd_link_impact(&source, &target),
GraphCmd::LinkAudit { apply } => cli::graph::cmd_link_audit(apply),
GraphCmd::LinkOrphans { min_degree, links_per, sim_threshold }
=> cli::graph::cmd_link_orphans(min_degree, links_per, sim_threshold),
GraphCmd::TriangleClose { min_degree, sim_threshold, max_per_hub }
=> cli::graph::cmd_triangle_close(min_degree, sim_threshold, max_per_hub),
GraphCmd::CapDegree { max_degree } => cli::graph::cmd_cap_degree(max_degree),
GraphCmd::NormalizeStrengths { apply } => cli::graph::cmd_normalize_strengths(apply),
GraphCmd::Differentiate { key, apply }
=> cli::graph::cmd_differentiate(key.as_deref(), apply),
GraphCmd::Trace { key } => cli::graph::cmd_trace(&key),
GraphCmd::Interference { threshold } => cli::graph::cmd_interference(threshold),
GraphCmd::Communities { top_n, min_size } => cli::graph::cmd_communities(top_n, min_size),
GraphCmd::Overview => cli::graph::cmd_graph(),
GraphCmd::Spectral { k } => cli::graph::cmd_spectral(k),
GraphCmd::SpectralSave { k } => cli::graph::cmd_spectral_save(k),
GraphCmd::SpectralNeighbors { key, n }
=> cli::graph::cmd_spectral_neighbors(&key, n),
GraphCmd::SpectralPositions { n } => cli::graph::cmd_spectral_positions(n),
GraphCmd::SpectralSuggest { n } => cli::graph::cmd_spectral_suggest(n),
GraphCmd::Organize { term, threshold, key_only, anchor }
=> cli::graph::cmd_organize(&term, threshold, key_only, anchor),
},
// Cursor
Command::Cursor(sub) => cmd_cursor(sub),
// Agent
Command::Agent(sub) => match sub {
AgentCmd::Daemon(sub) => cmd_daemon(sub),
AgentCmd::KnowledgeLoop { max_cycles, batch_size, window, max_depth }
=> cli::agent::cmd_knowledge_loop(max_cycles, batch_size, window, max_depth),
AgentCmd::ConsolidateBatch { count, auto, agent }
=> cli::agent::cmd_consolidate_batch(count, auto, agent),
AgentCmd::ConsolidateSession => cli::agent::cmd_consolidate_session(),
AgentCmd::ConsolidateFull => cli::agent::cmd_consolidate_full(),
AgentCmd::ApplyAgent { all } => cmd_apply_agent(all),
AgentCmd::ApplyConsolidation { apply, report }
=> cli::agent::cmd_apply_consolidation(apply, report.as_deref()),
AgentCmd::Digest { level } => cmd_digest(level),
AgentCmd::DigestLinks { apply } => cli::agent::cmd_digest_links(apply),
AgentCmd::ExperienceMine { jsonl_path } => cmd_experience_mine(jsonl_path),
AgentCmd::FactMine { path, batch, dry_run, output, min_messages }
=> cli::agent::cmd_fact_mine(&path, batch, dry_run, output.as_deref(), min_messages),
AgentCmd::FactMineStore { path } => cli::agent::cmd_fact_mine_store(&path),
AgentCmd::Run { agent, count, target, query, dry_run, local, state_dir }
=> cli::agent::cmd_run_agent(&agent, count, &target, query.as_deref(), dry_run, local, state_dir.as_deref()),
AgentCmd::ReplayQueue { count } => cli::agent::cmd_replay_queue(count),
AgentCmd::Evaluate { matchups, model, dry_run }
=> cli::agent::cmd_evaluate_agents(matchups, &model, dry_run),
},
// Admin
Command::Admin(sub) => match sub {
AdminCmd::Init => cli::admin::cmd_init(),
AdminCmd::Health => cli::admin::cmd_health(),
AdminCmd::Fsck => cli::admin::cmd_fsck(),
AdminCmd::Dedup { apply } => cli::admin::cmd_dedup(apply),
AdminCmd::BulkRename { from, to, apply } => cli::admin::cmd_bulk_rename(&from, &to, apply),
AdminCmd::DailyCheck => cli::admin::cmd_daily_check(),
AdminCmd::Import { files } => cli::admin::cmd_import(&files),
AdminCmd::Export { files, all } => cli::admin::cmd_export(&files, all),
AdminCmd::LoadContext { stats } => cli::misc::cmd_load_context(stats),
AdminCmd::Log => cli::misc::cmd_log(),
AdminCmd::Params => cli::misc::cmd_params(),
AdminCmd::LookupBump { keys } => cli::node::cmd_lookup_bump(&keys),
AdminCmd::Lookups { date } => cli::node::cmd_lookups(date.as_deref()),
AdminCmd::MigrateTranscriptProgress => (|| -> Result<(), String> {
let mut store = store::Store::load()?;
let count = store.migrate_transcript_progress()?;
println!("Migrated {} transcript segment markers", count);
Ok(())
})()
},
};
if let Err(e) = result {
eprintln!("Error: {}", e); eprintln!("Error: {}", e);
process::exit(1); process::exit(1);
} }
@ -1065,70 +1143,3 @@ fn cmd_digest(level: DigestLevel) -> Result<(), String> {
} }
} }
fn cmd_experience_mine(_jsonl_path: Option<String>) -> Result<(), String> {
Err("experience-mine has been removed — use the observation agent instead.".into())
}
fn cmd_daemon(sub: DaemonCmd) -> Result<(), String> {
match sub {
DaemonCmd::Start => daemon::run_daemon(),
DaemonCmd::Status => daemon::show_status(),
DaemonCmd::Log { job, task, lines } => {
if let Some(ref task_name) = task {
daemon::show_task_log(task_name, lines)
} else {
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(),
DaemonCmd::ReloadConfig => {
match daemon::send_rpc_pub("reload-config") {
Some(resp) => { eprintln!("{}", resp.trim()); Ok(()) }
None => Err("daemon not running".into()),
}
}
}
}
fn cmd_cursor(sub: CursorCmd) -> Result<(), String> {
match sub {
CursorCmd::Show => {
let store = crate::store::Store::load()?;
cursor::show(&store)
}
CursorCmd::Set { key } => {
if key.is_empty() {
return Err("cursor set requires a key".into());
}
let key = key.join(" ");
let store = crate::store::Store::load()?;
let bare = crate::store::strip_md_suffix(&key);
if !store.nodes.contains_key(&bare) {
return Err(format!("Node not found: {}", bare));
}
cursor::set(&bare)?;
cursor::show(&store)
}
CursorCmd::Forward => {
let store = crate::store::Store::load()?;
cursor::move_temporal(&store, true)
}
CursorCmd::Back => {
let store = crate::store::Store::load()?;
cursor::move_temporal(&store, false)
}
CursorCmd::Up => {
let store = crate::store::Store::load()?;
cursor::move_up(&store)
}
CursorCmd::Down => {
let store = crate::store::Store::load()?;
cursor::move_down(&store)
}
CursorCmd::Clear => cursor::clear(),
}
}