Files
agent-manager/src/help.rs
T
bruno 6d51ce9504 feat: v0.7.0 — am run/start --provider/--model : résolution cloud via le registre (base_url + keyring + modèle), fallback cloud quand le modèle local est inconnu, canal model_env/model_arg/env_map, jamais de token en clair (closes #92)
- apply_provider : providers::resolve_for (flag > agent > défaut) + apply_env_map_resolved (api_key = @secret, base_url/model du provider résolu)
- start/run/restart : --provider et --model dans le CLI, le REPL et la complétion ; specs help + man pages
- Tests unitaires : résolution, cascade, canal déclaré, erreur provider inconnu (liste), absence de fuite (env sans valeur sk-), fallback embarqué
2026-08-19 21:05:39 -04:00

2208 lines
89 KiB
Rust

//! Nushell-style help: every command documented as a spec, rendered like
//! 'nu help <command>' (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-<ID>.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 <file> {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 <file> {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} <agent>",
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} <agent>",
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} <agent>",
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} <update|add|list> [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 <query>",
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} <agent|--all>",
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} <add|remove|list> [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} <set|unset|list>",
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/<p>/<VAR>, 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} <list|show|add|remove|default>",
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/<name>/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} <keyword>",
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} <agent>",
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} <agent>",
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} <agent>",
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} <agent>",
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:<name>" },
],
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} <agent>",
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:<name>" },
],
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} <agent>",
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:<name>" },
],
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} <agent> ...(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 <agent> {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 <cmd...> {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 <id>" },
HelpExample { desc: "Run a schedule now (used by the OS task).", code: "schedule run <id>" },
],
},
HelpSpec {
name: "config",
category: "Commands",
usage: "config {flags} <subcommand>",
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 <file>", "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} <shell>",
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<bash, zsh, fish, powershell, elvish>", 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} <file>",
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} <agent>",
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} <agent>",
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} <agent> [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} <agent> <tag>...",
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} <agent> <tag>",
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} <list|show> [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-<command>.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} <column> <operator> <value>",
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} <column>",
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<usize> = 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 <name> for commands, aliases and
/// modules" behavior).
pub fn print_command(app: &App, name: &str) -> Result<i32, anyhow::Error> {
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 <text>'"
)
}
// ---------------------------------------------------------------------------
// 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 <name> - display help about a particular command or agent\n"));
out.push_str(&format!(" {bullet} help --find <text to search> - search through all help commands table\n"));
out.push_str(&format!(
" {bullet} theme <name> - switch the color theme (saved) · am --theme <name> 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<i32, anyhow::Error> {
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 <text>': 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<String> {
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<String> {
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<chrono::Utc> = 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 <command> -h|--help' (also 'am ls -h' for shell commands) → the
/// command help; flags after '--' are never intercepted, so
/// 'am run <agent> -- --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<i32> {
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<String> {
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("<agent>"));
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<usize> = 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}");
}
}