Axe 8 (sécurité) : - am audit : checksums SHA-256 des configs (embarquée, utilisateur, locale) stockés dans state.json, croisement avec events.jsonl pour dater les changements, détection des modifications manuelles hors de am (statut MANUAL), rapport lisible + --json stable (contrat) - backup automatique avant chaque am update (agent ou --all) : état + config utilisateur + répertoires d'installation + shims dans <state>/backups/<ts>/ avec manifest.json - am update --rollback [point] : restaure le dernier backup (ou un id précis, préfixe accepté), arrête les agents concernés d'abord, événement EventKind::Rollback journalisé - am update --rollback list : liste les points de restauration - rétention settings.backups_keep (défaut 5) appliquée après chaque backup - événements EventKind::Backup / EventKind::Rollback pour l'audit REPL : commande audit + update --rollback (parse, complétion), help specs, tip cheat sheet, man pages régénérées (50 pages). Version 0.4.6, ROADMAP cases cochées, 272 tests verts.
593 lines
21 KiB
Rust
593 lines
21 KiB
Rust
//! Command line interface definition (clap v4, derive API).
|
|
|
|
use clap::{ArgAction, Args, Parser, Subcommand};
|
|
use clap_complete::Shell;
|
|
use std::path::PathBuf;
|
|
|
|
/// Manage local AI coding agents: install, start, stop, update.
|
|
#[derive(Parser, Debug, Clone)]
|
|
#[command(
|
|
name = "am",
|
|
bin_name = "am",
|
|
version,
|
|
// The help subcommand is provided by the custom Nushell-style help
|
|
// (cli::Command::Help) instead of clap's generated one.
|
|
disable_help_subcommand = true,
|
|
about = "agent-manager (am) — manage local AI coding agents",
|
|
long_about = "agent-manager (am) — a CLI to manage local AI coding agents.
|
|
|
|
Install, uninstall, start, stop, restart and update AI agents from a YAML
|
|
catalog. Handles dependencies (Node, Python, Go, Rust, Bun), binaries from
|
|
GitHub Releases, install scripts, and git repositories. All state lives under
|
|
your user directories; nothing pollutes the system."
|
|
)]
|
|
pub struct Cli {
|
|
/// Use an alternate configuration file
|
|
#[arg(short = 'c', long, global = true, value_name = "FILE")]
|
|
pub config: Option<PathBuf>,
|
|
|
|
/// Verbose output: show every command executed and its details
|
|
#[arg(short = 'v', long, global = true, action = ArgAction::SetTrue)]
|
|
pub verbose: bool,
|
|
|
|
/// Quiet mode: only errors are printed
|
|
#[arg(short = 'q', long, global = true, conflicts_with = "verbose", action = ArgAction::SetTrue)]
|
|
pub quiet: bool,
|
|
|
|
/// Answer yes to every confirmation prompt
|
|
#[arg(short = 'y', long, global = true, action = ArgAction::SetTrue)]
|
|
pub yes: bool,
|
|
|
|
/// Simulate the action without changing anything
|
|
#[arg(long, global = true, action = ArgAction::SetTrue)]
|
|
pub dry_run: bool,
|
|
|
|
/// Emit machine-readable JSON on stdout (for scripts)
|
|
#[arg(long, global = true, action = ArgAction::SetTrue)]
|
|
pub json: bool,
|
|
|
|
/// Disable ANSI colors
|
|
#[arg(long, global = true, action = ArgAction::SetTrue)]
|
|
pub no_color: bool,
|
|
|
|
/// Color theme for the output (see 'am help theme' for the list)
|
|
#[arg(long, global = true, value_name = "THEME")]
|
|
pub theme: Option<String>,
|
|
|
|
#[command(subcommand)]
|
|
pub command: Option<Command>,
|
|
}
|
|
|
|
#[derive(Subcommand, Debug, Clone)]
|
|
pub enum Command {
|
|
/// List installed agents (default); use --all for the whole catalog
|
|
List {
|
|
/// Show every agent known to the catalog, not only the installed ones
|
|
#[arg(long)]
|
|
all: bool,
|
|
/// Only show agents of this category
|
|
#[arg(long, value_name = "CATEGORY")]
|
|
category: Option<String>,
|
|
/// Only show running agents
|
|
#[arg(long)]
|
|
running: bool,
|
|
/// Sort by name, version or status (default: name)
|
|
#[arg(long, value_name = "KEY")]
|
|
sort: Option<String>,
|
|
},
|
|
/// Tail the log file of an agent (--follow for a live view)
|
|
Logs {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
/// Number of lines to show (default: 20)
|
|
#[arg(long, value_name = "N", default_value = "20")]
|
|
lines: usize,
|
|
/// Follow the log as it grows
|
|
#[arg(long)]
|
|
follow: bool,
|
|
},
|
|
/// Open an agent's installation directory in the file manager
|
|
Open {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
},
|
|
/// Supervise an agent and optionally restart it on crash
|
|
Watch {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
/// Restart the agent automatically when it dies (with backoff)
|
|
#[arg(long)]
|
|
restart: bool,
|
|
/// Desktop notification on every restart
|
|
#[arg(long)]
|
|
notify: bool,
|
|
/// Check interval in seconds (default: 5)
|
|
#[arg(long, value_name = "SECS", default_value = "5")]
|
|
interval: u64,
|
|
},
|
|
/// Live TUI dashboard: overview, activity, stats, sessions, projects
|
|
Dashboard,
|
|
/// Mark an agent as a favorite (star in list and status)
|
|
Favorite {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
},
|
|
/// Remove the favorite star from an agent
|
|
Unfavorite {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
},
|
|
/// Attach a free-form note to an agent (shown in 'am info');
|
|
/// 'am note <agent>' without text prints the current note
|
|
Note {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
/// Note text (several words are joined); empty prints the note
|
|
#[arg(value_name = "TEXT...", num_args = 0..)]
|
|
text: Vec<String>,
|
|
},
|
|
/// Add a personal tag to an agent
|
|
Tag {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
/// One or more tags to add
|
|
#[arg(value_name = "TAG", num_args = 1.., required = true)]
|
|
words: Vec<String>,
|
|
},
|
|
/// Remove a personal tag from an agent
|
|
Untag {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
/// Tag to remove
|
|
tag: String,
|
|
},
|
|
/// List personal tags (with counts), optionally for one agent
|
|
Tags {
|
|
/// Only show the tags of this agent
|
|
agent: Option<String>,
|
|
},
|
|
/// Manage environment profiles (dev, prod, ...)
|
|
#[command(subcommand)]
|
|
Profile(ProfileCmd),
|
|
/// Generate a man page for am (or for one command)
|
|
Man {
|
|
/// Command name (omit for the whole CLI)
|
|
command: Option<String>,
|
|
/// Write the page(s) to this directory instead of stdout
|
|
#[arg(long, value_name = "DIR")]
|
|
output: Option<PathBuf>,
|
|
},
|
|
/// Set or show the active color theme (persisted for the next run)
|
|
Theme {
|
|
/// Theme name (omit to list available themes)
|
|
name: Option<String>,
|
|
},
|
|
/// Internal: list agent names for shell completions (hidden)
|
|
#[command(name = "_agents", hide = true)]
|
|
Agents {
|
|
/// Only installed agents (managed + external), aliases and groups
|
|
#[arg(long)]
|
|
installed: bool,
|
|
},
|
|
/// Manage command aliases (cc -> claude-code)
|
|
#[command(subcommand)]
|
|
Alias(AliasCmd),
|
|
/// Manage secrets in the OS keyring (never in plaintext config)
|
|
#[command(subcommand)]
|
|
Secret(SecretCmd),
|
|
/// Audit the configuration: who changed what, when (issue #63)
|
|
Audit,
|
|
/// List local models (ollama, llama.cpp, LM Studio) and prune unused ones
|
|
Models(ModelsArgs),
|
|
/// Manage remote catalogs: update the official one, add external ones
|
|
#[command(subcommand)]
|
|
Catalog(CatalogCmd),
|
|
/// Suggest agents matching a query, boosted by real usage (issue #61)
|
|
Suggest {
|
|
/// Free-form request ("un agent pour du Python")
|
|
#[arg(value_name = "QUERY", num_args = 1.., required = true)]
|
|
words: Vec<String>,
|
|
},
|
|
/// Start an agent (foreground by default, or detached with --background)
|
|
Start(StartArgs),
|
|
/// Stop a background agent (SIGTERM, then SIGKILL after the timeout)
|
|
Stop {
|
|
/// Agent name, alias, or group:<name>
|
|
agent: String,
|
|
/// Kill immediately instead of terminating gracefully
|
|
#[arg(long)]
|
|
force: bool,
|
|
/// Grace period in seconds before killing (default: from config, 5s)
|
|
#[arg(long, value_name = "SECS")]
|
|
timeout: Option<u64>,
|
|
},
|
|
/// Restart an agent: stop, then start with the same options
|
|
Restart {
|
|
#[command(flatten)]
|
|
start: StartArgs,
|
|
/// Kill immediately instead of terminating gracefully
|
|
#[arg(long)]
|
|
force: bool,
|
|
/// Grace period in seconds before killing (default: from config, 5s)
|
|
#[arg(long, value_name = "SECS")]
|
|
timeout: Option<u64>,
|
|
},
|
|
/// Show the state of one agent, or of every installed agent
|
|
Status {
|
|
/// Agent name or alias (omit to show all installed agents)
|
|
agent: Option<String>,
|
|
},
|
|
/// List the sessions of every agent (and of the interactive shell)
|
|
Sessions {
|
|
/// Only sessions of this agent
|
|
agent: Option<String>,
|
|
/// Only sessions of this project
|
|
#[arg(long, value_name = "PROJECT")]
|
|
project: Option<String>,
|
|
/// Only sessions with this status (running, stopped, failed, interrupted)
|
|
#[arg(long, value_name = "STATUS")]
|
|
status: Option<String>,
|
|
/// Show one session in detail (summary + log excerpt)
|
|
#[arg(long, value_name = "ID")]
|
|
show: Option<String>,
|
|
/// Resume a finished session: relaunch the agent with its recorded arguments
|
|
#[arg(long, value_name = "ID")]
|
|
resume: Option<String>,
|
|
},
|
|
/// Show usage statistics computed from the event journal
|
|
Stats {
|
|
/// Agent name or alias (omit for the global view)
|
|
agent: Option<String>,
|
|
/// Only events of the last period (7d, 30d, 90d, all)
|
|
#[arg(long, value_name = "PERIOD")]
|
|
period: Option<String>,
|
|
},
|
|
/// Show the most used agents (top 10)
|
|
Top {
|
|
/// Only events of the last period (7d, 30d, 90d, all)
|
|
#[arg(long, value_name = "PERIOD")]
|
|
period: Option<String>,
|
|
},
|
|
/// Write a markdown activity report
|
|
Report {
|
|
/// Cover the last 7 days
|
|
#[arg(long, conflicts_with = "last_month")]
|
|
last_week: bool,
|
|
/// Cover the last 30 days
|
|
#[arg(long)]
|
|
last_month: bool,
|
|
/// Output file (default: report-YYYYMMDD.md in the state directory)
|
|
#[arg(long, value_name = "FILE")]
|
|
output: Option<PathBuf>,
|
|
},
|
|
/// Show which agent works on which project
|
|
Projects {
|
|
/// Project name (omit to list every project)
|
|
name: Option<String>,
|
|
},
|
|
/// One chronological view of every activity (journal + REPL history)
|
|
Timeline {
|
|
/// Only activity of this agent
|
|
agent: Option<String>,
|
|
/// Only activity of this project
|
|
#[arg(long, value_name = "PROJECT")]
|
|
project: Option<String>,
|
|
/// Only activity at or after this date (YYYY-MM-DD or RFC 3339)
|
|
#[arg(long, value_name = "DATE")]
|
|
since: Option<String>,
|
|
/// Maximum number of entries (default: 50)
|
|
#[arg(long, value_name = "N", default_value = "50")]
|
|
limit: usize,
|
|
},
|
|
/// Read the event journal: everything am did
|
|
Log {
|
|
/// Only events of this agent
|
|
agent: Option<String>,
|
|
/// Only these event kinds (comma separated: start,stop,run,install,...)
|
|
#[arg(long, value_name = "KINDS")]
|
|
kind: Option<String>,
|
|
/// Only events at or after this date (YYYY-MM-DD or RFC 3339)
|
|
#[arg(long, value_name = "DATE")]
|
|
since: Option<String>,
|
|
/// Maximum number of events (default: 50)
|
|
#[arg(long, value_name = "N", default_value = "50")]
|
|
limit: usize,
|
|
/// Follow the journal as new events arrive
|
|
#[arg(long)]
|
|
follow: bool,
|
|
},
|
|
/// Install an agent and its dependencies
|
|
Install {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
/// Select the install method (index, or type: npm, pip, uv, cargo, go, bun, curl, binary, git)
|
|
#[arg(long, value_name = "METHOD")]
|
|
method: Option<String>,
|
|
/// Reinstall even if already installed
|
|
#[arg(long)]
|
|
force: bool,
|
|
},
|
|
/// Uninstall an agent (managed or external) and clean its files
|
|
Uninstall {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
/// Also remove logs and the agent entry from the user config file
|
|
#[arg(long)]
|
|
purge: bool,
|
|
},
|
|
/// Update an installed agent to the latest available version
|
|
Update {
|
|
/// Agent name or alias (required unless --all)
|
|
agent: Option<String>,
|
|
/// Update every installed managed agent
|
|
#[arg(long)]
|
|
all: bool,
|
|
/// Roll back to a pre-update backup instead of updating
|
|
/// ("latest" or a backup id; "--rollback list" shows them)
|
|
#[arg(long, num_args = 0..=1, default_missing_value = "latest")]
|
|
rollback: Option<String>,
|
|
},
|
|
/// Search the catalog by keyword (name, description, category, tags)
|
|
Search {
|
|
/// Keyword to search for (case-insensitive substring)
|
|
#[arg(value_name = "KEYWORD", required_unless_present = "tag")]
|
|
keyword: Option<String>,
|
|
/// Restrict to this category
|
|
#[arg(long, value_name = "CATEGORY")]
|
|
category: Option<String>,
|
|
/// Only agents carrying this personal tag
|
|
#[arg(long, value_name = "TAG")]
|
|
tag: Option<String>,
|
|
},
|
|
/// Search the command history (am and shell commands)
|
|
History {
|
|
/// Only commands of this kind (am or shell)
|
|
#[arg(long, value_name = "KIND")]
|
|
kind: Option<String>,
|
|
/// Only commands run in this directory
|
|
#[arg(long, value_name = "DIR")]
|
|
cwd: Option<String>,
|
|
/// Full-text search in the command line
|
|
#[arg(long, value_name = "TEXT")]
|
|
search: Option<String>,
|
|
/// Only failed commands
|
|
#[arg(long)]
|
|
failed: bool,
|
|
/// Only commands of this session
|
|
#[arg(long, value_name = "ID")]
|
|
session: Option<String>,
|
|
/// Maximum number of entries (default: 100)
|
|
#[arg(long, value_name = "N", default_value = "100")]
|
|
limit: usize,
|
|
/// Re-run the Nth most recent entry (with confirmation)
|
|
#[arg(long, value_name = "N")]
|
|
rerun: Option<usize>,
|
|
},
|
|
/// Show detailed information about one agent
|
|
Info {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
},
|
|
/// Generate a local agent-manager.yaml for the current directory
|
|
Init {
|
|
/// Overwrite an existing agent-manager.yaml
|
|
#[arg(long)]
|
|
force: bool,
|
|
},
|
|
/// Display help: the overview, a command, the command list, or a search
|
|
Help {
|
|
/// Command name (also accepts an agent name or alias); "commands" lists every command
|
|
command: Option<String>,
|
|
/// Search through all help commands table
|
|
#[arg(long, value_name = "TEXT")]
|
|
find: Option<String>,
|
|
},
|
|
/// Display version and build information
|
|
Version,
|
|
/// Cheat sheet: the most useful commands, their key options, your
|
|
/// most-used commands and a rotating tip of the day
|
|
Tip {
|
|
/// Show a single random tip instead of the whole page
|
|
#[arg(long)]
|
|
random: bool,
|
|
},
|
|
/// Manage the configuration file
|
|
#[command(subcommand)]
|
|
Config(ConfigCmd),
|
|
/// Check the environment: tools, config validity, paths, permissions
|
|
Doctor {
|
|
/// Attempt to repair problems (create directories, fix the state file)
|
|
#[arg(long)]
|
|
fix: bool,
|
|
},
|
|
/// Run the agent command directly with the given arguments (no process management)
|
|
Run {
|
|
/// Agent name or alias
|
|
agent: String,
|
|
/// Local model to use for this run (validated against the local runtimes)
|
|
#[arg(long, value_name = "MODEL")]
|
|
model: Option<String>,
|
|
/// Arguments passed through to the agent command
|
|
#[arg(trailing_var_arg = true, allow_hyphen_values = true, value_name = "ARGS...")]
|
|
args: Vec<std::ffi::OsString>,
|
|
},
|
|
/// Generate a shell completion script
|
|
Completion {
|
|
/// Shell to generate completions for
|
|
#[arg(value_enum, value_name = "SHELL")]
|
|
shell: Shell,
|
|
/// Add dynamic completion of the installed agents, aliases and
|
|
/// groups (queried from 'am _agents' at Tab time)
|
|
#[arg(long)]
|
|
installed: bool,
|
|
},
|
|
/// Update agent-manager itself from the latest release
|
|
SelfUpdate {
|
|
/// Only check whether a newer version exists
|
|
#[arg(long)]
|
|
check: bool,
|
|
/// Download the new binary to this path instead of replacing the current one
|
|
#[arg(long, value_name = "FILE")]
|
|
to: Option<PathBuf>,
|
|
},
|
|
/// Remove agent-manager and everything it created from this machine
|
|
///
|
|
/// Stops every managed background agent, then deletes the data, state and
|
|
/// configuration directories, and finally the executable itself. Combine
|
|
/// with --yes for a fully automated one-liner.
|
|
SelfUninstall,
|
|
/// Export the configuration and installation state (backup)
|
|
Export {
|
|
/// Output file (default: am-export.json)
|
|
#[arg(long, value_name = "FILE", default_value = "am-export.json")]
|
|
output: PathBuf,
|
|
},
|
|
/// Import a previously exported configuration and state
|
|
Import {
|
|
/// File exported by the export command
|
|
file: PathBuf,
|
|
},
|
|
}
|
|
|
|
/// Arguments shared by start and restart.
|
|
#[derive(Args, Debug, Clone, Default)]
|
|
pub struct StartArgs {
|
|
/// Agent name, alias, or group:<name> (defaults to the project's default agent)
|
|
pub agent: Option<String>,
|
|
/// Run detached in the background; output goes to the agent log file
|
|
#[arg(short = 'b', long, conflicts_with = "foreground", action = ArgAction::SetTrue)]
|
|
pub background: bool,
|
|
/// Run in the foreground, attached to this terminal (the default)
|
|
#[arg(short = 'f', long, action = ArgAction::SetTrue)]
|
|
pub foreground: bool,
|
|
/// Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)
|
|
#[arg(long, value_name = "ARGS", num_args = 0.., action = ArgAction::Append)]
|
|
pub args: Vec<String>,
|
|
/// Set an environment variable for the agent (repeatable): --env KEY=VALUE
|
|
#[arg(long, value_name = "KEY=VALUE", action = ArgAction::Append)]
|
|
pub env: Vec<String>,
|
|
/// Send a desktop notification once the agent has started
|
|
#[arg(long, action = ArgAction::SetTrue)]
|
|
pub notify: bool,
|
|
/// Apply an environment profile (env + args, defined in the config)
|
|
#[arg(long, value_name = "NAME")]
|
|
pub profile: Option<String>,
|
|
/// Local model to use for this run (validated against the local runtimes)
|
|
#[arg(long, value_name = "MODEL")]
|
|
pub model: Option<String>,
|
|
}
|
|
|
|
/// Arguments of the models command: inventory by default, prune with --prune.
|
|
#[derive(Args, Debug, Clone, Default)]
|
|
pub struct ModelsArgs {
|
|
/// Purge unused models instead of listing them
|
|
#[arg(long)]
|
|
pub prune: bool,
|
|
/// Only list what would be pruned, delete nothing
|
|
#[arg(long, conflicts_with = "yes")]
|
|
pub dry_run: bool,
|
|
/// Answer yes to the deletion prompt
|
|
#[arg(long)]
|
|
pub yes: bool,
|
|
/// Consider a model unused after N days (default: 30)
|
|
#[arg(long, value_name = "DAYS")]
|
|
pub days: Option<u64>,
|
|
}
|
|
|
|
/// Remote catalog subcommands (issues #60 #67).
|
|
#[derive(Subcommand, Debug, Clone)]
|
|
pub enum CatalogCmd {
|
|
/// Fetch the official catalog, show the diff and register it as an include
|
|
Update,
|
|
/// Register an external catalog (same YAML format) by URL
|
|
Add {
|
|
/// Catalog URL (http, https or file)
|
|
#[arg(value_name = "URL")]
|
|
url: String,
|
|
},
|
|
/// List the registered remote catalogs
|
|
List,
|
|
}
|
|
|
|
/// Environment profile subcommands (issue #40).
|
|
#[derive(Subcommand, Debug, Clone)]
|
|
pub enum ProfileCmd {
|
|
/// List every defined profile
|
|
List,
|
|
/// Show one profile in detail (agent, env, args)
|
|
Show {
|
|
/// Profile name
|
|
name: String,
|
|
},
|
|
}
|
|
|
|
/// Secret management subcommands.
|
|
#[derive(Subcommand, Debug, Clone)]
|
|
pub enum SecretCmd {
|
|
/// Store a secret for an agent
|
|
Set {
|
|
/// Secret name (the environment variable name)
|
|
name: String,
|
|
/// Agent it belongs to
|
|
#[arg(long, value_name = "AGENT")]
|
|
agent: String,
|
|
/// Secret value (prefer --value over shell history; never logged)
|
|
#[arg(long, value_name = "VALUE")]
|
|
value: String,
|
|
},
|
|
/// Remove a secret
|
|
Unset {
|
|
/// Secret name
|
|
name: String,
|
|
/// Agent it belongs to
|
|
#[arg(long, value_name = "AGENT")]
|
|
agent: String,
|
|
},
|
|
/// List stored secret names (values are never shown)
|
|
List,
|
|
}
|
|
|
|
/// Alias management subcommands.
|
|
#[derive(Subcommand, Debug, Clone)]
|
|
pub enum AliasCmd {
|
|
/// Add an alias (am alias add cc claude-code)
|
|
Add {
|
|
/// Short name of the alias
|
|
name: String,
|
|
/// Agent name (or another alias) it points to
|
|
target: String,
|
|
},
|
|
/// Remove an alias
|
|
Remove {
|
|
/// Alias to remove
|
|
name: String,
|
|
},
|
|
/// List every alias
|
|
List,
|
|
}
|
|
|
|
/// Configuration management subcommands.
|
|
#[derive(Subcommand, Debug, Clone)]
|
|
pub enum ConfigCmd {
|
|
/// Print the effective (merged) configuration as YAML
|
|
Show,
|
|
/// Print the location of the configuration file(s)
|
|
Path,
|
|
/// Open the user configuration file in your editor
|
|
Edit,
|
|
/// Validate the configuration file(s)
|
|
Validate,
|
|
/// Add another agent definition file to the user configuration
|
|
Add {
|
|
/// YAML (or JSON) file defining agents to include
|
|
file: PathBuf,
|
|
},
|
|
/// Set one configuration key without an editor (config set settings.default_shell pwsh)
|
|
Set {
|
|
/// Dotted key path, e.g. settings.default_shell
|
|
key: String,
|
|
/// New value (booleans and integers keep their type)
|
|
value: String,
|
|
},
|
|
}
|