//! Nushell-style help: every command documented as a spec, rendered like //! 'nu help ' (description, search terms, usage, flags, command //! type, parameters, input/output types, examples) with colors. //! //! Also provides the version table ('am version', Nushell-style) and the //! pre-parsing interception of -h/--help flags so that 'am install -h', //! 'am ls --help' and friends all show this help instead of the clap //! generated one. use crate::app::App; use crate::theme::Theme; use anyhow::bail; use serde::Serialize; use std::io::IsTerminal; // --------------------------------------------------------------------------- // Command specifications // --------------------------------------------------------------------------- pub struct HelpFlag { pub short: &'static str, pub long: &'static str, pub value: &'static str, pub desc: &'static str, } pub struct HelpParam { pub name: &'static str, pub typ: &'static str, pub desc: &'static str, } pub struct HelpExample { pub desc: &'static str, pub code: &'static str, } pub struct HelpSpec { pub name: &'static str, pub category: &'static str, pub usage: &'static str, pub about: &'static str, pub search_terms: &'static [&'static str], pub flags: &'static [HelpFlag], pub subcommands: &'static [(&'static str, &'static str)], pub parameters: &'static [HelpParam], pub io: Option<(&'static str, &'static str)>, pub examples: &'static [HelpExample], } const FLAG_HELP: HelpFlag = HelpFlag { short: "-h", long: "--help", value: "", desc: "Display the help message for this command", }; /// Flags shared by every am command. const GLOBAL_FLAGS: &[HelpFlag] = &[ HelpFlag { short: "-c", long: "--config", value: "FILE", desc: "Use an alternate configuration file", }, HelpFlag { short: "-v", long: "--verbose", value: "", desc: "Verbose output: show every command executed and its details", }, HelpFlag { short: "-q", long: "--quiet", value: "", desc: "Quiet mode: only errors are printed", }, HelpFlag { short: "-y", long: "--yes", value: "", desc: "Answer yes to every confirmation prompt", }, HelpFlag { short: "", long: "--dry-run", value: "", desc: "Simulate the action without changing anything", }, HelpFlag { short: "", long: "--json", value: "", desc: "Emit machine-readable JSON on stdout (for scripts)", }, HelpFlag { short: "", long: "--no-color", value: "", desc: "Disable ANSI colors", }, HelpFlag { short: "", long: "--theme", value: "THEME", desc: "Color theme for the output (see 'am help theme' for the list)", }, HelpFlag { short: "", long: "--lang", value: "LANG", desc: "Output language: fr (default) or en (issue #74)", }, ]; const START_FLAGS: &[HelpFlag] = &[ HelpFlag { short: "-b", long: "--background", value: "", desc: "Run detached in the background; output goes to the agent log file", }, HelpFlag { short: "-f", long: "--foreground", value: "", desc: "Run in the foreground, attached to this terminal (the default)", }, HelpFlag { short: "", long: "--args", value: "ARGS", desc: "Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)", }, HelpFlag { short: "", long: "--env", value: "KEY=VALUE", desc: "Set an environment variable for the agent (repeatable)", }, HelpFlag { short: "", long: "--notify", value: "", desc: "Send a desktop notification once the agent has started", }, HelpFlag { short: "", long: "--parallel", value: "", desc: "Start every group member back-to-back without waiting (issue #57)", }, HelpFlag { short: "", long: "--container", value: "", desc: "Run inside the agent's container profile (issue #58)", }, HelpFlag { short: "", long: "--model", value: "MODEL", desc: "Model to use: local runtime first, then the provider registry (issue #71/#92)", }, HelpFlag { short: "", long: "--provider", value: "PROVIDER", desc: "Provider of the registry to use (base_url + keyring token + model) (issue #92)", }, ]; pub static HELP_SPECS: &[HelpSpec] = &[ HelpSpec { name: "list", category: "Commands", usage: "list {flags}", about: "List installed agents, or every catalog entry with --all.", search_terms: &["catalog", "agents", "installed"], flags: &[ HelpFlag { short: "", long: "--all", value: "", desc: "Show every agent known to the catalog, not only the installed ones" }, HelpFlag { short: "", long: "--category", value: "CATEGORY", desc: "Only show agents of this category" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "List the installed agents.", code: "list" }, HelpExample { desc: "Show every agent in the catalog.", code: "list --all" }, HelpExample { desc: "Show only one category.", code: "list --category coding-agent" }, ], }, HelpSpec { name: "status", category: "Commands", usage: "status {flags} [agent]", about: "Show the state of one agent, or of every installed agent.", search_terms: &["state", "running", "pid"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias (omit to show all installed agents)" }, ], io: None, examples: &[ HelpExample { desc: "Show the state of every installed agent.", code: "status" }, HelpExample { desc: "Show one agent.", code: "status claude-code" }, ], }, HelpSpec { name: "sessions", category: "Commands", usage: "sessions {flags} [agent]", about: "List sessions; export one as JSON; enforce retention on old ones.", search_terms: &["session", "history", "log", "export", "retention", "purge", "resume"], flags: &[ HelpFlag { short: "", long: "--project", value: "PROJECT", desc: "Only sessions of this project" }, HelpFlag { short: "", long: "--status", value: "STATUS", desc: "Only sessions with this status (running, stopped, failed, interrupted)" }, HelpFlag { short: "", long: "--show", value: "ID", desc: "Show one session in detail (summary + log excerpt)" }, HelpFlag { short: "", long: "--export", value: "ID", desc: "Export a session (metadata + commands + log excerpt) to JSON" }, HelpFlag { short: "", long: "--output", value: "FILE", desc: "Export destination (default ./am-session-.json)" }, HelpFlag { short: "", long: "--retention", value: "DAYS", desc: "Purge sessions finished more than N days ago (settings default 90)" }, HelpFlag { short: "", long: "--resume", value: "ID", desc: "Relaunch a finished agent session with its recorded arguments" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Only sessions of this agent" }, ], io: None, examples: &[ HelpExample { desc: "List recent sessions.", code: "sessions" }, HelpExample { desc: "Export one session.", code: "sessions --export 20260815_143926_a1b2c3" }, HelpExample { desc: "Purge sessions older than 30 days.", code: "sessions --retention 30" }, HelpExample { desc: "Resume a finished session.", code: "sessions --resume 20260815_143926_a1b2c3" }, ], }, HelpSpec { name: "sync", category: "Commands", usage: "sync {flags}", about: "Push the state (state.json, journal, history) into the configured git repository (issue #66).", search_terms: &["git", "backup", "push", "sync_repo", "sauvegarde"], flags: &[ HelpFlag { short: "", long: "--message", value: "MESSAGE", desc: "Commit message (default: 'am sync — state update')" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Commit and push the state.", code: "sync" }, HelpExample { desc: "Custom commit message.", code: "sync --message \"daily backup\"" }, ], }, HelpSpec { name: "migrate", category: "Commands", usage: "migrate {flags} [bundle]", about: "Transfer the installation and state to another machine: export a .amx bundle, import it and verify with doctor (issue #68).", search_terms: &["transfer", "machine", "bundle", "export", "import", "migration"], flags: &[ HelpFlag { short: "", long: "--export", value: "", desc: "Export the bundle (default when no bundle path is given)" }, HelpFlag { short: "", long: "--output", value: "FILE", desc: "Bundle path for export (default agent-manager-migrate.amx)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "bundle", typ: "path", desc: "Bundle to import (with confirmation + doctor post-import)" }, ], io: None, examples: &[ HelpExample { desc: "Export the bundle.", code: "migrate --export --output backup.amx" }, HelpExample { desc: "Import on machine B.", code: "migrate backup.amx" }, ], }, HelpSpec { name: "stats", category: "Commands", usage: "stats {flags} [agent]", about: "Usage statistics from the event journal; --costs prices the token usage.", search_terms: &["usage", "cost", "tokens", "spend", "period"], flags: &[ HelpFlag { short: "", long: "--period", value: "PERIOD", desc: "7d, 30d, 90d or all" }, HelpFlag { short: "", long: "--costs", value: "", desc: "Show token usage and estimated spend per agent (issue #49)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias (omit for the global view)" }, ], io: None, examples: &[ HelpExample { desc: "Global statistics.", code: "stats" }, HelpExample { desc: "Last 30 days.", code: "stats --period 30d" }, HelpExample { desc: "Estimated spend.", code: "stats --period 30d --costs" }, ], }, HelpSpec { name: "monitor", category: "Commands", usage: "monitor {flags}", about: "Real-time TUI of the managed processes (CPU, RSS, uptime) with threshold alerts.", search_terms: &["process", "cpu", "memory", "rss", "alert", "top", "surveillance"], flags: &[ HelpFlag { short: "", long: "--interval", value: "SECONDS", desc: "Refresh interval (default 2)" }, HelpFlag { short: "", long: "--json", value: "", desc: "Print one JSON document per tick (for scripts)" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Live TUI (q / Esc quits).", code: "monitor" }, HelpExample { desc: "Refresh every 5s.", code: "monitor --interval 5" }, HelpExample { desc: "JSON stream for scripts.", code: "monitor --json --interval 10" }, ], }, HelpSpec { name: "web", category: "Commands", usage: "web {flags}", about: "Local read-only web dashboard: overview, charts, sessions, events, projects (issue #54). Serves an embedded page and a JSON API with the same stable contracts as the CLI --json outputs; bound to 127.0.0.1 only.", search_terms: &["web", "dashboard", "html", "http", "api", "graph", "chart", "navigateur", "browser", "port"], flags: &[ HelpFlag { short: "", long: "--port", value: "PORT", desc: "Port to listen on (default 7878)" }, HelpFlag { short: "", long: "--no-open", value: "", desc: "Do not open the browser automatically" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Open the dashboard in the browser.", code: "web" }, HelpExample { desc: "Serve on another port, no browser.", code: "web --port 9090 --no-open" }, HelpExample { desc: "API routes: /api/health, /api/overview, /api/stats, /api/sessions, /api/events, /api/projects.", code: "curl http://127.0.0.1:7878/api/health" }, ], }, HelpSpec { name: "history", category: "Commands", usage: "history {flags} [range]", about: "Structured history of every REPL command, with search, re-run and playbook export (issue #51).", search_terms: &["history", "search", "rerun", "playbook", "save", "replay"], flags: &[ HelpFlag { short: "", long: "--kind", value: "KIND", desc: "Only commands of this kind (am or shell)" }, HelpFlag { short: "", long: "--cwd", value: "DIR", desc: "Only commands run in this directory" }, HelpFlag { short: "", long: "--search", value: "TEXT", desc: "Full-text search in the command line" }, HelpFlag { short: "", long: "--failed", value: "", desc: "Only failed commands" }, HelpFlag { short: "", long: "--session", value: "ID", desc: "Only commands of this session" }, HelpFlag { short: "", long: "--limit", value: "N", desc: "Maximum entries (default 100)" }, HelpFlag { short: "", long: "--rerun", value: "N", desc: "Re-run the Nth most recent entry" }, HelpFlag { short: "", long: "--save", value: "FILE", desc: "Export the range as a playbook YAML (issue #51)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "range", typ: "from..to", desc: "1-based inclusive range, newest first (e.g. 12..25)" }, ], io: None, examples: &[ HelpExample { desc: "List the last commands.", code: "history" }, HelpExample { desc: "Re-run the 5th most recent entry.", code: "history --rerun 5" }, HelpExample { desc: "Save entries 12..25 as a playbook.", code: "history 12..25 --save deploy.yaml" }, ], }, HelpSpec { name: "playbook", category: "Commands", usage: "playbook {flags}", about: "Replay a saved playbook step by step with confirmation; stops with a non-zero exit code on failure (issue #51).", search_terms: &["playbook", "replay", "rejouer", "steps", "automation"], flags: &[ HelpFlag { short: "", long: "--var", value: "NAME=VALUE", desc: "Variable value, repeatable ({{name}} in the steps)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "file", typ: "path", desc: "Playbook file (bare name resolves under state_dir/playbooks/)" }, ], io: None, examples: &[ HelpExample { desc: "Replay with confirmation for each step.", code: "playbook deploy.yaml" }, HelpExample { desc: "Replay without asking, with a variable.", code: "playbook deploy.yaml --yes --var agent=claude-code" }, ], }, HelpSpec { name: "lab", category: "Commands", usage: "lab --agents a,b --task {flags}", about: "Benchmark: run the same task on several agents and compare duration, exit code, output and estimated cost (issue #62).", search_terms: &["benchmark", "compare", "comparer", "bench", "perf"], flags: &[ HelpFlag { short: "", long: "--agents", value: "LIST", desc: "Comma-separated agent names (or aliases) to benchmark (required)" }, HelpFlag { short: "", long: "--task", value: "FILE", desc: "Task file (bare name resolves under state_dir/lab/, .yaml appended)" }, HelpFlag { short: "", long: "--parallel", value: "", desc: "Run every agent concurrently instead of one after another" }, HelpFlag { short: "", long: "--timeout", value: "SECS", desc: "Override the task timeout in seconds" }, HelpFlag { short: "", long: "--list", value: "", desc: "List the task files of the lab directory" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Run the task sequentially on two agents.", code: "lab --agents claude-code,aider --task hello.yaml" }, HelpExample { desc: "Machine-readable comparative report.", code: "lab --agents claude-code,codex --task bench.yaml --json --parallel" }, ], }, HelpSpec { name: "plugins", category: "Commands", usage: "plugins {flags}", about: "List the event plugins of the state directory and test one against a synthetic event (issue #75).", search_terms: &["plugin", "extension", "hooks", "notify", "webhook"], flags: &[ HelpFlag { short: "", long: "--test", value: "NAME", desc: "Run one plugin on a synthetic event and print its response (CI hook)" }, ], subcommands: &[], parameters: &[], io: Some(("JSON (stdin)", "JSON object (stdout)")), examples: &[ HelpExample { desc: "List the installed plugins.", code: "plugins" }, HelpExample { desc: "Exercise the contract of one plugin.", code: "plugins --test notify" }, ], }, HelpSpec { name: "top", category: "Commands", usage: "top {flags}", about: "Show the most used agents (top 10).", search_terms: &["ranking", "most", "usage"], flags: &[ HelpFlag { short: "", long: "--period", value: "PERIOD", desc: "Only events of the last period (7d, 30d, 90d, all)" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "The most used agents.", code: "top" }, ], }, HelpSpec { name: "report", category: "Commands", usage: "report {flags}", about: "Write a markdown activity report.", search_terms: &["digest", "weekly", "monthly"], flags: &[ HelpFlag { short: "", long: "--last-week", value: "", desc: "Cover the last 7 days" }, HelpFlag { short: "", long: "--last-month", value: "", desc: "Cover the last 30 days" }, HelpFlag { short: "", long: "--output", value: "FILE", desc: "Output file (default: report-YYYYMMDD.md in the state directory)" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Weekly digest.", code: "report --last-week" }, HelpExample { desc: "Monthly digest to a file.", code: "report --last-month --output ~/rapport-am.md" }, ], }, HelpSpec { name: "projects", category: "Commands", usage: "projects {flags} [name]", about: "Show which agent works on which project (derived from the journal).", search_terms: &["workspace", "repository", "activity"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "name", typ: "string", desc: "Project name (omit to list every project)" }, ], io: None, examples: &[ HelpExample { desc: "Every project with activity.", code: "projects" }, HelpExample { desc: "One project in detail.", code: "projects api" }, ], }, HelpSpec { name: "timeline", category: "Commands", usage: "timeline {flags}", about: "One chronological view of every activity (journal + REPL history).", search_terms: &["chronology", "activity", "merged"], flags: &[ HelpFlag { short: "", long: "--project", value: "PROJECT", desc: "Only activity of this project" }, HelpFlag { short: "", long: "--since", value: "DATE", desc: "Only activity at or after this date" }, HelpFlag { short: "", long: "--limit", value: "N", desc: "Maximum number of entries (default: 50)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Only activity of this agent" }, ], io: None, examples: &[ HelpExample { desc: "The last 50 activities.", code: "timeline" }, HelpExample { desc: "One project since a date.", code: "timeline --project api --since 2026-08-01" }, ], }, HelpSpec { name: "log", category: "Commands", usage: "log {flags} [agent]", about: "Read the event journal: everything am did.", search_terms: &["journal", "events", "history"], flags: &[ HelpFlag { short: "", long: "--kind", value: "KINDS", desc: "Only these event kinds (comma separated)" }, HelpFlag { short: "", long: "--since", value: "DATE", desc: "Only events at or after this date (YYYY-MM-DD or RFC 3339)" }, HelpFlag { short: "", long: "--limit", value: "N", desc: "Maximum number of events (default: 50)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Only events of this agent" }, ], io: None, examples: &[ HelpExample { desc: "The last 50 events.", code: "log" }, HelpExample { desc: "Starts of one agent since a date.", code: "log claude-code --kind start --since 2026-08-01" }, ], }, HelpSpec { name: "logs", category: "Commands", usage: "logs {flags} ", about: "Tail the log file of an agent.", search_terms: &["tail", "follow", "output"], flags: &[ HelpFlag { short: "", long: "--lines", value: "N", desc: "Number of lines to show (default: 20)" }, HelpFlag { short: "", long: "--follow", value: "", desc: "Follow the log as it grows" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, ], io: None, examples: &[ HelpExample { desc: "The last 20 lines.", code: "logs claude-code" }, HelpExample { desc: "A live view.", code: "logs claude-code --follow" }, ], }, HelpSpec { name: "open", category: "Commands", usage: "open {flags} ", about: "Open an agent's installation directory in the file manager.", search_terms: &["explorer", "folder", "directory"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, ], io: None, examples: &[ HelpExample { desc: "Open the install directory.", code: "open claude-code" }, ], }, HelpSpec { name: "watch", category: "Commands", usage: "watch {flags} ", about: "Supervise an agent and optionally restart it on crash.", search_terms: &["monitor", "supervise", "restart"], flags: &[ HelpFlag { short: "", long: "--restart", value: "", desc: "Restart the agent automatically when it dies (with backoff)" }, HelpFlag { short: "", long: "--notify", value: "", desc: "Desktop notification on every restart" }, HelpFlag { short: "", long: "--interval", value: "SECS", desc: "Check interval in seconds (default: 5)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, ], io: None, examples: &[ HelpExample { desc: "Supervise with auto-restart.", code: "watch claude-code --restart --notify" }, ], }, HelpSpec { name: "models", category: "Commands", usage: "models {flags}", about: "List the models of the local runtimes (ollama, llama.cpp, LM Studio) and prune unused ones.", search_terms: &["ollama", "llama", "lm studio", "model", "prune", "gguf"], flags: &[ HelpFlag { short: "", long: "--prune", value: "", desc: "Purge unused models instead of listing them" }, HelpFlag { short: "", long: "--dry-run", value: "", desc: "Only list what would be pruned, delete nothing" }, HelpFlag { short: "", long: "--days", value: "DAYS", desc: "Consider a model unused after N days (default: 30)" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "List the local models.", code: "models" }, HelpExample { desc: "Preview the prune candidates.", code: "models --prune --dry-run" }, HelpExample { desc: "Prune models unused for 60 days.", code: "models --prune --days 60" }, ], }, HelpSpec { name: "catalog", category: "Commands", usage: "catalog {flags} [url]", about: "Manage remote agent catalogs: refresh the official one or register external catalogs by URL.", search_terms: &["remote", "url", "team", "share", "include"], flags: &[], subcommands: &[ ("update", "Fetch the official catalog, show the diff and register it"), ("add", "Register an external catalog by URL"), ("list", "List the registered remote catalogs"), ], parameters: &[], io: None, examples: &[ HelpExample { desc: "Refresh the official catalog.", code: "catalog update" }, HelpExample { desc: "Add a team catalog.", code: "catalog add https://git.dracodev.net/team/agents.yaml" }, ], }, HelpSpec { name: "suggest", category: "Commands", usage: "suggest ", about: "Recommend agents for a natural-language request, ranked by relevance and real usage.", search_terms: &["recommend", "recommendation", "find an agent", "what agent"], flags: &[], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Find an agent for Python.", code: "suggest un agent pour du Python" }, HelpExample { desc: "Machine-readable output.", code: "suggest frontend framework --json" }, ], }, HelpSpec { name: "audit", category: "Commands", usage: "audit {flags}", about: "Who changed what, when: config checksums vs the event journal, manual edits flagged.", search_terms: &["security", "trace", "checksum", "who changed", "history config"], flags: &[ HelpFlag { short: "", long: "--json", value: "", desc: "Stable machine-readable report" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Run the config audit.", code: "audit" }, HelpExample { desc: "Machine-readable report.", code: "audit --json" }, ], }, HelpSpec { name: "update", category: "Commands", usage: "update {flags} ", about: "Update installed agents; every update is backed up first and can be undone.", search_terms: &["upgrade", "rollback", "restore", "backup"], flags: &[ HelpFlag { short: "", long: "--all", value: "", desc: "Update every managed agent (one backup for the batch)" }, HelpFlag { short: "", long: "--rollback", value: "[POINT]", desc: "Restore a pre-update backup ('list' shows them, default: latest)" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Update one agent (backup taken first).", code: "update claude-code" }, HelpExample { desc: "List the available backups.", code: "update --rollback list" }, HelpExample { desc: "Undo the last update.", code: "update --rollback" }, ], }, HelpSpec { name: "alias", category: "Commands", usage: "alias {flags} [name] [target]", about: "Manage command aliases (cc -> claude-code) without editing the YAML.", search_terms: &["shortcut", "rename"], flags: &[], subcommands: &[ ("add", "Add an alias pointing to an agent"), ("remove", "Remove an alias"), ], parameters: &[], io: None, examples: &[ HelpExample { desc: "Shortcut for claude-code.", code: "alias add cc claude-code" }, HelpExample { desc: "List every alias.", code: "alias list" }, ], }, HelpSpec { name: "secret", category: "Commands", usage: "secret {flags} ", about: "Manage secrets in the OS keyring (values never shown or logged). Secrets are scoped to an agent (agent/NAME) or to a provider (providers/

/, shared by every agent of that provider, issue #89).", search_terms: &["keyring", "token", "password"], flags: &[], subcommands: &[ ("set", "Store a secret for an agent or a provider"), ("unset", "Remove a secret"), ], parameters: &[ HelpParam { name: "--agent", typ: "string", desc: "Agent the secret belongs to" }, HelpParam { name: "--provider", typ: "string", desc: "Provider the secret belongs to (issue #89)" }, HelpParam { name: "--value", typ: "string", desc: "Secret value (never logged)" }, ], io: None, examples: &[ HelpExample { desc: "Store a token.", code: "secret set OPENAI_KEY --agent claude-code --value sk-..." }, HelpExample { desc: "Store a provider token.", code: "secret set api_key --provider openai --value sk-... (or: providers set-token openai)" }, HelpExample { desc: "Use it at launch.", code: "start claude-code --env OPENAI_KEY=@secret" }, ], }, HelpSpec { name: "providers", category: "Commands", usage: "providers {flags} ", about: "Manage the LLM provider registry: base URLs, models, the default model and the default provider (issue #88). Tokens are never stored here — they live in the OS keyring under providers//api_key (issue #89).", search_terms: &["provider", "token", "registry", "llm", "model", "api", "keyring"], flags: &[ HelpFlag { short: "", long: "--base-url", value: "URL", desc: "Base URL of the provider API (add)" }, HelpFlag { short: "", long: "--model", value: "MODEL", desc: "Model used by default for this provider (add)" }, HelpFlag { short: "", long: "--models", value: "MODELS", desc: "Comma-separated model list (add)" }, HelpFlag { short: "", long: "--value", value: "VALUE", desc: "Token value (set-token; never logged)" }, ], subcommands: &[ ("list", "List the registered providers (default starred)"), ("show", "Show one provider in detail"), ("add", "Register or update a provider"), ("remove", "Remove a provider"), ("default", "Set the default provider"), ("set-token", "Store the provider token in the OS keyring (issue #89)"), ("token", "Check whether a provider token exists (value never shown)"), ], parameters: &[], io: None, examples: &[ HelpExample { desc: "List the providers.", code: "providers" }, HelpExample { desc: "Register DeepSeek.", code: "providers add deepseek --base-url https://api.deepseek.com --model deepseek-chat --models deepseek-chat,deepseek-reasoner" }, HelpExample { desc: "Store its shared token.", code: "providers set-token deepseek --value sk-..." }, HelpExample { desc: "Make it the default.", code: "providers default deepseek" }, HelpExample { desc: "Machine-readable list.", code: "providers --json" }, ], }, HelpSpec { name: "history", category: "Commands", usage: "history {flags}", about: "Search the command history (am and shell commands).", search_terms: &["past", "commands", "rerun"], flags: &[ HelpFlag { short: "", long: "--kind", value: "KIND", desc: "Only commands of this kind (am or shell)" }, HelpFlag { short: "", long: "--cwd", value: "DIR", desc: "Only commands run in this directory" }, HelpFlag { short: "", long: "--search", value: "TEXT", desc: "Full-text search in the command line" }, HelpFlag { short: "", long: "--failed", value: "", desc: "Only failed commands" }, HelpFlag { short: "", long: "--session", value: "ID", desc: "Only commands of this session" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "The last 100 commands.", code: "history" }, HelpExample { desc: "Find past installs.", code: "history --search install" }, HelpExample { desc: "Failed commands only.", code: "history --failed" }, ], }, HelpSpec { name: "search", category: "Commands", usage: "search {flags} ", about: "Search the catalog by keyword (name, description, category, tags); --tag filters on personal tags.", search_terms: &["find", "catalog", "keyword"], flags: &[ HelpFlag { short: "", long: "--category", value: "CATEGORY", desc: "Restrict to this category" }, HelpFlag { short: "", long: "--tag", value: "TAG", desc: "Only agents carrying this personal tag" }, ], subcommands: &[], parameters: &[ HelpParam { name: "keyword", typ: "string", desc: "Keyword to search for (case-insensitive substring)" }, ], io: None, examples: &[ HelpExample { desc: "Find agents related to rust.", code: "search rust" }, HelpExample { desc: "Search within one category.", code: "search coding --category coding-agent" }, HelpExample { desc: "Find agents tagged python.", code: "search --tag python" }, ], }, HelpSpec { name: "info", category: "Commands", usage: "info {flags} ", about: "Show detailed information about one agent.", search_terms: &["details", "show", "agent"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, ], io: None, examples: &[ HelpExample { desc: "Show details about an agent.", code: "info claude-code" }, HelpExample { desc: "Use an alias.", code: "info cc" }, ], }, HelpSpec { name: "init", category: "Commands", usage: "init {flags}", about: "Generate a local agent-manager.yaml — stack detection or an embedded template.", search_terms: &["project", "setup", "scaffold", "template"], flags: &[ HelpFlag { short: "", long: "--force", value: "", desc: "Overwrite an existing agent-manager.yaml" }, HelpFlag { short: "", long: "--template", value: "NAME", desc: "web | python | rust | cli ('list' shows them)" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Generate a project config (stack detection).", code: "init" }, HelpExample { desc: "Generate a web-stack config.", code: "init --template web" }, HelpExample { desc: "List the available templates.", code: "init --template list" }, ], }, HelpSpec { name: "install", category: "Commands", usage: "install {flags} ", about: "Install an agent and its dependencies, and resolve its LLM provider + model (issue #90): --provider > agent.provider > settings.default_provider; --model > agent.model > provider.default_model.", search_terms: &["add", "setup", "provider", "model"], flags: &[ HelpFlag { short: "", long: "--method", value: "METHOD", desc: "Select the install method (index, or type: npm, pip, uv, cargo, go, bun, curl, binary, git)" }, HelpFlag { short: "", long: "--force", value: "", desc: "Reinstall even if already installed" }, HelpFlag { short: "", long: "--provider", value: "PROVIDER", desc: "Provider to configure (default: the agent's provider, else settings.default_provider)" }, HelpFlag { short: "", long: "--model", value: "MODEL", desc: "Model to configure (default: the provider's default model)" }, HelpFlag { short: "", long: "--no-config", value: "", desc: "Skip the post-install provider configuration (issue #91)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, ], io: None, examples: &[ HelpExample { desc: "Install an agent with its dependencies.", code: "install claude-code" }, HelpExample { desc: "Pick a specific install method.", code: "install smelt --method uv" }, HelpExample { desc: "Reinstall over an existing install.", code: "install claude-code --force" }, HelpExample { desc: "Force a provider and a model.", code: "install agentty --provider deepseek --model deepseek-reasoner" }, HelpExample { desc: "Preview the resolved provider/model without installing.", code: "install agentty --dry-run" }, HelpExample { desc: "Install without any post-install configuration.", code: "install agentty --no-config" }, ], }, HelpSpec { name: "uninstall", category: "Commands", usage: "uninstall {flags} ", about: "Uninstall an agent (managed or external) and clean its files.", search_terms: &["remove", "delete"], flags: &[ HelpFlag { short: "", long: "--purge", value: "", desc: "Also remove logs and the agent entry from the user config file" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, ], io: None, examples: &[ HelpExample { desc: "Remove an agent.", code: "uninstall claude-code" }, HelpExample { desc: "Remove everything, including logs and the config entry.", code: "uninstall claude-code --purge" }, ], }, HelpSpec { name: "start", category: "Commands", usage: "start {flags} ", about: "Start an agent (foreground by default, or detached with --background). --profile applies an environment profile (env + args).", search_terms: &["launch", "begin", "group", "parallel", "healthcheck"], flags: START_FLAGS, subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name, alias, or group:" }, ], io: None, examples: &[ HelpExample { desc: "Start an agent in the background.", code: "start claude-code --background" }, HelpExample { desc: "Start with extra arguments.", code: "start claude-code --args \"--model sonnet\"" }, HelpExample { desc: "Start every agent of a group, waiting each health check.", code: "start group:dev --background" }, HelpExample { desc: "Start a group without waiting.", code: "start group:dev --parallel" }, HelpExample { desc: "Start with an environment profile.", code: "start claude-code --profile prod" }, ], }, HelpSpec { name: "stop", category: "Commands", usage: "stop {flags} ", about: "Stop a background agent (SIGTERM, then SIGKILL after the timeout).", search_terms: &["kill", "terminate"], flags: &[ HelpFlag { short: "", long: "--force", value: "", desc: "Kill immediately instead of terminating gracefully" }, HelpFlag { short: "", long: "--timeout", value: "SECS", desc: "Grace period in seconds before killing (default: from config, 5s)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name, alias, or group:" }, ], io: None, examples: &[ HelpExample { desc: "Stop an agent gracefully.", code: "stop claude-code" }, HelpExample { desc: "Kill immediately.", code: "stop claude-code --force" }, ], }, HelpSpec { name: "restart", category: "Commands", usage: "restart {flags} ", about: "Restart an agent: stop, then start with the same options.", search_terms: &["reboot", "reload"], flags: &[ HelpFlag { short: "-b", long: "--background", value: "", desc: "Restart detached in the background; output goes to the agent log file" }, HelpFlag { short: "-f", long: "--foreground", value: "", desc: "Restart in the foreground, attached to this terminal (the default)" }, HelpFlag { short: "", long: "--args", value: "ARGS", desc: "Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)" }, HelpFlag { short: "", long: "--env", value: "KEY=VALUE", desc: "Set an environment variable for the agent (repeatable)" }, HelpFlag { short: "", long: "--notify", value: "", desc: "Send a desktop notification once the agent has started" }, HelpFlag { short: "", long: "--force", value: "", desc: "Kill immediately instead of terminating gracefully" }, HelpFlag { short: "", long: "--timeout", value: "SECS", desc: "Grace period in seconds before killing (default: from config, 5s)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name, alias, or group:" }, ], io: None, examples: &[ HelpExample { desc: "Restart an agent in the background.", code: "restart claude-code --background" }, HelpExample { desc: "Restart with extra arguments.", code: "restart claude-code --args \"--model sonnet\"" }, ], }, HelpSpec { name: "run", category: "Commands", usage: "run {flags} ...(args)", about: "Run the agent command directly with the given arguments (no process management).", search_terms: &["exec", "pass-through", "container", "docker", "podman"], flags: &[ HelpFlag { short: "", long: "--model", value: "MODEL", desc: "Model to use: local runtime first, then the provider registry (issue #71/#92)" }, HelpFlag { short: "", long: "--provider", value: "PROVIDER", desc: "Provider of the registry to use (base_url + keyring token + model) (issue #92)" }, HelpFlag { short: "", long: "--container", value: "", desc: "Run inside the agent's container profile (issue #58)" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, HelpParam { name: "args", typ: "any", desc: "Arguments passed through to the agent command" }, ], io: None, examples: &[ HelpExample { desc: "Run an agent command directly.", code: "run claude-code" }, HelpExample { desc: "Use a provider of the registry.", code: "run claude-code --provider anthropic --model claude-sonnet-4" }, HelpExample { desc: "Pass --help through to the agent (after --).", code: "run claude-code -- --help" }, ], }, HelpSpec { name: "doctor", category: "Commands", usage: "doctor {flags}", about: "Check the environment; --watch re-runs periodically and alerts on failure.", search_terms: &["health", "watch", "check", "environment", "repair"], flags: &[ HelpFlag { short: "", long: "--fix", value: "", desc: "Repair what can be repaired automatically" }, HelpFlag { short: "", long: "--watch", value: "SECONDS", desc: "Re-run the checks every N seconds; alert + event on failure, recover silently" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "One-shot environment report.", code: "doctor" }, HelpExample { desc: "Watch every 30s, alerting on failure.", code: "doctor --watch 30" }, HelpExample { desc: "Repair what can be fixed.", code: "doctor --fix" }, ], }, HelpSpec { name: "service", category: "Commands", usage: "service install|uninstall|status {flags}", about: "Register an agent as a system service (systemd / launchd / Task Scheduler).", search_terms: &["systemd", "autostart", "launchd", "task scheduler", "daemon"], flags: &[ HelpFlag { short: "", long: "--autostart", value: "", desc: "Start the service at boot/logon" }, ], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name" }, ], io: None, examples: &[ HelpExample { desc: "Install with autostart.", code: "service install claude-code --autostart" }, HelpExample { desc: "Remove the service.", code: "service uninstall claude-code" }, HelpExample { desc: "Check the service.", code: "service status claude-code" }, ], }, HelpSpec { name: "schedule", category: "Commands", usage: "schedule add|list|remove {flags}", about: "Plan am commands (daily) and run them with health reporting.", search_terms: &["cron", "daily", "task scheduler", "scheduled", "planning"], flags: &[ HelpFlag { short: "", long: "--at", value: "HH:MM", desc: "Time of day (default 08:00)" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Daily update of every agent at 06:30.", code: "schedule add update --all --at 06:30" }, HelpExample { desc: "List the schedules.", code: "schedule list" }, HelpExample { desc: "Remove a schedule.", code: "schedule remove " }, HelpExample { desc: "Run a schedule now (used by the OS task).", code: "schedule run " }, ], }, HelpSpec { name: "config", category: "Commands", usage: "config {flags} ", about: "Manage the configuration file.", search_terms: &["settings", "yaml", "configure"], flags: &[], subcommands: &[ ("show", "Print the effective (merged) configuration as YAML"), ("path", "Print the location of the configuration file(s)"), ("edit", "Open the user configuration file in your editor"), ("validate", "Validate the configuration file(s)"), ("add ", "Add another agent definition file to the user configuration"), ], parameters: &[], io: None, examples: &[ HelpExample { desc: "Show the effective configuration.", code: "config show" }, HelpExample { desc: "Locate the config file.", code: "config path" }, HelpExample { desc: "Validate it.", code: "config validate" }, ], }, HelpSpec { name: "completion", category: "Commands", usage: "completion {flags} ", about: "Generate a shell completion script (bash, zsh, fish, powershell, elvish). With --installed, Tab also completes the installed agents, aliases and groups.", search_terms: &["autocomplete", "shell"], flags: &[ HelpFlag { short: "", long: "--installed", value: "", desc: "Add dynamic completion of installed agents, aliases and groups" }, ], subcommands: &[], parameters: &[ HelpParam { name: "shell", typ: "oneof", desc: "Shell to generate completions for" }, ], io: None, examples: &[ HelpExample { desc: "Generate PowerShell completions.", code: "completion powershell" }, HelpExample { desc: "Generate bash completions with the installed agents.", code: "completion bash --installed" }, ], }, HelpSpec { name: "self-update", category: "Commands", usage: "self-update {flags}", about: "Update agent-manager itself from the latest release (GitHub ou Gitea, selon settings.self_update_*).", search_terms: &["upgrade", "release", "gitea"], flags: &[ HelpFlag { short: "", long: "--check", value: "", desc: "Only check whether a newer version exists" }, HelpFlag { short: "", long: "--to", value: "FILE", desc: "Download the new binary to this path instead of replacing the current one" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Check for a newer version.", code: "self-update --check" }, HelpExample { desc: "Download without replacing the current binary.", code: "self-update --to ./am-new.exe" }, ], }, HelpSpec { name: "self-uninstall", category: "Commands", usage: "self-uninstall {flags}", about: "Remove agent-manager and everything it created from this machine.", search_terms: &["remove", "cleanup"], flags: &[], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Remove agent-manager completely.", code: "self-uninstall --yes" }, ], }, HelpSpec { name: "export", category: "Commands", usage: "export {flags}", about: "Export the configuration and installation state (backup).", search_terms: &["backup", "save"], flags: &[ HelpFlag { short: "", long: "--output", value: "FILE", desc: "Output file (default: am-export.json)" }, ], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Back up to the default file.", code: "export" }, HelpExample { desc: "Choose the output file.", code: "export --output backup.json" }, ], }, HelpSpec { name: "import", category: "Commands", usage: "import {flags} ", about: "Import a previously exported configuration and state.", search_terms: &["restore", "backup"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "file", typ: "path", desc: "File exported by the export command" }, ], io: None, examples: &[ HelpExample { desc: "Restore a backup.", code: "import am-export.json" }, ], }, HelpSpec { name: "dashboard", category: "Commands", usage: "dashboard {flags}", about: "Live TUI dashboard: the whole fleet in one screen — overview, activity, stats, sessions and projects, refreshed every second.", search_terms: &["tui", "monitor", "overview", "cockpit"], flags: &[], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Open the dashboard (Tab or arrows switch tabs, j/k scroll, q quits).", code: "dashboard" }, ], }, HelpSpec { name: "favorite", category: "Commands", usage: "favorite {flags} ", about: "Mark an agent as a favorite: a star appears in 'am list', 'am status' and 'am info'.", search_terms: &["star", "bookmark", "prefer"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, ], io: None, examples: &[ HelpExample { desc: "Mark claude-code as a favorite.", code: "favorite claude-code" }, ], }, HelpSpec { name: "unfavorite", category: "Commands", usage: "unfavorite {flags} ", about: "Remove the favorite star from an agent.", search_terms: &["star", "bookmark"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, ], io: None, examples: &[ HelpExample { desc: "Remove the star from claude-code.", code: "unfavorite claude-code" }, ], }, HelpSpec { name: "note", category: "Commands", usage: "note {flags} [text...]", about: "Attach a free-form note to an agent, shown in 'am info'. Without text, prints the current note.", search_terms: &["comment", "remark", "memo"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, HelpParam { name: "text", typ: "string", desc: "Note text (several words are joined); empty prints the note" }, ], io: None, examples: &[ HelpExample { desc: "Set a note.", code: "note claude-code use the sonnet model for this repo" }, HelpExample { desc: "Show the note.", code: "note claude-code" }, ], }, HelpSpec { name: "tag", category: "Commands", usage: "tag {flags} ...", about: "Add personal tags to an agent; they are searchable with 'am search --tag' and listed in 'am info'.", search_terms: &["label", "keyword", "filter"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, HelpParam { name: "tag", typ: "string", desc: "One or more tags to add" }, ], io: None, examples: &[ HelpExample { desc: "Tag claude-code for Python work.", code: "tag claude-code python frontend" }, HelpExample { desc: "Find everything tagged python.", code: "search --tag python" }, ], }, HelpSpec { name: "untag", category: "Commands", usage: "untag {flags} ", about: "Remove a personal tag from an agent.", search_terms: &["label", "remove"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias" }, HelpParam { name: "tag", typ: "string", desc: "Tag to remove" }, ], io: None, examples: &[ HelpExample { desc: "Remove the python tag from claude-code.", code: "untag claude-code python" }, ], }, HelpSpec { name: "tags", category: "Commands", usage: "tags {flags} [agent]", about: "List personal tags with the agents carrying them; with an agent, list only its tags.", search_terms: &["labels", "tag list"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "agent", typ: "string", desc: "Agent name or alias (optional)" }, ], io: None, examples: &[ HelpExample { desc: "List every personal tag.", code: "tags" }, HelpExample { desc: "List the tags of one agent.", code: "tags claude-code" }, ], }, HelpSpec { name: "profile", category: "Commands", usage: "profile {flags} [name]", about: "Environment profiles: a named set of agent, env and args defined in the config, applied with 'am start --profile dev'.", search_terms: &["environment", "context", "dev", "prod"], flags: &[], subcommands: &[ ("list", "List every defined profile"), ("show", "Show one profile in detail"), ], parameters: &[ HelpParam { name: "name", typ: "string", desc: "Profile to show" }, ], io: None, examples: &[ HelpExample { desc: "List the profiles.", code: "profile list" }, HelpExample { desc: "Show the dev profile.", code: "profile show dev" }, HelpExample { desc: "Start an agent with the prod profile.", code: "start claude-code --profile prod" }, ], }, HelpSpec { name: "man", category: "Commands", usage: "man {flags} [command]", about: "Readable man pages in the terminal (the raw roff source is kept when piped): one page for the whole CLI, or one per command with --output.", search_terms: &["documentation", "roff", "unix", "manual"], flags: &[ HelpFlag { short: "", long: "--output", value: "DIR", desc: "Write am.1 plus one am-.1 per command into this directory" }, ], subcommands: &[], parameters: &[ HelpParam { name: "command", typ: "string", desc: "Command to document; omit for the whole CLI" }, ], io: None, examples: &[ HelpExample { desc: "Read the man page of the whole CLI (rendered in the terminal).", code: "man" }, HelpExample { desc: "Read one command page.", code: "man install" }, HelpExample { desc: "Write every page into man/ (for a package).", code: "man --output man/" }, HelpExample { desc: "Pipe the raw roff source into a real man viewer.", code: "man | man -l -" }, ], }, HelpSpec { name: "help", category: "Commands", usage: "help {flags} [name]", about: "Display help: the overview, a particular command or agent, the command list, or a search through it.", search_terms: &["man"], flags: &[ HelpFlag { short: "", long: "--find", value: "TEXT", desc: "Search through all help commands table" }, ], subcommands: &[], parameters: &[ HelpParam { name: "name", typ: "string", desc: "Command name, agent name, or alias; 'commands' lists every command" }, ], io: None, examples: &[ HelpExample { desc: "Show the overview.", code: "help" }, HelpExample { desc: "Show one command.", code: "help install" }, HelpExample { desc: "List every command.", code: "help commands" }, HelpExample { desc: "Search the help.", code: "help --find install" }, ], }, HelpSpec { name: "version", category: "Commands", usage: "version {flags}", about: "Display version and build information.", search_terms: &["about"], flags: &[], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Show version and build information.", code: "version" }, ], }, HelpSpec { name: "theme", category: "Shell", usage: "theme {flags} [name]", about: "Show the color themes, or switch the active one inside the shell (the choice is saved to the config).", search_terms: &["color", "colors", "palette"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "name", typ: "string", desc: "Theme to switch to; omit to list every available theme" }, ], io: None, examples: &[ HelpExample { desc: "List the available themes.", code: "theme" }, HelpExample { desc: "Switch to the ocean theme (kept for the next start).", code: "theme ocean" }, ], }, HelpSpec { name: "tip", category: "Commands", usage: "tip {flags}", about: "Cheat sheet : les commandes les plus utiles avec leurs options clés, vos commandes les plus utilisées et une astuce du jour.", search_terms: &["cheat", "sheet", "tips", "astuce", "aide", "raccourci", "usage"], flags: &[HelpFlag { short: "", long: "--random", value: "", desc: "Show a single random tip instead of the whole page", }], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Show the whole cheat sheet.", code: "tip" }, HelpExample { desc: "Show a single random tip.", code: "tip --random" }, ], }, HelpSpec { name: "ls", category: "Shell", usage: "ls {flags} [path]", about: "List the filenames, sizes, and modification times of items in a directory.", search_terms: &["dir", "files", "directory"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "path", typ: "path", desc: "The directory or glob to list (default: current directory)" }, ], io: Some(("nothing", "table")), examples: &[ HelpExample { desc: "List the current directory.", code: "ls" }, HelpExample { desc: "List a subdirectory.", code: "ls src" }, HelpExample { desc: "List Rust files.", code: "ls *.rs" }, HelpExample { desc: "List files larger than 1 MB.", code: "ls | where size > 1mb" }, HelpExample { desc: "Select one column.", code: "ls | get name" }, ], }, HelpSpec { name: "dir", category: "Shell", usage: "dir {flags} [path]", about: "List the filenames, sizes, and modification times of items in a directory (Windows alias of ls).", search_terms: &["ls", "files", "directory"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "path", typ: "path", desc: "The directory or glob to list (default: current directory)" }, ], io: Some(("nothing", "table")), examples: &[ HelpExample { desc: "List the current directory.", code: "dir" }, HelpExample { desc: "List a subdirectory.", code: "dir src" }, ], }, HelpSpec { name: "ps", category: "Shell", usage: "ps {flags}", about: "List the processes on the system.", search_terms: &["processes", "task"], flags: &[], subcommands: &[], parameters: &[], io: Some(("nothing", "table")), examples: &[ HelpExample { desc: "List all processes.", code: "ps" }, HelpExample { desc: "Filter by name.", code: "ps | where name =~ am" }, ], }, HelpSpec { name: "where", category: "Shell", usage: "where {flags} ", about: "Filter the rows of the last table (ls or ps) with a predicate.", search_terms: &["filter", "query"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "column", typ: "string", desc: "Column to filter on" }, HelpParam { name: "operator", typ: "string", desc: "One of > < >= <= == != =~" }, HelpParam { name: "value", typ: "string", desc: "Value to compare (supports 1kb/1mb/1gb sizes)" }, ], io: Some(("table", "table")), examples: &[ HelpExample { desc: "Keep files larger than 1 MB.", code: "ls | where size > 1mb" }, HelpExample { desc: "Keep processes whose name contains 'am'.", code: "ps | where name =~ am" }, ], }, HelpSpec { name: "get", category: "Shell", usage: "get {flags} ", about: "Select one column of the last table.", search_terms: &["select", "column"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "column", typ: "string", desc: "Column to select" }, ], io: Some(("table", "table")), examples: &[ HelpExample { desc: "Select the name column.", code: "ls | get name" }, ], }, HelpSpec { name: "cd", category: "Shell", usage: "cd {flags} [dir]", about: "Change the session directory (Tab completes folders).", search_terms: &["chdir", "directory"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "dir", typ: "path", desc: "Target directory (default: home)" }, ], io: None, examples: &[ HelpExample { desc: "Go to a directory.", code: "cd src" }, HelpExample { desc: "Go home.", code: "cd" }, HelpExample { desc: "Go home explicitly.", code: "cd ~" }, ], }, HelpSpec { name: "shell", category: "Shell", usage: "shell {flags} [name]", about: "Show or switch the system shell used by the gateway.", search_terms: &["pwsh", "bash", "gateway"], flags: &[], subcommands: &[], parameters: &[ HelpParam { name: "name", typ: "string", desc: "Shell to switch to (pwsh, powershell, cmd, bash, zsh, fish, sh, nu, elvish); omit to show the status" }, ], io: None, examples: &[ HelpExample { desc: "Show the active shell.", code: "shell" }, HelpExample { desc: "Switch to bash.", code: "shell bash" }, ], }, HelpSpec { name: "exit", category: "Shell", usage: "exit {flags}", about: "Quit the interactive shell.", search_terms: &["quit", "q"], flags: &[], subcommands: &[], parameters: &[], io: None, examples: &[ HelpExample { desc: "Leave the shell.", code: "exit" }, ], }, ]; /// Find a help spec by command name (case-insensitive). pub fn find_spec(name: &str) -> Option<&'static HelpSpec> { let lower = name.to_lowercase(); HELP_SPECS.iter().find(|s| s.name.to_lowercase() == lower) } /// All specs matching a --find keyword (name, about, usage, search terms). fn matching_specs(text: &str) -> Vec<&'static HelpSpec> { let needle = text.to_lowercase(); HELP_SPECS .iter() .filter(|s| { s.name.to_lowercase().contains(&needle) || s.about.to_lowercase().contains(&needle) || s.usage.to_lowercase().contains(&needle) || s.search_terms .iter() .any(|t| t.to_lowercase().contains(&needle)) }) .collect() } // --------------------------------------------------------------------------- // Rendering helpers // --------------------------------------------------------------------------- fn header(s: &str, theme: &Theme, color: bool) -> String { if color { theme.hdr(s) } else { s.to_string() } } fn prompt(s: &str, theme: &Theme, color: bool) -> String { if color { theme.acc(s) } else { s.to_string() } } fn name_style(s: &str, theme: &Theme, color: bool) -> String { if color { theme.acc(s) } else { s.to_string() } } fn type_style(s: &str, theme: &Theme, color: bool) -> String { if color { theme.inf(s) } else { s.to_string() } } fn dim(s: &str, theme: &Theme, color: bool) -> String { if color { theme.dimmed(s) } else { s.to_string() } } fn flag_line(f: &HelpFlag, theme: &Theme, color: bool) -> String { let name = match (f.short.is_empty(), f.long.is_empty()) { (true, false) => f.long.to_string(), (false, true) => f.short.to_string(), _ => format!("{}, {}", f.short, f.long), }; let name = name_style(&name, theme, color); let value = if f.value.is_empty() { String::new() } else { format!(" {}", type_style(&format!("<{}>", f.value), theme, color)) }; format!(" {name}{value}: {}\n", f.desc) } /// The small '# | input | output' table shown in command help. fn io_table(input: &str, output: &str, theme: &Theme, color: bool) -> String { let headers = ["#", "input", "output"]; let cells = ["0", input, output]; let widths: Vec = headers .iter() .zip(cells.iter()) .map(|(h, c)| h.chars().count().max(c.chars().count())) .collect(); let border = |left: char, mid: char, right: char| -> String { let mut line = String::new(); line.push(left); for (i, w) in widths.iter().enumerate() { if i > 0 { line.push(mid); } line.push_str(&"─".repeat(w + 2)); } line.push(right); if color { theme.frame(&line) } else { line } }; let bar = |s: &str| -> String { if color { theme.frame(s) } else { s.to_string() } }; let row = |cells: &[&str], bold: bool| -> String { let mut line = bar("│"); for (i, c) in cells.iter().enumerate() { line.push(' '); let text = if bold { if color { theme.hdr(c) } else { (*c).to_string() } } else { (*c).to_string() }; line.push_str(&text); line.push_str(&" ".repeat(widths[i] - c.chars().count())); line.push(' '); line.push_str(&bar("│")); } line }; format!( "{}\n{}\n{}\n{}\n{}", border('╭', '┬', '╮'), row(&headers, true), border('├', '┼', '┤'), row(&cells, false), border('╰', '┴', '╯') ) } fn truncate(s: &str, max: usize) -> String { if s.chars().count() <= max { s.to_string() } else { let mut cut: String = s.chars().take(max.saturating_sub(1)).collect(); cut.push('…'); cut } } // --------------------------------------------------------------------------- // Command help // --------------------------------------------------------------------------- /// Render one command's help, Nushell-style. pub fn render_command(spec: &HelpSpec, theme: &Theme, color: bool) -> String { let mut out = String::new(); // Description. for line in spec.about.lines() { out.push_str(line); out.push('\n'); } out.push('\n'); // Search terms. if !spec.search_terms.is_empty() { out.push_str(&dim( &format!("Search terms: {}", spec.search_terms.join(", ")), theme, color, )); out.push('\n'); out.push('\n'); } // Usage. out.push_str(&format!("{}:\n", header("Usage", theme, color))); out.push_str(&format!(" {} {}\n", prompt(">", theme, color), spec.usage)); out.push('\n'); // Flags. out.push_str(&format!("{}:\n", header("Flags", theme, color))); out.push_str(&flag_line(&FLAG_HELP, theme, color)); for f in spec.flags { out.push_str(&flag_line(f, theme, color)); } out.push_str(&format!("{}:\n", header("Global Flags", theme, color))); for f in GLOBAL_FLAGS { out.push_str(&flag_line(f, theme, color)); } out.push('\n'); // Command type. out.push_str(&format!("{}:\n", header("Command Type", theme, color))); out.push_str(&format!( " {}\n", if color { theme.acc("built-in") } else { "built-in".to_string() } )); out.push('\n'); // Subcommands. if !spec.subcommands.is_empty() { out.push_str(&format!("{}:\n", header("Subcommands", theme, color))); for (sub, desc) in spec.subcommands { out.push_str(&format!(" {}: {desc}\n", name_style(sub, theme, color))); } out.push('\n'); } // Parameters. if !spec.parameters.is_empty() { out.push_str(&format!("{}:\n", header("Parameters", theme, color))); for p in spec.parameters { let name = name_style(p.name, theme, color); let typ = type_style(&format!("<{}>", p.typ), theme, color); out.push_str(&format!(" {name} {typ}: {}\n", p.desc)); } out.push('\n'); } // Input/output types. if let Some((input, output)) = spec.io { out.push_str(&format!( "{}:\n", header("Input/output types", theme, color) )); for line in io_table(input, output, theme, color).lines() { out.push_str(&format!(" {line}\n")); } out.push('\n'); } // Examples. if !spec.examples.is_empty() { out.push_str(&format!("{}:\n", header("Examples", theme, color))); for (i, ex) in spec.examples.iter().enumerate() { if i > 0 { out.push('\n'); } out.push_str(&format!(" {}\n", ex.desc)); out.push_str(&format!(" {} {}\n", prompt(">", theme, color), ex.code)); } } out } /// Print the help of one command; agent names and aliases fall back to /// the 'info' command (Nushell's "help for commands, aliases and /// modules" behavior). pub fn print_command(app: &App, name: &str) -> Result { if let Some(spec) = find_spec(name) { print!("{}", render_command(spec, app.theme(), app.color())); return Ok(0); } if app.catalog.resolve(name).is_some() { return crate::commands::info_cmd::run(app, name); } bail!( "unknown help topic '{name}' — try 'am help commands' or 'am help --find '" ) } // --------------------------------------------------------------------------- // General help (the 'help' overview, like 'nu help') // --------------------------------------------------------------------------- /// Render the general help overview. pub fn render_general(theme: &Theme, color: bool) -> String { let bullet = if color { theme.warn("*") } else { "*".to_string() }; let mut out = String::new(); out.push_str(&format!( "{}\n\n", if color { theme.hdr("Welcome to Agent Manager.") } else { "Welcome to Agent Manager.".to_string() } )); out.push_str("Here are some tips to help you get started.\n"); out.push_str(&format!(" {bullet} help -h or help help - show available 'help' subcommands and examples\n")); out.push_str(&format!(" {bullet} help commands - list all available commands\n")); out.push_str(&format!(" {bullet} help - display help about a particular command or agent\n")); out.push_str(&format!(" {bullet} help --find - search through all help commands table\n")); out.push_str(&format!( " {bullet} theme - switch the color theme (saved) · am --theme for a one-shot ({})\n", crate::theme::names().join(", ") )); out.push('\n'); out.push_str( "am works on the idea of a \"catalog\". Catalog entries describe AI coding\n\ agents: how to install them, which dependencies they need, and how to start\n\ them. am installs, starts, stops and updates those agents, and its\n\ interactive prompt doubles as a gateway to your system shell.\n", ); out.push('\n'); out.push_str(&format!("{}\n\n", header("[Examples]", theme, color))); let example = |desc: &str, code: &str, out: &mut String| { out.push_str(&format!(" {desc}\n")); out.push_str(&format!(" {} {code}\n\n", prompt(">", theme, color))); }; example("List the installed agents:", "am list", &mut out); example( "Install an agent and its dependencies:", "am install claude-code", &mut out, ); example( "Start an agent in the background:", "am start claude-code --background", &mut out, ); example("Search the catalog:", "am search coding", &mut out); out.push_str( "Type 'am help commands' for the complete command list, or run 'am' alone for\n\ the interactive shell.\n", ); out } /// Print the general help overview. pub fn print_general(app: &App) { print!("{}", render_general(app.theme(), app.color())); } /// Print a help topic: None → overview, "commands" → the command list, /// anything else → the command/agent help. pub fn print_topic(app: &App, topic: Option<&str>) -> Result { match topic.map(str::trim).filter(|s| !s.is_empty()) { None => { print_general(app); Ok(0) } Some("commands") => { print_commands_list(app); Ok(0) } Some(name) => print_command(app, name), } } /// The 'help commands' table (also used by 'help --find'). pub fn print_commands_list(app: &App) { let specs: Vec<&HelpSpec> = HELP_SPECS.iter().collect(); print_specs_table(app, &specs); } /// 'help --find ': search through all help commands. pub fn print_find(app: &App, text: &str) { let hits = matching_specs(text); if hits.is_empty() { app.log .info(format!("no help matches '{text}' — try 'am help commands'")); return; } print_specs_table(app, &hits); } fn print_specs_table(app: &App, specs: &[&HelpSpec]) { let color = app.color(); let theme = app.theme(); let width = crate::output::terminal_width().unwrap_or(120); let table = crate::tables::DataTable { columns: vec![ "name".to_string(), "category".to_string(), "usage".to_string(), "description".to_string(), ], rows: specs .iter() .map(|s| { vec![ crate::tables::Cell::str(name_style(s.name, theme, color)), crate::tables::Cell::str(s.category.to_string()), crate::tables::Cell::str(s.usage.to_string()), crate::tables::Cell::str(truncate( s.about.lines().next().unwrap_or(""), 48, )), ] }) .collect(), }; print!("{}", table.render_themed(theme, color, width)); } // --------------------------------------------------------------------------- // Version (Nushell-style table) // --------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize)] pub struct VersionInfo { pub version: String, pub major: u64, pub minor: u64, pub patch: u64, pub branch: String, pub commit_hash: String, pub build_os: String, pub build_target: String, pub rust_version: String, pub rust_channel: String, pub cargo_version: String, pub build_time: String, pub build_rust_channel: String, pub allocator: String, } /// Collect the version and build information shown by 'am version'. pub fn version_info() -> VersionInfo { let version = env!("CARGO_PKG_VERSION").to_string(); let mut parts = version.split('.'); let major = parts.next().and_then(|p| p.parse().ok()).unwrap_or(0); let minor = parts.next().and_then(|p| p.parse().ok()).unwrap_or(0); let patch = parts.next().and_then(|p| p.parse().ok()).unwrap_or(0); let (rust_version, rust_channel) = rust_info(); VersionInfo { version, major, minor, patch, branch: option_env!("AM_GIT_BRANCH").unwrap_or("").to_string(), commit_hash: option_env!("AM_GIT_COMMIT").unwrap_or("").to_string(), build_os: build_os(), build_target: build_target(), rust_version, rust_channel, cargo_version: tool_version("cargo", &["--version"]).unwrap_or_default(), build_time: build_time(), build_rust_channel: if cfg!(debug_assertions) { "debug".to_string() } else { "release".to_string() }, allocator: "standard".to_string(), } } fn tool_version(program: &str, args: &[&str]) -> Option { std::process::Command::new(program) .args(args) .output() .ok() .filter(|o| o.status.success()) .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) .filter(|s| !s.is_empty()) } fn line_of(text: &str, key: &str) -> Option { text.lines() .find_map(|l| l.trim().strip_prefix(key).map(|v| v.trim().to_string())) } fn rust_info() -> (String, String) { let version = tool_version("rustc", &["--version"]).unwrap_or_default(); let channel = tool_version("rustc", &["-vV"]) .and_then(|v| { let release = line_of(&v, "release:")?; match line_of(&v, "host:") { Some(host) => Some(format!("{release}-{host}")), None => Some(release), } }) .unwrap_or_default(); (version, channel) } fn build_os() -> String { let os = if cfg!(windows) { "windows" } else if cfg!(target_os = "macos") { "macos" } else { std::env::consts::OS }; format!("{os}-{}", std::env::consts::ARCH) } fn build_target() -> String { let vendor = if cfg!(target_vendor = "apple") { "apple" } else if cfg!(target_vendor = "pc") { "pc" } else { "unknown" }; let env = if cfg!(target_env = "msvc") { "msvc" } else if cfg!(target_env = "gnu") { "gnu" } else if cfg!(target_env = "musl") { "musl" } else { "" }; let suffix = if env.is_empty() { String::new() } else { format!("-{env}") }; format!( "{}-{}-{}{}", std::env::consts::ARCH, vendor, std::env::consts::OS, suffix ) } /// Build time: the modification time of the running executable (the moment /// cargo wrote it), formatted like the Nushell build_time row. fn build_time() -> String { let modified = std::env::current_exe() .ok() .and_then(|p| std::fs::metadata(p).ok()) .and_then(|m| m.modified().ok()); modified .map(|t| { let dt: chrono::DateTime = t.into(); dt.format("%Y-%m-%d %H:%M:%S +00:00").to_string() }) .unwrap_or_default() } /// Render the version table (two columns, boxed, no header row), colored /// with the active theme: keys in the key style, values in the value style. pub fn version_table(info: &VersionInfo, theme: &Theme, color: bool) -> String { let rows: Vec<(String, String)> = vec![ ("version".to_string(), info.version.clone()), ("major".to_string(), info.major.to_string()), ("minor".to_string(), info.minor.to_string()), ("patch".to_string(), info.patch.to_string()), ("branch".to_string(), info.branch.clone()), ("commit_hash".to_string(), info.commit_hash.clone()), ("build_os".to_string(), info.build_os.clone()), ("build_target".to_string(), info.build_target.clone()), ("rust_version".to_string(), info.rust_version.clone()), ("rust_channel".to_string(), info.rust_channel.clone()), ("cargo_version".to_string(), info.cargo_version.clone()), ("build_time".to_string(), info.build_time.clone()), ("build_rust_channel".to_string(), info.build_rust_channel.clone()), ("allocator".to_string(), info.allocator.clone()), ("theme".to_string(), theme.name.to_string()), ]; crate::output::kv_table(&rows, theme, color) } /// Print the version table. pub fn print_version(app: &App) { print!("{}", version_table(&version_info(), app.theme(), app.color())); } // --------------------------------------------------------------------------- // -h/--help interception (runs before clap parses the command line) // --------------------------------------------------------------------------- /// Intercept Nushell-style help requests on the raw command line: /// /// * 'am -h' / 'am --help' → the general help /// * 'am -h|--help' (also 'am ls -h' for shell commands) → the /// command help; flags after '--' are never intercepted, so /// 'am run -- --help' still passes --help through to the agent. /// /// Returns Some(exit code) when a help request was handled, None when clap /// should parse the command line normally. pub fn intercept(args: &[String]) -> Option { if args.len() < 2 { return None; } let color = !args.iter().any(|a| a == "--no-color") && std::io::stdout().is_terminal(); let theme = crate::theme::default_theme(); let mut topic: Option<&'static HelpSpec> = None; for token in args.iter().skip(1) { let t = token.as_str(); if t == "--" { break; } if t == "-h" || t == "--help" { match topic { None => { print!("{}", render_general(theme, color)); return Some(0); } Some(spec) => { print!("{}", render_command(spec, theme, color)); return Some(0); } } } if topic.is_none() { topic = find_spec(t); } } None } #[cfg(test)] mod tests { use super::*; fn t() -> &'static Theme { crate::theme::default_theme() } fn cli_args(v: &[&str]) -> Vec { std::iter::once("am".to_string()) .chain(v.iter().map(|s| s.to_string())) .collect() } #[test] fn renders_ls_help_sections() { let text = render_command(find_spec("ls").unwrap(), t(), false); for part in [ "List the filenames, sizes, and modification times", "Search terms:", "Usage:", "> ls {flags} [path]", "Flags:", "-h, --help", "Command Type:", "built-in", "Parameters:", "Input/output types:", "Examples:", "> ls", ] { assert!(text.contains(part), "missing {part:?} in:\n{text}"); } } #[test] fn renders_install_help_flags() { let text = render_command(find_spec("install").unwrap(), t(), false); assert!(text.contains("--force")); assert!(text.contains("--method")); assert!(text.contains("")); assert!(text.contains("Global Flags:")); } #[test] fn renders_config_subcommands() { let text = render_command(find_spec("config").unwrap(), t(), false); assert!(text.contains("Subcommands:")); assert!(text.contains("validate")); } #[test] fn intercepts_help_flags() { assert!(intercept(&cli_args(&["ls", "--help"])).is_some()); assert!(intercept(&cli_args(&["-h"])).is_some()); assert!(intercept(&cli_args(&["--help"])).is_some()); assert!(intercept(&cli_args(&["install", "-h"])).is_some()); assert!(intercept(&cli_args(&["list", "-v", "--help"])).is_some()); assert!(intercept(&cli_args(&["run", "--help"])).is_some()); assert!(intercept(&cli_args(&["run", "claude-code", "--help"])).is_some()); // No help flag at all: clap parses normally. assert!(intercept(&cli_args(&["list"])).is_none()); assert!(intercept(&cli_args(&["install", "claude-code"])).is_none()); // -- stops flag parsing. assert!(intercept(&cli_args(&["list", "--", "-h"])).is_none()); assert!(intercept(&cli_args(&[])).is_none()); } #[test] fn general_help_has_tips() { let text = render_general(t(), false); assert!(text.contains("Welcome to Agent Manager.")); assert!(text.contains("help commands")); assert!(text.contains("help --find")); assert!(text.contains("[Examples]")); assert!(text.contains("> am install claude-code")); } #[test] fn find_matches() { let hits = matching_specs("install"); assert!(hits.iter().any(|s| s.name == "install")); assert!(hits.iter().any(|s| s.name == "uninstall")); let hits = matching_specs("processes"); assert!(hits.iter().any(|s| s.name == "ps")); } #[test] fn every_spec_has_content() { for s in HELP_SPECS { assert!(!s.about.is_empty(), "{}: empty about", s.name); assert!(!s.usage.is_empty(), "{}: empty usage", s.name); assert!(!s.examples.is_empty(), "{}: no examples", s.name); } } #[test] fn version_table_is_aligned() { let info = version_info(); let text = version_table(&info, t(), false); let widths: Vec = text.lines().map(|l| l.chars().count()).collect(); assert!( widths.iter().all(|w| *w == widths[0]), "unaligned:\n{text}" ); assert!(text.contains("version")); assert!(text.contains("build_target")); assert!(text.contains("allocator")); assert_eq!(info.version, env!("CARGO_PKG_VERSION")); assert_eq!(info.major, 0); // The minor version follows Cargo.toml: parse it instead of pinning it. let minor: u64 = env!("CARGO_PKG_VERSION") .split('.') .nth(1) .unwrap() .parse() .unwrap(); assert_eq!(info.minor, minor); } #[test] fn build_target_looks_like_a_triple() { let target = build_target(); assert_eq!(target.split('-').count(), 4, "{target}"); assert!(target.contains(std::env::consts::ARCH), "{target}"); } }