Something went wrong. Try again.
Personal fork of prime agent. Mirror of: https://mnemosyne.sigilzero.dev/sigilzero/prime-agent
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634//! Command specs and help formatting.
use crate::config::APP_NAME;
#[derive(Debug, Clone)]pub struct CommandSpec { pub path: &'static [&'static str], pub usage: &'static str, pub summary: &'static str, pub description: Option<&'static str>, pub options: &'static [&'static str], pub examples: &'static [&'static str],}
impl CommandSpec { const fn new( path: &'static [&'static str], usage: &'static str, summary: &'static str, ) -> Self { CommandSpec { path, usage, summary, description: None, options: &[], examples: &[], } }
const fn description(mut self, description: &'static str) -> Self { self.description = Some(description); self }
const fn options(mut self, options: &'static [&'static str]) -> Self { self.options = options; self }
const fn examples(mut self, examples: &'static [&'static str]) -> Self { self.examples = examples; self }}
/// All public command specs, in registry order.pub const COMMAND_SPECS: &[CommandSpec] = &[ CommandSpec::new(&["help"], "help [command]", "Show command help"), CommandSpec::new(&["agents"], "agents", "Search and open sessions"), CommandSpec::new( &["list"], "list [--all] [--json]", "List agents", ) .options(&[ "-a, --all Include saved agents", "--json Print JSON", ]), CommandSpec::new( &["sessions"], "sessions [--all] [--json]", "Show agent status, activity, and usage", ) .options(&[ "-a, --all Include saved agents", "--json Print JSON", ]), CommandSpec::new( &["attach"], "attach <agent>", "Attach the interactive UI to an agent", ), CommandSpec::new(&["stop"], "stop <agent> [--json]", "Stop an agent"), CommandSpec::new( &["rename"], "rename <agent> <name> [--json]", "Rename an agent", ), CommandSpec::new( &["send"], "send [--from <agent>] <agent> <message>", "Send a message to an agent", ) .options(&[ "--from <agent> Identify the sending agent", "--steer Deliver as steering when the agent is busy", "--follow-up Queue the message after the current turn", "--json Print JSON", ]), CommandSpec::new( &["schedule"], "schedule <list|add|cancel>", "Manage prompts that run later or on a recurring schedule", ), CommandSpec::new( &["schedule", "list"], "schedule list [--all] [agent] [--json]", "List scheduled prompts", ), CommandSpec::new( &["schedule", "add"], "schedule add <agent> <schedule> -- <message>", "Schedule a prompt", ) .description("The schedule may be a cron expression or a supported one-time schedule.") .examples(&["schedule add worker \"0 9 * * 1-5\" -- \"Check open work\""]), CommandSpec::new( &["schedule", "cancel"], "schedule cancel <job-id>", "Cancel a scheduled prompt", ), CommandSpec::new( &["status"], "status [--json]", "Show background service status", ), CommandSpec::new( &["doctor"], "doctor [--fix] [--json]", "Inspect and safely clean up background services", ) .options(&[ "--fix Remove stale sockets and stop idle orphaned services", "--json Print JSON", ]), CommandSpec::new( &["incident"], "incident [--since <time>] [--until <time>] [--session <id>]", "Reconstruct a daemon incident from its logs", ) .description( "Summarizes the daemon logs for a time window into an operator timeline: supervisor and worker events, session anomalies, and recovery actions. Times without a timezone are read as UTC, matching the log; the default window is the last 24 hours.", ) .options(&[ "--since <time> Window start (ISO date/time, date, or HH:MM today; default: 24h ago)", "--until <time> Window end (default: now)", "--session <id> Only events naming this session id, worker id, or session name (prefix match)", ]) .examples(&[ "incident --since \"2026-09-16T20:02\" --until \"2026-09-16T20:21\"", "incident --session 2339fb7da605", ]), CommandSpec::new( &["shutdown"], "shutdown [--force] [--json]", "Stop every agent and background service", ) .description( "Without --force, an interactive confirmation is required. --force also kills unresponsive workers.", ) .options(&[ "--force Skip confirmation and kill unresponsive processes", "--json Print JSON", ]), CommandSpec::new( &["mcp"], "mcp <add|list|get|remove>", "Manage user MCP servers", ), CommandSpec::new( &["mcp", "add"], "mcp add <name> --url <url> [--bearer-token-env-var <env>|--oauth] [--force]", "Add an HTTP or stdio MCP server", ) .description("For stdio, use: mcp add <name> [--cwd <dir>] [--env CHILD=SOURCE] -- <command> [args...]"), CommandSpec::new(&["mcp", "list"], "mcp list", "List user MCP servers"), CommandSpec::new(&["mcp", "get"], "mcp get <name>", "Show a user MCP server"), CommandSpec::new(&["mcp", "remove"], "mcp remove <name>", "Remove a user MCP server"), CommandSpec::new( &["package"], "package <install|remove|list|update>", "Manage capability packages", ) .description("Packages can provide skills, prompts, and themes."), CommandSpec::new( &["package", "install"], "package install <source> [--local]", "Install a capability package", ) .options(&["--local Install into the current project instead of the user configuration"]), CommandSpec::new( &["package", "remove"], "package remove <source> [--local]", "Remove a capability package", ) .options(&["--local Remove from the current project configuration"]), CommandSpec::new( &["package", "list"], "package list", "List installed capability packages", ), CommandSpec::new( &["package", "update"], "package update [source]", "Update capability packages", ), CommandSpec::new( &["update"], "update [--check] [--nightly|--stable]", "Update to the latest Rust build (uninstalls the TypeScript version)", ) .options(&[ "--check Print the update channel's latest release vs the running version, without installing", "--force Reinstall the latest build of the channel", "--rollback Restore the previous version the last update kept", "--nightly Switch updates to the nightly channel (the latest main build)", "--stable Return updates to the stable channel", "--archive <path> Install a local release archive", "--source <url> The https:// origin recorded as the release's install source (managed installs; required there with --archive)", ]) .description( "Move from the TypeScript version to the Rust port in one step: `prime-agent update` \ fetches the update channel's installer (stable: \ https://app.primeintellect.ai/prime-agent/install.sh; nightly: install-beta.sh from the \ release download base — never a GitHub raw or workflow URL) and runs it, which \ uninstalls the TypeScript version and installs the latest Rust build; your sessions and \ configuration (~/.prime/agent) are never touched. Restart prime-agent after the update \ to run the new build. This command exists only in the Rust binary — the TypeScript \ version does not have it; the move happens when you run the installer's curl|sh URL (the \ README's Install section) or `prime-agent update` (after the Rust install exists).", ), CommandSpec::new(&["model"], "model list [search]", "Inspect available models"), CommandSpec::new(&["model", "list"], "model list [search]", "List available models"), CommandSpec::new( &["session"], "session export <file> [output]", "Manage saved sessions", ), CommandSpec::new( &["session", "export"], "session export <file> [output]", "Export a saved session to HTML", ), CommandSpec::new(&["config"], "config", "Configure package resources"), CommandSpec::new( &["factory"], "factory <list|import|export>", "Share and run factory machines from the machine library", ) .description( "Machines are MACHINE.md templates (frontmatter plus a fenced machine-spec block) \resolved from the bundled library shipped inside the runtime, then from the personal \library under the agent dir. Run one with `await rlm.factory.run(\"<name>\")` from a session.", ), CommandSpec::new( &["factory", "list"], "factory list [--json]", "List the machine library", ) .description( "Lists the bundled machines and the personal machines with their descriptions; \files that fail the parser skip with a warning.", ) .options(&["--json Print JSON"]), CommandSpec::new( &["factory", "import"], "factory import <path> [--json]", "Validate and import a MACHINE.md into the personal library", ) .description( "The machine's spec passes the kernel's write-time validator; an invalid spec \never persists and the exact errors print verbatim.", ) .options(&["--json Print JSON"]), CommandSpec::new( &["factory", "export"], "factory export <name> --out <path> [--json]", "Export a machine to a MACHINE.md file", ) .description( "Resolves the library machine named <name> (bundled first, then the personal \library) and copies its MACHINE.md to the output path; an existing target is \refused, never overwritten.", ) .options(&[ "--out <path> Destination MACHINE.md path", "--json Print JSON", ]) .examples(&["factory export review-sweep --out shared-review-sweep.MACHINE.md"]), CommandSpec::new( &["prompt"], "prompt [--model <selector>] [--cwd <dir>] [--json]", "Print the assembled system prompt with its layer breakdown", ) .description( "Assembles the effective system prompt the way a fresh root session does \and prints the per-layer breakdown (cached static layers, then the dynamic tail) followed by the full prompt text.", ) .options(&[ "--model <selector> Preview the per-model additions (model-prompts.toml) for a selector", "--cwd <dir> Assemble for this working directory (default: current)", "--json Print segments and prompt as JSON", ]),];
/// Command names that once existed and now print removal guidance.pub const REMOVED_COMMAND_NAMES: &[&str] = &["app", "daemon", "install", "manage", "remove", "uninstall"];
pub fn public_command_names() -> Vec<&'static str> { COMMAND_SPECS .iter() .filter(|spec| spec.path.len() == 1) .map(|spec| spec.path[0]) .collect()}
struct OptionGroup { heading: &'static str, options: &'static [(&'static str, &'static str)],}
const TOP_LEVEL_OPTION_GROUPS: &[OptionGroup] = &[ OptionGroup { heading: "Run options", options: &[ ("-p, --print", "Print a response and exit"), ( "--mode <text|json|rpc|acp|daemon>", "Select the output mode (default: text)", ), ("--cwd <dir>", "Use a specific working directory"), ("--offline", "Disable startup network operations"), ( "--refresh", "Refresh model catalogs and exit without starting a session", ), ("--verbose", "Force verbose startup"), ("--daemon-socket <path>", "Use a specific daemon socket"), ], }, OptionGroup { heading: "Model options", options: &[ ("--provider <name>", "Select a model provider"), ("--model <id>", "Select a model"), ("--api-key <key>", "Use an API key for this run"), ( "--models <patterns>", "Set comma-separated models for cycling", ), ( "--thinking <level>", "Set reasoning: off, minimal, low, medium, high, xhigh, max", ), ], }, OptionGroup { heading: "Session options", options: &[ ("-c, --continue", "Continue the previous session"), ( "-r, --resume [path|id]", "Open the agents view, or resume a saved session", ), ( "--fork <path|id>", "Fork a saved session into a new session", ), ("--session-dir <dir>", "Use a custom session directory"), ("--no-session", "Do not save the session"), ( "--goal <objective>", "Seed a persistent goal for a new root session", ), ( "--goal-token-budget <n>", "Set a positive token budget for --goal", ), ], }, OptionGroup { heading: "Resource options", options: &[ ("--skill <path>", "Load a skill (repeatable)"), ("-ns, --no-skills", "Disable skill discovery"), ( "--prompt-template <path>", "Load a prompt template (repeatable)", ), ( "-np, --no-prompt-templates", "Disable prompt template discovery", ), ("--theme <path>", "Load a theme (repeatable)"), ("--no-themes", "Disable theme discovery"), ( "-nc, --no-context-files", "Disable AGENTS.md and CLAUDE.md discovery", ), ], }, OptionGroup { heading: "Prompt options", options: &[ ( "--system-prompt <text>", "Replace the default system prompt", ), ( "--append-system-prompt <text>", "Append to the system prompt (repeatable)", ), ("--", "Treat all following arguments as messages"), ], }, OptionGroup { heading: "Autonomous options", options: &[ ( "--autonomous", "Continue until gates pass or a limit is reached", ), ( "--autonomous-gate <command>", "Run a completion gate (repeatable)", ), ( "--autonomous-gate-retries <n>", "Set positive retries per failed gate (default: 3)", ), ( "--autonomous-gate-timeout-ms <n>", "Set positive per-gate timeout in ms (default: 300000)", ), ( "--autonomous-max-continuations <n>", "Set positive follow-up limit (default: 3)", ), ( "--autonomous-max-turns <n>", "Set positive assistant-turn limit (default: 12)", ), ( "--autonomous-max-tokens <n>", "Set positive token limit (default: 80000)", ), ( "--autonomous-timeout-ms <n>", "Set positive wall-clock limit in ms (default: 1800000)", ), ], }, OptionGroup { heading: "Help", options: &[ ("-v, --version", "Show version and exit"), ("-h, --help", "Show this help"), ], },];
pub fn get_command_spec(path: &[&str]) -> Option<&'static CommandSpec> { COMMAND_SPECS.iter().find(|spec| { spec.path.len() == path.len() && spec.path.iter().zip(path).all(|(a, b)| a == b) })}
pub fn get_child_command_specs(path: &[&str]) -> Vec<&'static CommandSpec> { COMMAND_SPECS .iter() .filter(|spec| { spec.path.len() == path.len() + 1 && path .iter() .enumerate() .all(|(index, segment)| spec.path[index] == *segment) }) .collect()}
/// True when a `help <path>` request resolves to help rather than a message.pub fn is_help_command_request(path: &[&str]) -> bool { if path.is_empty() || get_command_spec(path).is_some() { return true; } if REMOVED_COMMAND_NAMES.contains(&path[0]) { return true; } if get_command_spec(&path[..1]).is_some() { return true; } let parent = &path[..path.len() - 1]; let candidates: Vec<&str> = get_child_command_specs(parent) .into_iter() .map(|spec| spec.path[spec.path.len() - 1]) .collect(); find_command_suggestion(path[path.len() - 1], &candidates).is_some()}
/// Suggest the closest candidate command name (the shared edit-distance/// heuristic in `pa_types::slash_commands`).pub fn find_command_suggestion<'a>(input: &str, candidates: &[&'a str]) -> Option<&'a str> { pa_types::slash_commands::find_slash_command_suggestion(input, candidates)}
fn pad_end(value: &str, width: usize) -> String { let len = value.chars().count(); if len >= width { value.to_string() } else { format!("{value}{}", " ".repeat(width - len)) }}
/// The full `--help` output.pub fn format_top_level_help() -> String { let commands: Vec<&CommandSpec> = COMMAND_SPECS .iter() .filter(|spec| spec.path.len() == 1) .collect(); let command_width = commands .iter() .map(|spec| spec.path[0].len()) .max() .unwrap_or(0); let options = TOP_LEVEL_OPTION_GROUPS .iter() .map(|group| { let width = group .options .iter() .map(|(option, _)| option.chars().count()) .max() .unwrap_or(0); let lines: Vec<String> = group .options .iter() .map(|(option, summary)| format!(" {} {}", pad_end(option, width), summary)) .collect(); format!("{}:\n{}", group.heading, lines.join("\n")) }) .collect::<Vec<_>>() .join("\n\n"); let command_lines: Vec<String> = commands .iter() .map(|spec| { format!( " {} {}", pad_end(spec.path[0], command_width), spec.summary ) }) .collect(); format!( "{APP_NAME} - AI coding assistant with a Python REPL tool\n\ \n\ Usage:\n\ \x20 {APP_NAME} [options] [@files...] [message...]\n\ \x20 {APP_NAME} <command> [args...]\n\ \n\ Options:\n\ {options}\n\ \n\ Commands:\n\ {}\n\ \n\ Run \"{APP_NAME} help <command>\" for command details.", command_lines.join("\n") )}
/// Per-command help output.pub fn format_command_help(path: &[&str]) -> Option<String> { let spec = get_command_spec(path)?; let children = get_child_command_specs(path); let mut sections = vec![ format!("Usage:\n {APP_NAME} {}", spec.usage), String::new(), format!("{}.", spec.summary), ]; if let Some(description) = spec.description { sections.push(String::new()); sections.push(description.to_string()); } if !children.is_empty() { let width = children .iter() .map(|child| child.path[child.path.len() - 1].len()) .max() .unwrap_or(0); sections.push(String::new()); sections.push("Commands:".to_string()); for child in children { sections.push(format!( " {} {}", pad_end(child.path[child.path.len() - 1], width), child.summary )); } } if !spec.options.is_empty() { sections.push(String::new()); sections.push("Options:".to_string()); for option in spec.options { sections.push(format!(" {option}")); } } if !spec.examples.is_empty() { sections.push(String::new()); sections.push("Examples:".to_string()); for example in spec.examples { sections.push(format!(" {APP_NAME} {example}")); } } Some(sections.join("\n"))}
#[cfg(test)]mod tests { use super::*;
#[test] fn suggestion_thresholds() { assert_eq!( find_command_suggestion("schedul", &["schedule", "list"]), Some("schedule") ); // Two-character tolerance would let "tmp" masquerade as "mcp", so // short tokens allow a single-character typo only. assert_eq!(find_command_suggestion("tmp", &["mcp"]), None); assert_eq!(find_command_suggestion("bogus", &["list"]), None); }
#[test] fn help_command_request() { assert!(is_help_command_request(&["schedule", "bogus"])); assert!(!is_help_command_request(&["bogus"])); assert!(is_help_command_request(&[])); assert!(is_help_command_request(&["daemon"])); }}