Compare commits

...
70 Commits
Author SHA1 Message Date
bruno 91253b79c5 ci : files de gitea-release-action en bloc YAML (retours a la ligne) - l'action splitte sur \n, le bloc plie en une ligne et ne trouvait aucun fichier ('not include valid file' -> rien n'etait uploade)
release / linux (push) Successful in 13m21s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s
2026-08-17 14:38:13 -04:00
bruno 3c8bb93b8a ci : installer nodejs dans le conteneur avant actions/checkout (le post-step checkout exige node, absent de rust:1.94-alpine) - le job linux echouait avec 'exec: node: executable file not found' apres la reussite du pull d'image
release / linux (push) Successful in 13m4s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s
2026-08-17 14:20:11 -04:00
bruno 363d5e3286 ci : documente l'espace disque requis sur le runner dans le workflow
release / linux (push) Failing after 48s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s
2026-08-17 14:17:09 -04:00
bruno 4c60ab5bcc docs : methode de build linux musl avec cargo-zigbuild (aarch64 + x86_64) dans RELEASING.md
release / linux (push) Failing after 22s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s
2026-08-17 13:42:53 -04:00
bruno e24e518846 ci : job linux avec cargo-zigbuild (aarch64 musl) + empreinte disque reduite - rust:1.94-alpine n'a pas de compilateur croise aarch64 (le step aarch64 echouait avec 'aarch64-linux-musl-gcc not found') ; cargo-zigbuild produit x86_64 + aarch64 musl avec zig (~50 Mo) en une seule image ; CARGO_TARGET_DIR=/tmp + CARGO_INCREMENTAL=0 pour ne rien laisser sur le runner ; concurrency cancel-in-progress ; le setup job peut aussi echouer sur disque plein (no space left on device) : nettoyer docker sur l'hote
release / linux (push) Failing after 25s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s
2026-08-17 13:38:07 -04:00
bruno 2c079b0ccd v0.4.2 : menu de completion Nushell (reedline) + correction des chemins Windows - remplace rustyline par reedline (menu Tab interactif : 1er choix surbrille, Tab navigue, Entree valide, Esc ferme, filtrage en direct, descriptions, couleurs du theme), tokenizer Windows preservant les backslashes pour cd/ls (shell_words mangeait les \), historique history.txt conserve, repli sans terminal, tests + man pages regeneres
release / linux (push) Failing after 24s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s
2026-08-17 13:08:50 -04:00
bruno e6e85e2a9e ci : release_overwrite + prerequis runner documentes (pull docker.io, secret GITEA_TOKEN)
release / linux (push) Failing after 24s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s
2026-08-17 10:54:36 -04:00
bruno 56732d672b v0.4.1 : Phase 1 complete (17/17) - dashboard TUI (#33), favoris/notes/tags (#39), profils d'environnement (#40), completions dynamiques + man pages (#41), packaging officiel + CI (#42), self-update Gitea, state v3, 212 tests
release / linux (push) Failing after 23s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s
2026-08-17 10:50:13 -04:00
bruno 4f4cd034b2 v0.4.0 : Phase 1 partielle (13/17) - recherche fuzzy, reexecution d'historique, projects+profils, start contextuel, timeline, sessions resume, hooks, watch, secret (keyring), top, report, log --follow 2026-08-17 09:14:48 -04:00
bruno aeca797ae0 Merge pull request 'secret : trousseau OS + injection @secret (issue #36)' (#86) from feat/secrets into main 2026-08-17 09:09:16 -04:00
bruno 354c914c43 secret : trousseau OS (keyring) + injection @secret au start/run (issue #36) - valeurs jamais affichees ni journalisees, mock testable, fallback clair sans trousseau 2026-08-17 09:09:04 -04:00
bruno c57d3a333d Merge pull request 'top + report + log --follow (issues #37 #38)' (#85) from feat/top-report into main 2026-08-17 09:03:58 -04:00
bruno 16926ffb00 top + report + log --follow (issues #37 #38) - top 10 par lancements, digest markdown hebdo/mensuel, suivi du journal en direct 2026-08-17 09:03:44 -04:00
bruno f14b59e63b Merge pull request 'watch : supervision + redémarrage automatique (issue #35)' (#84) from feat/watch into main 2026-08-17 08:58:32 -04:00
bruno 8d93eece1e watch : supervision d'un agent avec redemarrage automatique et backoff (issue #35) - check_once testable, notification desktop, Ctrl-C n'affecte pas l'agent detache 2026-08-17 08:58:19 -04:00
bruno 2a1fcfeed6 Merge pull request 'hooks : on_install/on_start/on_stop/on_update (issue #34)' (#83) from feat/hooks into main 2026-08-17 08:55:49 -04:00
bruno 18784ba9f7 hooks : on_install/on_start/on_stop/on_update globaux (settings.hooks) et par projet (issue #34) - echec non bloquant, dry-run respecte, ordre global puis projet 2026-08-17 08:55:35 -04:00
bruno 3220a323ef Merge pull request 'sessions : am sessions --resume (issue #32)' (#82) from feat/sessions-resume into main 2026-08-17 08:48:45 -04:00
bruno 30cc49de3c sessions : am sessions --resume <id> (issue #32) - relance en arriere-plan avec les arguments et le cwd d'origine, cwd optionnel dans spawn_background, gardes running/repl/erreurs claires 2026-08-17 08:48:30 -04:00
bruno 314583ceee Merge pull request 'timeline : vue chronologique unifiée (issue #31)' (#81) from feat/timeline into main 2026-08-17 08:45:16 -04:00
bruno ed795c522f timeline : vue chronologique unifiee journal + historique REPL (issue #31) - filtres agent/projet/date, limite, parse_since partage 2026-08-17 08:45:03 -04:00
bruno bbc2bb889d roadmap : decoupage des phases 2 (v0.5.0, issues #47 #49-#75) et 3 (v1.0, issues #48 #76-#80) sur Gitea 2026-08-17 08:15:16 -04:00
bruno 904e340cdd Merge pull request 'start contextuel : agent par défaut du projet (issue #30)' (#46) from feat/start-context into main 2026-08-17 08:13:35 -04:00
bruno 5f8b973789 start contextuel : am start sans argument lance l'agent par defaut du projet (issue #30) - resolution pure resolve_start_target, restart identique, REPL sans agent obligatoire 2026-08-17 08:13:19 -04:00
bruno 463fa6b937 Merge pull request 'projects : am projects + profils de projet (issue #29)' (#45) from feat/projects into main 2026-08-17 08:10:57 -04:00
bruno dc7372f277 projects : am projects (agregats par depot depuis le journal) + profils de projet dans la config (issue #29) - agent dominant, sessions, durees, cache projects.json rafraichi par doctor --fix 2026-08-17 08:10:42 -04:00
bruno a23bc6b5ae Merge pull request 'history : réexécution !! !N ^old^new + am history --rerun (issues #27 #28)' (#44) from feat/history-rerun into main 2026-08-17 08:06:16 -04:00
bruno 81c69db17b history : reexecution (issues #27 #28) - expansion !! !N !prefix ^old^new dans le REPL + am history --rerun N avec confirmation (shell ou commande am) 2026-08-17 08:05:58 -04:00
bruno 9e6c8aca08 Merge pull request 'search : recherche fuzzy + suggestions (issue #26)' (#43) from feat/fuzzy-search into main 2026-08-17 08:01:56 -04:00
bruno 4ef89cde72 search : recherche fuzzy avec scoring + suggestions vouliez-vous-dire (issue #26) - sous-chaine, subsequence, tolerance de frappe (Levenshtein), tri par pertinence 2026-08-17 08:01:39 -04:00
bruno 2eaafe6e2a v0.3.0 : picoclaw en binaire 64-bit officiel (sipeed/picoclaw releases) + shims git en .cmd sur Windows (fini le batch renomme en .exe qui donnait Unsupported 16-Bit Application) avec routage via cmd.exe 2026-08-17 07:52:17 -04:00
bruno deeb489b4e v0.3.0 : menu d autocompletion - les barres verticales des menus en colonne unique (ex. completion de theme) prennent la couleur du cadre du theme 2026-08-17 06:55:07 -04:00
bruno fc243d2f21 docs: decoupage Phase 1 (v0.4.0) dans le ROADMAP - 17 issues + epique, milestone v0.4.0 2026-08-16 23:20:07 -04:00
bruno 6ecfa0d21c v0.3.0 : Phase 0 du ROADMAP livree - journal d'evenements, sessions, stats, log, historique structure, am init, quick wins; README et ROADMAP a jour 2026-08-16 22:48:53 -04:00
bruno 3902bbcb37 Merge pull request 'quick wins #9-#13 et #16 : logs, list --sort, config set, alias, dedup, open' (#24) from feat/quick-wins into main 2026-08-16 22:45:27 -04:00
bruno d4a6aeae1d quick wins (issues #9 #10 #11 #12 #13 #16) : am logs (tail+follow), am list --running/--sort, am config set, am alias add/remove/list, dedup historique, am open 2026-08-16 22:45:11 -04:00
bruno 38d0729cb3 Merge pull request 'init + contexte : am init et détection de projet (issue #8)' (#23) from feat/init-context into main 2026-08-16 22:40:15 -04:00
bruno 85d44f9e56 init+contexte : am init genere un agent-manager.yaml selon la stack detectee + contexte projet (git root, branche, stack) attache aux evenements et sessions (issue #8) 2026-08-16 22:40:00 -04:00
bruno 48e85555f7 Merge pull request 'history : historique structuré + am history (issue #7)' (#22) from feat/history into main 2026-08-16 22:35:01 -04:00
bruno c9e2543f50 history : historique structure par session + am history (issue #7) - migration one-shot de history.txt, filtres kind/cwd/search/failed/session, enregistrement REPL avec duree et exit 2026-08-16 22:34:48 -04:00
bruno 9f4a13eae2 Merge pull request 'stats + log : observabilité du journal (issue #6)' (#21) from feat/stats-log into main 2026-08-16 22:30:54 -04:00
bruno 33748bea36 stats+log : am stats (agregats par agent, periodes 7d/30d/90d, barres ASCII) et am log (filtres agent/kind/date, limite) (issue #6) 2026-08-16 22:30:41 -04:00
bruno 19e77fd261 Merge pull request 'sessions : registre central + am sessions (issue #5)' (#20) from feat/sessions into main 2026-08-16 22:22:23 -04:00
bruno 38b7a3a915 sessions : registre des sessions + commande am sessions (issue #5) - lifecycle agent/repl, reconciliation des sessions interrompues, compteur sessions_count, filtres agent/projet/statut, show avec extrait de log 2026-08-16 22:22:09 -04:00
bruno 3af0834bae Merge pull request 'state : v2 — sessions_count et last_used (issue #4)' (#19) from feat/state-v2 into main 2026-08-16 22:16:56 -04:00
bruno c6518bcfd5 state : v2 avec sessions_count et last_used (issue #4) - migration additive persistee au premier contact, touch() sur start/run, fixture v1 testee 2026-08-16 22:16:44 -04:00
bruno a672880e34 Merge pull request 'events : instrumentation des commandes (issue #3)' (#18) from feat/instrumentation into main 2026-08-16 22:14:03 -04:00
bruno 310da96a63 events : instrumentation start/stop/run/install/update/uninstall/doctor/repl/shell (issue #3) - un evenement JSONL par action reelle, dry-run sans evenement, session id sur start, duree et exit code sur stop/run 2026-08-16 22:13:49 -04:00
bruno ba901df953 v0.2.14 : fallback web GitHub pour les releases - quand l API est rate-limitee, le tag vient de la redirection /releases/latest et les assets de expanded_assets (aucune limite) - jcode et zeroclaw installables sans GITHUB_TOKEN 2026-08-16 21:53:25 -04:00
bruno e50e8d3e41 v0.2.14 : kimi-cli via uv (paquet PyPI kimi-cli, fini le npm inexistant) + uv venv autonome (python 3.13 gere par uv, plus besoin de python systeme pour les installs uv) 2026-08-16 21:41:21 -04:00
bruno 498af684c9 v0.2.14 : support GITHUB_TOKEN/GH_TOKEN pour l API GitHub (limite 60 -> 5000 requetes/h) et message clair sur le rate limit 403 2026-08-16 21:30:31 -04:00
bruno 2282f71829 v0.2.14 : correction install binaire - detection des archives par magic bytes (un zip renomme n est plus copie tel quel), preference des archives sur les setup/installers, validation de l executable (MZ/ELF/Mach-O) avec erreur claire avant le premier start 2026-08-16 21:23:08 -04:00
bruno f968d7d0b0 v0.2.14 : 9 nouveaux themes - nord, solarized, gruvbox, catppuccin, tokyo-night, monokai, matrix, rose-pine, amber (15 au total), liste des themes sur deux lignes dans la banniere 2026-08-16 21:01:15 -04:00
bruno b3437b0601 v0.2.14 : correction bordure haute du splash - les tirets a droite du titre gardent la couleur du theme (reset du titre ne casse plus le cadre) 2026-08-16 20:45:21 -04:00
bruno 843c3e1278 Merge pull request 'events : journal d'evenements JSONL (issue #2)' (#17) from feat/events-ledger into main 2026-08-16 13:50:24 -04:00
bruno 4db567eebe events : journal d'evenements JSONL (issue #2) - append-only, rotation mensuelle, lecture tolerante aux lignes corrompues, env_keys sans valeurs secretes, 8 tests 2026-08-16 13:49:11 -04:00
bruno 2bbe45e508 v0.2.14 : barres verticales des cadres aux couleurs du theme + commande theme dans le shell (theme liste les themes, theme <name> change a chaud, completion et menu synchronises) 2026-08-16 13:40:51 -04:00
bruno 4b0f02a7c4 v0.2.14 : cadres aux couleurs du theme - bordures des tableaux (list, search, status, doctor, ls, dir, ps, help), version, menu d autocompletion et banniere colorees par theme (border) 2026-08-16 13:17:07 -04:00
bruno 79143e197d v0.2.14 : splash screen theme - art ASCII et bordures aux couleurs du theme, section Theme (theme actif + liste des themes disponibles), ligne de statut theme 2026-08-16 13:10:07 -04:00
bruno 14f873bfdd v0.2.14 : aide Nushell, themes de couleurs et cadres blancs
- Aide facon Nushell : help <cmd>, <cmd> -h/--help, help, help commands,
  help --find <texte>, help <agent> (fiche info) - sections Usage/Flags/
  Command Type/Parameters/Input-output/Examples avec couleurs
- Commande version : tableau Nushell (commit, build, rustc, theme...) + --json ;
  build.rs pour commit/branche git ; -V reste la version courte
- Themes : --theme <nom> (default, ocean, sunset, forest, dracula, mono)
  + settings.theme dans la config ; activation VT Windows pour les couleurs
- Cadres blancs Nushell partages : list, search, status, doctor, ls/ps/
  where/get et menu d autocompletion Tab encadre (colonnes name/description)
2026-08-16 12:52:12 -04:00
bruno 53f3fa34e7 docs: roadmap section 11 liee aux issues Gitea (suivi phase 0 en place) 2026-08-16 12:51:52 -04:00
bruno 5841926b25 docs: roadmap v2 - lecture facilitee (sommaire, legende, quick wins, KPI), 12 axes enrichis (modeles locaux, lab, rapports, couts, favoris, profils), jalons versionnes 2026-08-16 12:31:29 -04:00
bruno 979875fa63 v0.2.13 : prompt avec emojis colores — 🗂️ repertoire, 🤖 catalogue, 🌐 externes, 📊 geres, ⚡ duree de la derniere commande, ⏲ session 2026-08-16 10:43:26 -04:00
bruno 93c32b60ba v0.2.12 : ligne de statut — temps dexecution de la derniere commande (⚡ ms/s) + ls parallele (tailles de dossiers reparties sur les coeurs, un seul appel de metadonnees par entree) — 130 entrees instantanees 2026-08-16 10:29:38 -04:00
bruno 0f4ba71219 v0.2.11 : ligne de statut et banniere — compteurs distincts agents externes (PATH) et agents geres (am install) pour lever l ambiguite du 0 installed 2026-08-16 10:08:54 -04:00
bruno e7cb136c38 v0.2.10 : affichage ls/dir adapte a la largeur du terminal — detection de la largeur de la console (AM_TABLE_WIDTH pour tests), troncature de la colonne nom avec …, suppression des colonnes de fin si necessaire, une ligne par entree 2026-08-16 09:38:54 -04:00
bruno 57234b7a38 v0.2.9 : ps/where/get facon Nushell avec mini-pipeline (ls | where size > 1mb | get name), dir = ls sur Windows, ls beaucoup plus rapide (taille des dossiers bornee a 500 entrees), autocomplet : prefixe commun + menu avec descriptions + fallback chemins 2026-08-16 09:11:09 -04:00
bruno 3e1b66809a v0.2.8 : navigation facon Nushell — commande ls en tableau encadre (type, taille, date), completion Tab des dossiers pour cd (niveau par niveau) et des chemins pour ls, descriptions dans le menu Tab 2026-08-15 23:42:32 -04:00
bruno d886de322c v0.2.7 : self-uninstall supprime maintenant le binaire — renommage immediat de l executable en cours dexecution (am.exe.deleteme) + script .bat temporaire avec reessais, balayage des dossiers vides et auto-suppression 2026-08-15 23:19:28 -04:00
bruno bc38cd59e5 v0.2.6 : colonne AGENT (slug) dans am list + desinstallation des agents externes (npm/pip/uv/cargo/bun ou suppression des fichiers, shims .cmd gerees) 2026-08-15 22:20:51 -04:00
144 changed files with 21104 additions and 1655 deletions
+145
View File
@@ -0,0 +1,145 @@
# Gitea Actions : pipeline de release (issue #42).
#
# Sur chaque tag v* : construit les binaires de toutes les plateformes,
# archive + publie la release Gitea avec les artefacts. Les installateurs
# (install.sh / install.ps1) et 'am self-update' consomment ces artefacts.
#
# Labels de runners attendus : ubuntu-latest, windows-latest, macos-latest.
# Un runner manquant laisse son job en attente ; le job linux couvre la
# majorite des cas (les artefacts windows/macos peuvent aussi etre produits
# par les scripts locaux documentes dans RELEASING.md).
#
# Prérequis du runner : pouvoir tirer les images docker.io (rust:1.94-alpine,
# ~300 Mo) et le secret GITEA_TOKEN (jeton avec droit 'write:release')
# positionné sur le dépôt. La release existante est écrasée
# (release_overwrite).
#
# Note disque (runner avec espace limite) :
# - il faut ~1 Go libres sur / pour tirer l'image (rust:1.94-alpine) ;
# en cas de 'no space left on device' : docker system prune -af --volumes
# sur l'HOTE du runner (pas une autre machine !) ;
# - les builds compilent dans CARGO_TARGET_DIR=/tmp (hors du volume
# workspace) avec l'incremental desactive, donc rien ne persiste ;
# - le job linux utilise cargo-zigbuild : zig (~50 Mo) fournit le linker
# croise pour les DEUX cibles musl statiques dans une seule image legere
# (rust:1.94-alpine n'embarque pas de compilateur croise aarch64).
name: release
on:
push:
tags:
- "v*"
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
jobs:
linux:
runs-on: ubuntu-latest
container: rust:1.94-alpine
env:
CARGO_TARGET_DIR: /tmp/am-target
CARGO_INCREMENTAL: "0"
steps:
# actions/checkout (et son post-step) s'execute via node, absent de
# l'image rust:alpine : sans nodejs le job echoue des le checkout.
- name: Prepare container (node for checkout action)
run: apk add --no-cache nodejs
- uses: actions/checkout@v4
- name: Install build tools (zig + cargo-zigbuild)
run: |
apk add --no-cache build-base zip curl
rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl
curl -fsSL https://ziglang.org/download/0.14.1/zig-x86_64-linux-0.14.1.tar.xz -o /tmp/zig.tar.xz
tar -xf /tmp/zig.tar.xz -C /opt
echo "/opt/zig-x86_64-linux-0.14.1" >> "$GITHUB_PATH"
cargo install cargo-zigbuild --locked
- name: Build linux x86_64 + aarch64 (static musl, zig)
run: cargo zigbuild --release --locked --target x86_64-unknown-linux-musl --target aarch64-unknown-linux-musl
- name: Stage archives
run: |
mkdir -p dist
cp "$CARGO_TARGET_DIR/x86_64-unknown-linux-musl/release/am" dist/am-linux-x86_64
cp "$CARGO_TARGET_DIR/aarch64-unknown-linux-musl/release/am" dist/am-linux-aarch64
cd dist
tar -czf am-linux-x86_64.tar.gz am-linux-x86_64
tar -czf am-linux-aarch64.tar.gz am-linux-aarch64
sha256sum am-linux-*.tar.gz > checksums.txt
- name: Publish release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
# L'action splitte 'files' sur les retours a la ligne (pas les
# espaces) : un bloc YAML plie en une seule ligne et ne trouve
# alors aucun fichier -> 'not include valid file', rien n'upload.
files: |
dist/am-linux-x86_64.tar.gz
dist/am-linux-aarch64.tar.gz
dist/checksums.txt
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
release_overwrite: true
windows:
runs-on: windows-latest
env:
CARGO_TARGET_DIR: C:/am-target
CARGO_INCREMENTAL: "0"
steps:
- uses: actions/checkout@v4
- name: Build windows x86_64
run: cargo build --release --locked
- name: Stage archive
run: |
New-Item -ItemType Directory -Force dist | Out-Null
Compress-Archive -Path C:/am-target/release/am.exe -DestinationPath dist/am-windows-x86_64.zip -Force
(Get-FileHash dist/am-windows-x86_64.zip -Algorithm SHA256).Hash.ToLower() | Out-File dist/checksums.txt
- name: Publish release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
files: |
dist/am-windows-x86_64.zip
dist/checksums.txt
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
release_overwrite: true
macos:
runs-on: macos-latest
env:
CARGO_TARGET_DIR: /tmp/am-target
CARGO_INCREMENTAL: "0"
steps:
- uses: actions/checkout@v4
- name: Build macos x86_64 + aarch64
run: |
rustup target add x86_64-apple-darwin aarch64-apple-darwin
cargo build --release --locked --target x86_64-apple-darwin
cargo build --release --locked --target aarch64-apple-darwin
- name: Stage archives
run: |
mkdir -p dist
cp "$CARGO_TARGET_DIR/x86_64-apple-darwin/release/am" dist/am-macos-x86_64
cp "$CARGO_TARGET_DIR/aarch64-apple-darwin/release/am" dist/am-macos-aarch64
cd dist
tar -czf am-macos-x86_64.tar.gz am-macos-x86_64
tar -czf am-macos-aarch64.tar.gz am-macos-aarch64
shasum -a 256 am-*.tar.gz > checksums.txt
- name: Publish release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
files: |
dist/am-macos-x86_64.tar.gz
dist/am-macos-aarch64.tar.gz
dist/checksums.txt
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
release_overwrite: true
+688
View File
@@ -0,0 +1,688 @@
# 🏗️ Document d'architecture — agent-manager (`am`)
> Ce document décrit la conception, le fonctionnement et les fonctionnalités de **agent-manager** (binaire `am`), un CLI Rust multiplateforme pour gérer des agents IA de coding locaux.
>
> 📦 Version : `0.4.2` · 🦀 Rust 2021 · ✅ Windows · ✅ Linux · ✅ macOS
---
## 🎯 Vue d'ensemble
`am` est un gestionnaire d'agents IA de coding qui permet de :
- 📋 Découvrir et lister plus de **72 agents** via un catalogue YAML embarqué
- 📦 Les installer avec **9 méthodes** différentes (npm, pip, uv, cargo, go, bun, curl, binaire, git)
- 🚀 Les démarrer, arrêter, redémarrer en avant-plan ou en arrière-plan
- 🔍 Observer leur état, leurs logs, leurs statistiques d'utilisation
- 🛠️ Gérer dépendances, alias, groupes, profils, secrets, favoris et annotations
Le tout sans toucher au système : chaque agent est installé dans un répertoire utilisateur isolé.
---
## 🧩 Architecture globale
```mermaid
flowchart TB
subgraph UI["🖥️ Interfaces utilisateur"]
CLI["Ligne de commande<br/>clap v4"]
REPL["Shell interactif<br/>rustyline"]
TUI["Dashboard TUI<br/>ratatui + crossterm"]
end
subgraph CORE["⚙️ Noyau applicatif"]
APP["App<br/>contexte partagé"]
CLI_DEF["cli.rs<br/>définition des commandes"]
CMD["commands/<br/>dispatch"]
end
subgraph DATA["💾 Données persistantes"]
CONFIG["Configuration YAML<br/>embarquée + utilisateur"]
CATALOG["Catalog<br/>index agents/alias/groupes"]
STATE["StateStore<br/>state.json"]
EVENTS["Journal JSONL<br/>events-YYYYMM.jsonl"]
CACHE["Probe cache<br/>probe-cache.json"]
end
subgraph EXEC["🔧 Exécution"]
RUNNER["Runner trait<br/>système / mock"]
INSTALLERS["installers/<br/>9 méthodes"]
DEPS["deps.rs<br/>vérification dépendances"]
PROCESS["process.rs<br/>PID / signaux"]
end
subgraph OBS["👁️ Observation"]
PROBE["probe.rs<br/>détection agents externes"]
DASH["dashboard.rs<br/>agrégation"]
STATS["stats.rs<br/>statistiques"]
SESSIONS["sessions.rs<br/>registre sessions"]
end
CLI --> APP
REPL --> APP
TUI --> APP
APP --> CLI_DEF
APP --> CONFIG
APP --> CATALOG
APP --> STATE
APP --> EVENTS
APP --> CACHE
CMD --> RUNNER
CMD --> INSTALLERS
CMD --> DEPS
CMD --> PROCESS
CMD --> PROBE
CMD --> DASH
CMD --> STATS
CMD --> SESSIONS
```
---
## 🚀 Flux d'exécution d'une commande
```mermaid
sequenceDiagram
autonumber
participant User
participant main as main.rs
participant lib as lib.rs
participant help as help.rs
participant cli as cli.rs
participant app as app.rs
participant cfg as config.rs
participant state as state.rs
participant cmd as commands/mod.rs
participant impl as commands/xxx_cmd.rs
User->>main: am install jcode
main->>lib: main_entry()
lib->>lib: spawn thread 8 Mo
lib->>help: intercept(-h/--help) ?
help-->>lib: None
lib->>cli: Cli::parse()
cli-->>lib: Cli { command: Install {...} }
lib->>app: App::from_cli(cli)
app->>cfg: load() config embarquée + user
cfg-->>app: Config
app->>state: StateStore::new()
state-->>app: state.json chargé
app-->>lib: App
lib->>cmd: commands::execute(&app)
cmd->>impl: install_cmd::run(...)
impl->>impl: vérifier dépendances, installer
impl->>state: persister
impl->>app: emit(event install)
impl-->>cmd: Ok(0)
cmd-->>lib: 0
lib-->>main: code sortie
```
---
## 📁 Structure du code source
```
src/
├── main.rs 🚪 Point d'entrée
├── lib.rs 🧭 main_entry(), doc du crate
├── cli.rs 📋 Définition clap de toutes les commandes
├── app.rs 🧰 Contexte App (config, state, paths, logger, theme)
├── config.rs ⚙️ Schéma YAML, chargement, fusion, validation
├── catalog.rs 🔍 Index agents + recherche fuzzy + suggestions
├── state.rs 💾 Base JSON des installations et annotations
├── events.rs 📝 Journal d'événements JSONL
├── runner.rs 🏃 Trait d'exécution système / mock
├── process.rs ⚙️ Lancement, arrêt, signaux des processus
├── deps.rs ✅ Vérification et auto-installation des dépendances
├── toolchain.rs 🖥️ Détection OS / gestionnaire de paquets
├── download.rs ⬇️ Téléchargement, checksums, extraction
├── probe.rs 🔎 Détection des agents externes sur le PATH
├── dashboard.rs 📊 Données agrégées du dashboard
├── ps.rs 🧮 Table des processus
├── context.rs 📂 Contexte du projet courant
├── repl.rs 💬 Shell interactif
├── shell.rs 🐚 Gestion des shells supportés
├── help.rs ❓ Aide Nushell-style
├── output.rs 🖨️ Logger et rendu
├── tables.rs 📋 Rendu tabulaire
├── theme.rs 🎨 Thèmes de couleur
├── history.rs ⏪ Historique des commandes
├── sessions.rs 📅 Registre des sessions
├── projects.rs 🗂️️ Agrégation par projet
├── hooks.rs 🪝 Hooks de cycle de vie
├── secrets.rs 🔒 Gestion des secrets (keyring OS)
├── version.rs 🏷️ Informations de version
├── commands/ 📦 35 modules, un par commande
└── installers/ 📦 9 installateurs spécialisés
```
---
## ⚙️ Configuration : catalogue YAML extensible
### Hiérarchie de chargement
```mermaid
flowchart LR
EMB["📦 config.yaml<br/>embarqué dans le binaire"] --> MERGE["🔀 Fusion"]
USER["👤 ~/.config/agent-manager/config.yaml"] --> MERGE
LOCAL["📂 ./agent-manager.yaml"] --> MERGE
FLAG["⚡ --config <file>"] --> MERGE
MERGE --> EFFECTIVE["✅ Configuration effective"]
```
Priorité (la plus prioritaire en dernier) :
1. `config.yaml` embarqué (catalogue par défaut, ~72 agents)
2. `~/.config/agent-manager/config.yaml`
3. `./agent-manager.yaml` (configuration locale par projet)
4. `--config <fichier>`
### Schéma de configuration
```yaml
version: "1.0"
settings:
install_dir: null # ~/.local/share/agent-manager/agents
log_dir: null # ~/.local/state/agent-manager/logs
default_shell: null # shell utilisateur par défaut
auto_install_deps: true # proposer d'installer les dépendances manquantes
confirm_before_run: true # confirmer avant les scripts d'installation
stop_timeout_secs: 5 # délai SIGTERM → SIGKILL
self_update_repo: Projets/agent-manager
self_update_base_url: https://git.dracodev.net/api/v1
theme: null
hooks:
on_install: []
on_start: []
on_stop: []
on_update: []
aliases:
cc: claude-code
gemini: gemini-cli
groups:
dev: [claude-code, aider, codex]
include:
- ./extra-agents.yaml
agents:
- name: claude-code
display_name: "Claude Code"
description: "..."
category: coding-agent
website: https://github.com/anthropics/claude-code
install:
type: npm
package: "@anthropic-ai/claude-code"
dependencies:
- { name: node, min_version: "18.0.0" }
run: claude
tags: [anthropic, assistant]
profiles:
dev:
agent: claude-code
env: { API_ENV: dev }
args: [--verbose]
projects:
mon-projet:
root: ~/projets/mon-projet
default_agent: claude-code
env: { KEY: value }
```
### Définition d'un agent (`AgentDef`)
| Champ | Description |
|-------|-------------|
| `name` | Identifiant unique (slug) |
| `display_name` | Nom lisible |
| `description` | Description longue |
| `category` | Catégorie (coding-agent, assistant, local-first...) |
| `website` | URL du projet |
| `install` | Spécification d'installation |
| `dependencies` | Outils requis (node, python, go...) avec version min |
| `run` | Commande de lancement |
| `args` | Arguments par défaut |
| `env` | Variables d'environnement par défaut |
| `tags` | Tags pour recherche/filtrage |
| `installable` | `false` pour les agents SaaS/Desktop |
| `platforms` | Restriction `linux`/`macos`/`windows` |
### Méthodes d'installation supportées
| Type | Fichier | Principe |
|------|---------|----------|
| `npm` | `installers/npm.rs` | `npm install -g --prefix <root>` |
| `bun` | `installers/npm.rs` | `bun install -g` |
| `pip` / `uv` | `installers/pipuv.rs` | virtualenv privé dans `install_dir` |
| `cargo` | `installers/cargo.rs` | `cargo install --root` |
| `go` | `installers/golang.rs` | `go install` avec `GOBIN` local |
| `curl` | `installers/script.rs` | Télécharge et exécute un script |
| `binary` | `installers/binary.rs` | Release GitHub/Gitea → extraction archive |
| `git` | `installers/git.rs` | Clone + build + `binary_path` |
---
## 💾 Gestion des états
### `state.json` — base d'installation locale
Format JSON version 3, atomique (écriture `.tmp` + `rename`).
```json
{
"version": 3,
"installed": {
"jcode": {
"name": "jcode",
"version": "0.76.0",
"method": "binary",
"directory": ".../agents/jcode",
"binaries": ["jcode"],
"pid": null,
"installed_at": "...",
"updated_at": "..."
}
},
"annotations": {
"jcode": {
"favorite": true,
"note": "mon agent préféré",
"tags": ["perso"]
}
}
}
```
### Journal d'événements JSONL
- Un fichier par mois : `events-YYYYMM.jsonl`
- Append-only, horodaté
- Types d'événements : `start`, `stop`, `run`, `install`, `update`, `uninstall`, `doctor`, `config`, `repl`, `shell`, `annotate`
- Les secrets ne sont jamais journalisés
- Source de vérité pour les statistiques, sessions, projets et timeline
### Cache de sondes
`probe-cache.json` évite de re-scanner le PATH à chaque commande pour détecter les agents externes et leurs versions.
---
## 🛡️ Gestion des dépendances
```mermaid
flowchart LR
INSTALL["am install jcode"] --> CHECK["deps::check_dep"]
CHECK --> MISSING{Manquant ?}
MISSING -->|Oui| DETECT["toolchain::detect OS"]
DETECT --> COMMANDS["toolchain::install_commands"]
COMMANDS --> PROMPT["Proposer la commande"]
PROMPT --> AUTO["Exécuter si --yes"]
AUTO --> INSTALL2["installers::run_install"]
MISSING -->|Non| INSTALL2
```
- `deps.rs` vérifie chaque dépendance via `<outil> --version`
- `toolchain.rs` détecte l'OS et le gestionnaire de paquets :
- Windows : `scoop` → `winget`
- Linux : `apt` / `dnf` / `pacman` / `apk`
- macOS : `brew`
- Si `auto_install_deps: true`, `am` propose et peut exécuter la commande d'installation
- Les dépendances de l'agent (modules npm/pip) restent isolées dans le répertoire `install_dir`
---
## ⚡ Gestion des processus
```mermaid
flowchart TB
START["am start <agent>"] --> MODE{Mode ?}
MODE -->|Foreground| FG["Attache au terminal<br/>Ctrl-C pour quitter"]
MODE -->|Background| BG["spawn_background()"]
BG --> LOG["Redirection stdout/stderr<br/>vers log agent"]
LOG --> PID["Enregistrement PID<br/>dans state.json"]
PID --> EVENT["Émission événement start"]
STOP["am stop <agent>"] --> SIGTERM["SIGTERM"]
SIGTERM --> WAIT{"Processus terminé ?"}
WAIT -->|Non| SIGKILL["SIGKILL après timeout"]
WAIT -->|Oui| CLEAN["PID effacé"]
```
- Démarrage avant-plan (`-f`) ou arrière-plan (`-b`, `--background`)
- Variables d'environnement injectées via `--env KEY=VALUE`
- Secrets via `--env KEY=@secret` (résolu depuis le trousseau OS)
- Arrêt gracieux avec timeout configurable (défaut 5 s)
- Notifications desktop optionnelles (`--notify`)
---
## 📋 Commandes détaillées
### 🔍 Découvrir
| Commande | Description |
|----------|-------------|
| `am list` | Agents installés + détectés sur le PATH |
| `am list --all` | Tout le catalogue avec état |
| `am search <mot>` | Recherche fuzzy avec suggestions |
| `am info <agent>` | Fiche détaillée |
| `am status [agent]` | État, version, PID, logs |
### 📦 Cycle de vie
| Commande | Description |
|----------|-------------|
| `am install <agent>` | Installe un agent et ses dépendances |
| `am install <agent> --method pip` | Choisit la méthode |
| `am uninstall <agent> [--purge]` | Désinstalle et nettoie |
| `am update <agent>` / `--all` | Met à jour |
| `am start [agent]` | Lance en avant-plan |
| `am start <agent> -b` | Lance en arrière-plan |
| `am stop <agent>` | Arrêt gracieux |
| `am restart <agent>` | Redémarrage |
| `am run <agent> [args...]` | Exécution directe sans gestion de PID |
### 👁️ Observer
| Commande | Description |
|----------|-------------|
| `am logs <agent>` | Tail du log agent |
| `am log [agent]` | Journal des événements `am` |
| `am timeline` | Vue chronologique unifiée |
| `am sessions [agent]` | Registre des sessions |
| `am stats [agent] --period 7d` | Statistiques d'utilisation |
| `am top --period 30d` | Top 10 agents |
| `am report --last-week` | Rapport markdown |
| `am projects [nom]` | Agents par projet |
| `am dashboard` | Dashboard TUI temps réel |
| `am watch <agent> --restart` | Supervision et relance automatique |
### 🎯 Personnaliser
| Commande | Description |
|----------|-------------|
| `am favorite <agent>` | Marquer comme favori |
| `am note <agent> <texte>` | Ajouter une note |
| `am tag <agent> <tag>` | Taguer |
| `am tags [agent]` | Lister les tags |
| `am profile list/show <name>` | Profils d'environnement |
| `am alias add <nom> <cible>` | Créer un alias |
| `am secret set <nom> --agent <a> --value <v>` | Secret dans le trousseau |
### 🛠️ Système
| Commande | Description |
|----------|-------------|
| `am doctor [--fix]` | Diagnostic environnement |
| `am config show/path/edit/validate/set` | Gestion config |
| `am completion <shell>` | Script de complétion |
| `am man [commande]` | Page de manuel |
| `am open <agent>` | Ouvrir le répertoire d'installation |
| `am export/import` | Sauvegarde/restauration |
| `am self-update [--check]` | Mise à jour de `am` |
| `am self-uninstall` | Désinstallation complète |
| `am version` | Version et build |
| `am` (sans commande) | REPL interactif |
---
## 🌐 Options globales
Disponibles avant ou après la sous-commande.
| Option | Effet |
|--------|-------|
| `-c, --config <FILE>` | Fichier de configuration alternatif |
| `-v, --verbose` | Affiche chaque commande exécutée |
| `-q, --quiet` | Seules les erreurs sont affichées |
| `-y, --yes` | Oui à toutes les confirmations |
| `--dry-run` | Simulation sans modification |
| `--json` | Sortie JSON structurée |
| `--no-color` | Désactive les couleurs |
| `--theme <THEME>` | Thème de couleur |
---
## 🎨 Fonctionnalités avancées
### Alias
```yaml
aliases:
cc: claude-code
gemini: gemini-cli
```
`am start cc` démarre `claude-code`.
### Groupes
```yaml
groups:
dev: [claude-code, aider, codex]
```
`am start group:dev` démarre tous les agents du groupe en arrière-plan.
### Profils d'environnement
```yaml
profiles:
dev:
agent: claude-code
env: { API_ENV: dev }
args: [--verbose]
```
`am start --profile dev` lance `claude-code` avec les variables et arguments du profil.
### Projets
```yaml
projects:
mon-projet:
root: ~/projets/mon-projet
default_agent: claude-code
```
Dans le répertoire du projet, `am start` sans argument lance l'agent par défaut du projet.
### Secrets
Les secrets sont stockés dans le trousseau du système d'exploitation (via `keyring`), jamais en clair dans la config.
```bash
am secret set OPENAI_API_KEY --agent claude-code --value sk-...
am start claude-code --env OPENAI_API_KEY=@secret
```
### Hooks
Commandes exécutées automatiquement aux étapes clés du cycle de vie :
- `on_install`
- `on_start`
- `on_stop`
- `on_update`
Définissables globalement dans `settings.hooks` ou par projet.
### Shell interactif (REPL)
Lancé par `am` sans sous-commande :
- Complétion Tab personnalisée (commandes, agents, alias, groupes, flags, shells, thèmes)
- Historique des commandes
- Passerelle système : commandes inconnues exécutées dans le shell actif
- Commandes internes : `ls`, `dir`, `cd`, `ps`, `where`, `get`, `shell`, `theme`, `exit`, `/help`
- Bannière ASCII art
---
## 📊 Dashboard TUI
```mermaid
flowchart LR
DASH["am dashboard"] --> OVER["Overview"]
DASH --> ACT["Activité"]
DASH --> STATS["Statistiques"]
DASH --> SESS["Sessions"]
DASH --> PROJ["Projets"]
```
- Navigation : `Tab` / `←` / `→` pour les onglets
- Défilement : `j` / `k` ou flèches
- Quitter : `q`
- Données agrégées depuis `state.json`, le journal d'événements, les sessions et les projets
---
## 🧪 Tests
### Organisation
- **Tests unitaires** : dans chaque module `src/*.rs` sous `#[cfg(test)]`
- **Tests d'intégration** : dans `tests/`
### Fichiers de tests notables
| Fichier | Couverture |
|---------|------------|
| `annotations_test.rs` | Favoris, notes, tags |
| `config_test.rs` | Chargement/validation/fusion |
| `dashboard_test.rs` | Dashboard |
| `doctor_test.rs` | `am doctor` |
| `dry_run_test.rs` | Mode `--dry-run` |
| `events_test.rs` | Journal d'événements |
| `history_test.rs` | Historique |
| `process_test.rs` | Processus |
| `profiles_test.rs` | Profils |
| `projects_test.rs` | Projets |
| `sessions_test.rs` | Sessions |
| `stats_test.rs` | Statistiques |
Lancer les tests :
```bash
cargo test
```
---
## 📦 Packaging et distribution
```
dist/ Binaires précompilés
├── am-linux-x86_64
├── am-linux-aarch64
├── am-windows-x86_64.zip
├── am_0.4.2_amd64.deb
└── ...
packaging/ Scripts de packaging
├── deb/
├── rpm/
├── homebrew/
├── scoop/
└── winget/
completions/ Scripts de complétion
├── _am zsh
├── _am.ps1 PowerShell
├── am.bash bash
├── am.elv elvish
└── am.fish fish
man/ Pages de manuel générées
```
- `build.rs` enregistre le commit et la branche git pour `am version`
- `scripts/build-release.ps1` construit les releases multiplateformes
- `scripts/render-manifests.ps1` génère les manifests de packaging
---
## 🗂️️ Arborescence des données utilisateur
### Windows
```
%LOCALAPPDATA%\agent-manager\
├── agents\ installations
├── logs\ logs agents
└── state\ state.json, events-*.jsonl, probe-cache.json
```
### Linux
```
~/.local/share/agent-manager/ données
~/.local/state/agent-manager/ logs, state, events, cache
~/.config/agent-manager/ config.yaml
```
### macOS
```
~/Library/Application Support/agent-manager/ données
~/.local/state/agent-manager/ logs, state, events, cache
~/.config/agent-manager/ config.yaml
```
Variables d'environnement de débogage/test :
- `AGENT_MANAGER_DATA` : remplace le répertoire de données
- `AGENT_MANAGER_STATE` : remplace le répertoire d'état
---
## 🔐 Sécurité
- Mode `--dry-run` universel pour simuler sans modifier
- Checksums `sha256` pour les binaires téléchargés
- Confirmation avant exécution des scripts d'installation
- Secrets dans le trousseau OS, jamais dans les logs ni le journal
- Installation isolée : aucune modification système
---
## 🔁 Résumé du flux de données
```mermaid
flowchart LR
CONFIG["📄 YAML config"] --> CATALOG["📚 Catalog"]
CATALOG --> INSTALL["📦 Installateurs"]
INSTALL --> STATE["💾 state.json"]
STATE --> PROCESS["⚙️ Processus"]
PROCESS --> EVENTS["📝 Journal JSONL"]
EVENTS --> STATS["📊 Stats / Dashboard"]
EVENTS --> SESSIONS["📅 Sessions"]
EVENTS --> PROJECTS["🗂️️ Projets"]
```
---
## 📚 Références
- `src/lib.rs:4` : documentation d'architecture du crate
- `src/cli.rs:24` : options globales
- `src/cli.rs:61` : énumération des commandes
- `src/app.rs:16` : struct `App`
- `src/config.rs:25` : struct `Config`
- `src/config.rs:148` : struct `AgentDef`
- `src/config.rs:241` : enum `InstallType`
- `src/state.rs:12` : struct `StateFile`
- `src/events.rs:57` : struct `Event`
- `README.md` : documentation utilisateur complète
---
*Document généré pour agent-manager v0.4.2* 🚀
Generated
+1266 -41
View File
File diff suppressed because it is too large Load Diff
+10 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "agent-manager"
version = "0.2.5"
version = "0.4.2"
edition = "2021"
description = "Manage local AI coding agents: list, install, start, stop, update — with automatic dependency handling and a YAML-driven catalog."
license = "MIT"
@@ -25,15 +25,23 @@ semver = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
rustyline = { version = "14", default-features = false, features = ["with-file-history"] }
reedline = "0.49"
nu-ansi-term = "0.50"
sha2 = "0.10"
shell-words = "1"
tar = "0.4"
tempfile = "3"
ureq = { version = "2", default-features = false, features = ["tls"] }
wait-timeout = "0.2"
keyring = "3"
which = "7"
zip = "0.6"
crossterm = "0.29"
ratatui = "0.30"
clap_mangen = "0.3"
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_System_ProcessStatus", "Win32_System_Diagnostics_ToolHelp", "Win32_System_Console"] }
[profile.release]
lto = "thin"
+96
View File
@@ -285,3 +285,99 @@ avec purge.
$ am uninstall cowsay --purge --yes
removed 'cowsay' from .../cowsay.yaml (backup: .../cowsay.yaml.bak)
uninstalled Cowsay (files removed, config purged)
## Favoris, notes et tags personnels (v0.4.1)
$ am favorite claude-code
⭐ claude-code added to favorites
$ am note claude-code use sonnet for this repo
note saved for 'claude-code'
$ am tag claude-code python frontend
tagged 'claude-code' with python, frontend
$ am tags
┌───┬────────────┬───────┬─────────────┐
│ # │ TAG │ COUNT │ AGENTS │
├───┼────────────┼───────┼─────────────┤
│ 0 │ #frontend │ 1 │ claude-code │
│ 1 │ #python │ 1 │ claude-code │
└───┴────────────┴───────┴─────────────┘
L'étoile apparaît dans am list, am status et am info ; les tags sont
filtrables avec am search --tag, et tout survit au redémarrage (état v3) :
$ am info claude-code | tail
tags anthropic · assistant · source-available · mainstream
personal
● favorite ★
● tags #frontend #python
● note use sonnet for this repo
$ am search --tag python
┌───┬──────────────────────────┬──────────────┬─────────────┬─────────────┐
│ # │ NAME │ CATEGORY │ INSTALLABLE │ DESCRIPTION │
├───┼──────────────────────────┼──────────────┼─────────────┼─────────────┤
│ 0 │ ★ Claude Code │ coding-agent │ yes │ Agent termi… │
└───┴──────────────────────────┴──────────────┴─────────────┴─────────────┘
## Profils d'environnement (v0.4.1)
Des profils nommés dans la configuration (env + args + agent par défaut)
s'appliquent au lancement :
profiles:
dev:
agent: claude-code
env: {DEBUG: "1", LOG_LEVEL: debug}
args: [--model, sonnet]
prod:
env: {LOG_LEVEL: info}
$ am profile list
PROFILE AGENT ENV ARGS
------- ----------- --------------------- ----------------
dev claude-code DEBUG=1 LOG_LEVEL=… --model sonnet
prod — LOG_LEVEL=info —
$ am start claude-code --profile dev
# lance claude --model sonnet avec DEBUG=1 et LOG_LEVEL=debug
$ am start --profile dev # agent par défaut du profil
$ am start --profile staging # -> error: unknown profile 'staging' — …
# available profiles: dev, prod
## Dashboard TUI (v0.4.1)
am dashboard ouvre un tableau de bord temps réel (ratatui/crossterm, aucune
dépendance runtime) : vue d'ensemble (statuts, PID, uptime, ★), activité
(journal d'événements), stats par agent, sessions et projets. Tab / ← / →
changent d'onglet, j/k défilent, q quitte ; rafraîchissement chaque seconde.
Sur une sortie non interactive (pipe, script), un instantané texte est
affiché à la place :
$ am dashboard | head
agent-manager dashboard — 2 installed · 1 running · 12 sessions
overview
★ claude-code running 2.1.233 pid 4242 (up 3h12m)
aider installed 0.76.0 —
codex external — —
$ am dashboard --json # l'instantané complet en JSON pour les scripts
## Pages de manuel et complétions dynamiques (v0.4.1)
$ am man dashboard
.TH am-dashboard 1 "dashboard "
.SH NAME
dashboard - Live TUI dashboard: overview, activity, stats, sessions, projects
...
$ am man --output man/ # am.1 + une page par commande (44 pages)
man pages written to man/ (am.1 + 44 pages)
$ am completion bash --installed # Tab complète dynamiquement les
# agents installés, alias et groupes
# (interroge 'am _agents --installed')
+79 -9
View File
@@ -76,7 +76,7 @@ plateforme, il compile automatiquement depuis les sources.
|----------|------|
| am list | agents installés (gérés + détectés sur le PATH) |
| am list --all | tout le catalogue, avec l'état de chacun |
| am search <mot-clé> | recherche par nom, description, catégorie, tag |
| am search <mot-clé> | recherche fuzzy : tolère les fautes de frappe, classe par pertinence, propose « vouliez-vous dire » |
| am info <agent> | fiche détaillée (installation, dépendances, site…) |
| am status [agent] | état, version, PID, logs |
@@ -90,27 +90,60 @@ sur le PATH) · ⚪ not-installed · 🔴 not-installable (SaaS/desktop)
| am install <agent> | installe (dépendances vérifiées et proposées) |
| am install <agent> --method pip | choisit une méthode d'installation |
| am update <agent> | met à jour vers la dernière version connue |
| am uninstall <agent> | désinstalle et nettoie (--purge : logs + config) |
| am config set settings.default_shell pwsh | modifie la configuration sans éditeur |
| am alias add cc claude-code | raccourci pour un agent (list / remove aussi) |
| am init | génère un agent-manager.yaml selon la pile du projet (Node/Rust/Python…) |
| am uninstall <agent> | désinstalle et nettoie (--purge : logs + config) — gère aussi les agents externes (npm/pip/uv/cargo/bun, sinon suppression des fichiers) |
| am export / am import | sauvegarde et restaure config + état |
### 🚀 Exécuter
| Commande | Rôle |
|----------|------|
| am start <agent> | avant-plan (Ctrl-C pour quitter) |
| am start <agent> --background --env KEY=VALEUR | détaché, PID et log gérés |
| am start [agent] | avant-plan (Ctrl-C pour quitter) — sans argument, lance l'agent par défaut du projet |
| am start <agent> --background --env KEY=VALEUR | détaché, PID et log gérés ; --env NOM=@secret injecte un secret du trousseau |
| am stop <agent> | arrêt gracieux puis forcé (--force : immédiat) |
| am restart <agent> | arrête puis redémarre |
| am run <agent> [args…] | exécution directe, sans gestion de processus |
### 📊 Observer & retrouver
| Commande | Rôle |
|----------|------|
| am stats [agent] --period 7d/30d/90d | statistiques d'utilisation : lancements, durées, échecs, barres ASCII |
| am top --period | les 10 agents les plus utilisés |
| am report --last-week / --last-month | digest markdown (lancements, échecs, changements, sessions) |
| am log [agent] --kind --since --follow | journal des événements : tout ce que am a fait, en direct avec --follow |
| am timeline --project --since | vue chronologique unifiée (journal + commandes du REPL) |
| am projects [nom] | quel agent travaille sur quel projet (agent dominant, sessions, durées) |
| am sessions [agent] --status --show --resume | registre des sessions + reprise d'une session arrêtée |
| am history --search --failed --rerun N | historique des commandes (am + shell), filtrable et réexécutable |
| am logs <agent> --follow | tail du log d'un agent, en direct |
| am dashboard | tableau de bord TUI temps réel : vue d'ensemble, activité, stats, sessions, projets (Tab/←/→ : onglets, j/k : défilement, q : quitter) |
### 🎯 Personnaliser
| Commande | Rôle |
|----------|------|
| am favorite <agent> / am unfavorite | étoile ★ dans am list, am status et am info (persistante) |
| am note <agent> <texte…> | note libre affichée dans am info (am note <agent> : affiche) |
| am tag <agent> <mot>… / am untag <agent> <mot> | tags personnels, filtrables avec am search --tag |
| am tags [agent] | liste les tags personnels et les agents qui les portent |
| am profile list / show | profils d'environnement (env + args + agent par défaut) |
| am start <agent> --profile dev | lance un agent avec un profil ; am start --profile dev utilise l'agent du profil |
| am man [commande] --output man/ | génère les pages de manuel (am.1 + une page par commande) |
### 🧰 Système
| Commande | Rôle |
|----------|------|
| am doctor | vérifie outils, config, chemins — avec commandes d'installation |
| am doctor --fix | répare ce qui peut l'être, propose d'installer les outils manquants |
| am config show / edit / add | gère votre configuration |
| am completion <shell> | script de complétion (bash, zsh, fish, powershell) |
| am watch <agent> --restart --notify | supervise un agent et le relance en cas de crash |
| am secret set NOM --agent A --value … / list / unset | secrets dans le trousseau du système (jamais en clair) |
| am config show / edit / add / set | gère votre configuration (hooks on_start/on_stop/… dans settings.hooks) |
| am open <agent> | ouvre le répertoire d'installation dans l'explorateur |
| am completion <shell> [--installed] | script de complétion (bash, zsh, fish, powershell, elvish) ; --installed complète dynamiquement les agents installés, alias et groupes |
| am self-update | met à jour am lui-même (si configuré) |
| am self-uninstall | désinstalle am et tout ce qu'il a créé (confirmation) |
| am | shell interactif : bannière, passerelle système, Tab et historique |
@@ -166,10 +199,16 @@ Disponibles avant **ou après** la sous-commande.
am start cc # alias (cc -> claude-code)
am start group:dev # démarre tout un groupe en arrière-plan
am stop group:dev
am start cc --profile dev # profil d'environnement : env + args injectés
am dashboard # TUI temps réel : toute l'activité d'un coup d'œil
am favorite cc && am tag cc python # personnalisez votre parc
am search --tag python # retrouvez les agents que vous avez tagués
am # shell interactif (bannière style Hermes) :
❯ inst<Tab> # complétion Tab : commandes, agents, groupes, options
❯ inst<Tab> # complétion Tab : commandes, agents installés, groupes, options
❯ install jcode
❯ dashboard # le dashboard se lance aussi depuis le REPL
❯ exit # historique persistant (flèches haut/bas)
### 🐚 Passerelle système
@@ -181,13 +220,29 @@ démarrage et mis en évidence** dans la bannière (config
`settings.default_shell`, puis `SHELL`/`COMSPEC`, puis détection sur le
PATH — pwsh, powershell, cmd, bash, zsh, fish, sh, nu, elvish).
❯ ls # exécuté par le shell actif (cmd, pwsh, bash, ...)
❯ ls # liste façon Nushell : tableau encadré (type, taille, date)
❯ dir # sur Windows, même affichage que ls
❯ ls src # un chemin précis — Tab complète fichiers et dossiers
❯ ps # liste des processus (pid, ppid, cpu, mem, threads)
❯ ls | where size > 1mb # filtre la dernière table (>, <, >=, <=, ==, !=, =~)
❯ ps | where name =~ am # =~ cherche dans le texte
❯ ls | get name # sélectionne une colonne
❯ cd <Tab> # complétion des dossiers — menu interactif façon Nushell
❯ shell # affiche le shell courant + les shells disponibles
❯ shell bash # change le shell de la session
❯ !list # force l'exécution système (même nom qu'une commande am)
❯ !ls · !list # force l'exécution système (même nom qu'une commande am)
❯ cd ~/projets # change le répertoire de la session (persistant)
❯ /help # commandes slash : /help · /version · /exit · /shell <nom>
La complétion Tab est un **menu interactif façon Nushell** : une
correspondance unique est insérée directement ; plusieurs correspondances
ouvrent un menu sous la ligne avec le **premier choix en surbrillance**, et
chaque Tab déplace la surbrillance vers le choix suivant (Shift+Tab vers
l'arrière). **Entrée** valide le choix, **Échap** ferme le menu, les flèches
↑/↓ naviguent et continuer à taper filtre la liste en direct. Le menu
affiche une **description** à côté de chaque candidat (commandes et agents)
et suit la palette du thème actif.
Pour rendre le choix permanent : `settings.default_shell: pwsh` dans
`config.yaml` (voir `am config path`).
@@ -283,6 +338,21 @@ multi-fichiers pris en charge ; configuration validée au démarrage.
(Windows) — l'état et les logs dans ~/.local/state/agent-manager (Linux) ou
%LOCALAPPDATA%\\agent-manager (Windows).
**Où vivent les statistiques, les sessions et l'historique ?** Dans le
répertoire d'état, au format JSON lisible et 100 % local : events-YYYYMM.jsonl
(journal de chaque action), sessions.json (registre des sessions),
history/ (historique structuré du shell), projects.json (agrégats par
projet). Interrogeables avec am stats, am top, am log, am timeline,
am projects, am sessions et am history.
**Où vivent les secrets ?** Dans le trousseau de votre système (Windows
Credential Manager, macOS Keychain, Linux Secret Service) — jamais dans la
configuration ni dans les logs. Injection avec --env NOM=@secret.
**Puis-je automatiser des actions ?** Oui : hooks on_install/on_start/on_stop/
on_update dans settings.hooks (globaux) ou dans le profil d'un projet, et
am watch pour superviser un agent avec redémarrage automatique.
**Comment mettre à jour am ?** Relancez l'installeur one-liner (ou
am self-update si le dépôt de release est configuré).
+81 -33
View File
@@ -1,62 +1,110 @@
# Publier une release
Les installateurs one-liner (`install.sh` et `install.ps1`) téléchargent
les binaires attachés à la **dernière release** du dépôt Gitea
Les installateurs one-liner (`install.sh` et `install.ps1`), la commande
`am self-update` et les paquets (scoop/winget/homebrew) consomment les
**artefacts attachés à la dernière release** du dépôt Gitea
(https://git.dracodev.net/Projets/agent-manager). Sans binaire pour la
plateforme, ils retombent sur `cargo install --git` (plus lent mais toujours
fonctionnel).
plateforme, les installateurs retombent sur `cargo install --git`.
La phase 1 (v0.4.1) a ajouté le pipeline CI (issue #42) : sur chaque tag
`v*`, `.gitea/workflows/release.yml` construit Linux (x86_64 + aarch64
musl statiques), Windows (x86_64) et macOS (x86_64 + aarch64) et publie la
release. La procédure locale ci-dessous reste disponible pour produire
manuellement les mêmes artefacts (ou les plateformes sans runner CI).
## 1. Bumper la version
Dans Cargo.toml (ex. `0.2.1`), puis mettre à jour Cargo.lock
(`cargo check`) et compiler :
Dans Cargo.toml (ex. `0.4.1`), puis `cargo check` pour mettre à jour
Cargo.lock, et lancer toute la batterie :
cargo build --release # binaire Windows (am.exe) sur Windows
cargo test --locked # 240+ tests
cargo clippy --all-targets -- -D warnings # si possible
# binaire Linux STATIQUE (musl) : compatible toutes distributions,
# y compris Debian 12 (glibc 2.36) — ne JAMAIS publier un binaire glibc
# compilé sur une base récente.
docker run --rm -v "${PWD}:/work" -w /work -e CARGO_TARGET_DIR=/tmp/target-musl \
rust:1.94-alpine sh -c "apk add --no-cache build-base && \
rustup target add x86_64-unknown-linux-musl && \
cargo build --release --locked --target x86_64-unknown-linux-musl && \
cp /tmp/target-musl/x86_64-unknown-linux-musl/release/am dist/am-linux-x86_64"
## 2. Produire les binaires et les archives
## 2. Créer les archives des binaires
### Windows (x86_64) — natif
Windows (PowerShell) :
scripts/build-release.ps1 # target/release/am.exe -> dist/am-windows-x86_64.zip
Compress-Archive target\release\am.exe am-windows-x86_64.zip
### Linux — binaire STATIQUE (musl), compatible toutes distributions,
### y compris Debian 12 (glibc 2.36) — ne JAMAIS publier un binaire glibc
### compilé sur une base récente.
Linux (l'archive doit contenir un fichier nommé « am ») :
# cargo-zigbuild : zig (~50 Mo) fournit le linker croisé pour les DEUX
# cibles musl (rust:1.94-alpine n'embarque pas de compilateur croisé
# aarch64 : 'aarch64-linux-musl-gcc not found').
docker run --rm -v "${PWD}:/work" -w /work -e CARGO_TARGET_DIR=/tmp/am-target \
-e CARGO_INCREMENTAL=0 rust:1.94-alpine sh -c "apk add --no-cache build-base zip curl >/dev/null && \
rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl && \
curl -fsSL https://ziglang.org/download/0.14.1/zig-x86_64-linux-0.14.1.tar.xz -o /tmp/zig.tar.xz && \
tar -xf /tmp/zig.tar.xz -C /opt && \
export PATH=/opt/zig-x86_64-linux-0.14.1:\$PATH && \
cargo install cargo-zigbuild --locked && \
cargo zigbuild --release --locked --target x86_64-unknown-linux-musl --target aarch64-unknown-linux-musl && \
mkdir -p dist && \
cp /tmp/am-target/x86_64-unknown-linux-musl/release/am dist/am-linux-x86_64 && \
cp /tmp/am-target/aarch64-unknown-linux-musl/release/am dist/am-linux-aarch64 && \
cd dist && tar -czf am-linux-x86_64.tar.gz am-linux-x86_64 && \
tar -czf am-linux-aarch64.tar.gz am-linux-aarch64"
docker run --rm -v "${PWD}:/work" -w /work rust:1.94-alpine sh -c \
"cd dist && cp am-linux-x86_64 am && tar -czf am-linux-x86_64.tar.gz am && rm am"
### macOS (x86_64 + aarch64) — via CI (runner macOS) :
## 3. Créer la release sur Gitea
# le job 'macos' du workflow release.yml produit :
# am-macos-x86_64.tar.gz am-macos-aarch64.tar.gz
# (compilation croisée macOS impossible depuis Windows/Linux ; sans
# runner macOS, ces archives manquent et l'install.sh compile depuis
# les sources sur macOS, ce qui reste fonctionnel.)
Via l'interface web : Releases > New Release, tag v0.2.0, attacher les
archives ci-dessus.
Vérifier chaque archive : `am version` doit répondre, et `am doctor` ne
doit signaler aucune dépendance manquante.
Ou via l'API (jeton Gitea requis) :
## 3. Rendre les manifests de packaging (issue #42)
Une fois toutes les archives dans `dist/` :
scripts/render-manifests.ps1 -Version 0.4.2
Ce script rend, avec la version et les sha256 réels :
- `dist/scoop/am.json` → `scoop install dist/scoop/am.json`
(ou à ajouter à un bucket ; checkver/autoupdate inclus)
- `dist/winget/*.yaml` → à soumettre à microsoft/winget-pkgs
(`winget install BrunoDracodev.AgentManager`)
- `dist/homebrew/am.rb` → à servir via un tap, ou à soumettre
- `dist/rpm/am.spec` → rpmbuild -bb (avec am-linux-x86_64.tar.gz
dans ~/rpmbuild/SOURCES)
- Paquet deb : `packaging/deb/make-deb.sh 0.4.2` → dist/am_0.4.2_amd64.deb
## 4. Créer la release sur Gitea
Via l'interface web : Releases > New Release, tag v0.4.2, attacher les
archives. Ou via l'API (jeton Gitea requis) :
curl -X POST -H "Authorization: token <JETON>" \
-H "Content-Type: application/json" \
-d '{"tag_name":"v0.2.0","name":"v0.2.0","body":"notes de version"}' \
-d '{"tag_name":"v0.4.2","name":"v0.4.2","body":"notes de version"}' \
https://git.dracodev.net/api/v1/repos/Projets/agent-manager/releases
curl -X POST -H "Authorization: token <JETON>" \
-F "attachment=@am-windows-x86_64.zip" \
-F "attachment=@dist/am-windows-x86_64.zip" \
https://git.dracodev.net/api/v1/repos/Projets/agent-manager/releases/<id>/assets?name=am-windows-x86_64.zip
curl -X POST -H "Authorization: token <JETON>" \
-F "attachment=@am-linux-x86_64.tar.gz" \
https://git.dracodev.net/api/v1/repos/Projets/agent-manager/releases/<id>/assets?name=am-linux-x86_64.tar.gz
# idem pour am-linux-x86_64.tar.gz, am-linux-aarch64.tar.gz,
# am-macos-x86_64.tar.gz, am-macos-aarch64.tar.gz (si CI) et
# checksums.txt.
## 4. Tag git
## 5. Tag git (déclenche aussi le pipeline CI)
git tag v0.2.0 && git push origin v0.2.0
git tag v0.4.2 && git push origin v0.4.2
## Conventions de nommage (attendues par les installateurs et self-update)
Les conventions de nommage attendues par les installateurs :
- am-windows-x86_64.zip (contient am.exe)
- am-linux-x86_64.tar.gz (contient am)
- am-linux-aarch64.tar.gz (contient am)
- am-macos-x86_64.tar.gz / am-macos-aarch64.tar.gz (contiennent am)
## Vérification finale
am self-update --check # doit annoncer la nouvelle version
am self-update # met à jour le binaire courant
+482
View File
@@ -0,0 +1,482 @@
# 🗺️ ROADMAP agent-manager — vers le « super outil »
> **Version du document : 2.2** · Statut : propositions, non engagées
> Base : analyse du code **v0.2.7** (Rust, 45+ tests, zéro dépendance runtime)
>
> ✅ **Phase 0 livrée en v0.3.0 (2026-08-17)** : issues #2 à #16 clôturées,
> 157 tests, release publiée sur Gitea. Les découpages des phases 1 à 3 sont
> en place sur Gitea (issues #25 à #80) : v0.4.0, v0.5.0, v1.0.
>
> ✅ **Phase 1 livrée en v0.4.1 (2026-08-17)** : 17/17 issues clôturées
> (#26–#42), 240+ tests. v0.4.0 a livré 13/17 (#26–#32, #34–#38) ; v0.4.1
> complète avec le dashboard TUI (#33), les favoris/notes/tags (#39), les
> profils d'environnement (#40), les complétions dynamiques + man pages
> (#41) et le packaging officiel + CI (#42). Binaires Windows/Linux publiés
> sur Gitea — am self-update et les installateurs servent la v0.4.1.
---
## 📖 Sommaire
1. [Résumé exécutif](#1--résumé-exécutif)
2. [Légende de lecture](#2--légende-de-lecture)
3. [Quick wins — valeur immédiate](#3--quick-wins--valeur-immédiate)
4. [État des lieux](#4--état-des-lieux)
5. [Vision & principes](#5--vision--principes)
6. [Socle technique : le modèle de données](#6--socle-technique--le-modèle-de-données)
7. [Les 12 axes](#7--les-12-axes)
8. [Jalons versionnés](#8--jalons-versionnés)
9. [Indicateurs de succès (KPI)](#9--indicateurs-de-succès-kpi)
10. [Risques & garde-fous](#10--risques--garde-fous)
11. [Par où commencer](#11--par-où-commencer)
---
## 1. 📌 Résumé exécutif
**am** est aujourd'hui un excellent *gestionnaire* d'agents : 72 agents au
catalogue, 9 méthodes d'installation, dépendances résolues automatiquement,
processus pilotés, REPL avec passerelle shell, sauvegarde export/import.
Mais il est **aveugle** : il ne retient ni ce qui a été fait, ni quand, ni
par quel agent, ni dans quel projet, ni avec quel résultat. Les logs sont du
texte libre non interrogeable, l'état ne contient que les installations et
le PID courant, l'historique est un simple fichier plat.
Cette roadmap transforme **am** en **cockpit de votre parc d'agents IA** :
| Rôle | Promesse | Porté par |
|---|---|---|
| 📓 Journal de bord | tout est tracé, rien ne se perd | Axe 1 (journal d'événements) |
| 🔎 Rétroviseur | tout est cherchable en 2 secondes | Axes 2, 3, 4 |
| 📊 Tableau de bord | tout est mesuré | Axes 1, 5, 9 |
| 🤖 Copilote | il pilote à votre place | Axes 6, 7 |
**Chiffres clés :** 12 axes · ~55 fonctionnalités · 4 jalons (v0.3.0 → v1.0)
· ~15 semaines de travail cumulé · 0 dépendance runtime ajoutée.
---
## 2. 📚 Légende de lecture
| Symbole | Signification |
|---|---|
| **P0** | Fondations — à démarrer tout de suite (socle des autres axes) |
| **P1** | Court terme — après la phase 0 |
| **P2** | Moyen terme |
| **P3** | Vision — long terme |
| ⭐ | Reprend une demande explicite de l'utilisateur |
| **S** | Effort : moins d'1 jour |
| **M** | Effort : 2 à 4 jours |
| **L** | Effort : 1 à 2 semaines |
| **XL** | Effort : plus de 2 semaines |
| ⬜ / 🔨 / ✅ | Proposé / En cours / Livré (rien n'est commencé aujourd'hui) |
---
## 3. ⚡ Quick wins — valeur immédiate
*Tous en effort S, phase P0, sans dépendance sur le socle. Livrables en
quelques jours, à caler avant ou pendant la construction du journal.*
| Fonctionnalité | Commande | Apport |
|---|---|---|
| Voir les logs d'un agent | am logs claude-code (--follow) | tail des logs déjà écrits par process.rs |
| Filtres sur la liste | am list --running · --sort name,version,status | repérage instantané |
| Éditer la config sans éditeur | am config set key value | moins de friction YAML |
| Alias à la volée | am alias add cc claude-code | sans éditer le fichier |
| Historique propre | dédup + horodatage de history.txt | base saine pour l'axe 2 |
| Doctor scriptable | am doctor --json | CI et monitoring |
| Ouvrir l'installation | am open claude-code | explorer le répertoire de l'agent |
| Version scriptable | am version --json | introspection des scripts |
---
## 4. 🧭 État des lieux (points d'ancrage dans le code)
| Brique actuelle | Fichier | Limite aujourd'hui |
|---|---|---|
| Catalogue 72 agents, alias, groupes | src/catalog.rs, config.yaml | recherche = sous-chaîne stricte, pas de ranking |
| Installation (9 méthodes), dépendances OS | src/installers/, src/deps.rs, src/toolchain.rs | solide — rien à redire |
| État local | src/state.rs (state.json v1) | installations + PID courant uniquement, **aucun historique** |
| Détection externe + cache | src/probe.rs | excellent pattern de cache — à généraliser |
| Processus | src/process.rs | logs non structurés, pas de sessions, pas de métriques |
| Journal applicatif | src/output.rs | texte libre horodaté, non interrogeable |
| REPL + passerelle shell | src/repl.rs (history.txt, session_id()) | historique plat, pas de recherche ni de stats |
| Backup | src/commands/export_import.rs | manuel, pas de synchronisation |
---
## 5. 🎯 Vision & principes directeurs
**Vision :** le cockpit unique du parc d'agents — journal de bord, rétroviseur,
tableau de bord et copilote.
**Principes :**
1. **Zéro dépendance runtime** — tout est Rust pur et fichiers locaux
(JSONL/JSON), jamais de base de données externe.
2. **Local par défaut, opt-in pour le distant** — la télémétrie n'existe que
si l'utilisateur l'active ; tout le reste est 100 % local.
3. **Contrats stables** — les sorties --json sont la base des scripts et des
futurs UI : champs ajoutés, jamais retirés.
4. **Performance** — écriture en append (JSONL), lectures par fenêtres
temporelles, index dérivés régénérables, caches sur le modèle de probe.rs.
5. **Livraison continue** — chaque phase produit un binaire utilisable et
testé ; la barre des 45+ tests monte à chaque jalon.
---
## 6. 🧱 Socle technique : le modèle de données
*Tout dépend de ceci. À construire en premier, avant les commandes visibles.*
state_dir/
state.json # v2 : + sessions_count, last_used (migration auto depuis v1)
events.jsonl # NOUVEAU — journal d'événements (source de vérité)
events-202608.jsonl # rotation mensuelle
sessions.json # NOUVEAU — registre des sessions (index dérivé)
history/
<session>.jsonl # NOUVEAU — historique structuré du REPL
projects.json # NOUVEAU — agrégats par projet (recalculable)
probe-cache.json # existant — pattern de cache à suivre
| Fichier | Rôle | Alimenté par | Reconstruction |
|---|---|---|---|
| events.jsonl | faits bruts (start, stop, run, install…) | process.rs, run_cmd.rs, install_cmd.rs, repl.rs, doctor_cmd.rs | source de vérité, jamais reconstruit |
| sessions.json | index des sessions | dérivé du journal | doctor --fix (rejoue le journal) |
| projects.json | agrégats par projet | dérivé du journal | doctor --fix |
| history/*.jsonl | commandes REPL | repl.rs | non (données primaires) |
| state.json v3 | installations + résumé + annotations (★, notes, tags) | state.rs | partiellement (compteurs depuis le journal) |
**Règles :** JSONL append-only pour les faits · JSON dérivés régénérables ·
migration additive (state v1 reste lisible partout) · doctor --fix répare
tout ce qui peut l'être.
---
## 7. 🗂️ Les 12 axes
### Axe 1 — 📊 Observabilité & statistiques d'utilisation ⭐
🎯 Savoir **quoi** a tourné, **quand**, **combien de temps**, avec **quel
taux de succès** — et **combien ça coûte**.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| Journal d'événements events.jsonl (toutes les actions de am) | M | P0 |
| am stats + --period 7d/30d/90d + --json (tableaux et barres ASCII) | M | P0 |
| am top — classement des agents par utilisation | S | P1 |
| am log — filtres agent/type/date, --follow | S → M | P0 → P1 |
| am report — rapport hebdo/mensuel en markdown (top agents, échecs, changements) | M | P1 |
| Suivi des coûts — tokens/€ par session quand l'agent expose son usage (Claude Code --output-format json, Codex…) | M | P2 |
| am monitor — TUI temps réel : CPU/mémoire par PID, uptime, alertes | L | P2 |
| Télémétrie anonyme **opt-in** (compteurs agrégés uniquement, jamais de chemins) | M | P3 |
am stats # vue globale
am stats claude-code --period 30d
am top # qui tourne le plus
am report --last-week # digest markdown
### Axe 2 — 💾 Historique des commandes ⭐
🎯 Gérer, **rechercher** et **réutiliser** tout ce qui a été tapé.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| Historique structuré : history/<session>.jsonl (cmd, kind am/shell, cwd, durée, exit, agent) + migration de history.txt | M | P0 |
| am history — filtres --kind, --cwd, --search, --failed, --session | M | P0 |
| Ctrl-R + !! + !install + !42 + ^old^new dans le REPL | M | P1 |
| am history --rerun 42 (réexécution confirmée) | S | P1 |
| Playbooks : am history 12..25 --save deploy.yaml puis am playbook deploy.yaml | L | P2 |
am history --search "install" --failed
am history --rerun 42
### Axe 3 — 🧭 Projets & workspaces ⭐
🎯 Centraliser **quel agent travaille sur quel projet** — automatiquement.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| Contexte auto au lancement : racine + branche git, stack détectée (Cargo.toml, package.json…), mis en cache | S | P0 |
| am projects / am projects api — agents, sessions, durées, dernière activité | M | P1 |
| Profils de projet dans config : default_agent, env, hooks | M | P1 |
| am start (sans argument) = agent par défaut du dossier courant ; REPL contextuel | S | P1 |
| am init — génère un agent-manager.yaml selon la stack détectée | S → M | P0 |
| Templates : am init --template web | M | P2 |
am projects
am projects api
am init # dans un projet Node → config pré-remplie
### Axe 4 — 🗂️ Sessions centralisées ⭐
🎯 Un registre unique de **toutes les sessions de tous les agents** —
consultable, reprenable, archivable.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| sessions.json + session_id() partagé (repl.rs) ; fin de session avec exit code et durée | M | P0 |
| Réconciliation au démarrage : sessions interrompues (crash, reboot) marquées et datées | S | P0 |
| am sessions — filtres agent/projet/statut ; show = résumé + extrait de log | M | P0 |
| am timeline — vue chronologique unifiée de toute l'activité | M | P1 |
| am sessions resume <id> — relance avec les mêmes args/env/cwd | M | P1 |
| am sessions export <id> + rétention configurable (settings.sessions_retention_days) | M | P2 |
am sessions --status failed
am sessions show 20260815_143926_a1b2c3
am timeline --project api
### Axe 5 — 🖥️ Tableau de bord
🎯 Voir l'état du parc d'un coup d'œil.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| am dashboard — TUI (ratatui/crossterm) : vue d'ensemble, activité en direct, stats, sessions, projets | L | P1 |
| am web — serveur local + API JSON + page HTML embarquée avec graphiques | XL | P2 |
### Axe 6 — 🤖 Automatisation & orchestration
🎯 Laisser am **superviser et piloter seul** le parc.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| Hooks on_install / on_start / on_stop / on_update (agent, projet, global) — via le runner existant | M | P0 |
| am watch claude-code --restart-on-crash --notify | M | P1 |
| Services : unités systemd / plists launchd / tâches Windows, am service install claude-code --autostart | L | P2 |
| Planification : am schedule --daily update --all + check santé (cron / Task Scheduler) | M | P2 |
| Orchestration de groupes : ordre, --parallel, attente de santé | L | P2 |
| Conteneurs : profil docker/podman par agent (isolation à la demande) | L | P2 |
| am doctor --watch — vérifications périodiques avec alertes | M | P2 |
### Axe 7 — 🧠 Intelligence & catalogue
🎯 Trouver le bon agent, suivre leurs sorties, et comparer.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| Recherche fuzzy dans am search (typos, scoring, « vouliez-vous dire ») | M | P1 |
| am compare a b — tableau côte à côte (méthode, deps, catégorie, activité) | S | P1 |
| am news — dernières releases des agents installés (API GitHub/Gitea, cache) | M | P1 |
| Catalogue distant : am catalog update / am catalog add <url> | M | P2 |
| am suggest "un agent pour du Python" — tags + usage réel | M | P2 |
| am lab — benchmark : même tâche sur N agents, comparaison durée/résultat/coût | L | P2 |
| Registre communautaire : publier son catalogue (Gitea) + am registry | L | P3 |
| am ask "installe claude et lance-le" — langage naturel → commande am (fournisseur LLM configurable, optionnel) | L | P3 |
### Axe 8 — 🔒 Sécurité & gouvernance
🎯 Protéger les secrets, tracer les changements, pouvoir revenir en arrière.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| am secret set OPENAI_KEY --agent claude-code (keyring OS) + injection --env automatique | M | P1 |
| am audit — qui a modifié quoi quand (checksums des configs, événements) | M | P2 |
| am update --rollback — backup automatique avant chaque mise à jour | M | P2 |
| Politiques : pin de version, settings.update_policy | S | P2 |
| Profils sandbox par agent (commandes/répertoires autorisés) | L | P3 |
### Axe 9 — 🌐 Multi-machine & collaboration
🎯 Retrouver son cockpit partout, et le partager.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| Synchronisation git automatique : settings.sync_repo, am sync, push à la fermeture du REPL | L | P2 |
| Partage de catalogue d'équipe : include par URL | S | P2 |
| am migrate — assistant de transfert machine A → B | M | P2 |
| am serve --token — API HTTP + WebSocket pour piloter à distance | XL | P3 |
### Axe 10 — 🧩 Confort & personnalisation
🎯 Adapter am à sa façon de travailler.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| Favoris : am favorite / am unfavorite + ⭐ dans am list | S | P1 |
| Notes : am note claude-code "utiliser pour …" | S | P1 |
| Tags personnels : am tag claude-code python | S | P1 |
| Raccourcis de commandes : settings.shortcuts (i → install, s → start) | S | P1 |
| Profils d'environnement : am profile dev / prod (env + args + agent par profil) | M | P1 |
| Thèmes couleurs du REPL | S | P2 |
### Axe 11 — 🎛️ Modèles locaux
🎯 Étendre le cockpit aux modèles : **ollama, llama.cpp, LM Studio**.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| am models — inventaire : nom, taille disque, quantisation, dernière utilisation | M | P2 |
| Lien agent ↔ modèle : am run mon-agent --model llama3.1 | M | P2 |
| doctor vérifie ollama / llama-server comme n'importe quel outil | S | P2 |
| am models prune — purge des modèles inutilisés | S | P2 |
### Axe 12 — 🛠️ Écosystème & expérience développeur
🎯 Rendre am disponible partout et contribuable.
| Fonctionnalité | Effort | Phase |
|---|---|---|
| Fixtures d'événements rejouables + tests de bout en bout du journal | S | P0 |
| Packaging officiel : winget, scoop, deb/rpm, Homebrew + CI de release | M | P1 |
| Complétions dynamiques (agents installés, groupes personnalisés) | S | P1 |
| Man pages + documentation générée | S | P1 |
| i18n : messages EN/FR | L | P2 |
| Plugin scripts (hooks avancés, intégration CI) | M | P2 |
---
## 8. 🚀 Jalons versionnés
| Jalon | Version | Contenu principal | Durée | Critère de sortie |
|---|---|---|---|---|
| **J0 — Fondations** | v0.3.0 | events.jsonl, sessions, am stats, am log, am history, am init, quick wins | ≈ 2 sem | 100 % des actions tracées · 100+ tests |
| **J1 — Cockpit** | v0.4.0 | recherche fuzzy, Ctrl-R, projects, timeline, dashboard TUI, hooks, secret, favoris, packaging | ≈ 3 sem | retrouver n'importe quelle action passée en < 2 s |
| **J2 — Automatisation** | v0.5.0 | watch, services, schedule, news/compare, catalogue distant, lab, models, sync | ≈ 4 sem | parc auto-supervisé (crash = redémarrage + alerte) |
| **J3 — Plateforme** | v1.0 | am web, am serve, am ask, registry, télémétrie opt-in, sandbox | ≈ 6 sem | dashboard web complet + API distante |
---
## 9. 📈 Indicateurs de succès (KPI)
| Indicateur | Cible |
|---|---|
| Part des actions tracées dans events.jsonl | 100 % dès la v0.3.0 |
| Temps pour retrouver une commande ou session passée | < 2 s |
| am list à chaud, journal actif | < 100 ms |
| Dépendances runtime ajoutées | 0 |
| Couverture de tests | ≥ 100 tests en v0.3, +25 par jalon |
| Contrats --json cassés entre versions mineures | 0 |
---
## 10. ⚠️ Risques & garde-fous
1. **ADN « zéro runtime »** : toute dépendance nouvelle est compilée dans le
binaire, jamais requise à l'exécution.
2. **Performance** : le journal grossit — fenêtres temporelles, index dérivés,
rotation mensuelle, caches façon probe.rs.
3. **Vie privée** : local par défaut ; télémétrie opt-in, agrégée, anonyme.
4. **Contrats --json** : ajout de champs sans retrait (scripts utilisateurs).
5. **Compatibilité** : migrations additives ; les exports 0.2.x restent
importables en 0.3+.
6. **Complexité UX** : les fonctions avancées restent derrière
settings.experimental jusqu'à maturité.
7. **Périmètre** : rester un orchestrateur — ne pas réimplémenter les
fonctionnalités propres aux agents (tokens, MCP, prompts), sauf valeur
transversale (stats, sessions, secrets, coûts).
---
## 11. 🏁 Par où commencer
*Le suivi est en place sur Gitea : 4 milestones (v0.3.0 → v1.0), 22 labels
(P0–P3, axe-1…axe-12, S/M/L/XL, quick-win, epic) et les issues ci-dessous.
Chaque PR doit référencer son issue avec `closes #N` pour une clôture
automatique. Ordre de construction proposé pour la phase 0 — chaque étape
est un PR indépendant et testé :*
1. **[PR 1](https://git.dracodev.net/Projets/agent-manager/issues/2)** — src/events.rs : journal JSONL + rotation + tests.
2. **[PR 2](https://git.dracodev.net/Projets/agent-manager/issues/3)** — instrumentation start/stop/run (process.rs, run_cmd.rs) — dépend de PR 1.
3. **[PR 3](https://git.dracodev.net/Projets/agent-manager/issues/4)** — state.json v2 + migration additive — dépend de PR 1.
4. **[PR 4](https://git.dracodev.net/Projets/agent-manager/issues/5)** — sessions.json + am sessions + réconciliation — dépend de PR 2.
5. **[PR 5](https://git.dracodev.net/Projets/agent-manager/issues/6)** — am stats + am log (lecture du journal) — dépend de PR 2.
6. **[PR 6](https://git.dracodev.net/Projets/agent-manager/issues/7)** — historique structuré + am history (migration de history.txt).
7. **[PR 7](https://git.dracodev.net/Projets/agent-manager/issues/8)** — am init + détection de contexte (git, stack, cache) — dépend de PR 1.
8. **En parallèle** — les quick wins de la section 3 :
[am logs #9](https://git.dracodev.net/Projets/agent-manager/issues/9) ·
[am list --sort #10](https://git.dracodev.net/Projets/agent-manager/issues/10) ·
[am config set #11](https://git.dracodev.net/Projets/agent-manager/issues/11) ·
[am alias add #12](https://git.dracodev.net/Projets/agent-manager/issues/12) ·
[dedup history.txt #13](https://git.dracodev.net/Projets/agent-manager/issues/13) ·
[am doctor --json #14](https://git.dracodev.net/Projets/agent-manager/issues/14) ·
[am version --json #15](https://git.dracodev.net/Projets/agent-manager/issues/15) ·
[am open #16](https://git.dracodev.net/Projets/agent-manager/issues/16).
Épique de suivi : [issue #1](https://git.dracodev.net/Projets/agent-manager/issues/1) (Phase 0).
### Phase 1 — Cockpit (v0.4.0) — découpage créé le 2026-08-17
Épique : [issue #25](https://git.dracodev.net/Projets/agent-manager/issues/25) · milestone v0.4.0 · 17 issues.
| # | Issue | Effort |
|---|---|---|
| [#26](https://git.dracodev.net/Projets/agent-manager/issues/26) | Recherche fuzzy dans am search | M |
| [#27](https://git.dracodev.net/Projets/agent-manager/issues/27) | Ctrl-R + réexécution dans le REPL | M |
| [#28](https://git.dracodev.net/Projets/agent-manager/issues/28) | am history --rerun N | S |
| [#29](https://git.dracodev.net/Projets/agent-manager/issues/29) | am projects + profils de projet | M |
| [#30](https://git.dracodev.net/Projets/agent-manager/issues/30) | am start contextuel (dépend de #29) | S |
| [#31](https://git.dracodev.net/Projets/agent-manager/issues/31) | am timeline (vue chronologique unifiée) | M |
| [#32](https://git.dracodev.net/Projets/agent-manager/issues/32) | am sessions resume <id> | M |
| [#33](https://git.dracodev.net/Projets/agent-manager/issues/33) | am dashboard (TUI temps réel) | L |
| [#34](https://git.dracodev.net/Projets/agent-manager/issues/34) | Hooks on_install/on_start/on_stop/on_update | M |
| [#35](https://git.dracodev.net/Projets/agent-manager/issues/35) | am watch --restart-on-crash --notify | M |
| [#36](https://git.dracodev.net/Projets/agent-manager/issues/36) | am secret (keyring OS) + injection --env | M |
| [#37](https://git.dracodev.net/Projets/agent-manager/issues/37) | am top + am report (rapport hebdo/mensuel) | M |
| [#38](https://git.dracodev.net/Projets/agent-manager/issues/38) | am log --follow (flux en direct) | S |
| [#39](https://git.dracodev.net/Projets/agent-manager/issues/39) | Favoris, notes et tags personnels | M |
| [#40](https://git.dracodev.net/Projets/agent-manager/issues/40) | Profils d'environnement (am profile dev/prod) | M |
| [#41](https://git.dracodev.net/Projets/agent-manager/issues/41) | Complétions dynamiques + man pages | S |
| [#42](https://git.dracodev.net/Projets/agent-manager/issues/42) | Packaging officiel (winget/scoop/deb/Homebrew) + CI | M |
Critère de sortie du jalon : retrouver n'importe quelle action passée en
moins de 2 secondes.
### Phase 2 — Automatisation (v0.5.0) — découpage créé le 2026-08-17
Épique : [issue #47](https://git.dracodev.net/Projets/agent-manager/issues/47) · milestone v0.5.0 · 27 issues.
| # | Issue | Effort |
|---|---|---|
| [#49](https://git.dracodev.net/Projets/agent-manager/issues/49) | Suivi des coûts — tokens/€ par session | M |
| [#50](https://git.dracodev.net/Projets/agent-manager/issues/50) | am monitor — TUI temps réel (CPU/mémoire par PID, alertes) | L |
| [#51](https://git.dracodev.net/Projets/agent-manager/issues/51) | Playbooks — am history --save + am playbook | L |
| [#52](https://git.dracodev.net/Projets/agent-manager/issues/52) | Templates — am init --template web | M |
| [#53](https://git.dracodev.net/Projets/agent-manager/issues/53) | am sessions export + rétention configurable | M |
| [#54](https://git.dracodev.net/Projets/agent-manager/issues/54) | am web — serveur local + API JSON + graphiques | XL |
| [#55](https://git.dracodev.net/Projets/agent-manager/issues/55) | Services système — am service install (systemd/launchd/tâches Windows) | L |
| [#56](https://git.dracodev.net/Projets/agent-manager/issues/56) | Planification — am schedule (--daily update --all, check santé) | M |
| [#57](https://git.dracodev.net/Projets/agent-manager/issues/57) | Orchestration de groupes (ordre, --parallel, attente de santé) | L |
| [#58](https://git.dracodev.net/Projets/agent-manager/issues/58) | Conteneurs — profils docker/podman par agent | L |
| [#59](https://git.dracodev.net/Projets/agent-manager/issues/59) | am doctor --watch — vérifications périodiques avec alertes | M |
| [#60](https://git.dracodev.net/Projets/agent-manager/issues/60) | Catalogue distant — am catalog update / am catalog add <url> | M |
| [#61](https://git.dracodev.net/Projets/agent-manager/issues/61) | am suggest — « un agent pour du Python » (tags + usage réel) | M |
| [#62](https://git.dracodev.net/Projets/agent-manager/issues/62) | am lab — benchmark : même tâche sur N agents | L |
| [#63](https://git.dracodev.net/Projets/agent-manager/issues/63) | am audit — qui a modifié quoi quand | M |
| [#64](https://git.dracodev.net/Projets/agent-manager/issues/64) | am update --rollback — backup automatique avant mise à jour | M |
| [#65](https://git.dracodev.net/Projets/agent-manager/issues/65) | Politiques — pin de version, settings.update_policy | S |
| [#66](https://git.dracodev.net/Projets/agent-manager/issues/66) | Synchronisation git automatique — am sync | L |
| [#67](https://git.dracodev.net/Projets/agent-manager/issues/67) | Partage de catalogue d'équipe (include par URL) | S |
| [#68](https://git.dracodev.net/Projets/agent-manager/issues/68) | am migrate — assistant de transfert machine A → B | M |
| [#69](https://git.dracodev.net/Projets/agent-manager/issues/69) | Thèmes couleurs du REPL | S |
| [#70](https://git.dracodev.net/Projets/agent-manager/issues/70) | am models — inventaire des modèles locaux (ollama, llama.cpp, LM Studio) | M |
| [#71](https://git.dracodev.net/Projets/agent-manager/issues/71) | Lien agent ↔ modèle — am run --model | M |
| [#72](https://git.dracodev.net/Projets/agent-manager/issues/72) | doctor vérifie ollama / llama-server comme n'importe quel outil | S |
| [#73](https://git.dracodev.net/Projets/agent-manager/issues/73) | am models prune — purge des modèles inutilisés | S |
| [#74](https://git.dracodev.net/Projets/agent-manager/issues/74) | i18n — messages EN/FR | L |
| [#75](https://git.dracodev.net/Projets/agent-manager/issues/75) | Plugin scripts (hooks avancés, intégration CI) | M |
Critère de sortie du jalon : parc auto-supervisé (crash = redémarrage +
alerte).
### Phase 3 — Plateforme (v1.0) — découpage créé le 2026-08-17
Épique : [issue #48](https://git.dracodev.net/Projets/agent-manager/issues/48) · milestone v1.0 · 5 issues.
| # | Issue | Effort |
|---|---|---|
| [#76](https://git.dracodev.net/Projets/agent-manager/issues/76) | Télémétrie anonyme opt-in (compteurs agrégés uniquement) | M |
| [#77](https://git.dracodev.net/Projets/agent-manager/issues/77) | Registre communautaire — am registry (publication + recherche sur Gitea) | L |
| [#78](https://git.dracodev.net/Projets/agent-manager/issues/78) | am ask — langage naturel → commande am (fournisseur LLM optionnel) | L |
| [#79](https://git.dracodev.net/Projets/agent-manager/issues/79) | Profils sandbox par agent (commandes/répertoires autorisés) | L |
| [#80](https://git.dracodev.net/Projets/agent-manager/issues/80) | am serve --token — API HTTP + WebSocket pour piloter à distance | XL |
Critère de sortie du jalon : dashboard web complet + API distante.
---
*Document généré à partir de l'analyse du code (v0.2.7, commit d886de3).
La phase 0 est livrée (v0.3.0) ; les découpages des phases 1 (v0.4.0),
2 (v0.5.0) et 3 (v1.0) sont en place sur Gitea (issues #25 à #80).*
+32
View File
@@ -0,0 +1,32 @@
//! Record build metadata (git commit and branch) at compile time so the
//! 'am version' command can display it, like Nushell's version table.
//!
//! When git is unavailable (e.g. building from a source tarball) the values
//! are simply empty.
use std::process::Command;
fn git(args: &[&str]) -> String {
Command::new("git")
.args(args)
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_default()
}
fn main() {
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-changed=.git/refs/heads");
let commit = git(&["rev-parse", "HEAD"]);
let branch = git(&["rev-parse", "--abbrev-ref", "HEAD"]);
// A detached HEAD reports the literal branch name "HEAD".
let branch = if branch == "HEAD" {
String::new()
} else {
branch
};
println!("cargo:rustc-env=AM_GIT_COMMIT={commit}");
println!("cargo:rustc-env=AM_GIT_BRANCH={branch}");
}
+991 -347
View File
File diff suppressed because it is too large Load Diff
+713 -126
View File
File diff suppressed because it is too large Load Diff
+1274 -545
View File
File diff suppressed because it is too large Load Diff
+678 -96
View File
@@ -20,6 +20,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -33,31 +34,445 @@ set edit:completion:arg-completer[am] = {|@words|
cand --help 'Print help (see more with ''--help'')'
cand -V 'Print version'
cand --version 'Print version'
cand list 'List agents: installed ones, or every agent known to the catalog'
cand list 'List installed agents (default); use --all for the whole catalog'
cand logs 'Tail the log file of an agent (--follow for a live view)'
cand open 'Open an agent''s installation directory in the file manager'
cand watch 'Supervise an agent and optionally restart it on crash'
cand dashboard 'Live TUI dashboard: overview, activity, stats, sessions, projects'
cand favorite 'Mark an agent as a favorite (star in list and status)'
cand unfavorite 'Remove the favorite star from an agent'
cand note 'Attach a free-form note to an agent (shown in ''am info''); ''am note <agent>'' without text prints the current note'
cand tag 'Add a personal tag to an agent'
cand untag 'Remove a personal tag from an agent'
cand tags 'List personal tags (with counts), optionally for one agent'
cand profile 'Manage environment profiles (dev, prod, ...)'
cand man 'Generate a man page for am (or for one command)'
cand _agents 'Internal: list agent names for shell completions (hidden)'
cand alias 'Manage command aliases (cc -> claude-code)'
cand secret 'Manage secrets in the OS keyring (never in plaintext config)'
cand start 'Start an agent (foreground by default, or detached with --background)'
cand stop 'Stop a background agent (SIGTERM, then SIGKILL after the timeout)'
cand restart 'Restart an agent: stop, then start with the same options'
cand status 'Show the state of one agent, or of every installed agent'
cand sessions 'List the sessions of every agent (and of the interactive shell)'
cand stats 'Show usage statistics computed from the event journal'
cand top 'Show the most used agents (top 10)'
cand report 'Write a markdown activity report'
cand projects 'Show which agent works on which project'
cand timeline 'One chronological view of every activity (journal + REPL history)'
cand log 'Read the event journal: everything am did'
cand install 'Install an agent and its dependencies'
cand uninstall 'Uninstall an agent and clean its files'
cand uninstall 'Uninstall an agent (managed or external) and clean its files'
cand update 'Update an installed agent to the latest available version'
cand search 'Search the catalog by keyword (name, description, category, tags)'
cand history 'Search the command history (am and shell commands)'
cand info 'Show detailed information about one agent'
cand init 'Generate a local agent-manager.yaml for the current directory'
cand help 'Display help: the overview, a command, the command list, or a search'
cand version 'Display version and build information'
cand config 'Manage the configuration file'
cand doctor 'Check the environment: tools, config validity, paths, permissions'
cand run 'Run the agent command directly with the given arguments (no process management)'
cand completion 'Generate a shell completion script'
cand self-update 'Update agent-manager itself from GitHub Releases'
cand self-uninstall 'Remove agent-manager and everything it created from this machine'
cand export 'Export the configuration and installation state (backup)'
cand import 'Import a previously exported configuration and state'
cand help 'Print this message or the help of the given subcommand(s)'
}
&'am;list'= {
cand --category 'Only show agents of this category'
cand --sort 'Sort by name, version or status (default: name)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --installed-only 'Only show agents that are installed (managed or external)'
cand --all 'Include hidden agents as well'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --all 'Show every agent known to the catalog, not only the installed ones'
cand --running 'Only show running agents'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;logs'= {
cand --lines 'Number of lines to show (default: 20)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --follow 'Follow the log as it grows'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;open'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;watch'= {
cand --interval 'Check interval in seconds (default: 5)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --restart 'Restart the agent automatically when it dies (with backoff)'
cand --notify 'Desktop notification on every restart'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;dashboard'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;favorite'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;unfavorite'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;note'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;tag'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;untag'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;tags'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;profile'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
cand list 'List every defined profile'
cand show 'Show one profile in detail (agent, env, args)'
}
&'am;profile;list'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;profile;show'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;man'= {
cand --output 'Write the page(s) to this directory instead of stdout'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;_agents'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --installed 'Only installed agents (managed + external), aliases and groups'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;alias'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
cand add 'Add an alias (am alias add cc claude-code)'
cand remove 'Remove an alias'
cand list 'List every alias'
}
&'am;alias;add'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;alias;remove'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;alias;list'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;secret'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
cand set 'Store a secret for an agent'
cand unset 'Remove a secret'
cand list 'List stored secret names (values are never shown)'
}
&'am;secret;set'= {
cand --agent 'Agent it belongs to'
cand --value 'Secret value (prefer --value over shell history; never logged)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;secret;unset'= {
cand --agent 'Agent it belongs to'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;secret;list'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -73,8 +488,10 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;start'= {
cand --args 'Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)'
cand --env 'Set an environment variable for the agent (repeatable): --env KEY=VALUE'
cand --profile 'Apply an environment profile (env + args, defined in the config)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -b 'Run detached in the background; output goes to the agent log file'
cand --background 'Run detached in the background; output goes to the agent log file'
cand -f 'Run in the foreground, attached to this terminal (the default)'
@@ -96,6 +513,7 @@ set edit:completion:arg-completer[am] = {|@words|
cand --timeout 'Grace period in seconds before killing (default: from config, 5s)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --force 'Kill immediately instead of terminating gracefully'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
@@ -112,9 +530,11 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;restart'= {
cand --args 'Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)'
cand --env 'Set an environment variable for the agent (repeatable): --env KEY=VALUE'
cand --profile 'Apply an environment profile (env + args, defined in the config)'
cand --timeout 'Grace period in seconds before killing (default: from config, 5s)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -b 'Run detached in the background; output goes to the agent log file'
cand --background 'Run detached in the background; output goes to the agent log file'
cand -f 'Run in the foreground, attached to this terminal (the default)'
@@ -136,6 +556,135 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;status'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;sessions'= {
cand --project 'Only sessions of this project'
cand --status 'Only sessions with this status (running, stopped, failed, interrupted)'
cand --show 'Show one session in detail (summary + log excerpt)'
cand --resume 'Resume a finished session: relaunch the agent with its recorded arguments'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;stats'= {
cand --period 'Only events of the last period (7d, 30d, 90d, all)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;top'= {
cand --period 'Only events of the last period (7d, 30d, 90d, all)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;report'= {
cand --output 'Output file (default: report-YYYYMMDD.md in the state directory)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --last-week 'Cover the last 7 days'
cand --last-month 'Cover the last 30 days'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;projects'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;timeline'= {
cand --project 'Only activity of this project'
cand --since 'Only activity at or after this date (YYYY-MM-DD or RFC 3339)'
cand --limit 'Maximum number of entries (default: 50)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;log'= {
cand --kind 'Only these event kinds (comma separated: start,stop,run,install,...)'
cand --since 'Only events at or after this date (YYYY-MM-DD or RFC 3339)'
cand --limit 'Maximum number of events (default: 50)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --follow 'Follow the journal as new events arrive'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -152,6 +701,7 @@ set edit:completion:arg-completer[am] = {|@words|
cand --method 'Select the install method (index, or type: npm, pip, uv, cargo, go, bun, curl, binary, git)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --force 'Reinstall even if already installed'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
@@ -168,6 +718,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;uninstall'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --purge 'Also remove logs and the agent entry from the user config file'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
@@ -184,6 +735,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;update'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --all 'Update every installed managed agent'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
@@ -199,8 +751,33 @@ set edit:completion:arg-completer[am] = {|@words|
}
&'am;search'= {
cand --category 'Restrict to this category'
cand --tag 'Only agents carrying this personal tag'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;history'= {
cand --kind 'Only commands of this kind (am or shell)'
cand --cwd 'Only commands run in this directory'
cand --search 'Full-text search in the command line'
cand --session 'Only commands of this session'
cand --limit 'Maximum number of entries (default: 100)'
cand --rerun 'Re-run the Nth most recent entry (with confirmation)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --failed 'Only failed commands'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -216,6 +793,57 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;info'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;init'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --force 'Overwrite an existing agent-manager.yaml'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;help'= {
cand --find 'Search through all help commands table'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;version'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -231,6 +859,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;config'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -247,11 +876,12 @@ set edit:completion:arg-completer[am] = {|@words|
cand edit 'Open the user configuration file in your editor'
cand validate 'Validate the configuration file(s)'
cand add 'Add another agent definition file to the user configuration'
cand help 'Print this message or the help of the given subcommand(s)'
cand set 'Set one configuration key without an editor (config set settings.default_shell pwsh)'
}
&'am;config;show'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -267,6 +897,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;config;path'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -282,6 +913,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;config;edit'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -297,6 +929,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;config;validate'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -312,6 +945,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;config;add'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -324,29 +958,26 @@ set edit:completion:arg-completer[am] = {|@words|
cand -h 'Print help'
cand --help 'Print help'
}
&'am;config;help'= {
cand show 'Print the effective (merged) configuration as YAML'
cand path 'Print the location of the configuration file(s)'
cand edit 'Open the user configuration file in your editor'
cand validate 'Validate the configuration file(s)'
cand add 'Add another agent definition file to the user configuration'
cand help 'Print this message or the help of the given subcommand(s)'
}
&'am;config;help;show'= {
}
&'am;config;help;path'= {
}
&'am;config;help;edit'= {
}
&'am;config;help;validate'= {
}
&'am;config;help;add'= {
}
&'am;config;help;help'= {
&'am;config;set'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help'
cand --help 'Print help'
}
&'am;doctor'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --fix 'Attempt to repair problems (create directories, fix the state file)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
@@ -363,6 +994,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;run'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -378,6 +1010,8 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;completion'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --installed 'Add dynamic completion of the installed agents, aliases and groups (queried from ''am _agents'' at Tab time)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -394,6 +1028,7 @@ set edit:completion:arg-completer[am] = {|@words|
cand --to 'Download the new binary to this path instead of replacing the current one'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand --check 'Only check whether a newer version exists'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
@@ -407,10 +1042,27 @@ set edit:completion:arg-completer[am] = {|@words|
cand -h 'Print help'
cand --help 'Print help'
}
&'am;self-uninstall'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
cand --quiet 'Quiet mode: only errors are printed'
cand -y 'Answer yes to every confirmation prompt'
cand --yes 'Answer yes to every confirmation prompt'
cand --dry-run 'Simulate the action without changing anything'
cand --json 'Emit machine-readable JSON on stdout (for scripts)'
cand --no-color 'Disable ANSI colors'
cand -h 'Print help (see more with ''--help'')'
cand --help 'Print help (see more with ''--help'')'
}
&'am;export'= {
cand --output 'Output file (default: am-export.json)'
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -426,6 +1078,7 @@ set edit:completion:arg-completer[am] = {|@words|
&'am;import'= {
cand -c 'Use an alternate configuration file'
cand --config 'Use an alternate configuration file'
cand --theme 'Color theme for the output (see ''am help theme'' for the list)'
cand -v 'Verbose output: show every command executed and its details'
cand --verbose 'Verbose output: show every command executed and its details'
cand -q 'Quiet mode: only errors are printed'
@@ -438,77 +1091,6 @@ set edit:completion:arg-completer[am] = {|@words|
cand -h 'Print help'
cand --help 'Print help'
}
&'am;help'= {
cand list 'List agents: installed ones, or every agent known to the catalog'
cand start 'Start an agent (foreground by default, or detached with --background)'
cand stop 'Stop a background agent (SIGTERM, then SIGKILL after the timeout)'
cand restart 'Restart an agent: stop, then start with the same options'
cand status 'Show the state of one agent, or of every installed agent'
cand install 'Install an agent and its dependencies'
cand uninstall 'Uninstall an agent and clean its files'
cand update 'Update an installed agent to the latest available version'
cand search 'Search the catalog by keyword (name, description, category, tags)'
cand info 'Show detailed information about one agent'
cand config 'Manage the configuration file'
cand doctor 'Check the environment: tools, config validity, paths, permissions'
cand run 'Run the agent command directly with the given arguments (no process management)'
cand completion 'Generate a shell completion script'
cand self-update 'Update agent-manager itself from GitHub Releases'
cand export 'Export the configuration and installation state (backup)'
cand import 'Import a previously exported configuration and state'
cand help 'Print this message or the help of the given subcommand(s)'
}
&'am;help;list'= {
}
&'am;help;start'= {
}
&'am;help;stop'= {
}
&'am;help;restart'= {
}
&'am;help;status'= {
}
&'am;help;install'= {
}
&'am;help;uninstall'= {
}
&'am;help;update'= {
}
&'am;help;search'= {
}
&'am;help;info'= {
}
&'am;help;config'= {
cand show 'Print the effective (merged) configuration as YAML'
cand path 'Print the location of the configuration file(s)'
cand edit 'Open the user configuration file in your editor'
cand validate 'Validate the configuration file(s)'
cand add 'Add another agent definition file to the user configuration'
}
&'am;help;config;show'= {
}
&'am;help;config;path'= {
}
&'am;help;config;edit'= {
}
&'am;help;config;validate'= {
}
&'am;help;config;add'= {
}
&'am;help;doctor'= {
}
&'am;help;run'= {
}
&'am;help;completion'= {
}
&'am;help;self-update'= {
}
&'am;help;export'= {
}
&'am;help;import'= {
}
&'am;help;help'= {
}
]
$completions[$command]
}
+441 -49
View File
@@ -1,6 +1,6 @@
# Print an optspec for argparse to handle cmd's options that are independent of any subcommand.
function __fish_am_global_optspecs
string join \n c/config= v/verbose q/quiet y/yes dry-run json no-color h/help V/version
string join \n c/config= v/verbose q/quiet y/yes dry-run json no-color theme= h/help V/version
end
function __fish_am_needs_command
@@ -25,6 +25,7 @@ function __fish_am_using_subcommand
end
complete -c am -n "__fish_am_needs_command" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_needs_command" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_needs_command" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_needs_command" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_needs_command" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -33,28 +34,56 @@ complete -c am -n "__fish_am_needs_command" -l json -d 'Emit machine-readable JS
complete -c am -n "__fish_am_needs_command" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_needs_command" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c am -n "__fish_am_needs_command" -s V -l version -d 'Print version'
complete -c am -n "__fish_am_needs_command" -f -a "list" -d 'List agents: installed ones, or every agent known to the catalog'
complete -c am -n "__fish_am_needs_command" -f -a "list" -d 'List installed agents (default); use --all for the whole catalog'
complete -c am -n "__fish_am_needs_command" -f -a "logs" -d 'Tail the log file of an agent (--follow for a live view)'
complete -c am -n "__fish_am_needs_command" -f -a "open" -d 'Open an agent\'s installation directory in the file manager'
complete -c am -n "__fish_am_needs_command" -f -a "watch" -d 'Supervise an agent and optionally restart it on crash'
complete -c am -n "__fish_am_needs_command" -f -a "dashboard" -d 'Live TUI dashboard: overview, activity, stats, sessions, projects'
complete -c am -n "__fish_am_needs_command" -f -a "favorite" -d 'Mark an agent as a favorite (star in list and status)'
complete -c am -n "__fish_am_needs_command" -f -a "unfavorite" -d 'Remove the favorite star from an agent'
complete -c am -n "__fish_am_needs_command" -f -a "note" -d 'Attach a free-form note to an agent (shown in \'am info\'); \'am note <agent>\' without text prints the current note'
complete -c am -n "__fish_am_needs_command" -f -a "tag" -d 'Add a personal tag to an agent'
complete -c am -n "__fish_am_needs_command" -f -a "untag" -d 'Remove a personal tag from an agent'
complete -c am -n "__fish_am_needs_command" -f -a "tags" -d 'List personal tags (with counts), optionally for one agent'
complete -c am -n "__fish_am_needs_command" -f -a "profile" -d 'Manage environment profiles (dev, prod, ...)'
complete -c am -n "__fish_am_needs_command" -f -a "man" -d 'Generate a man page for am (or for one command)'
complete -c am -n "__fish_am_needs_command" -f -a "_agents" -d 'Internal: list agent names for shell completions (hidden)'
complete -c am -n "__fish_am_needs_command" -f -a "alias" -d 'Manage command aliases (cc -> claude-code)'
complete -c am -n "__fish_am_needs_command" -f -a "secret" -d 'Manage secrets in the OS keyring (never in plaintext config)'
complete -c am -n "__fish_am_needs_command" -f -a "start" -d 'Start an agent (foreground by default, or detached with --background)'
complete -c am -n "__fish_am_needs_command" -f -a "stop" -d 'Stop a background agent (SIGTERM, then SIGKILL after the timeout)'
complete -c am -n "__fish_am_needs_command" -f -a "restart" -d 'Restart an agent: stop, then start with the same options'
complete -c am -n "__fish_am_needs_command" -f -a "status" -d 'Show the state of one agent, or of every installed agent'
complete -c am -n "__fish_am_needs_command" -f -a "sessions" -d 'List the sessions of every agent (and of the interactive shell)'
complete -c am -n "__fish_am_needs_command" -f -a "stats" -d 'Show usage statistics computed from the event journal'
complete -c am -n "__fish_am_needs_command" -f -a "top" -d 'Show the most used agents (top 10)'
complete -c am -n "__fish_am_needs_command" -f -a "report" -d 'Write a markdown activity report'
complete -c am -n "__fish_am_needs_command" -f -a "projects" -d 'Show which agent works on which project'
complete -c am -n "__fish_am_needs_command" -f -a "timeline" -d 'One chronological view of every activity (journal + REPL history)'
complete -c am -n "__fish_am_needs_command" -f -a "log" -d 'Read the event journal: everything am did'
complete -c am -n "__fish_am_needs_command" -f -a "install" -d 'Install an agent and its dependencies'
complete -c am -n "__fish_am_needs_command" -f -a "uninstall" -d 'Uninstall an agent and clean its files'
complete -c am -n "__fish_am_needs_command" -f -a "uninstall" -d 'Uninstall an agent (managed or external) and clean its files'
complete -c am -n "__fish_am_needs_command" -f -a "update" -d 'Update an installed agent to the latest available version'
complete -c am -n "__fish_am_needs_command" -f -a "search" -d 'Search the catalog by keyword (name, description, category, tags)'
complete -c am -n "__fish_am_needs_command" -f -a "history" -d 'Search the command history (am and shell commands)'
complete -c am -n "__fish_am_needs_command" -f -a "info" -d 'Show detailed information about one agent'
complete -c am -n "__fish_am_needs_command" -f -a "init" -d 'Generate a local agent-manager.yaml for the current directory'
complete -c am -n "__fish_am_needs_command" -f -a "help" -d 'Display help: the overview, a command, the command list, or a search'
complete -c am -n "__fish_am_needs_command" -f -a "version" -d 'Display version and build information'
complete -c am -n "__fish_am_needs_command" -f -a "config" -d 'Manage the configuration file'
complete -c am -n "__fish_am_needs_command" -f -a "doctor" -d 'Check the environment: tools, config validity, paths, permissions'
complete -c am -n "__fish_am_needs_command" -f -a "run" -d 'Run the agent command directly with the given arguments (no process management)'
complete -c am -n "__fish_am_needs_command" -f -a "completion" -d 'Generate a shell completion script'
complete -c am -n "__fish_am_needs_command" -f -a "self-update" -d 'Update agent-manager itself from GitHub Releases'
complete -c am -n "__fish_am_needs_command" -f -a "self-uninstall" -d 'Remove agent-manager and everything it created from this machine'
complete -c am -n "__fish_am_needs_command" -f -a "export" -d 'Export the configuration and installation state (backup)'
complete -c am -n "__fish_am_needs_command" -f -a "import" -d 'Import a previously exported configuration and state'
complete -c am -n "__fish_am_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c am -n "__fish_am_using_subcommand list" -l category -d 'Only show agents of this category' -r
complete -c am -n "__fish_am_using_subcommand list" -l sort -d 'Sort by name, version or status (default: name)' -r
complete -c am -n "__fish_am_using_subcommand list" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand list" -l installed-only -d 'Only show agents that are installed (managed or external)'
complete -c am -n "__fish_am_using_subcommand list" -l all -d 'Include hidden agents as well'
complete -c am -n "__fish_am_using_subcommand list" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand list" -l all -d 'Show every agent known to the catalog, not only the installed ones'
complete -c am -n "__fish_am_using_subcommand list" -l running -d 'Only show running agents'
complete -c am -n "__fish_am_using_subcommand list" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand list" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand list" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -62,9 +91,236 @@ complete -c am -n "__fish_am_using_subcommand list" -l dry-run -d 'Simulate the
complete -c am -n "__fish_am_using_subcommand list" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand list" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand list" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand logs" -l lines -d 'Number of lines to show (default: 20)' -r
complete -c am -n "__fish_am_using_subcommand logs" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand logs" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand logs" -l follow -d 'Follow the log as it grows'
complete -c am -n "__fish_am_using_subcommand logs" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand logs" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand logs" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand logs" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand logs" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand logs" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand logs" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand open" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand open" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand open" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand open" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand open" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand open" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand open" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand open" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand open" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand watch" -l interval -d 'Check interval in seconds (default: 5)' -r
complete -c am -n "__fish_am_using_subcommand watch" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand watch" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand watch" -l restart -d 'Restart the agent automatically when it dies (with backoff)'
complete -c am -n "__fish_am_using_subcommand watch" -l notify -d 'Desktop notification on every restart'
complete -c am -n "__fish_am_using_subcommand watch" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand watch" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand watch" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand watch" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand watch" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand watch" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand watch" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand dashboard" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand dashboard" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand dashboard" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand dashboard" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand dashboard" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand dashboard" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand dashboard" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand dashboard" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand dashboard" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand favorite" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand favorite" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand favorite" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand favorite" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand favorite" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand favorite" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand favorite" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand favorite" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand favorite" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand unfavorite" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand unfavorite" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand unfavorite" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand unfavorite" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand unfavorite" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand unfavorite" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand unfavorite" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand unfavorite" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand unfavorite" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand note" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand note" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand note" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand note" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand note" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand note" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand note" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand note" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand note" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand tag" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand tag" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand tag" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand tag" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand tag" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand tag" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand tag" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand tag" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand tag" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand untag" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand untag" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand untag" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand untag" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand untag" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand untag" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand untag" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand untag" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand untag" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand tags" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand tags" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand tags" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand tags" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand tags" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand tags" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand tags" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand tags" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand tags" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -f -a "list" -d 'List every defined profile'
complete -c am -n "__fish_am_using_subcommand profile; and not __fish_seen_subcommand_from list show" -f -a "show" -d 'Show one profile in detail (agent, env, args)'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand profile; and __fish_seen_subcommand_from show" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand man" -l output -d 'Write the page(s) to this directory instead of stdout' -r -F
complete -c am -n "__fish_am_using_subcommand man" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand man" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand man" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand man" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand man" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand man" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand man" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand man" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand man" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand _agents" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand _agents" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand _agents" -l installed -d 'Only installed agents (managed + external), aliases and groups'
complete -c am -n "__fish_am_using_subcommand _agents" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand _agents" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand _agents" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand _agents" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand _agents" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand _agents" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand _agents" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -f -a "add" -d 'Add an alias (am alias add cc claude-code)'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -f -a "remove" -d 'Remove an alias'
complete -c am -n "__fish_am_using_subcommand alias; and not __fish_seen_subcommand_from add remove list" -f -a "list" -d 'List every alias'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from add" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from remove" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand alias; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -f -a "set" -d 'Store a secret for an agent'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -f -a "unset" -d 'Remove a secret'
complete -c am -n "__fish_am_using_subcommand secret; and not __fish_seen_subcommand_from set unset list" -f -a "list" -d 'List stored secret names (values are never shown)'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -l agent -d 'Agent it belongs to' -r
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -l value -d 'Secret value (prefer --value over shell history; never logged)' -r
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from set" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -l agent -d 'Agent it belongs to' -r
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from unset" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand secret; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand start" -l args -d 'Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)' -r
complete -c am -n "__fish_am_using_subcommand start" -l env -d 'Set an environment variable for the agent (repeatable): --env KEY=VALUE' -r
complete -c am -n "__fish_am_using_subcommand start" -l profile -d 'Apply an environment profile (env + args, defined in the config)' -r
complete -c am -n "__fish_am_using_subcommand start" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand start" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand start" -s b -l background -d 'Run detached in the background; output goes to the agent log file'
complete -c am -n "__fish_am_using_subcommand start" -s f -l foreground -d 'Run in the foreground, attached to this terminal (the default)'
complete -c am -n "__fish_am_using_subcommand start" -l notify -d 'Send a desktop notification once the agent has started'
@@ -77,6 +333,7 @@ complete -c am -n "__fish_am_using_subcommand start" -l no-color -d 'Disable ANS
complete -c am -n "__fish_am_using_subcommand start" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand stop" -l timeout -d 'Grace period in seconds before killing (default: from config, 5s)' -r
complete -c am -n "__fish_am_using_subcommand stop" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand stop" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand stop" -l force -d 'Kill immediately instead of terminating gracefully'
complete -c am -n "__fish_am_using_subcommand stop" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand stop" -s q -l quiet -d 'Quiet mode: only errors are printed'
@@ -87,8 +344,10 @@ complete -c am -n "__fish_am_using_subcommand stop" -l no-color -d 'Disable ANSI
complete -c am -n "__fish_am_using_subcommand stop" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand restart" -l args -d 'Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)' -r
complete -c am -n "__fish_am_using_subcommand restart" -l env -d 'Set an environment variable for the agent (repeatable): --env KEY=VALUE' -r
complete -c am -n "__fish_am_using_subcommand restart" -l profile -d 'Apply an environment profile (env + args, defined in the config)' -r
complete -c am -n "__fish_am_using_subcommand restart" -l timeout -d 'Grace period in seconds before killing (default: from config, 5s)' -r
complete -c am -n "__fish_am_using_subcommand restart" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand restart" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand restart" -s b -l background -d 'Run detached in the background; output goes to the agent log file'
complete -c am -n "__fish_am_using_subcommand restart" -s f -l foreground -d 'Run in the foreground, attached to this terminal (the default)'
complete -c am -n "__fish_am_using_subcommand restart" -l notify -d 'Send a desktop notification once the agent has started'
@@ -101,6 +360,7 @@ complete -c am -n "__fish_am_using_subcommand restart" -l json -d 'Emit machine-
complete -c am -n "__fish_am_using_subcommand restart" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand restart" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand status" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand status" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand status" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand status" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand status" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -108,8 +368,88 @@ complete -c am -n "__fish_am_using_subcommand status" -l dry-run -d 'Simulate th
complete -c am -n "__fish_am_using_subcommand status" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand status" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand status" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand sessions" -l project -d 'Only sessions of this project' -r
complete -c am -n "__fish_am_using_subcommand sessions" -l status -d 'Only sessions with this status (running, stopped, failed, interrupted)' -r
complete -c am -n "__fish_am_using_subcommand sessions" -l show -d 'Show one session in detail (summary + log excerpt)' -r
complete -c am -n "__fish_am_using_subcommand sessions" -l resume -d 'Resume a finished session: relaunch the agent with its recorded arguments' -r
complete -c am -n "__fish_am_using_subcommand sessions" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand sessions" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand sessions" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand sessions" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand sessions" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand sessions" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand sessions" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand sessions" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand sessions" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand stats" -l period -d 'Only events of the last period (7d, 30d, 90d, all)' -r
complete -c am -n "__fish_am_using_subcommand stats" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand stats" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand stats" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand stats" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand stats" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand stats" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand stats" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand stats" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand stats" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand top" -l period -d 'Only events of the last period (7d, 30d, 90d, all)' -r
complete -c am -n "__fish_am_using_subcommand top" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand top" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand top" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand top" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand top" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand top" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand top" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand top" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand top" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand report" -l output -d 'Output file (default: report-YYYYMMDD.md in the state directory)' -r -F
complete -c am -n "__fish_am_using_subcommand report" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand report" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand report" -l last-week -d 'Cover the last 7 days'
complete -c am -n "__fish_am_using_subcommand report" -l last-month -d 'Cover the last 30 days'
complete -c am -n "__fish_am_using_subcommand report" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand report" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand report" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand report" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand report" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand report" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand report" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand projects" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand projects" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand projects" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand projects" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand projects" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand projects" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand projects" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand projects" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand projects" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand timeline" -l project -d 'Only activity of this project' -r
complete -c am -n "__fish_am_using_subcommand timeline" -l since -d 'Only activity at or after this date (YYYY-MM-DD or RFC 3339)' -r
complete -c am -n "__fish_am_using_subcommand timeline" -l limit -d 'Maximum number of entries (default: 50)' -r
complete -c am -n "__fish_am_using_subcommand timeline" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand timeline" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand timeline" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand timeline" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand timeline" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand timeline" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand timeline" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand timeline" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand timeline" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand log" -l kind -d 'Only these event kinds (comma separated: start,stop,run,install,...)' -r
complete -c am -n "__fish_am_using_subcommand log" -l since -d 'Only events at or after this date (YYYY-MM-DD or RFC 3339)' -r
complete -c am -n "__fish_am_using_subcommand log" -l limit -d 'Maximum number of events (default: 50)' -r
complete -c am -n "__fish_am_using_subcommand log" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand log" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand log" -l follow -d 'Follow the journal as new events arrive'
complete -c am -n "__fish_am_using_subcommand log" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand log" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand log" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand log" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand log" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand log" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand log" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand install" -l method -d 'Select the install method (index, or type: npm, pip, uv, cargo, go, bun, curl, binary, git)' -r
complete -c am -n "__fish_am_using_subcommand install" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand install" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand install" -l force -d 'Reinstall even if already installed'
complete -c am -n "__fish_am_using_subcommand install" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand install" -s q -l quiet -d 'Quiet mode: only errors are printed'
@@ -119,6 +459,7 @@ complete -c am -n "__fish_am_using_subcommand install" -l json -d 'Emit machine-
complete -c am -n "__fish_am_using_subcommand install" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand install" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand uninstall" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand uninstall" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand uninstall" -l purge -d 'Also remove logs and the agent entry from the user config file'
complete -c am -n "__fish_am_using_subcommand uninstall" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand uninstall" -s q -l quiet -d 'Quiet mode: only errors are printed'
@@ -128,6 +469,7 @@ complete -c am -n "__fish_am_using_subcommand uninstall" -l json -d 'Emit machin
complete -c am -n "__fish_am_using_subcommand uninstall" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand uninstall" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand update" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand update" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand update" -l all -d 'Update every installed managed agent'
complete -c am -n "__fish_am_using_subcommand update" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand update" -s q -l quiet -d 'Quiet mode: only errors are printed'
@@ -137,7 +479,9 @@ complete -c am -n "__fish_am_using_subcommand update" -l json -d 'Emit machine-r
complete -c am -n "__fish_am_using_subcommand update" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand update" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand search" -l category -d 'Restrict to this category' -r
complete -c am -n "__fish_am_using_subcommand search" -l tag -d 'Only agents carrying this personal tag' -r
complete -c am -n "__fish_am_using_subcommand search" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand search" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand search" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand search" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand search" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -145,7 +489,24 @@ complete -c am -n "__fish_am_using_subcommand search" -l dry-run -d 'Simulate th
complete -c am -n "__fish_am_using_subcommand search" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand search" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand search" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand history" -l kind -d 'Only commands of this kind (am or shell)' -r
complete -c am -n "__fish_am_using_subcommand history" -l cwd -d 'Only commands run in this directory' -r
complete -c am -n "__fish_am_using_subcommand history" -l search -d 'Full-text search in the command line' -r
complete -c am -n "__fish_am_using_subcommand history" -l session -d 'Only commands of this session' -r
complete -c am -n "__fish_am_using_subcommand history" -l limit -d 'Maximum number of entries (default: 100)' -r
complete -c am -n "__fish_am_using_subcommand history" -l rerun -d 'Re-run the Nth most recent entry (with confirmation)' -r
complete -c am -n "__fish_am_using_subcommand history" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand history" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand history" -l failed -d 'Only failed commands'
complete -c am -n "__fish_am_using_subcommand history" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand history" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand history" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand history" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand history" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand history" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand history" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand info" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand info" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand info" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand info" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand info" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -153,21 +514,52 @@ complete -c am -n "__fish_am_using_subcommand info" -l dry-run -d 'Simulate the
complete -c am -n "__fish_am_using_subcommand info" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand info" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand info" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -f -a "show" -d 'Print the effective (merged) configuration as YAML'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -f -a "path" -d 'Print the location of the configuration file(s)'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -f -a "edit" -d 'Open the user configuration file in your editor'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -f -a "validate" -d 'Validate the configuration file(s)'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -f -a "add" -d 'Add another agent definition file to the user configuration'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c am -n "__fish_am_using_subcommand init" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand init" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand init" -l force -d 'Overwrite an existing agent-manager.yaml'
complete -c am -n "__fish_am_using_subcommand init" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand init" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand init" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand init" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand init" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand init" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand init" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand help" -l find -d 'Search through all help commands table' -r
complete -c am -n "__fish_am_using_subcommand help" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand help" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand help" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand help" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand help" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand help" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand help" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand help" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand help" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand version" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand version" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand version" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand version" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand version" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand version" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand version" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand version" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand version" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -f -a "show" -d 'Print the effective (merged) configuration as YAML'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -f -a "path" -d 'Print the location of the configuration file(s)'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -f -a "edit" -d 'Open the user configuration file in your editor'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -f -a "validate" -d 'Validate the configuration file(s)'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -f -a "add" -d 'Add another agent definition file to the user configuration'
complete -c am -n "__fish_am_using_subcommand config; and not __fish_seen_subcommand_from show path edit validate add set" -f -a "set" -d 'Set one configuration key without an editor (config set settings.default_shell pwsh)'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from show" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from show" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from show" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from show" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from show" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -176,6 +568,7 @@ complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from show" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from show" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from path" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from path" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from path" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from path" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from path" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -184,6 +577,7 @@ complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from path" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from path" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from edit" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from edit" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from edit" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from edit" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from edit" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -192,6 +586,7 @@ complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from edit" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from edit" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from validate" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from validate" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from validate" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from validate" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from validate" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -200,6 +595,7 @@ complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from validate" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from validate" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from add" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from add" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from add" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from add" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from add" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -207,13 +603,17 @@ complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from add" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from add" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from add" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "show" -d 'Print the effective (merged) configuration as YAML'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "path" -d 'Print the location of the configuration file(s)'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "edit" -d 'Open the user configuration file in your editor'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "validate" -d 'Validate the configuration file(s)'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "add" -d 'Add another agent definition file to the user configuration'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand config; and __fish_seen_subcommand_from set" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand doctor" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand doctor" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand doctor" -l fix -d 'Attempt to repair problems (create directories, fix the state file)'
complete -c am -n "__fish_am_using_subcommand doctor" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand doctor" -s q -l quiet -d 'Quiet mode: only errors are printed'
@@ -223,6 +623,7 @@ complete -c am -n "__fish_am_using_subcommand doctor" -l json -d 'Emit machine-r
complete -c am -n "__fish_am_using_subcommand doctor" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand doctor" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand run" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand run" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand run" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand run" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand run" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -231,6 +632,8 @@ complete -c am -n "__fish_am_using_subcommand run" -l json -d 'Emit machine-read
complete -c am -n "__fish_am_using_subcommand run" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand run" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand completion" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand completion" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand completion" -l installed -d 'Add dynamic completion of the installed agents, aliases and groups (queried from \'am _agents\' at Tab time)'
complete -c am -n "__fish_am_using_subcommand completion" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand completion" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand completion" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -240,6 +643,7 @@ complete -c am -n "__fish_am_using_subcommand completion" -l no-color -d 'Disabl
complete -c am -n "__fish_am_using_subcommand completion" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand self-update" -l to -d 'Download the new binary to this path instead of replacing the current one' -r -F
complete -c am -n "__fish_am_using_subcommand self-update" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand self-update" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand self-update" -l check -d 'Only check whether a newer version exists'
complete -c am -n "__fish_am_using_subcommand self-update" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand self-update" -s q -l quiet -d 'Quiet mode: only errors are printed'
@@ -248,8 +652,18 @@ complete -c am -n "__fish_am_using_subcommand self-update" -l dry-run -d 'Simula
complete -c am -n "__fish_am_using_subcommand self-update" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand self-update" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand self-update" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand self-uninstall" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand self-uninstall" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand self-uninstall" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand self-uninstall" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand self-uninstall" -s y -l yes -d 'Answer yes to every confirmation prompt'
complete -c am -n "__fish_am_using_subcommand self-uninstall" -l dry-run -d 'Simulate the action without changing anything'
complete -c am -n "__fish_am_using_subcommand self-uninstall" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand self-uninstall" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand self-uninstall" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c am -n "__fish_am_using_subcommand export" -l output -d 'Output file (default: am-export.json)' -r -F
complete -c am -n "__fish_am_using_subcommand export" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand export" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand export" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand export" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand export" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -258,6 +672,7 @@ complete -c am -n "__fish_am_using_subcommand export" -l json -d 'Emit machine-r
complete -c am -n "__fish_am_using_subcommand export" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand export" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand import" -s c -l config -d 'Use an alternate configuration file' -r -F
complete -c am -n "__fish_am_using_subcommand import" -l theme -d 'Color theme for the output (see \'am help theme\' for the list)' -r
complete -c am -n "__fish_am_using_subcommand import" -s v -l verbose -d 'Verbose output: show every command executed and its details'
complete -c am -n "__fish_am_using_subcommand import" -s q -l quiet -d 'Quiet mode: only errors are printed'
complete -c am -n "__fish_am_using_subcommand import" -s y -l yes -d 'Answer yes to every confirmation prompt'
@@ -265,26 +680,3 @@ complete -c am -n "__fish_am_using_subcommand import" -l dry-run -d 'Simulate th
complete -c am -n "__fish_am_using_subcommand import" -l json -d 'Emit machine-readable JSON on stdout (for scripts)'
complete -c am -n "__fish_am_using_subcommand import" -l no-color -d 'Disable ANSI colors'
complete -c am -n "__fish_am_using_subcommand import" -s h -l help -d 'Print help'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "list" -d 'List agents: installed ones, or every agent known to the catalog'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "start" -d 'Start an agent (foreground by default, or detached with --background)'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "stop" -d 'Stop a background agent (SIGTERM, then SIGKILL after the timeout)'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "restart" -d 'Restart an agent: stop, then start with the same options'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "status" -d 'Show the state of one agent, or of every installed agent'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "install" -d 'Install an agent and its dependencies'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "uninstall" -d 'Uninstall an agent and clean its files'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "update" -d 'Update an installed agent to the latest available version'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "search" -d 'Search the catalog by keyword (name, description, category, tags)'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "info" -d 'Show detailed information about one agent'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "config" -d 'Manage the configuration file'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "doctor" -d 'Check the environment: tools, config validity, paths, permissions'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "run" -d 'Run the agent command directly with the given arguments (no process management)'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "completion" -d 'Generate a shell completion script'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "self-update" -d 'Update agent-manager itself from GitHub Releases'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "export" -d 'Export the configuration and installation state (backup)'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "import" -d 'Import a previously exported configuration and state'
complete -c am -n "__fish_am_using_subcommand help; and not __fish_seen_subcommand_from list start stop restart status install uninstall update search info config doctor run completion self-update export import help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c am -n "__fish_am_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "show" -d 'Print the effective (merged) configuration as YAML'
complete -c am -n "__fish_am_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "path" -d 'Print the location of the configuration file(s)'
complete -c am -n "__fish_am_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "edit" -d 'Open the user configuration file in your editor'
complete -c am -n "__fish_am_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "validate" -d 'Validate the configuration file(s)'
complete -c am -n "__fish_am_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "add" -d 'Add another agent definition file to the user configuration'
+9 -9
View File
@@ -18,7 +18,8 @@ settings:
auto_install_deps: true # offer to install missing dependencies
confirm_before_run: true # confirm before running install scripts
stop_timeout_secs: 5 # SIGTERM grace period before SIGKILL
self_update_repo: null # e.g. "you/agent-manager" for am self-update
self_update_repo: Projets/agent-manager # repo used by am self-update (release assets)
self_update_base_url: https://git.dracodev.net/api/v1 # serveur de release (Gitea ici; GitHub: https://api.github.com)
# --- Command aliases ----------------------------------------------------------
aliases:
@@ -341,14 +342,14 @@ agents:
- name: kimi-cli
display_name: "Kimi CLI"
description: "Agent CLI officiel Moonshot AI, skills, MCP, intégration ACP IDE (Apache-2.0)."
description: "Agent CLI officiel Moonshot AI, skills, MCP, intégration ACP IDE (Apache-2.0). Installé via uv (paquet PyPI kimi-cli)."
category: coding-agent
website: https://github.com/MoonshotAI/kimi-cli
install:
type: npm
package: "@moonshotai/kimi-cli"
type: uv
package: kimi-cli
dependencies:
- { name: node, min_version: "18.0.0", install_hint: "https://nodejs.org" }
- { name: uv, min_version: "0.4.0", install_hint: "https://docs.astral.sh/uv" }
run: kimi
tags: [moonshot, skills, mcp]
@@ -829,10 +830,9 @@ agents:
category: assistant
website: https://github.com/sipeed/picoclaw
install:
type: git
repo: "https://github.com/sipeed/picoclaw"
dependencies:
- { name: go, min_version: "1.21.0", install_hint: "https://go.dev/dl" }
type: binary
repo: sipeed/picoclaw
binary: picoclaw
run: picoclaw
tags: [go, tiny]
+26
View File
@@ -0,0 +1,26 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-alias 1 "alias "
.SH NAME
alias \- Manage command aliases (cc \-> claude\-code)
.SH SYNOPSIS
\fBalias\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIsubcommands\fR>
.SH DESCRIPTION
Manage command aliases (cc \-> claude\-code)
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.SH SUBCOMMANDS
.TP
alias\-add(1)
Add an alias (am alias add cc claude\-code)
.TP
alias\-remove(1)
Remove an alias
.TP
alias\-list(1)
List every alias
.TP
alias\-help(1)
Print this message or the help of the given subcommand(s)
+35
View File
@@ -0,0 +1,35 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-completion 1 "completion "
.SH NAME
completion \- Generate a shell completion script
.SH SYNOPSIS
\fBcompletion\fR [\fB\-\-installed\fR] [\fB\-h\fR|\fB\-\-help\fR] <\fISHELL\fR>
.SH DESCRIPTION
Generate a shell completion script
.SH OPTIONS
.TP
\fB\-\-installed\fR
Add dynamic completion of the installed agents, aliases and groups (queried from \*(Aqam _agents\*(Aq at Tab time)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fISHELL\fR>
Shell to generate completions for
.br
.br
\fIPossible values:\fR
.RS 14
.IP \(bu 2
bash
.IP \(bu 2
elvish
.IP \(bu 2
fish
.IP \(bu 2
powershell
.IP \(bu 2
zsh
.RE
+35
View File
@@ -0,0 +1,35 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-config 1 "config "
.SH NAME
config \- Manage the configuration file
.SH SYNOPSIS
\fBconfig\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIsubcommands\fR>
.SH DESCRIPTION
Manage the configuration file
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.SH SUBCOMMANDS
.TP
config\-show(1)
Print the effective (merged) configuration as YAML
.TP
config\-path(1)
Print the location of the configuration file(s)
.TP
config\-edit(1)
Open the user configuration file in your editor
.TP
config\-validate(1)
Validate the configuration file(s)
.TP
config\-add(1)
Add another agent definition file to the user configuration
.TP
config\-set(1)
Set one configuration key without an editor (config set settings.default_shell pwsh)
.TP
config\-help(1)
Print this message or the help of the given subcommand(s)
+13
View File
@@ -0,0 +1,13 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-dashboard 1 "dashboard "
.SH NAME
dashboard \- Live TUI dashboard: overview, activity, stats, sessions, projects
.SH SYNOPSIS
\fBdashboard\fR [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Live TUI dashboard: overview, activity, stats, sessions, projects
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-doctor 1 "doctor "
.SH NAME
doctor \- Check the environment: tools, config validity, paths, permissions
.SH SYNOPSIS
\fBdoctor\fR [\fB\-\-fix\fR] [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Check the environment: tools, config validity, paths, permissions
.SH OPTIONS
.TP
\fB\-\-fix\fR
Attempt to repair problems (create directories, fix the state file)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-export 1 "export "
.SH NAME
export \- Export the configuration and installation state (backup)
.SH SYNOPSIS
\fBexport\fR [\fB\-\-output\fR] [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Export the configuration and installation state (backup)
.SH OPTIONS
.TP
\fB\-\-output\fR \fI<FILE>\fR [default: am\-export.json]
Output file (default: am\-export.json)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-favorite 1 "favorite "
.SH NAME
favorite \- Mark an agent as a favorite (star in list and status)
.SH SYNOPSIS
\fBfavorite\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Mark an agent as a favorite (star in list and status)
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-help 1 "help "
.SH NAME
help \- Display help: the overview, a command, the command list, or a search
.SH SYNOPSIS
\fBhelp\fR [\fB\-\-find\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fICOMMAND\fR]
.SH DESCRIPTION
Display help: the overview, a command, the command list, or a search
.SH OPTIONS
.TP
\fB\-\-find\fR \fI<TEXT>\fR
Search through all help commands table
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fICOMMAND\fR]
Command name (also accepts an agent name or alias); "commands" lists every command
+34
View File
@@ -0,0 +1,34 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-history 1 "history "
.SH NAME
history \- Search the command history (am and shell commands)
.SH SYNOPSIS
\fBhistory\fR [\fB\-\-kind\fR] [\fB\-\-cwd\fR] [\fB\-\-search\fR] [\fB\-\-failed\fR] [\fB\-\-session\fR] [\fB\-\-limit\fR] [\fB\-\-rerun\fR] [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Search the command history (am and shell commands)
.SH OPTIONS
.TP
\fB\-\-kind\fR \fI<KIND>\fR
Only commands of this kind (am or shell)
.TP
\fB\-\-cwd\fR \fI<DIR>\fR
Only commands run in this directory
.TP
\fB\-\-search\fR \fI<TEXT>\fR
Full\-text search in the command line
.TP
\fB\-\-failed\fR
Only failed commands
.TP
\fB\-\-session\fR \fI<ID>\fR
Only commands of this session
.TP
\fB\-\-limit\fR \fI<N>\fR [default: 100]
Maximum number of entries (default: 100)
.TP
\fB\-\-rerun\fR \fI<N>\fR
Re\-run the Nth most recent entry (with confirmation)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-import 1 "import "
.SH NAME
import \- Import a previously exported configuration and state
.SH SYNOPSIS
\fBimport\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIFILE\fR>
.SH DESCRIPTION
Import a previously exported configuration and state
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIFILE\fR>
File exported by the export command
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-info 1 "info "
.SH NAME
info \- Show detailed information about one agent
.SH SYNOPSIS
\fBinfo\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Show detailed information about one agent
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-init 1 "init "
.SH NAME
init \- Generate a local agent\-manager.yaml for the current directory
.SH SYNOPSIS
\fBinit\fR [\fB\-\-force\fR] [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Generate a local agent\-manager.yaml for the current directory
.SH OPTIONS
.TP
\fB\-\-force\fR
Overwrite an existing agent\-manager.yaml
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+22
View File
@@ -0,0 +1,22 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-install 1 "install "
.SH NAME
install \- Install an agent and its dependencies
.SH SYNOPSIS
\fBinstall\fR [\fB\-\-method\fR] [\fB\-\-force\fR] [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Install an agent and its dependencies
.SH OPTIONS
.TP
\fB\-\-method\fR \fI<METHOD>\fR
Select the install method (index, or type: npm, pip, uv, cargo, go, bun, curl, binary, git)
.TP
\fB\-\-force\fR
Reinstall even if already installed
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
+25
View File
@@ -0,0 +1,25 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-list 1 "list "
.SH NAME
list \- List installed agents (default); use \-\-all for the whole catalog
.SH SYNOPSIS
\fBlist\fR [\fB\-\-all\fR] [\fB\-\-category\fR] [\fB\-\-running\fR] [\fB\-\-sort\fR] [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
List installed agents (default); use \-\-all for the whole catalog
.SH OPTIONS
.TP
\fB\-\-all\fR
Show every agent known to the catalog, not only the installed ones
.TP
\fB\-\-category\fR \fI<CATEGORY>\fR
Only show agents of this category
.TP
\fB\-\-running\fR
Only show running agents
.TP
\fB\-\-sort\fR \fI<KEY>\fR
Sort by name, version or status (default: name)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+28
View File
@@ -0,0 +1,28 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-log 1 "log "
.SH NAME
log \- Read the event journal: everything am did
.SH SYNOPSIS
\fBlog\fR [\fB\-\-kind\fR] [\fB\-\-since\fR] [\fB\-\-limit\fR] [\fB\-\-follow\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
Read the event journal: everything am did
.SH OPTIONS
.TP
\fB\-\-kind\fR \fI<KINDS>\fR
Only these event kinds (comma separated: start,stop,run,install,...)
.TP
\fB\-\-since\fR \fI<DATE>\fR
Only events at or after this date (YYYY\-MM\-DD or RFC 3339)
.TP
\fB\-\-limit\fR \fI<N>\fR [default: 50]
Maximum number of events (default: 50)
.TP
\fB\-\-follow\fR
Follow the journal as new events arrive
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Only events of this agent
+22
View File
@@ -0,0 +1,22 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-logs 1 "logs "
.SH NAME
logs \- Tail the log file of an agent (\-\-follow for a live view)
.SH SYNOPSIS
\fBlogs\fR [\fB\-\-lines\fR] [\fB\-\-follow\fR] [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Tail the log file of an agent (\-\-follow for a live view)
.SH OPTIONS
.TP
\fB\-\-lines\fR \fI<N>\fR [default: 20]
Number of lines to show (default: 20)
.TP
\fB\-\-follow\fR
Follow the log as it grows
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-man 1 "man "
.SH NAME
man \- Generate a man page for am (or for one command)
.SH SYNOPSIS
\fBman\fR [\fB\-\-output\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fICOMMAND\fR]
.SH DESCRIPTION
Generate a man page for am (or for one command)
.SH OPTIONS
.TP
\fB\-\-output\fR \fI<DIR>\fR
Write the page(s) to this directory instead of stdout
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fICOMMAND\fR]
Command name (omit for the whole CLI)
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-note 1 "note "
.SH NAME
note \- Attach a free\-form note to an agent (shown in \*(Aqam info\*(Aq); \*(Aqam note <agent>\*(Aq without text prints the current note
.SH SYNOPSIS
\fBnote\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR> [\fITEXT...\fR]
.SH DESCRIPTION
Attach a free\-form note to an agent (shown in \*(Aqam info\*(Aq); \*(Aqam note <agent>\*(Aq without text prints the current note
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
.TP
[\fITEXT...\fR]
Note text (several words are joined); empty prints the note
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-open 1 "open "
.SH NAME
open \- Open an agent\*(Aqs installation directory in the file manager
.SH SYNOPSIS
\fBopen\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Open an agent\*(Aqs installation directory in the file manager
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
+23
View File
@@ -0,0 +1,23 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-profile 1 "profile "
.SH NAME
profile \- Manage environment profiles (dev, prod, ...)
.SH SYNOPSIS
\fBprofile\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIsubcommands\fR>
.SH DESCRIPTION
Manage environment profiles (dev, prod, ...)
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.SH SUBCOMMANDS
.TP
profile\-list(1)
List every defined profile
.TP
profile\-show(1)
Show one profile in detail (agent, env, args)
.TP
profile\-help(1)
Print this message or the help of the given subcommand(s)
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-projects 1 "projects "
.SH NAME
projects \- Show which agent works on which project
.SH SYNOPSIS
\fBprojects\fR [\fB\-h\fR|\fB\-\-help\fR] [\fINAME\fR]
.SH DESCRIPTION
Show which agent works on which project
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fINAME\fR]
Project name (omit to list every project)
+22
View File
@@ -0,0 +1,22 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-report 1 "report "
.SH NAME
report \- Write a markdown activity report
.SH SYNOPSIS
\fBreport\fR [\fB\-\-last\-week\fR] [\fB\-\-last\-month\fR] [\fB\-\-output\fR] [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Write a markdown activity report
.SH OPTIONS
.TP
\fB\-\-last\-week\fR
Cover the last 7 days
.TP
\fB\-\-last\-month\fR
Cover the last 30 days
.TP
\fB\-\-output\fR \fI<FILE>\fR
Output file (default: report\-YYYYMMDD.md in the state directory)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+40
View File
@@ -0,0 +1,40 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-restart 1 "restart "
.SH NAME
restart \- Restart an agent: stop, then start with the same options
.SH SYNOPSIS
\fBrestart\fR [\fB\-b\fR|\fB\-\-background\fR] [\fB\-f\fR|\fB\-\-foreground\fR] [\fB\-\-args\fR] [\fB\-\-env\fR] [\fB\-\-notify\fR] [\fB\-\-profile\fR] [\fB\-\-force\fR] [\fB\-\-timeout\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
Restart an agent: stop, then start with the same options
.SH OPTIONS
.TP
\fB\-b\fR, \fB\-\-background\fR
Run detached in the background; output goes to the agent log file
.TP
\fB\-f\fR, \fB\-\-foreground\fR
Run in the foreground, attached to this terminal (the default)
.TP
\fB\-\-args\fR [\fI<ARGS>...\fR]
Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)
.TP
\fB\-\-env\fR \fI<KEY=VALUE>\fR
Set an environment variable for the agent (repeatable): \-\-env KEY=VALUE
.TP
\fB\-\-notify\fR
Send a desktop notification once the agent has started
.TP
\fB\-\-profile\fR \fI<NAME>\fR
Apply an environment profile (env + args, defined in the config)
.TP
\fB\-\-force\fR
Kill immediately instead of terminating gracefully
.TP
\fB\-\-timeout\fR \fI<SECS>\fR
Grace period in seconds before killing (default: from config, 5s)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Agent name, alias, or group:<name> (defaults to the project\*(Aqs default agent)
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-run 1 "run "
.SH NAME
run \- Run the agent command directly with the given arguments (no process management)
.SH SYNOPSIS
\fBrun\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR> [\fIARGS...\fR]
.SH DESCRIPTION
Run the agent command directly with the given arguments (no process management)
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
.TP
[\fIARGS...\fR]
Arguments passed through to the agent command
+22
View File
@@ -0,0 +1,22 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-search 1 "search "
.SH NAME
search \- Search the catalog by keyword (name, description, category, tags)
.SH SYNOPSIS
\fBsearch\fR [\fB\-\-category\fR] [\fB\-\-tag\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIKEYWORD\fR]
.SH DESCRIPTION
Search the catalog by keyword (name, description, category, tags)
.SH OPTIONS
.TP
\fB\-\-category\fR \fI<CATEGORY>\fR
Restrict to this category
.TP
\fB\-\-tag\fR \fI<TAG>\fR
Only agents carrying this personal tag
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIKEYWORD\fR]
Keyword to search for (case\-insensitive substring)
+26
View File
@@ -0,0 +1,26 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-secret 1 "secret "
.SH NAME
secret \- Manage secrets in the OS keyring (never in plaintext config)
.SH SYNOPSIS
\fBsecret\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIsubcommands\fR>
.SH DESCRIPTION
Manage secrets in the OS keyring (never in plaintext config)
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.SH SUBCOMMANDS
.TP
secret\-set(1)
Store a secret for an agent
.TP
secret\-unset(1)
Remove a secret
.TP
secret\-list(1)
List stored secret names (values are never shown)
.TP
secret\-help(1)
Print this message or the help of the given subcommand(s)
+15
View File
@@ -0,0 +1,15 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-self-uninstall 1 "self-uninstall "
.SH NAME
self\-uninstall \- Remove agent\-manager and everything it created from this machine
.SH SYNOPSIS
\fBself\-uninstall\fR [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Remove agent\-manager and everything it created from this machine
.PP
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.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help (see a summary with \*(Aq\-h\*(Aq)
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-self-update 1 "self-update "
.SH NAME
self\-update \- Update agent\-manager itself from the latest release
.SH SYNOPSIS
\fBself\-update\fR [\fB\-\-check\fR] [\fB\-\-to\fR] [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Update agent\-manager itself from the latest release
.SH OPTIONS
.TP
\fB\-\-check\fR
Only check whether a newer version exists
.TP
\fB\-\-to\fR \fI<FILE>\fR
Download the new binary to this path instead of replacing the current one
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+28
View File
@@ -0,0 +1,28 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-sessions 1 "sessions "
.SH NAME
sessions \- List the sessions of every agent (and of the interactive shell)
.SH SYNOPSIS
\fBsessions\fR [\fB\-\-project\fR] [\fB\-\-status\fR] [\fB\-\-show\fR] [\fB\-\-resume\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
List the sessions of every agent (and of the interactive shell)
.SH OPTIONS
.TP
\fB\-\-project\fR \fI<PROJECT>\fR
Only sessions of this project
.TP
\fB\-\-status\fR \fI<STATUS>\fR
Only sessions with this status (running, stopped, failed, interrupted)
.TP
\fB\-\-show\fR \fI<ID>\fR
Show one session in detail (summary + log excerpt)
.TP
\fB\-\-resume\fR \fI<ID>\fR
Resume a finished session: relaunch the agent with its recorded arguments
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Only sessions of this agent
+34
View File
@@ -0,0 +1,34 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-start 1 "start "
.SH NAME
start \- Start an agent (foreground by default, or detached with \-\-background)
.SH SYNOPSIS
\fBstart\fR [\fB\-b\fR|\fB\-\-background\fR] [\fB\-f\fR|\fB\-\-foreground\fR] [\fB\-\-args\fR] [\fB\-\-env\fR] [\fB\-\-notify\fR] [\fB\-\-profile\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
Start an agent (foreground by default, or detached with \-\-background)
.SH OPTIONS
.TP
\fB\-b\fR, \fB\-\-background\fR
Run detached in the background; output goes to the agent log file
.TP
\fB\-f\fR, \fB\-\-foreground\fR
Run in the foreground, attached to this terminal (the default)
.TP
\fB\-\-args\fR [\fI<ARGS>...\fR]
Extra arguments passed to the agent (repeatable; quoted strings are split on spaces)
.TP
\fB\-\-env\fR \fI<KEY=VALUE>\fR
Set an environment variable for the agent (repeatable): \-\-env KEY=VALUE
.TP
\fB\-\-notify\fR
Send a desktop notification once the agent has started
.TP
\fB\-\-profile\fR \fI<NAME>\fR
Apply an environment profile (env + args, defined in the config)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Agent name, alias, or group:<name> (defaults to the project\*(Aqs default agent)
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-stats 1 "stats "
.SH NAME
stats \- Show usage statistics computed from the event journal
.SH SYNOPSIS
\fBstats\fR [\fB\-\-period\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
Show usage statistics computed from the event journal
.SH OPTIONS
.TP
\fB\-\-period\fR \fI<PERIOD>\fR
Only events of the last period (7d, 30d, 90d, all)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Agent name or alias (omit for the global view)
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-status 1 "status "
.SH NAME
status \- Show the state of one agent, or of every installed agent
.SH SYNOPSIS
\fBstatus\fR [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
Show the state of one agent, or of every installed agent
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Agent name or alias (omit to show all installed agents)
+22
View File
@@ -0,0 +1,22 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-stop 1 "stop "
.SH NAME
stop \- Stop a background agent (SIGTERM, then SIGKILL after the timeout)
.SH SYNOPSIS
\fBstop\fR [\fB\-\-force\fR] [\fB\-\-timeout\fR] [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Stop a background agent (SIGTERM, then SIGKILL after the timeout)
.SH OPTIONS
.TP
\fB\-\-force\fR
Kill immediately instead of terminating gracefully
.TP
\fB\-\-timeout\fR \fI<SECS>\fR
Grace period in seconds before killing (default: from config, 5s)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name, alias, or group:<name>
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-tag 1 "tag "
.SH NAME
tag \- Add a personal tag to an agent
.SH SYNOPSIS
\fBtag\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR> <\fITAG\fR>
.SH DESCRIPTION
Add a personal tag to an agent
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
.TP
<\fITAG\fR>
One or more tags to add
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-tags 1 "tags "
.SH NAME
tags \- List personal tags (with counts), optionally for one agent
.SH SYNOPSIS
\fBtags\fR [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
List personal tags (with counts), optionally for one agent
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Only show the tags of this agent
+25
View File
@@ -0,0 +1,25 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-timeline 1 "timeline "
.SH NAME
timeline \- One chronological view of every activity (journal + REPL history)
.SH SYNOPSIS
\fBtimeline\fR [\fB\-\-project\fR] [\fB\-\-since\fR] [\fB\-\-limit\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
One chronological view of every activity (journal + REPL history)
.SH OPTIONS
.TP
\fB\-\-project\fR \fI<PROJECT>\fR
Only activity of this project
.TP
\fB\-\-since\fR \fI<DATE>\fR
Only activity at or after this date (YYYY\-MM\-DD or RFC 3339)
.TP
\fB\-\-limit\fR \fI<N>\fR [default: 50]
Maximum number of entries (default: 50)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Only activity of this agent
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-top 1 "top "
.SH NAME
top \- Show the most used agents (top 10)
.SH SYNOPSIS
\fBtop\fR [\fB\-\-period\fR] [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Show the most used agents (top 10)
.SH OPTIONS
.TP
\fB\-\-period\fR \fI<PERIOD>\fR
Only events of the last period (7d, 30d, 90d, all)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+16
View File
@@ -0,0 +1,16 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-unfavorite 1 "unfavorite "
.SH NAME
unfavorite \- Remove the favorite star from an agent
.SH SYNOPSIS
\fBunfavorite\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Remove the favorite star from an agent
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-uninstall 1 "uninstall "
.SH NAME
uninstall \- Uninstall an agent (managed or external) and clean its files
.SH SYNOPSIS
\fBuninstall\fR [\fB\-\-purge\fR] [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Uninstall an agent (managed or external) and clean its files
.SH OPTIONS
.TP
\fB\-\-purge\fR
Also remove logs and the agent entry from the user config file
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-untag 1 "untag "
.SH NAME
untag \- Remove a personal tag from an agent
.SH SYNOPSIS
\fBuntag\fR [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR> <\fITAG\fR>
.SH DESCRIPTION
Remove a personal tag from an agent
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
.TP
<\fITAG\fR>
Tag to remove
+19
View File
@@ -0,0 +1,19 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-update 1 "update "
.SH NAME
update \- Update an installed agent to the latest available version
.SH SYNOPSIS
\fBupdate\fR [\fB\-\-all\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
.SH DESCRIPTION
Update an installed agent to the latest available version
.SH OPTIONS
.TP
\fB\-\-all\fR
Update every installed managed agent
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
[\fIAGENT\fR]
Agent name or alias (required unless \-\-all)
+13
View File
@@ -0,0 +1,13 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-version 1 "version "
.SH NAME
version \- Display version and build information
.SH SYNOPSIS
\fBversion\fR [\fB\-h\fR|\fB\-\-help\fR]
.SH DESCRIPTION
Display version and build information
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
+25
View File
@@ -0,0 +1,25 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am-watch 1 "watch "
.SH NAME
watch \- Supervise an agent and optionally restart it on crash
.SH SYNOPSIS
\fBwatch\fR [\fB\-\-restart\fR] [\fB\-\-notify\fR] [\fB\-\-interval\fR] [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR>
.SH DESCRIPTION
Supervise an agent and optionally restart it on crash
.SH OPTIONS
.TP
\fB\-\-restart\fR
Restart the agent automatically when it dies (with backoff)
.TP
\fB\-\-notify\fR
Desktop notification on every restart
.TP
\fB\-\-interval\fR \fI<SECS>\fR [default: 5]
Check interval in seconds (default: 5)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
<\fIAGENT\fR>
Agent name or alias
+177
View File
@@ -0,0 +1,177 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH am 1 "am 0.4.2"
.SH NAME
am \- agent\-manager (am) — manage local AI coding agents
.SH SYNOPSIS
\fBam\fR [\fB\-c\fR|\fB\-\-config\fR] [\fB\-v\fR|\fB\-\-verbose\fR] [\fB\-q\fR|\fB\-\-quiet\fR] [\fB\-y\fR|\fB\-\-yes\fR] [\fB\-\-dry\-run\fR] [\fB\-\-json\fR] [\fB\-\-no\-color\fR] [\fB\-\-theme\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIsubcommands\fR]
.SH DESCRIPTION
agent\-manager (am) — a CLI to manage local AI coding agents.
.PP
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.
.SH OPTIONS
.TP
\fB\-c\fR, \fB\-\-config\fR \fI<FILE>\fR
Use an alternate configuration file
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Verbose output: show every command executed and its details
.TP
\fB\-q\fR, \fB\-\-quiet\fR
Quiet mode: only errors are printed
.TP
\fB\-y\fR, \fB\-\-yes\fR
Answer yes to every confirmation prompt
.TP
\fB\-\-dry\-run\fR
Simulate the action without changing anything
.TP
\fB\-\-json\fR
Emit machine\-readable JSON on stdout (for scripts)
.TP
\fB\-\-no\-color\fR
Disable ANSI colors
.TP
\fB\-\-theme\fR \fI<THEME>\fR
Color theme for the output (see \*(Aqam help theme\*(Aq for the list)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help (see a summary with \*(Aq\-h\*(Aq)
.TP
\fB\-V\fR, \fB\-\-version\fR
Print version
.SH SUBCOMMANDS
.TP
am\-list(1)
List installed agents (default); use \-\-all for the whole catalog
.TP
am\-logs(1)
Tail the log file of an agent (\-\-follow for a live view)
.TP
am\-open(1)
Open an agent\*(Aqs installation directory in the file manager
.TP
am\-watch(1)
Supervise an agent and optionally restart it on crash
.TP
am\-dashboard(1)
Live TUI dashboard: overview, activity, stats, sessions, projects
.TP
am\-favorite(1)
Mark an agent as a favorite (star in list and status)
.TP
am\-unfavorite(1)
Remove the favorite star from an agent
.TP
am\-note(1)
Attach a free\-form note to an agent (shown in \*(Aqam info\*(Aq); \*(Aqam note <agent>\*(Aq without text prints the current note
.TP
am\-tag(1)
Add a personal tag to an agent
.TP
am\-untag(1)
Remove a personal tag from an agent
.TP
am\-tags(1)
List personal tags (with counts), optionally for one agent
.TP
am\-profile(1)
Manage environment profiles (dev, prod, ...)
.TP
am\-man(1)
Generate a man page for am (or for one command)
.TP
am\-alias(1)
Manage command aliases (cc \-> claude\-code)
.TP
am\-secret(1)
Manage secrets in the OS keyring (never in plaintext config)
.TP
am\-start(1)
Start an agent (foreground by default, or detached with \-\-background)
.TP
am\-stop(1)
Stop a background agent (SIGTERM, then SIGKILL after the timeout)
.TP
am\-restart(1)
Restart an agent: stop, then start with the same options
.TP
am\-status(1)
Show the state of one agent, or of every installed agent
.TP
am\-sessions(1)
List the sessions of every agent (and of the interactive shell)
.TP
am\-stats(1)
Show usage statistics computed from the event journal
.TP
am\-top(1)
Show the most used agents (top 10)
.TP
am\-report(1)
Write a markdown activity report
.TP
am\-projects(1)
Show which agent works on which project
.TP
am\-timeline(1)
One chronological view of every activity (journal + REPL history)
.TP
am\-log(1)
Read the event journal: everything am did
.TP
am\-install(1)
Install an agent and its dependencies
.TP
am\-uninstall(1)
Uninstall an agent (managed or external) and clean its files
.TP
am\-update(1)
Update an installed agent to the latest available version
.TP
am\-search(1)
Search the catalog by keyword (name, description, category, tags)
.TP
am\-history(1)
Search the command history (am and shell commands)
.TP
am\-info(1)
Show detailed information about one agent
.TP
am\-init(1)
Generate a local agent\-manager.yaml for the current directory
.TP
am\-help(1)
Display help: the overview, a command, the command list, or a search
.TP
am\-version(1)
Display version and build information
.TP
am\-config(1)
Manage the configuration file
.TP
am\-doctor(1)
Check the environment: tools, config validity, paths, permissions
.TP
am\-run(1)
Run the agent command directly with the given arguments (no process management)
.TP
am\-completion(1)
Generate a shell completion script
.TP
am\-self\-update(1)
Update agent\-manager itself from the latest release
.TP
am\-self\-uninstall(1)
Remove agent\-manager and everything it created from this machine
.TP
am\-export(1)
Export the configuration and installation state (backup)
.TP
am\-import(1)
Import a previously exported configuration and state
.SH VERSION
v0.4.2
+33
View File
@@ -0,0 +1,33 @@
#!/bin/sh
# Builds an am_<version>_amd64.deb from dist/am-linux-x86_64.
# Usage: packaging/deb/make-deb.sh <version> [arch]
set -e
VERSION="$1"
ARCH="${2:-amd64}"
ROOT="$(mktemp -d)"
PKG="am_${VERSION}_${ARCH}"
mkdir -p "$ROOT/$PKG/usr/bin" "$ROOT/$PKG/usr/share/doc/am" "$ROOT/$PKG/DEBIAN"
cp dist/am-linux-x86_64 "$ROOT/$PKG/usr/bin/am"
chmod 755 "$ROOT/$PKG/usr/bin/am"
cat > "$ROOT/$PKG/DEBIAN/control" <<EOF
Package: am
Version: $VERSION
Section: utils
Priority: optional
Architecture: $ARCH
Maintainer: bruno <[email protected]>
Description: Manage local AI coding agents
List, install, start, stop and update local AI coding agents with
automatic dependency handling and a YAML-driven catalog.
EOF
cat > "$ROOT/$PKG/usr/share/doc/am/copyright" <<EOF
Copyright (c) 2026 bruno
License: MIT
EOF
dpkg-deb --build --root-owner-group "$ROOT/$PKG" "dist/$PKG.deb"
rm -rf "$ROOT"
echo "built dist/$PKG.deb"
+36
View File
@@ -0,0 +1,36 @@
# Homebrew formula - pour un tap local, ou a soumettre a homebrew/core.
# Rendu : scripts/render-manifests.ps1 (remplace {{VERSION}} et les sha256).
class Am < Formula
desc "Manage local AI coding agents: list, install, start, stop, update"
homepage "https://git.dracodev.net/Projets/agent-manager"
license "MIT"
version "{{VERSION}}"
on_macos do
if Hardware::CPU.arm?
url "https://git.dracodev.net/Projets/agent-manager/releases/download/v{{VERSION}}/am-macos-aarch64.tar.gz"
sha256 "{{SHA256_MACOS_AARCH64}}"
else
url "https://git.dracodev.net/Projets/agent-manager/releases/download/v{{VERSION}}/am-macos-x86_64.tar.gz"
sha256 "{{SHA256_MACOS_X86_64}}"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://git.dracodev.net/Projets/agent-manager/releases/download/v{{VERSION}}/am-linux-aarch64.tar.gz"
sha256 "{{SHA256_LINUX_AARCH64}}"
else
url "https://git.dracodev.net/Projets/agent-manager/releases/download/v{{VERSION}}/am-linux-x86_64.tar.gz"
sha256 "{{SHA256_LINUX_X86_64}}"
end
end
def install
bin.install "am"
end
test do
system bin/"am", "version"
end
end
+26
View File
@@ -0,0 +1,26 @@
# RPM spec - build avec: rpmbuild -bb packaging/rpm/am.spec
# (rendu par scripts/render-manifests.ps1: %%{VERSION} -> version)
Name: am
Version: {{VERSION}}
Release: 1%{?dist}
Summary: Manage local AI coding agents
License: MIT
URL: https://git.dracodev.net/Projets/agent-manager
Source0: am-linux-x86_64.tar.gz
%description
am lists, installs, starts, stops and updates local AI coding agents
(Claude Code, Codex, Aider, ...) with automatic dependency handling and a
YAML-driven catalog.
%prep
%setup -c -T
tar -xzf %{SOURCE0}
%install
install -Dm755 am %{buildroot}%{_bindir}/am
%files
%{_bindir}/am
%changelog
+24
View File
@@ -0,0 +1,24 @@
{
"version": "{{VERSION}}",
"description": "Manage local AI coding agents: list, install, start, stop, update - with automatic dependency handling and a YAML-driven catalog.",
"homepage": "https://git.dracodev.net/Projets/agent-manager",
"license": "MIT",
"architecture": {
"64bit": {
"url": "https://git.dracodev.net/Projets/agent-manager/releases/download/v{{VERSION}}/am-windows-x86_64.zip",
"hash": "{{SHA256_WINDOWS}}"
}
},
"bin": "am.exe",
"checkver": {
"url": "https://git.dracodev.net/api/v1/repos/Projets/agent-manager/releases/latest",
"regex": "tag_name\":\s*\"v([\d.]+)\""
},
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://git.dracodev.net/Projets/agent-manager/releases/download/v$version/am-windows-x86_64.zip"
}
}
}
}
@@ -0,0 +1,16 @@
# Winget manifest (version 1.6) - submit to microsoft/winget-pkgs
# Rendering: scripts/render-manifests.ps1
PackageIdentifier: BrunoDracodev.AgentManager
PackageVersion: {{VERSION}}
MinimumOSVersion: 10.0.0.0
InstallerType: zip
Installers:
- Architecture: x64
InstallerUrl: https://git.dracodev.net/Projets/agent-manager/releases/download/v{{VERSION}}/am-windows-x86_64.zip
InstallerSha256: {{SHA256_WINDOWS}}
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: am.exe
PortableCommandAlias: am
ManifestType: installer
ManifestVersion: 1.6.0
@@ -0,0 +1,18 @@
# Winget locale manifest - submit to microsoft/winget-pkgs
PackageIdentifier: BrunoDracodev.AgentManager
PackageVersion: {{VERSION}}
PackageLocale: en-US
Publisher: bruno (dracodev)
PackageName: agent-manager (am)
PackageUrl: https://git.dracodev.net/Projets/agent-manager
License: MIT
ShortDescription: Manage local AI coding agents - list, install, start, stop, update - with automatic dependency handling.
Moniker: am
Tags:
- agent
- claude
- cli
- coding
- llm
ManifestType: defaultLocale
ManifestVersion: 1.6.0
+6
View File
@@ -0,0 +1,6 @@
# Winget version manifest - submit to microsoft/winget-pkgs
PackageIdentifier: BrunoDracodev.AgentManager
PackageVersion: {{VERSION}}
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.6.0
+20
View File
@@ -0,0 +1,20 @@
# Builds the release artefacts for the current platform (Windows):
# target/release/am.exe -> dist/am-windows-x86_64.zip
# Also prints the sha256 of every archive (for the packaging manifests).
param([switch]$NoTest)
$ErrorActionPreference = "Stop"
cargo build --release --locked
if (-not $NoTest) { cargo test --release --locked }
New-Item -ItemType Directory -Force dist | Out-Null
$zip = "dist/am-windows-x86_64.zip"
Remove-Item $zip -ErrorAction SilentlyContinue
Compress-Archive -Path target/release/am.exe -DestinationPath $zip
Write-Host ""
Write-Host "Artefacts:"
Get-ChildItem dist | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
Write-Host (" {0,-32} {1} sha256:{2}" -f $_.Name, $_.Length, $hash)
}
+50
View File
@@ -0,0 +1,50 @@
# Renders the packaging templates (scoop, winget, homebrew, rpm) with the
# version and the sha256 of every release archive.
# Usage: scripts/render-manifests.ps1 -Version 0.4.2 [-DistDir dist]
param(
[Parameter(Mandatory = $true)][string]$Version,
[string]$DistDir = "dist"
)
$ErrorActionPreference = "Stop"
function Get-Sha([string]$file) {
$p = Join-Path $DistDir $file
if (-not (Test-Path $p)) {
Write-Warning "missing $p - placeholder 0000... (build it or wait for the CI runner)"
return "0000000000000000000000000000000000000000000000000000000000000000"
}
(Get-FileHash $p -Algorithm SHA256).Hash.ToLower()
}
$vars = [ordered]@{
"{{VERSION}}" = $Version
"{{SHA256_WINDOWS}}" = Get-Sha "am-windows-x86_64.zip"
"{{SHA256_LINUX_X86_64}}" = Get-Sha "am-linux-x86_64.tar.gz"
"{{SHA256_LINUX_AARCH64}}" = Get-Sha "am-linux-aarch64.tar.gz"
"{{SHA256_MACOS_X86_64}}" = Get-Sha "am-macos-x86_64.tar.gz"
"{{SHA256_MACOS_AARCH64}}" = Get-Sha "am-macos-aarch64.tar.gz"
}
$targets = @(
@{ Src = "packaging/scoop/am.json"; Dst = "dist/scoop/am.json" },
@{ Src = "packaging/winget/agent-manager.installer.yaml"; Dst = "dist/winget/agent-manager.installer.yaml" },
@{ Src = "packaging/winget/agent-manager.locale.en-US.yaml"; Dst = "dist/winget/agent-manager.locale.en-US.yaml" },
@{ Src = "packaging/winget/agent-manager.yaml"; Dst = "dist/winget/agent-manager.yaml" },
@{ Src = "packaging/homebrew/am.rb"; Dst = "dist/homebrew/am.rb" },
@{ Src = "packaging/rpm/am.spec"; Dst = "dist/rpm/am.spec" }
)
foreach ($t in $targets) {
$text = Get-Content $t.Src -Raw
foreach ($k in $vars.Keys) { $text = $text.Replace($k, $vars[$k]) }
New-Item -ItemType Directory -Force (Split-Path $t.Dst) | Out-Null
Set-Content -Path $t.Dst -Value $text -NoNewline -Encoding utf8
Write-Host "rendered $($t.Dst)"
}
Write-Host ""
Write-Host "Manifests rendus dans dist/. Puis :"
Write-Host " - scoop : scoop install dist/scoop/am.json (ou ajoutez-le a votre bucket)"
Write-Host " - winget : soumettez dist/winget/*.yaml a microsoft/winget-pkgs"
Write-Host " - homebrew : servez dist/homebrew/am.rb via un tap, ou soumettez a homebrew/core"
Write-Host " - rpm : copiez dist/rpm/am.spec + dist/am-linux-x86_64.tar.gz dans ~/rpmbuild/{SPECS,SOURCES} puis rpmbuild -bb"
Write-Host " - deb : packaging/deb/make-deb.sh $Version"
+51 -1
View File
@@ -6,7 +6,9 @@ use crate::cli::Cli;
use crate::config::{self, Config, Settings};
use crate::output::Logger;
use crate::state::StateStore;
use anyhow::{Context, Result};
use crate::theme::{self, Theme};
use anyhow::{anyhow, Context, Result};
use std::cell::Cell;
use std::io::{IsTerminal, Write};
use std::path::PathBuf;
@@ -19,6 +21,9 @@ pub struct App {
pub catalog: Catalog,
pub state: StateStore,
pub log: Logger,
/// Active output color theme (switchable at runtime with the REPL
/// 'theme <name>' command).
theme: Cell<&'static Theme>,
}
#[derive(Debug, Clone)]
@@ -78,6 +83,7 @@ impl App {
loaded.config.aliases.clone(),
loaded.config.groups.clone(),
);
let theme = resolve_theme(cli.theme.as_deref(), loaded.config.settings.theme.as_deref())?;
Ok(Self {
cli,
config: loaded.config,
@@ -86,6 +92,7 @@ impl App {
catalog,
state,
log,
theme: Cell::new(theme),
})
}
@@ -105,6 +112,32 @@ impl App {
!self.cli.no_color && std::io::stdout().is_terminal()
}
pub fn theme(&self) -> &'static Theme {
self.theme.get()
}
/// Switch the active theme (used by the REPL 'theme <name>' command).
pub fn set_theme(&self, theme: &'static Theme) {
self.theme.set(theme);
}
/// Directory of the event journal (the state directory).
pub fn events_dir(&self) -> PathBuf {
self.paths
.state_file
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| self.paths.state_file.clone())
}
/// Append an event to the journal; failures are never fatal (logged at
/// verbose level only).
pub fn emit(&self, event: &crate::events::Event) {
if let Err(e) = crate::events::append(&self.events_dir(), event) {
self.log.verbose(&format!("cannot write event journal: {e:#}"));
}
}
/// Ask the user for confirmation on stderr. Accepts y/yes/o/oui.
pub fn confirm(&self, question: &str) -> Result<bool> {
if self.cli.yes {
@@ -119,6 +152,23 @@ impl App {
}
}
/// Resolve the output color theme: the --theme flag wins, then the
/// configuration entry, then the default theme. An unknown --theme value is
/// an error; an unknown configured value silently falls back to the default.
fn resolve_theme(flag: Option<&str>, configured: Option<&str>) -> Result<&'static Theme> {
if let Some(name) = flag {
return theme::find(name).ok_or_else(|| {
anyhow!(
"unknown theme '{name}' — available: {}",
theme::names().join(", ")
)
});
}
Ok(configured
.and_then(theme::find)
.unwrap_or_else(theme::default_theme))
}
/// Root directory for agent data (overridable with AGENT_MANAGER_DATA,
/// mostly used by tests).
pub fn data_dir() -> PathBuf {
+159 -23
View File
@@ -80,35 +80,125 @@ impl Catalog {
}
/// Case-insensitive search over name, display name, description,
/// category, tags and note.
/// category, tags and note — typo-tolerant, ranked by relevance.
pub fn search(&self, keyword: &str, category: Option<&str>) -> Vec<&AgentDef> {
let kw = keyword.to_lowercase();
self.agents
let mut scored: Vec<(u32, &AgentDef)> = self
.agents
.iter()
.filter(|a| {
if a.hidden {
return false;
}
if let Some(cat) = category {
if a.category.as_deref() != Some(cat) {
return false;
}
}
let hay = format!(
"{} {} {} {} {} {}",
a.name,
a.title(),
a.description.as_deref().unwrap_or(""),
a.category.as_deref().unwrap_or(""),
a.tags.join(" "),
a.note.as_deref().unwrap_or("")
)
.to_lowercase();
hay.contains(&kw)
.filter(|a| !a.hidden)
.filter(|a| category.map_or(true, |c| a.category.as_deref() == Some(c)))
.filter_map(|a| {
let name = a.name.to_lowercase();
let title = a.title().to_lowercase();
let rest = format!(
"{} {} {}",
a.description.as_deref().unwrap_or("").to_lowercase(),
a.category.as_deref().unwrap_or("").to_lowercase(),
a.tags.join(" ").to_lowercase()
);
let score = score_keyword(&kw, &name)
.saturating_mul(3)
.saturating_add(score_keyword(&kw, &title).saturating_mul(2))
.saturating_add(score_keyword(&kw, &rest));
(score > 0).then_some((score, a))
})
.collect()
.collect();
scored.sort_by(|(sa, a), (sb, b)| sb.cmp(sa).then(a.name.cmp(&b.name)));
scored.into_iter().map(|(_, a)| a).collect()
}
/// "Did you mean" candidates for a keyword with no results: closest
/// agent names and display names by edit distance.
pub fn suggest(&self, keyword: &str) -> Vec<String> {
let kw = keyword.to_lowercase();
let mut out: Vec<(usize, String)> = Vec::new();
for a in self.agents.iter().filter(|a| !a.hidden) {
for field in [&a.name, &a.title().to_string()] {
let f = field.to_lowercase();
if f.is_empty() {
continue;
}
let d = levenshtein(&kw, &f);
if d <= 3 && !out.iter().any(|(_, n)| n == &a.name) {
out.push((d, a.name.clone()));
}
}
}
out.sort_by(|(da, na), (db, nb)| da.cmp(db).then(na.cmp(nb)));
out.truncate(4);
out.into_iter().map(|(_, n)| n).collect()
}
}
/// Score a keyword against one haystack field: 100 for a full hit,
/// then subsequence and typo bonuses; 0 = no relation at all.
fn score_keyword(keyword: &str, hay: &str) -> u32 {
if keyword.is_empty() {
return 0;
}
let tokens: Vec<&str> = keyword.split_whitespace().collect();
let mut total = 0u32;
for token in &tokens {
if token.is_empty() {
continue;
}
let s = score_token(token, hay);
if s == 0 {
return 0;
}
total += s;
}
total
}
fn score_token(token: &str, hay: &str) -> u32 {
if hay.contains(token) {
return 100;
}
if subsequence(token, hay) {
return 40;
}
// Typo tolerance: compare the token with every word of the field.
let mut best = usize::MAX;
for word in hay.split(|c: char| !c.is_alphanumeric()) {
if word.is_empty() {
continue;
}
best = best.min(levenshtein(token, word));
}
match best {
0..=1 => 25,
2 => 15,
3 => 5,
_ => 0,
}
}
/// Do the characters of needle appear in order inside hay?
fn subsequence(needle: &str, hay: &str) -> bool {
let mut it = hay.chars();
needle.chars().all(|c| it.any(|h| h == c))
}
/// Classic Levenshtein distance on characters.
fn levenshtein(a: &str, b: &str) -> usize {
let a: Vec<char> = a.chars().collect();
let b: Vec<char> = b.chars().collect();
let mut prev: Vec<usize> = (0..=b.len()).collect();
let mut cur = vec![0usize; b.len() + 1];
for (i, ca) in a.iter().enumerate() {
cur[0] = i + 1;
for (j, cb) in b.iter().enumerate() {
let cost = if ca == cb { 0 } else { 1 };
cur[j + 1] = (prev[j] + cost).min(prev[j + 1] + 1).min(cur[j] + 1);
}
std::mem::swap(&mut prev, &mut cur);
}
prev[b.len()]
}
impl Catalog {
/// Sorted list of all categories present in the catalog.
pub fn categories(&self) -> Vec<String> {
let mut cats: Vec<String> = self
@@ -156,4 +246,50 @@ mod tests {
let cats = cat.categories();
assert!(cats.contains(&"coding-agent".to_string()));
}
#[test]
fn fuzzy_search_tolerates_missing_separator() {
let cat = catalog();
let hits = cat.search("claude cod", None);
assert!(
hits.iter().any(|a| a.name == "claude-code"),
"expected claude-code in {:?}",
hits.iter().map(|a| &a.name).collect::<Vec<_>>()
);
}
#[test]
fn fuzzy_search_tolerates_typos() {
let cat = catalog();
let hits = cat.search("cludecode", None);
assert!(hits.iter().any(|a| a.name == "claude-code"));
}
#[test]
fn exact_matches_rank_first() {
let cat = catalog();
let hits = cat.search("claude", None);
assert!(!hits.is_empty());
assert_eq!(
hits[0].name, "claude-code",
"exact name match must rank first: {:?}",
hits.iter().map(|a| &a.name).collect::<Vec<_>>()
);
}
#[test]
fn suggest_proposes_close_names() {
let cat = catalog();
let sug = cat.suggest("aiderr");
assert!(sug.contains(&"aider".to_string()), "suggestions: {sug:?}");
assert!(sug.len() <= 4);
}
#[test]
fn levenshtein_distance() {
assert_eq!(levenshtein("aider", "aider"), 0);
assert_eq!(levenshtein("aider", "aiderr"), 1);
assert_eq!(levenshtein("abc", "axc"), 1);
assert_eq!(levenshtein("abc", "xyz"), 3);
}
}
+307 -5
View File
@@ -10,6 +10,9 @@ use std::path::PathBuf;
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.
@@ -47,6 +50,10 @@ pub struct Cli {
#[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>,
}
@@ -61,7 +68,108 @@ pub enum Command {
/// 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>,
},
/// 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),
/// Start an agent (foreground by default, or detached with --background)
Start(StartArgs),
/// Stop a background agent (SIGTERM, then SIGKILL after the timeout)
@@ -91,6 +199,85 @@ pub enum Command {
/// 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
@@ -102,7 +289,7 @@ pub enum Command {
#[arg(long)]
force: bool,
},
/// Uninstall an agent and clean its files
/// Uninstall an agent (managed or external) and clean its files
Uninstall {
/// Agent name or alias
agent: String,
@@ -121,16 +308,60 @@ pub enum Command {
/// Search the catalog by keyword (name, description, category, tags)
Search {
/// Keyword to search for (case-insensitive substring)
keyword: String,
#[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,
/// Manage the configuration file
#[command(subcommand)]
Config(ConfigCmd),
@@ -153,8 +384,12 @@ pub enum Command {
/// 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 GitHub Releases
/// Update agent-manager itself from the latest release
SelfUpdate {
/// Only check whether a newer version exists
#[arg(long)]
@@ -185,8 +420,8 @@ pub enum Command {
/// Arguments shared by start and restart.
#[derive(Args, Debug, Clone, Default)]
pub struct StartArgs {
/// Agent name, alias, or group:<name>
pub agent: String,
/// 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,
@@ -202,6 +437,66 @@ pub struct StartArgs {
/// 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>,
}
/// 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.
@@ -220,4 +515,11 @@ pub enum ConfigCmd {
/// 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,
},
}
+37
View File
@@ -0,0 +1,37 @@
//! _agents (hidden): list agent names for shell completions (issue #41).
//! Prints one name per line; --installed restricts to what can actually be
//! launched: installed agents (managed + external), aliases and groups.
use super::*;
/// The completion pool: installed agents (managed + external), aliases and
/// groups when installed is true, the whole catalog otherwise.
pub fn agent_names(app: &App, installed: bool) -> Vec<String> {
let mut names: Vec<String> = Vec::new();
if installed {
let probe = crate::probe::detect_externals(app);
for a in app.catalog.agents() {
if app.state.get(&a.name).ok().flatten().is_some()
|| probe.path_of(&a.name).is_some()
{
names.push(a.name.clone());
}
}
names.extend(app.config.aliases.keys().cloned());
for g in app.config.groups.keys() {
names.push(format!("group:{g}"));
}
} else {
names.extend(app.catalog.agents().iter().map(|a| a.name.clone()));
}
names.sort();
names.dedup();
names
}
pub fn run(app: &App, installed: bool) -> Result<i32> {
for n in agent_names(app, installed) {
println!("{n}");
}
Ok(0)
}
+103
View File
@@ -0,0 +1,103 @@
//! alias: manage command aliases (cc -> claude-code) in the user config.
use super::*;
use crate::cli::AliasCmd;
use crate::output::print_json;
use anyhow::{anyhow, Context, Result};
use std::path::PathBuf;
/// Ensure the user config file exists and return its path.
fn user_config(app: &App) -> Result<PathBuf> {
let dir = app
.paths
.config_dir
.clone()
.ok_or_else(|| anyhow!("cannot determine the user config directory (no HOME set)"))?;
std::fs::create_dir_all(&dir)?;
let path = dir.join(crate::config::CONFIG_FILE_NAME);
if !path.exists() {
std::fs::write(&path, concat!("version: \"1.0\"\n", "agents: []\n"))
.with_context(|| format!("cannot create {}", path.display()))?;
}
Ok(path)
}
/// Read the aliases map from the user config file.
fn read_aliases(app: &App) -> Result<(PathBuf, serde_yaml::Mapping)> {
let path = user_config(app)?;
let text = std::fs::read_to_string(&path)
.with_context(|| format!("cannot read {}", path.display()))?;
let mut value: serde_yaml::Value = serde_yaml::from_str(&text)
.with_context(|| format!("{} is not valid YAML", path.display()))?;
let mapping = value
.as_mapping_mut()
.ok_or_else(|| anyhow!("{} must contain a YAML mapping", path.display()))?;
let aliases = mapping
.entry(serde_yaml::Value::String("aliases".to_string()))
.or_insert_with(|| serde_yaml::Value::Mapping(Default::default()))
.as_mapping()
.cloned()
.unwrap_or_default();
Ok((path, aliases))
}
fn write_aliases(app: &App, path: &std::path::Path, aliases: serde_yaml::Mapping) -> Result<()> {
let text = std::fs::read_to_string(path)
.with_context(|| format!("cannot read {}", path.display()))?;
let mut value: serde_yaml::Value = serde_yaml::from_str(&text)
.with_context(|| format!("{} is not valid YAML", path.display()))?;
if let Some(mapping) = value.as_mapping_mut() {
mapping.insert(
serde_yaml::Value::String("aliases".to_string()),
serde_yaml::Value::Mapping(aliases),
);
}
std::fs::write(path, serde_yaml::to_string(&value)?)
.with_context(|| format!("cannot write {}", path.display()))
}
pub fn run(app: &App, sub: &AliasCmd) -> Result<i32> {
match sub {
AliasCmd::Add { name, target } => {
// The target must be a known agent (or another alias).
require_agent(app, target)?;
let (path, mut aliases) = read_aliases(app)?;
aliases.insert(
serde_yaml::Value::String(name.clone()),
serde_yaml::Value::String(target.clone()),
);
write_aliases(app, &path, aliases)?;
app.log.success(&format!("alias '{name}' -> '{target}'"));
Ok(0)
}
AliasCmd::Remove { name } => {
let (path, mut aliases) = read_aliases(app)?;
let removed = aliases
.remove(&serde_yaml::Value::String(name.clone()))
.is_some();
if removed {
write_aliases(app, &path, aliases)?;
app.log.success(&format!("alias '{name}' removed"));
} else {
app.log.info(&format!("alias '{name}' does not exist"));
}
Ok(0)
}
AliasCmd::List => {
if app.json() {
print_json(&app.config.aliases);
return Ok(0);
}
if app.config.aliases.is_empty() {
app.log.info("no aliases defined — 'am alias add cc claude-code'");
return Ok(0);
}
let mut table = crate::output::Table::new(vec!["ALIAS", "TARGET"]);
for (k, v) in &app.config.aliases {
table.row(vec![k.clone(), v.clone()]);
}
print!("{}", table.render());
Ok(0)
}
}
}
+209
View File
@@ -0,0 +1,209 @@
//! favorite / unfavorite / note / tag / untag / tags: personal
//! annotations attached to agents (issue #39). Persisted in state.json
//! (additive since state v3) and traced in the event journal.
use super::*;
use crate::events::{Event, EventKind};
use crate::output::{boxed_table, print_json};
use crate::state::Annotation;
use anyhow::bail;
use serde::Serialize;
#[derive(Serialize)]
struct AnnotationJson {
agent: String,
favorite: bool,
note: Option<String>,
tags: Vec<String>,
}
/// Save the annotation block, emitting a journal event.
fn save_annotation(app: &App, def: &AgentDef, ann: &Annotation, detail: &str) -> Result<()> {
app.state.set_annotation(&def.name, ann)?;
app.emit(
&Event::now(EventKind::Annotate)
.with_agent(&def.name)
.with_reason(detail),
);
Ok(())
}
pub fn run_favorite(app: &App, agent: &str) -> Result<i32> {
let def = require_agent(app, agent)?;
let mut ann = app.state.annotation(&def.name)?;
if ann.favorite {
app.log.info(&format!("'{}' is already a favorite", def.name));
return Ok(0);
}
ann.favorite = true;
save_annotation(app, def, &ann, "favorite")?;
app.log.success(&format!("⭐ {} added to favorites", def.name));
Ok(0)
}
pub fn run_unfavorite(app: &App, agent: &str) -> Result<i32> {
let def = require_agent(app, agent)?;
let mut ann = app.state.annotation(&def.name)?;
if !ann.favorite {
app.log.info(&format!("'{}' is not a favorite", def.name));
return Ok(0);
}
ann.favorite = false;
save_annotation(app, def, &ann, "unfavorite")?;
app.log.success(&format!("★ {} removed from favorites", def.name));
Ok(0)
}
pub fn run_note(app: &App, agent: &str, text: &[String]) -> Result<i32> {
let def = require_agent(app, agent)?;
let mut ann = app.state.annotation(&def.name)?;
if text.is_empty() {
match &ann.note {
Some(n) => {
println!("{}", n);
Ok(0)
}
None => {
app.log.info(&format!("no note for '{}' — 'am note {} <text>' to set one", def.name, def.name));
Ok(0)
}
}
} else {
let note = text.join(" ");
ann.note = Some(note.clone());
save_annotation(app, def, &ann, "note")?;
app.log.success(&format!("note saved for '{}'", def.name));
Ok(0)
}
}
pub fn run_tag(app: &App, agent: &str, words: &[String]) -> Result<i32> {
if words.is_empty() {
bail!("usage: am tag <agent> <tag>... — at least one tag is required");
}
let def = require_agent(app, agent)?;
let mut ann = app.state.annotation(&def.name)?;
let mut added = Vec::new();
for w in words {
let tag = w.trim().trim_start_matches('#').to_lowercase();
if tag.is_empty() {
continue;
}
if !ann.tags.contains(&tag) {
ann.tags.push(tag.clone());
added.push(tag);
}
}
if added.is_empty() {
app.log.info(&format!("'{}' already carries those tags", def.name));
return Ok(0);
}
ann.tags.sort();
save_annotation(app, def, &ann, &format!("tags +{}", added.join(",")))?;
app.log.success(&format!(
"tagged '{}' with {}",
def.name,
added.join(", ")
));
Ok(0)
}
pub fn run_untag(app: &App, agent: &str, tag: &str) -> Result<i32> {
let def = require_agent(app, agent)?;
let mut ann = app.state.annotation(&def.name)?;
let needle = tag.trim().trim_start_matches('#').to_lowercase();
let before = ann.tags.len();
ann.tags.retain(|t| t != &needle);
if ann.tags.len() == before {
app.log.info(&format!("'{}' has no tag '{needle}'", def.name));
return Ok(0);
}
save_annotation(app, def, &ann, &format!("tags -{needle}"))?;
app.log.success(&format!("removed tag '{needle}' from '{}'", def.name));
Ok(0)
}
pub fn run_tags(app: &App, agent: Option<&str>) -> Result<i32> {
let all = app.state.annotations()?;
if let Some(name_or_alias) = agent {
let def = require_agent(app, name_or_alias)?;
let ann = app.state.annotation(&def.name)?;
if app.json() {
print_json(&AnnotationJson {
agent: def.name.clone(),
favorite: ann.favorite,
note: ann.note.clone(),
tags: ann.tags.clone(),
});
return Ok(0);
}
if ann.tags.is_empty() {
app.log.info(&format!("no personal tags for '{}'", def.name));
} else {
println!("{}", ann.tags.join(" · "));
}
return Ok(0);
}
// Global view: tag -> agents (sorted by tag).
let mut by_tag: BTreeMap<String, Vec<String>> = BTreeMap::new();
for (name, ann) in &all {
for t in &ann.tags {
by_tag.entry(t.clone()).or_default().push(name.clone());
}
}
if app.json() {
let rows: Vec<serde_json::Value> = by_tag
.iter()
.map(|(tag, agents)| {
serde_json::json!({ "tag": tag, "count": agents.len(), "agents": agents })
})
.collect();
print_json(&rows);
return Ok(0);
}
if by_tag.is_empty() {
app.log.info("no personal tags yet — 'am tag <agent> <word>' to add one");
return Ok(0);
}
let mut table_rows = Vec::new();
for (tag, agents) in &by_tag {
table_rows.push(vec![
format!("#{tag}"),
agents.len().to_string(),
agents.join(", "),
]);
}
print!(
"{}",
boxed_table(
vec!["TAG", "COUNT", "AGENTS"],
table_rows,
app.theme(),
app.color(),
)
);
Ok(0)
}
/// Is 'agent' a favorite? (used by list/status to print the star)
pub fn is_favorite(app: &App, agent: &str) -> bool {
app.state.annotation(agent).map(|a| a.favorite).unwrap_or(false)
}
/// Personal tags of 'agent' (used by search --tag and info).
pub fn tags_of(app: &App, agent: &str) -> Vec<String> {
app.state.annotation(agent).map(|a| a.tags).unwrap_or_default()
}
/// Agent names whose annotations match a personal tag (issue #39).
pub fn agents_with_tag(app: &App, tag: &str) -> Vec<String> {
let needle = tag.trim().trim_start_matches('#').to_lowercase();
app.state
.annotations()
.unwrap_or_default()
.into_iter()
.filter(|(_, a)| a.tags.iter().any(|t| t == &needle))
.map(|(name, _)| name)
.collect()
}
+142 -3
View File
@@ -1,13 +1,152 @@
//! completion: generate shell completion scripts (bash, zsh, fish,
//! powershell, elvish) via clap_complete.
//! powershell, elvish) via clap_complete. With --installed, a dynamic
//! layer is appended: Tab then queries 'am _agents --installed' at runtime,
//! so only installed agents, aliases and groups are proposed (issue #41).
use crate::cli::Cli;
use anyhow::Result;
use clap::CommandFactory;
use clap_complete::{generate, Shell};
use std::io::Write;
pub fn run(shell: Shell) -> Result<i32> {
pub fn run(shell: Shell, installed: bool) -> Result<i32> {
let mut cmd = Cli::command();
generate(shell, &mut cmd, "am", &mut std::io::stdout());
let mut buf: Vec<u8> = Vec::new();
generate(shell, &mut cmd, "am", &mut buf);
if installed {
let mut text = String::from_utf8_lossy(&buf).to_string();
match shell {
Shell::PowerShell => {
// Re-bind the base completer to a variable so the wrapper
// can delegate to it.
let marker = "Register-ArgumentCompleter -Native -CommandName 'am' -ScriptBlock {";
if let Some(pos) = text.find(marker) {
text.replace_range(pos..pos + marker.len(), "$amBase = {");
text.push('\n');
text.push_str(powershell_snippet());
}
}
_ => {
text.push('\n');
text.push_str(snippet(shell));
}
}
std::io::stdout().write_all(text.as_bytes())?;
} else {
std::io::stdout().write_all(&buf)?;
}
Ok(0)
}
pub fn snippet(shell: Shell) -> &'static str {
match shell {
Shell::Bash => bash_snippet(),
Shell::Zsh => zsh_snippet(),
Shell::Fish => fish_snippet(),
Shell::Elvish => elvish_snippet(),
Shell::PowerShell => powershell_snippet(),
_ => "",
}
}
pub fn bash_snippet() -> &'static str {
r#"# --- dynamic completion of installed agents, aliases and groups (--installed) ---
_am_installed() {
local cur cmd
cur="${COMP_WORDS[COMP_CWORD]}"
cmd="${COMP_WORDS[1]}"
case "${cmd}" in
start|stop|restart|run|info|logs|open|install|uninstall|update|watch|favorite|unfavorite|note|tag|untag|status|sessions|stats|log)
if [[ ${COMP_CWORD} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "$(am _agents --installed 2>/dev/null)" -- "${cur}") )
return 0
fi
;;
esac
_am "$@"
}
complete -F _am_installed -o nosort -o bashdefault -o default am
"#
}
pub fn zsh_snippet() -> &'static str {
r#"# --- dynamic completion of installed agents, aliases and groups (--installed) ---
_am_installed() {
local -a agents
if [[ ${CURRENT} -eq 3 ]] && [[ ${words[2]} =~ ^(start|stop|restart|run|info|logs|open|install|uninstall|update|watch|favorite|unfavorite|note|tag|untag|status|sessions|stats|log)$ ]]; then
agents=("${(@f)$(am _agents --installed 2>/dev/null)}")
_describe -t agents 'agent' agents
return
fi
_am "$@"
}
compdef _am_installed am
"#
}
pub fn fish_snippet() -> &'static str {
r#"# --- dynamic completion of installed agents, aliases and groups (--installed) ---
complete -c am -n "__fish_seen_subcommand_from start stop restart run info logs open install uninstall update watch favorite unfavorite note tag untag status sessions stats log" -f -a "(am _agents --installed 2>/dev/null)"
"#
}
pub fn elvish_snippet() -> &'static str {
r#"# --- dynamic completion of installed agents, aliases and groups (--installed) ---
var am-base = $edit:completion:arg-completer[am]
set edit:completion:arg-completer[am] = {|@words|
if (and (>= (count $words) 2) (<= (count $words) 3) (has-value [start stop restart run info logs open install uninstall update watch favorite unfavorite note tag untag status sessions stats log] $words[1])) {
put $@(am _agents --installed)
} else {
$am-base $@words
}
}
"#
}
pub fn powershell_snippet() -> &'static str {
r#"
# --- dynamic completion of installed agents, aliases and groups (--installed) ---
Register-ArgumentCompleter -Native -CommandName 'am' -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$tokens = $commandAst.CommandElements | ForEach-Object { $_.Extent.Text }
$agentCommands = @('start','stop','restart','run','info','logs','open','install','uninstall','update','watch','favorite','unfavorite','note','tag','untag','status','sessions','stats','log')
if ($tokens.Count -ge 2 -and $agentCommands -contains $tokens[1]) {
(am _agents --installed 2>$null) | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
return
}
& $amBase $wordToComplete $commandAst $cursorPosition
}
"#
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dynamic_snippets_are_produced_for_every_shell() {
for shell in [
Shell::Bash,
Shell::Zsh,
Shell::Fish,
Shell::Elvish,
Shell::PowerShell,
] {
let text = snippet(shell);
assert!(text.contains("_agents --installed"), "{shell:?}");
assert!(text.contains("favorite"), "{shell:?}");
}
}
#[test]
fn base_script_has_no_dynamic_layer() {
let mut cmd = Cli::command();
let mut buf: Vec<u8> = Vec::new();
generate(Shell::Bash, &mut cmd, "am", &mut buf);
let base = String::from_utf8(buf).unwrap();
assert!(!base.contains("_am_installed"));
assert!(base.contains("complete -F _am "));
}
}
+55
View File
@@ -5,6 +5,7 @@ use crate::cli::ConfigCmd;
use crate::config;
use crate::output::print_json;
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -94,9 +95,63 @@ pub fn run(app: &App, cmd: &ConfigCmd) -> Result<i32> {
Ok(status.code().unwrap_or(1))
}
ConfigCmd::Add { file } => add_file(app, file),
ConfigCmd::Set { key, value } => set_value(app, key, value),
}
}
/// config set: set one dotted key (e.g. settings.default_shell) in the user
/// configuration file, then validate the result.
pub fn set_value(app: &App, key: &str, value: &str) -> Result<i32> {
let path = user_config_path(app)?;
let text = std::fs::read_to_string(&path)
.with_context(|| format!("cannot read {}", path.display()))?;
let mut root: serde_yaml::Value = serde_yaml::from_str(&text)
.with_context(|| format!("{} is not valid YAML", path.display()))?;
let parts: Vec<&str> = key.split('.').collect();
if parts.is_empty() || parts.iter().any(|p| p.is_empty()) {
return Err(anyhow!("invalid key '{key}' — expected a dotted path like settings.default_shell"));
}
let parsed = parse_scalar(value);
let mut node = root
.as_mapping_mut()
.ok_or_else(|| anyhow!("{} must contain a YAML mapping", path.display()))?;
for part in &parts[..parts.len() - 1] {
let entry = node
.entry(serde_yaml::Value::String(part.to_string()))
.or_insert_with(|| serde_yaml::Value::Mapping(Default::default()));
node = entry
.as_mapping_mut()
.ok_or_else(|| anyhow!("'{part}' is not a mapping — cannot descend into it"))?;
}
node.insert(
serde_yaml::Value::String(parts.last().unwrap().to_string()),
parsed,
);
std::fs::write(&path, serde_yaml::to_string(&root)?)
.with_context(|| format!("cannot write {}", path.display()))?;
// Validate the new configuration end to end.
let cli = crate::cli::Cli::parse_from(["am", "--config", path.to_str().unwrap_or_default()]);
config::load(&cli).map_err(|e| {
anyhow!("the new value makes the configuration invalid: {e:#}")
})?;
app.log.success(&format!("{key} = {value}"));
Ok(0)
}
/// Parse a scalar: booleans and integers keep their type, everything else
/// stays a string.
fn parse_scalar(value: &str) -> serde_yaml::Value {
if let Ok(b) = value.parse::<bool>() {
return serde_yaml::Value::Bool(b);
}
if let Ok(i) = value.parse::<i64>() {
return serde_yaml::Value::Number(i.into());
}
serde_yaml::Value::String(value.to_string())
}
fn user_config_path_if_any(app: &App) -> Option<PathBuf> {
app.paths
.config_dir
+379
View File
@@ -0,0 +1,379 @@
//! dashboard: live TUI over the whole fleet (issue #33) — five tabs
//! (overview, activity, stats, sessions, projects), 1 s refresh, keyboard
//! navigation, clean exit. When stdout is not a terminal (or --json), a
//! plain snapshot is printed instead of entering the TUI.
use super::*;
use crate::dashboard::{collect, ActivityRow, DashData, OverviewRow, SessionRow, StatsRow};
use crate::projects::ProjectSummary;
use crate::output::print_json;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph, Tabs};
use ratatui::{DefaultTerminal, Frame};
use std::io::IsTerminal;
use std::time::Duration;
pub const TABS: [&str; 5] = ["1 overview", "2 activity", "3 stats", "4 sessions", "5 projects"];
struct Ui {
tab: usize,
scroll: [usize; 5],
}
pub fn run(app: &App) -> Result<i32> {
if app.json() {
print_json(&collect(app));
return Ok(0);
}
if !std::io::stdout().is_terminal() {
print_snapshot(app)?;
return Ok(0);
}
let mut terminal = ratatui::init();
let result = run_loop(app, &mut terminal);
ratatui::restore();
// Leave the prompt on a fresh line when launched from the REPL.
println!();
result
}
fn run_loop(app: &App, terminal: &mut DefaultTerminal) -> Result<i32> {
let mut ui = Ui { tab: 0, scroll: [0; 5] };
let mut data = collect(app);
loop {
terminal.draw(|f| draw(f, &data, &ui))?;
if !event::poll(Duration::from_secs(1))? {
// 1 s refresh: repaint with fresh data.
data = collect(app);
continue;
}
match event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Press => match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(0),
KeyCode::Char('h') | KeyCode::Left => ui.tab = ui.tab.saturating_sub(1),
KeyCode::Char('l') | KeyCode::Right | KeyCode::Tab => {
ui.tab = (ui.tab + 1) % TABS.len()
}
KeyCode::Char(c) if c.is_ascii_digit() && c != '0' => {
ui.tab = (c as usize - '1' as usize).min(TABS.len() - 1);
}
KeyCode::Char('k') | KeyCode::Up => {
ui.scroll[ui.tab] = ui.scroll[ui.tab].saturating_sub(1)
}
KeyCode::Char('j') | KeyCode::Down => ui.scroll[ui.tab] += 1,
KeyCode::PageUp => ui.scroll[ui.tab] = ui.scroll[ui.tab].saturating_sub(10),
KeyCode::PageDown => ui.scroll[ui.tab] += 10,
KeyCode::Home => ui.scroll[ui.tab] = 0,
KeyCode::Char('r') => data = collect(app),
_ => {}
},
Event::Resize(_, _) => data = collect(app),
_ => {}
}
}
}
fn draw(f: &mut Frame, data: &DashData, ui: &Ui) {
let [header, body] =
Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).areas(f.area());
let counts = &data.counts;
let title = Line::from(vec![
Span::styled(
" agent-manager ",
Style::default()
.fg(Color::Black)
.bg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
Span::raw(format!(
" {} installed · {} running · {} sessions{}",
counts.installed,
counts.running,
counts.sessions_count,
counts
.last_used
.as_deref()
.map(|t| format!(" · last used {t}"))
.unwrap_or_default(),
)),
]);
let tabs = Tabs::new(TABS)
.block(
Block::default()
.borders(Borders::ALL)
.title(title)
.title_alignment(Alignment::Left),
)
.select(ui.tab)
.highlight_style(
Style::default()
.fg(Color::Black)
.bg(Color::Cyan)
.add_modifier(Modifier::BOLD),
);
f.render_widget(tabs, header);
match ui.tab {
0 => draw_overview(f, data, ui, body),
1 => draw_activity(f, data, ui, body),
2 => draw_stats(f, data, ui, body),
3 => draw_sessions(f, data, ui, body),
_ => draw_projects(f, data, ui, body),
}
}
fn status_style(status: &str) -> Style {
match status {
"running" => Style::default().fg(Color::Green).add_modifier(Modifier::BOLD),
"installed" | "external" => Style::default().fg(Color::Cyan),
"failed" | "interrupted" => Style::default().fg(Color::Red),
"not-installed" | "not-installable" => Style::default().fg(Color::DarkGray),
_ => Style::default().fg(Color::Yellow),
}
}
fn block<'a>(title: &str, hint: &str, scroll: usize) -> Block<'a> {
Block::default()
.borders(Borders::ALL)
.title(format!(" {title} "))
.title_bottom(format!(" {hint} · scroll {scroll} "))
.title_alignment(Alignment::Left)
}
fn fmt_duration(secs: u64) -> String {
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m{:02}s", secs / 60, secs % 60)
} else if secs < 86400 {
format!("{}h{:02}m", secs / 3600, (secs % 3600) / 60)
} else {
format!("{}d{:02}h", secs / 86400, (secs % 86400) / 3600)
}
}
fn overview_line(r: &OverviewRow) -> Line<'static> {
let star = if r.favorite { "★ " } else { " " };
let status = r.status.as_str();
let version = r.version.as_deref().unwrap_or("—");
let uptime = r
.uptime_s
.map(|s| format!("up {}", fmt_duration(s)))
.unwrap_or_default();
let pid = r
.pid
.map(|p| format!("pid {p}"))
.unwrap_or_default();
Line::from(vec![
Span::styled(format!("{star}{:<22}", r.name), Style::default().add_modifier(Modifier::BOLD)),
Span::styled(format!("{:<16}", status), status_style(status)),
Span::raw(format!("{:<12} ", version)),
Span::raw(format!("{pid:<10} {uptime}")),
])
}
fn draw_overview(f: &mut Frame, data: &DashData, ui: &Ui, area: Rect) {
let mut lines: Vec<Line> = data
.overview
.iter()
.map(overview_line)
.collect();
if lines.is_empty() {
lines.push(Line::from("no agents in the catalog"));
}
let p = Paragraph::new(lines)
.block(block("overview", "j/k scroll · q quit", ui.scroll[0]))
.scroll((ui.scroll[0] as u16, 0));
f.render_widget(p, area);
}
fn activity_line(r: &ActivityRow) -> Line<'static> {
Line::from(vec![
Span::styled(format!("{:<21} ", r.ts), Style::default().fg(Color::DarkGray)),
Span::styled(
format!("{:<10} ", r.kind),
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
),
Span::styled(format!("{:<22} ", r.agent.as_deref().unwrap_or("-")), Style::default().fg(Color::Yellow)),
Span::raw(r.detail.clone()),
])
}
fn draw_activity(f: &mut Frame, data: &DashData, ui: &Ui, area: Rect) {
let lines: Vec<Line> = data.activity.iter().map(activity_line).collect();
let p = Paragraph::new(if lines.is_empty() {
vec![Line::from("the event journal is empty — every am action will appear here")]
} else {
lines
})
.block(block("activity — event journal", "j/k scroll · q quit", ui.scroll[1]))
.scroll((ui.scroll[1] as u16, 0));
f.render_widget(p, area);
}
fn stats_line(r: &StatsRow) -> Line<'static> {
let rate = if r.launches > 0 {
format!("{:>6.0}% ok", 100.0 * (r.launches.saturating_sub(r.failures)) as f64 / r.launches as f64)
} else {
" —".to_string()
};
Line::from(vec![
Span::styled(format!("{:<22}", r.agent), Style::default().add_modifier(Modifier::BOLD)),
Span::raw(format!("{:>8} launches ", r.launches)),
Span::raw(format!("{:>8} failures ", r.failures)),
Span::raw(format!("{:>10} ", fmt_duration(r.total_duration_s))),
Span::styled(rate, status_style(if r.failures > 0 { "failed" } else { "running" })),
])
}
fn draw_stats(f: &mut Frame, data: &DashData, ui: &Ui, area: Rect) {
let lines: Vec<Line> = data.stats.iter().map(stats_line).collect();
let p = Paragraph::new(if lines.is_empty() {
vec![Line::from("no usage data yet — start an agent to fill the journal")]
} else {
lines
})
.block(block("stats — per agent", "j/k scroll · q quit", ui.scroll[2]))
.scroll((ui.scroll[2] as u16, 0));
f.render_widget(p, area);
}
fn session_line(r: &SessionRow) -> Line<'static> {
let dur = r
.duration_s
.map(fmt_duration)
.unwrap_or_else(|| "running…".to_string());
Line::from(vec![
Span::styled(format!("{:<18} ", r.id), Style::default().fg(Color::Yellow)),
Span::styled(format!("{:<11} ", r.status), status_style(&r.status)),
Span::styled(
format!("{:<22} ", r.agent.as_deref().unwrap_or("repl")),
Style::default().add_modifier(Modifier::BOLD),
),
Span::raw(format!(
"{:<26} ",
r.project.as_deref().unwrap_or("-")
)),
Span::styled(format!("{:<10} ", r.started_at), Style::default().fg(Color::DarkGray)),
Span::raw(dur),
])
}
fn draw_sessions(f: &mut Frame, data: &DashData, ui: &Ui, area: Rect) {
let lines: Vec<Line> = data.sessions.iter().map(session_line).collect();
let p = Paragraph::new(if lines.is_empty() {
vec![Line::from("no sessions recorded yet")]
} else {
lines
})
.block(block("sessions", "j/k scroll · q quit", ui.scroll[3]))
.scroll((ui.scroll[3] as u16, 0));
f.render_widget(p, area);
}
fn project_line(r: &ProjectSummary) -> Line<'static> {
let mut spans: Vec<Span<'static>> = Vec::new();
spans.push(Span::styled(
format!("{:<28} ", r.root),
Style::default().add_modifier(Modifier::BOLD),
));
spans.push(Span::styled(
format!("{:<22} ", r.dominant.as_deref().unwrap_or("-")),
Style::default().fg(Color::Cyan),
));
spans.push(Span::raw(format!(
"{:>4} sessions {:>5} launches {:>10} ",
r.sessions, r.launches, fmt_duration(r.total_duration_s)
)));
spans.push(Span::styled(
r.last_activity.as_deref().unwrap_or("-").to_string(),
Style::default().fg(Color::DarkGray),
));
Line::from(spans)
}
fn draw_projects(f: &mut Frame, data: &DashData, ui: &Ui, area: Rect) {
let lines: Vec<Line> = data.projects.iter().map(project_line).collect();
let p = Paragraph::new(if lines.is_empty() {
vec![Line::from("no project activity recorded yet")]
} else {
lines
})
.block(block("projects", "j/k scroll · q quit", ui.scroll[4]))
.scroll((ui.scroll[4] as u16, 0));
f.render_widget(p, area);
}
/// Plain-text snapshot (piped stdout): same data, no TUI.
fn print_snapshot(app: &App) -> Result<i32> {
let data = collect(app);
let c = &data.counts;
println!(
"agent-manager dashboard — {} installed · {} running · {} sessions",
c.installed, c.running, c.sessions_count
);
println!();
println!("overview");
for r in &data.overview {
let star = if r.favorite { "★ " } else { " " };
let uptime = r
.uptime_s
.map(|s| format!(" (up {})", fmt_duration(s)))
.unwrap_or_default();
println!(
" {star}{:<22} {:<16} {:<12} {}{}",
r.name,
r.status,
r.version.as_deref().unwrap_or("—"),
r.pid.map(|p| format!("pid {p}")).unwrap_or_default(),
uptime,
);
}
println!();
println!("activity (last {})", data.activity.len());
for r in data.activity.iter().take(20) {
println!(
" {:<21} {:<10} {:<22} {}",
r.ts,
r.kind,
r.agent.as_deref().unwrap_or("-"),
r.detail
);
}
println!();
println!("stats");
for r in &data.stats {
println!(
" {:<22} {:>8} launches {:>8} failures {:>10}",
r.agent,
r.launches,
r.failures,
fmt_duration(r.total_duration_s),
);
}
println!();
println!("sessions");
for r in data.sessions.iter().take(20) {
println!(
" {:<18} {:<11} {:<22} {}",
r.id,
r.status,
r.agent.as_deref().unwrap_or("repl"),
r.duration_s.map(fmt_duration).unwrap_or_else(|| "running…".to_string()),
);
}
println!();
println!("projects");
for r in &data.projects {
println!(
" {:<28} {:<22} {} sessions {} launches",
r.root,
r.dominant.as_deref().unwrap_or("-"),
r.sessions,
r.launches,
);
}
Ok(0)
}
+23 -7
View File
@@ -2,7 +2,8 @@
use super::*;
use crate::doctor::{self, Status};
use crate::output::{print_json, status_style, Table};
use crate::events::{Event, EventKind};
use crate::output::{boxed_table, print_json, status_style};
use crate::runner::SystemRunner;
use anyhow::Result;
@@ -22,21 +23,36 @@ pub fn run(app: &App, fix: bool) -> Result<i32> {
// The doctor report probes real tools, even under --dry-run.
let runner = SystemRunner::new(false, false, &app.log);
let report = doctor::run(app, &runner);
if !app.dry_run() {
app.emit(
&Event::now(EventKind::Doctor)
.with_exit_code(if report.ok() { 0 } else { 1 }),
);
}
if app.json() {
print_json(&report.checks);
return Ok(if report.ok() { 0 } else { 1 });
}
let color = app.color();
let mut table = Table::new(vec!["CHECK", "STATUS", "DETAIL"]);
let theme = app.theme();
let mut table_rows = Vec::new();
for c in &report.checks {
let status = match c.status {
Status::Ok => status_style("ok", color),
Status::Warn => status_style("warn", color),
Status::Error => status_style("error", color),
Status::Ok => status_style("ok", theme, color),
Status::Warn => status_style("warn", theme, color),
Status::Error => status_style("error", theme, color),
};
table.row(vec![c.name.clone(), status, c.detail.clone()]);
table_rows.push(vec![c.name.clone(), status, c.detail.clone()]);
}
print!("{}", table.render());
print!(
"{}",
boxed_table(
vec!["CHECK", "STATUS", "DETAIL"],
table_rows,
theme,
color,
)
);
if report.problems() > 0 {
app.log
.warn(&format!("{} problem(s) found — 'am doctor --fix' repairs what it can", report.problems()));
+2
View File
@@ -77,6 +77,8 @@ pub fn import_cmd(app: &App, file: &Path) -> Result<i32> {
groups: Default::default(),
include: vec![],
agents: export.config.agents.clone(),
projects: Default::default(),
profiles: export.config.profiles.clone(),
};
std::fs::write(&dest, serde_yaml::to_string(&cfg)?)
.with_context(|| format!("cannot write {}", dest.display()))?;
+12
View File
@@ -0,0 +1,12 @@
//! help: Nushell-style help — the overview, one command, the command list,
//! or a keyword search through it.
use super::*;
pub fn run(app: &App, command: Option<&str>, find: Option<&str>) -> Result<i32> {
if let Some(text) = find {
crate::help::print_find(app, text);
return Ok(0);
}
crate::help::print_topic(app, command)
}
+138
View File
@@ -0,0 +1,138 @@
//! history: search and reuse the structured command history.
use super::*;
use crate::history::{self, HistoryEntry};
use crate::output::print_json;
use anyhow::{anyhow, Result};
use clap::Parser;
/// Re-run the Nth most recent entry: shell commands go through the
/// active shell, am commands are parsed and dispatched like the REPL does.
fn rerun_entry(app: &App, n: usize) -> Result<i32> {
if n == 0 {
return Err(anyhow!("invalid entry number 0 — am history numbers start at 1"));
}
let entries = history::read_all(app, n);
let entry = entries.get(n - 1).cloned().ok_or_else(|| {
anyhow!("history entry {n} not found — 'am history' lists them")
})?;
let question = format!("re-run: {} [y/N]", entry.cmd);
if !app.confirm(&question)? {
app.log.info("cancelled");
return Ok(0);
}
let cmd = entry.cmd.strip_prefix('!').unwrap_or(&entry.cmd);
if entry.kind == "shell" {
let session = crate::shell::ShellSession::start(&app.config);
return Ok(session.run(cmd)?);
}
let tokens = shell_words::split(cmd)
.map_err(|e| anyhow!("cannot parse '{cmd}': {e}"))?;
if tokens.is_empty() {
return Ok(0);
}
let cli = crate::cli::Cli::try_parse_from(std::iter::once("am".to_string()).chain(tokens))
.map_err(|e| anyhow!("cannot parse '{cmd}': {e}"))?;
match &cli.command {
Some(cmd) => execute_command(app, cmd),
None => {
app.log.info("nothing to re-run (no subcommand)");
Ok(0)
}
}
}
/// Filter history entries; returns them most recent first.
pub fn filter(
app: &App,
kind: Option<&str>,
cwd: Option<&str>,
search: Option<&str>,
failed: bool,
session: Option<&str>,
limit: usize,
) -> Vec<(String, HistoryEntry)> {
let _ = session; // session filtering happens by reading one file directly
let mut out: Vec<(String, HistoryEntry)> = Vec::new();
let dir = history::dir(app);
for f in history::files(&dir).iter().rev() {
let sid = history::session_of_file(f).unwrap_or_default();
if let Some(s) = session {
if sid != s {
continue;
}
}
let Ok(text) = std::fs::read_to_string(f) else {
continue;
};
let mut entries: Vec<HistoryEntry> = text
.lines()
.filter(|l| !l.trim().is_empty())
.filter_map(|l| serde_json::from_str::<HistoryEntry>(l).ok())
.collect();
entries.reverse();
for e in entries {
if kind.map_or(true, |k| e.kind == k)
&& cwd.map_or(true, |c| e.cwd.as_deref() == Some(c))
&& search.map_or(true, |s| e.cmd.to_lowercase().contains(&s.to_lowercase()))
&& (!failed || e.exit_code.is_some())
{
out.push((sid.clone(), e));
if limit > 0 && out.len() >= limit {
return out;
}
}
}
}
out
}
pub fn run(
app: &App,
kind: Option<&str>,
cwd: Option<&str>,
search: Option<&str>,
failed: bool,
session: Option<&str>,
limit: usize,
rerun: Option<usize>,
) -> Result<i32> {
history::migrate(app)?;
if let Some(n) = rerun {
return rerun_entry(app, n);
}
let rows = filter(app, kind, cwd, search, failed, session, limit);
if app.json() {
let payload: Vec<serde_json::Value> = rows
.iter()
.enumerate()
.map(|(i, (sid, e))| {
serde_json::json!({
"n": rows.len() - i,
"ts": e.ts,
"cmd": e.cmd,
"kind": e.kind,
"cwd": e.cwd,
"exit_code": e.exit_code,
"duration_ms": e.duration_ms,
"session": sid,
})
})
.collect();
print_json(&payload);
return Ok(0);
}
for (i, (_sid, e)) in rows.iter().enumerate() {
let n = rows.len() - i;
let ts = e.ts.get(0..16).unwrap_or(&e.ts);
let flag = match e.exit_code {
Some(_) => "✗".to_string(),
None => " ".to_string(),
};
println!("{n:>4} {flag} {ts} {}", e.cmd);
}
if rows.is_empty() {
app.log.info("no matching history — run 'am' and type some commands");
}
Ok(0)
}
+49 -1
View File
@@ -20,6 +20,12 @@ struct InfoJson {
pid: Option<u32>,
installable: bool,
note: Option<String>,
/// Personal note (issue #39).
personal_note: Option<String>,
/// Personal tags (issue #39).
personal_tags: Vec<String>,
/// Favorite (issue #39).
favorite: bool,
platforms: Vec<String>,
run: Option<String>,
args: Vec<String>,
@@ -83,6 +89,9 @@ pub fn run(app: &App, name: &str) -> Result<i32> {
pid: if running { entry.as_ref().and_then(|e| e.pid) } else { None },
installable: agent.installable,
note: agent.note.clone(),
personal_note: annotations_note(app, &agent.name),
personal_tags: annotations_tags(app, &agent.name),
favorite: annotations_favorite(app, &agent.name),
platforms: agent.platforms.clone(),
run: agent.run.clone(),
args: agent.args.clone(),
@@ -154,7 +163,7 @@ pub fn run(app: &App, name: &str) -> Result<i32> {
} else {
("not-installed", String::new())
};
let word = crate::output::status_style(status_word, color);
let word = crate::output::status_style(status_word, app.theme(), color);
println!(" {} {}", dim("● status"), format!("{word}{detail}"));
// Version: recorded for managed installs, detected for external ones.
@@ -274,9 +283,48 @@ pub fn run(app: &App, name: &str) -> Result<i32> {
println!();
println!(" {}", dim(&format!("note {n}")));
}
// --- Personal annotations (issue #39) -------------------------------------
let p_note = annotations_note(app, &agent.name);
let p_tags = annotations_tags(app, &agent.name);
let p_fav = annotations_favorite(app, &agent.name);
if p_fav || p_tags.iter().any(|t| !agent.tags.contains(t)) || p_note.is_some() {
println!();
println!(" {}", bold("personal"));
if p_fav {
println!(" {} {}", dim("● favorite"), "★");
}
if !p_tags.is_empty() {
println!(
" {} {}",
dim("● tags"),
p_tags
.iter()
.map(|t| format!("#{t}"))
.collect::<Vec<_>>()
.join(" ")
);
}
if let Some(n) = &p_note {
println!(" {} {}", dim("● note"), n);
}
}
Ok(0)
}
// Personal annotations helpers (issue #39) — never fail.
fn annotations_note(app: &App, name: &str) -> Option<String> {
app.state.annotation(name).ok().and_then(|a| a.note)
}
fn annotations_tags(app: &App, name: &str) -> Vec<String> {
app.state.annotation(name).map(|a| a.tags).unwrap_or_default()
}
fn annotations_favorite(app: &App, name: &str) -> bool {
app.state
.annotation(name)
.map(|a| a.favorite)
.unwrap_or(false)
}
fn external_path(agent: &AgentDef) -> Option<std::path::PathBuf> {
let first = agent.first_token()?;
which::which(first).ok()
+70
View File
@@ -0,0 +1,70 @@
//! init: generate a local agent-manager.yaml for the current directory,
//! based on the detected technology stack.
use super::*;
use crate::context;
use anyhow::{anyhow, Context, Result};
use std::path::{Path, PathBuf};
/// Generate a local config file in dir; refuses to overwrite unless force.
/// Returns the written path.
pub fn generate(dir: &Path, force: bool) -> Result<PathBuf> {
let target = dir.join(crate::config::LOCAL_CONFIG_NAME);
if target.exists() && !force {
return Err(anyhow!(
"{} already exists — use --force to overwrite it",
target.display()
));
}
let ctx = context::detect(dir);
let agents = context::recommended_agents(&ctx.stack);
let members = agents
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", ");
let stack = if ctx.stack.is_empty() {
"aucune détectée".to_string()
} else {
ctx.stack.join(", ")
};
let content = format!(
"# Généré par 'am init' le {date}
# Pile détectée : {stack}
version: \"1.0\"
groups:
dev: [{members}]
agents: []
",
date = crate::installers::now_rfc3339(),
);
std::fs::write(&target, content)
.with_context(|| format!("cannot write {}", target.display()))?;
Ok(target)
}
pub fn run(app: &App, force: bool) -> Result<i32> {
if app.dry_run() {
app.log.dry(format!(
"would generate {} in the current directory",
crate::config::LOCAL_CONFIG_NAME
));
return Ok(0);
}
let cwd = std::env::current_dir().context("cannot determine the current directory")?;
let ctx = context::detect(&cwd);
let path = generate(&cwd, force)?;
app.log.success(&format!(
"wrote {} (stack: {})",
path.display(),
if ctx.stack.is_empty() {
"aucune détectée".to_string()
} else {
ctx.stack.join(", ")
}
));
app.log.info(&format!(
"group 'dev' prêt : am start group:dev — ajustez ensuite la config générée"
));
Ok(0)
}
+11
View File
@@ -2,6 +2,7 @@
//! installer, and record the installation in the state database.
use super::*;
use crate::events::{Event, EventKind};
use crate::installers;
use crate::runner::SystemRunner;
use anyhow::{anyhow, bail, Result};
@@ -137,6 +138,16 @@ pub fn run(app: &App, agent_name: &str, method_sel: Option<&str>, force: bool) -
let entry = installers::make_entry(agent, method, &outcome, app);
let version = entry.version.clone().unwrap_or_else(|| "unknown".to_string());
app.state.set(&entry)?;
app.emit(
&Event::now(EventKind::Install)
.with_agent(agent.name.clone())
.with_args(vec![format!("method={}", method.kind)]),
);
crate::hooks::run_hooks(
app,
"on_install",
&std::env::current_dir().unwrap_or_default(),
);
app.log.success(&format!(
"installed {} (v{version}) in {}",
agent.title(),
+39 -10
View File
@@ -2,7 +2,7 @@
//! the whole catalog with --all.
use super::*;
use crate::output::{print_json, status_style, Table};
use crate::output::{boxed_table, print_json, status_style};
use crate::probe::ExternalInfo;
use crate::state::InstalledEntry;
use serde::Serialize;
@@ -22,10 +22,18 @@ struct AgentRow {
pid: Option<u32>,
installable: bool,
note: Option<String>,
/// Personal favorite (issue #39).
favorite: bool,
}
/// run: installed agents only, unless all is set (whole catalog).
pub fn run(app: &App, all: bool, category: Option<&str>) -> Result<i32> {
pub fn run(
app: &App,
all: bool,
category: Option<&str>,
running: bool,
sort: Option<&str>,
) -> Result<i32> {
let entries = app.state.all().unwrap_or_default();
let probe = crate::probe::detect_externals(app);
let mut rows: Vec<AgentRow> = Vec::new();
@@ -38,29 +46,49 @@ pub fn run(app: &App, all: bool, category: Option<&str>) -> Result<i32> {
continue;
}
}
rows.push(row_for(agent, entries.get(&agent.name), &probe));
rows.push(row_for(app, agent, entries.get(&agent.name), &probe));
}
if !all {
rows.retain(|r| r.status == "running" || r.status == "installed" || r.status == "external");
}
rows.sort_by(|a, b| a.name.cmp(&b.name));
if running {
rows.retain(|r| r.status == "running");
}
match sort {
Some("version") => rows.sort_by(|a, b| b.version.cmp(&a.version)),
Some("status") => rows.sort_by(|a, b| a.status.cmp(&b.status)),
Some(other) => bail!("invalid sort key '{other}' — expected name, version or status"),
None => rows.sort_by(|a, b| a.name.cmp(&b.name)),
}
if app.json() {
print_json(&rows);
return Ok(0);
}
let color = app.color();
let mut table = Table::new(vec!["NAME", "VERSION", "STATUS", "SOURCE", "PATH"]);
let theme = app.theme();
let mut table_rows = Vec::new();
for r in &rows {
table.row(vec![
r.display_name.clone(),
// Favorite star (issue #39).
let star = if r.favorite { "★ " } else { "" };
table_rows.push(vec![
format!("{star}{}", r.display_name),
r.name.clone(),
r.version.clone().unwrap_or_else(|| "—".to_string()),
status_style(&r.status, color),
status_style(&r.status, theme, color),
r.source.clone(),
r.path.clone().unwrap_or_else(|| "—".to_string()),
]);
}
print!("{}", table.render());
print!(
"{}",
boxed_table(
vec!["NAME", "AGENT", "VERSION", "STATUS", "SOURCE", "PATH"],
table_rows,
theme,
color,
)
);
if rows.is_empty() {
app.log.info(if all {
"no agents found"
@@ -71,7 +99,7 @@ pub fn run(app: &App, all: bool, category: Option<&str>) -> Result<i32> {
Ok(0)
}
fn row_for(agent: &AgentDef, entry: Option<&InstalledEntry>, probe: &ExternalInfo) -> AgentRow {
fn row_for(app: &App, agent: &AgentDef, entry: Option<&InstalledEntry>, probe: &ExternalInfo) -> AgentRow {
let (status, source, path, version, running, pid, method) = if let Some(e) = entry {
let running = e.pid.map(crate::process::is_running).unwrap_or(false);
let status = if running { "running" } else { "installed" };
@@ -147,5 +175,6 @@ fn row_for(agent: &AgentDef, entry: Option<&InstalledEntry>, probe: &ExternalInf
pid,
installable: agent.installable,
note: agent.note.clone(),
favorite: crate::commands::annotations_cmd::is_favorite(app, &agent.name),
}
}
+160
View File
@@ -0,0 +1,160 @@
//! log: a filtered view of the event journal — everything am did.
use super::*;
use crate::events::{self, Event};
use crate::output::print_json;
use serde::Serialize;
/// Filter journal events: agent, kind list, since date, and a max count.
/// Returns events in reverse chronological order (most recent first).
pub fn filter_events(
app: &App,
agent: Option<&str>,
kinds: Option<&str>,
since: Option<&str>,
limit: usize,
) -> Result<Vec<Event>> {
let mut events = events::read_events(&app.events_dir(), 0);
if let Some(a) = agent {
events.retain(|e| e.agent.as_deref() == Some(a));
}
if let Some(list) = kinds {
let wanted: Vec<&str> = list.split(',').map(|s| s.trim()).collect();
events.retain(|e| wanted.iter().any(|w| e.kind.as_str() == *w));
}
if let Some(s) = since {
let cut = parse_since(s)?;
events.retain(|e| {
chrono::DateTime::parse_from_rfc3339(&e.ts)
.map(|t| t >= cut)
.unwrap_or(false)
});
}
events.reverse(); // most recent first
events.truncate(limit);
Ok(events)
}
/// Live view: print matching events as they arrive (issue #38).
fn follow_loop(app: &App, agent: Option<&str>, kinds: Option<&str>, since: Option<&str>) -> Result<i32> {
app.log.info("following the journal (Ctrl-C to stop)");
let mut seen: usize = 0;
loop {
let events = filter_events(app, agent, kinds, since, 200)?;
let mut rendered = String::new();
for e in events.iter().rev() {
rendered.push_str(&e.ts);
rendered.push(' ');
rendered.push_str(e.kind.as_str());
if let Some(a) = &e.agent {
rendered.push(' ');
rendered.push_str(a);
}
rendered.push('\n');
}
let bytes = rendered.as_bytes();
if bytes.len() > seen {
print!("{}", text_after(&rendered, seen));
use std::io::Write;
let _ = std::io::stdout().flush();
}
seen = bytes.len();
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
pub fn parse_since(s: &str) -> Result<chrono::DateTime<chrono::FixedOffset>> {
if let Ok(t) = chrono::DateTime::parse_from_rfc3339(s) {
return Ok(t);
}
chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
.map_err(|_| anyhow!("invalid date '{s}' — expected YYYY-MM-DD or an RFC 3339 timestamp"))?
.and_hms_opt(0, 0, 0)
.map(|d| chrono::DateTime::from_naive_utc_and_offset(d, chrono::FixedOffset::east_opt(0).unwrap()))
.ok_or_else(|| anyhow!("invalid date '{s}'"))
}
#[derive(Serialize)]
struct LogRow {
ts: String,
kind: String,
agent: Option<String>,
pid: Option<u32>,
exit_code: Option<i32>,
duration_s: Option<u64>,
session: Option<String>,
}
impl From<&Event> for LogRow {
fn from(e: &Event) -> Self {
LogRow {
ts: e.ts.clone(),
kind: e.kind.as_str().to_string(),
agent: e.agent.clone(),
pid: e.pid,
exit_code: e.exit_code,
duration_s: e.duration_s,
session: e.session.clone(),
}
}
}
/// Chars of text after byte offset seen (UTF-8 safe).
pub fn text_after(text: &str, seen: usize) -> String {
text.char_indices()
.filter(|(i, _)| *i >= seen)
.map(|(_, c)| c)
.collect()
}
pub fn run(
app: &App,
agent: Option<&str>,
kinds: Option<&str>,
since: Option<&str>,
limit: usize,
follow: bool,
) -> Result<i32> {
if follow {
return follow_loop(app, agent, kinds, since);
}
let events = filter_events(app, agent, kinds, since, limit)?;
if app.json() {
let rows: Vec<LogRow> = events.iter().map(LogRow::from).collect();
print_json(&rows);
return Ok(0);
}
for e in &events {
let mut parts = vec![e.ts.clone(), e.kind.as_str().to_string()];
if let Some(a) = &e.agent {
parts.push(a.clone());
}
if let Some(p) = e.pid {
parts.push(format!("pid={p}"));
}
if let Some(c) = e.exit_code {
parts.push(format!("exit={c}"));
}
if let Some(d) = e.duration_s {
parts.push(format!("dur={d}s"));
}
println!("{}", parts.join(" "));
}
if events.is_empty() {
app.log.info("no matching events in the journal");
}
Ok(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_since_accepts_date_and_timestamp() {
let d = parse_since("2026-08-01").unwrap();
assert_eq!(d.format("%Y-%m-%d").to_string(), "2026-08-01");
assert!(parse_since("2026-08-01T00:00:00Z").is_ok());
assert!(parse_since("hier").is_err());
}
}
+56
View File
@@ -0,0 +1,56 @@
//! logs: tail the log file of an agent (--follow for a live view).
use super::*;
use crate::output::print_json;
use anyhow::{bail, Result};
use std::path::PathBuf;
/// Resolve the log path of an agent (validates the agent first).
pub fn log_path(app: &App, agent_name: &str) -> Result<PathBuf> {
let agent = require_agent(app, agent_name)?;
Ok(crate::process::agent_log_path(app, &agent.name))
}
pub fn run(app: &App, agent: &str, lines: usize, follow: bool) -> Result<i32> {
let path = log_path(app, agent)?;
if !path.exists() {
bail!(
"no log yet for '{}' — start it with 'am start {} --background'",
agent,
agent
);
}
if app.json() {
let tail = crate::process::last_lines(&path, lines);
print_json(&serde_json::json!({
"agent": agent,
"log": path.display().to_string(),
"tail": tail,
}));
return Ok(0);
}
if !follow {
print!("{}", crate::process::last_lines(&path, lines));
return Ok(0);
}
// Live follow: print the initial tail, then new bytes as they arrive.
let mut seen: usize = std::fs::metadata(&path).map(|m| m.len() as usize).unwrap_or(0);
loop {
if let Ok(text) = std::fs::read_to_string(&path) {
let bytes = text.as_bytes();
if bytes.len() > seen {
// Slicer sans risque de couper un caractere UTF-8.
let tail: String = text
.char_indices()
.filter(|(i, _)| *i >= seen)
.map(|(_, c)| c)
.collect();
print!("{tail}");
use std::io::Write;
let _ = std::io::stdout().flush();
}
seen = bytes.len();
}
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
+70
View File
@@ -0,0 +1,70 @@
//! man: generate roff man pages from the clap definition (issue #41).
//! 'am man' prints am.1 for the whole CLI; 'am man <command>' prints one
//! page; '--output DIR' writes am.1 plus one am-<command>.1 per command.
use super::*;
use anyhow::Context;
use clap::CommandFactory;
use std::io::Write;
use std::path::Path;
pub fn run(app: &App, command: Option<&str>, output: Option<&Path>) -> Result<i32> {
let mut cmd = crate::cli::Cli::command();
if app.dry_run() {
app.log.dry("would generate man pages");
return Ok(0);
}
match command {
Some(name) => {
let sub = cmd.find_subcommand_mut(name).ok_or_else(|| {
anyhow!(
"unknown command '{name}' — 'am help commands' lists every command"
)
})?;
let man = clap_mangen::Man::new(sub.clone()).title(format!("am-{name}"));
match output {
Some(dir) => {
write_page(dir, &format!("am-{name}"), &man)?;
app.log.success(&format!(
"man page written to {}",
dir.join(format!("am-{name}.1")).display()
));
}
None => man.render(&mut std::io::stdout())?,
}
}
None => match output {
Some(dir) => {
let main = clap_mangen::Man::new(cmd.clone());
write_page(dir, "am", &main)?;
for sub in cmd.get_subcommands() {
if sub.is_hide_set() {
continue;
}
let name = format!("am-{}", sub.get_name());
let m = clap_mangen::Man::new(sub.clone()).title(name.clone());
write_page(dir, &name, &m)?;
}
app.log.success(&format!(
"man pages written to {} (am.1 + {} pages)",
dir.display(),
cmd.get_subcommands().count()
));
}
None => clap_mangen::Man::new(cmd).render(&mut std::io::stdout())?,
},
}
Ok(0)
}
fn write_page(dir: &Path, name: &str, man: &clap_mangen::Man) -> Result<()> {
std::fs::create_dir_all(dir)
.with_context(|| format!("cannot create {}", dir.display()))?;
let path = dir.join(format!("{name}.1"));
let mut file = std::fs::File::create(&path)
.with_context(|| format!("cannot create {}", path.display()))?;
man.render(&mut file)
.with_context(|| format!("cannot render {}", path.display()))?;
file.flush().ok();
Ok(())
}
+161 -3
View File
@@ -1,19 +1,38 @@
//! Command implementations and dispatch.
pub mod agents_cmd;
pub mod alias_cmd;
pub mod annotations_cmd;
pub mod completion_cmd;
pub mod config_cmd;
pub mod dashboard_cmd;
pub mod doctor_cmd;
pub mod export_import;
pub mod help_cmd;
pub mod history_cmd;
pub mod info_cmd;
pub mod init_cmd;
pub mod install_cmd;
pub mod list_cmd;
pub mod log_cmd;
pub mod logs_cmd;
pub mod man_cmd;
pub mod open_cmd;
pub mod profile_cmd;
pub mod projects_cmd;
pub mod run_cmd;
pub mod search_cmd;
pub mod secret_cmd;
pub mod self_uninstall;
pub mod sessions_cmd;
pub mod stats_cmd;
pub mod self_update;
pub mod status_cmd;
pub mod timeline_cmd;
pub mod uninstall_cmd;
pub mod update_cmd;
pub mod watch_cmd;
pub mod version_cmd;
use crate::app::App;
use crate::cli::Command;
@@ -33,7 +52,36 @@ pub fn execute(app: &App) -> Result<i32> {
/// Execute one parsed command (also used by the interactive shell).
pub fn execute_command(app: &App, cmd: &Command) -> Result<i32> {
match cmd {
Command::List { all, category } => list_cmd::run(app, *all, category.as_deref()),
Command::List {
all,
category,
running,
sort,
} => list_cmd::run(app, *all, category.as_deref(), *running, sort.as_deref()),
Command::Logs {
agent,
lines,
follow,
} => logs_cmd::run(app, agent, *lines, *follow),
Command::Open { agent } => open_cmd::run(app, agent),
Command::Watch {
agent,
restart,
notify,
interval,
} => watch_cmd::run(app, agent, *restart, *notify, *interval),
Command::Dashboard => dashboard_cmd::run(app),
Command::Favorite { agent } => annotations_cmd::run_favorite(app, agent),
Command::Unfavorite { agent } => annotations_cmd::run_unfavorite(app, agent),
Command::Note { agent, text } => annotations_cmd::run_note(app, agent, text),
Command::Tag { agent, words } => annotations_cmd::run_tag(app, agent, words),
Command::Untag { agent, tag } => annotations_cmd::run_untag(app, agent, tag),
Command::Tags { agent } => annotations_cmd::run_tags(app, agent.as_deref()),
Command::Profile(sub) => profile_cmd::run(app, sub),
Command::Man { command, output } => man_cmd::run(app, command.as_deref(), output.as_deref()),
Command::Agents { installed } => agents_cmd::run(app, *installed),
Command::Alias(sub) => alias_cmd::run(app, sub),
Command::Secret(sub) => secret_cmd::run(app, sub),
Command::Start(a) => run_cmd::start(app, a),
Command::Stop {
agent,
@@ -46,6 +94,41 @@ pub fn execute_command(app: &App, cmd: &Command) -> Result<i32> {
timeout,
} => run_cmd::restart(app, start, *force, *timeout),
Command::Status { agent } => status_cmd::run(app, agent.as_deref()),
Command::Stats { agent, period } => stats_cmd::run(app, agent.as_deref(), period.as_deref()),
Command::Top { period } => stats_cmd::run_top(app, period.as_deref()),
Command::Report {
last_week,
last_month,
output,
} => stats_cmd::run_report(app, *last_week, *last_month, output.as_deref()),
Command::Log {
agent,
kind,
since,
limit,
follow,
} => log_cmd::run(app, agent.as_deref(), kind.as_deref(), since.as_deref(), *limit, *follow),
Command::Projects { name } => projects_cmd::run(app, name.as_deref()),
Command::Timeline {
agent,
project,
since,
limit,
} => timeline_cmd::run(app, agent.as_deref(), project.as_deref(), since.as_deref(), *limit),
Command::Sessions {
agent,
project,
status,
show,
resume,
} => sessions_cmd::run(
app,
agent.as_deref(),
project.as_deref(),
status.as_deref(),
show.as_deref(),
resume.as_deref(),
),
Command::Install {
agent,
method,
@@ -53,12 +136,35 @@ pub fn execute_command(app: &App, cmd: &Command) -> Result<i32> {
} => install_cmd::run(app, agent, method.as_deref(), *force),
Command::Uninstall { agent, purge } => uninstall_cmd::run(app, agent, *purge),
Command::Update { agent, all } => update_cmd::run(app, agent.as_deref(), *all),
Command::Search { keyword, category } => search_cmd::run(app, keyword, category.as_deref()),
Command::Search { keyword, category, tag } => {
search_cmd::run(app, keyword.as_deref(), category.as_deref(), tag.as_deref())
}
Command::History {
kind,
cwd,
search,
failed,
session,
limit,
rerun,
} => history_cmd::run(
app,
kind.as_deref(),
cwd.as_deref(),
search.as_deref(),
*failed,
session.as_deref(),
*limit,
*rerun,
),
Command::Info { agent } => info_cmd::run(app, agent),
Command::Init { force } => init_cmd::run(app, *force),
Command::Help { command, find } => help_cmd::run(app, command.as_deref(), find.as_deref()),
Command::Version => version_cmd::run(app),
Command::Config(sub) => config_cmd::run(app, sub),
Command::Doctor { fix } => doctor_cmd::run(app, *fix),
Command::Run { agent, args } => run_cmd::run(app, agent, args),
Command::Completion { shell } => completion_cmd::run(*shell),
Command::Completion { shell, installed } => completion_cmd::run(*shell, *installed),
Command::SelfUpdate { check, to } => self_update::run(app, *check, to.as_deref()),
Command::SelfUninstall => self_uninstall::run(app),
Command::Export { output } => export_import::export_cmd(app, output),
@@ -115,9 +221,31 @@ pub fn resolve_exec(
args.extend(extra_args.iter().cloned());
let mut env = agent.env.clone();
env.extend(extra_env.clone());
let warnings =
crate::secrets::resolve_env_secrets(&crate::secrets::store(), &agent.name, &mut env);
for w in warnings {
app.log.warn(&w);
}
let (program, args) = route_cmd_script(program, args);
Ok(ResolvedExec { program, args, env })
}
/// On Windows, .cmd/.bat shims (git installs without a build step) must be
/// executed through cmd.exe — spawning them directly fails.
pub fn route_cmd_script(program: String, args: Vec<String>) -> (String, Vec<String>) {
let lower = program.to_lowercase();
if cfg!(windows) && (lower.ends_with(".cmd") || lower.ends_with(".bat")) {
let shell = std::env::var_os("COMSPEC")
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "cmd.exe".to_string());
let mut full = vec!["/C".to_string(), program];
full.extend(args);
(shell, full)
} else {
(program, args)
}
}
/// Parse --env KEY=VALUE entries.
pub fn parse_env_list(items: &[String]) -> Result<BTreeMap<String, String>> {
let mut map = BTreeMap::new();
@@ -213,3 +341,33 @@ pub fn select_method<'a>(
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn routes_cmd_scripts_through_comspec() {
let (program, args) = route_cmd_script(
"C:/x/agent.cmd".to_string(),
vec!["--flag".to_string()],
);
let lower = program.to_lowercase();
if cfg!(windows) {
assert!(lower.ends_with("cmd.exe"), "{program}");
assert_eq!(args[0], "/C");
assert_eq!(args[1], "C:/x/agent.cmd");
assert_eq!(args[2], "--flag");
} else {
assert_eq!(program, "C:/x/agent.cmd");
assert_eq!(args, vec!["--flag".to_string()]);
}
}
#[test]
fn leaves_regular_programs_untouched() {
let (program, args) = route_cmd_script("node".to_string(), vec!["-v".to_string()]);
assert_eq!(program, "node");
assert_eq!(args, vec!["-v".to_string()]);
}
}
+42
View File
@@ -0,0 +1,42 @@
//! open: open an agent's installation directory in the file manager.
use super::*;
use anyhow::{bail, Context, Result};
use std::path::PathBuf;
/// Resolve the directory to open: the managed install dir, or the parent of
/// the detected external binary.
pub fn resolve_target(app: &App, agent_name: &str) -> Result<PathBuf> {
let agent = require_agent(app, agent_name)?;
if let Some(entry) = app.state.get(&agent.name).ok().flatten() {
return Ok(PathBuf::from(&entry.install_dir));
}
let probe = crate::probe::detect_externals(app);
if let Some(bin) = probe.path_of(&agent.name) {
return Ok(bin
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| bin.clone()));
}
bail!("agent '{}' is not installed — run 'am install {}' first", agent_name, agent_name);
}
pub fn run(app: &App, agent: &str) -> Result<i32> {
let target = resolve_target(app, agent)?;
if app.dry_run() {
app.log.dry(format!("would open {}", target.display()));
return Ok(0);
}
if !target.exists() {
bail!("directory {} does not exist", target.display());
}
#[cfg(windows)]
let opened = std::process::Command::new("explorer").arg(&target).status();
#[cfg(target_os = "macos")]
let opened = std::process::Command::new("open").arg(&target).status();
#[cfg(all(unix, not(target_os = "macos")))]
let opened = std::process::Command::new("xdg-open").arg(&target).status();
opened
.with_context(|| format!("cannot open {}", target.display()))?;
Ok(0)
}
+214
View File
@@ -0,0 +1,214 @@
//! profile: environment profiles (issue #40) — a named set of
//! agent + env + args, defined in the config, applied with
//! 'am start <agent> --profile dev'.
use super::*;
use crate::cli::ProfileCmd;
use crate::config::Profile;
use crate::output::{boxed_table, print_json};
use serde::Serialize;
#[derive(Serialize)]
struct ProfileJson {
name: String,
agent: Option<String>,
env: BTreeMap<String, String>,
args: Vec<String>,
}
pub fn run(app: &App, cmd: &ProfileCmd) -> Result<i32> {
match cmd {
ProfileCmd::List => run_list(app),
ProfileCmd::Show { name } => run_show(app, name),
}
}
pub fn run_list(app: &App) -> Result<i32> {
if app.config.profiles.is_empty() {
app.log.info(
"no profiles defined — add a 'profiles:' section to your config, e.g.:\n\
profiles:\n\
\x20 dev:\n\
\x20 agent: claude-code\n\
\x20 env: {DEBUG: \"1\"}\n\
\x20 args: [--model, sonnet]",
);
return Ok(0);
}
let rows: Vec<ProfileJson> = app
.config
.profiles
.iter()
.map(|(name, p)| ProfileJson {
name: name.clone(),
agent: p.agent.clone(),
env: p.env.clone(),
args: p.args.clone(),
})
.collect();
if app.json() {
print_json(&rows);
return Ok(0);
}
let mut table_rows = Vec::new();
for r in &rows {
table_rows.push(vec![
r.name.clone(),
r.agent.clone().unwrap_or_else(|| "—".to_string()),
if r.env.is_empty() {
"—".to_string()
} else {
r.env
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join(" ")
},
if r.args.is_empty() {
"—".to_string()
} else {
r.args.join(" ")
},
]);
}
print!(
"{}",
boxed_table(
vec!["PROFILE", "AGENT", "ENV", "ARGS"],
table_rows,
app.theme(),
app.color(),
)
);
Ok(0)
}
pub fn run_show(app: &App, name: &str) -> Result<i32> {
let profile = resolve(app, name)?;
if app.json() {
print_json(&ProfileJson {
name: name.to_string(),
agent: profile.agent.clone(),
env: profile.env.clone(),
args: profile.args.clone(),
});
return Ok(0);
}
println!("{}", name);
if let Some(a) = &profile.agent {
println!(" agent {a}");
}
if profile.env.is_empty() {
println!(" env —");
} else {
for (k, v) in &profile.env {
println!(" env {k}={v}");
}
}
if profile.args.is_empty() {
println!(" args —");
} else {
println!(" args {}", profile.args.join(" "));
}
Ok(0)
}
/// Resolve a profile by name; unknown names fail with the list of profiles.
pub fn resolve<'a>(app: &'a App, name: &str) -> Result<&'a Profile> {
app.config.profiles.get(name).ok_or_else(|| {
let available = if app.config.profiles.is_empty() {
"no profile is defined in the config".to_string()
} else {
format!(
"available profiles: {}",
app.config.profiles.keys().cloned().collect::<Vec<_>>().join(", ")
)
};
anyhow!("unknown profile '{name}' — {available}")
})
}
/// Apply a profile to launch parameters: its args are appended and its env
/// entries override the base ones (the profile wins — it is the most
/// specific input at launch time).
pub fn apply(
app: &App,
name: Option<&str>,
base_args: &[String],
base_env: &BTreeMap<String, String>,
) -> Result<(Vec<String>, BTreeMap<String, String>)> {
let Some(name) = name else {
return Ok((base_args.to_vec(), base_env.clone()));
};
let profile = resolve(app, name)?;
let mut args = base_args.to_vec();
args.extend(profile.args.iter().cloned());
let mut env = base_env.clone();
env.extend(profile.env.clone());
Ok((args, env))
}
/// The default agent declared by a profile, if any.
pub fn default_agent<'a>(app: &'a App, name: Option<&str>) -> Result<Option<&'a str>> {
match name {
None => Ok(None),
Some(n) => Ok(resolve(app, n)?.agent.as_deref()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
fn cfg_with_profiles() -> App {
// The TempDir guard would delete the directory when this helper
// returns; leak it on purpose so the App keeps a live state dir.
let guard = tempfile::tempdir().unwrap();
let dir = guard.path().to_path_buf();
std::mem::forget(guard);
let cfg = dir.join("config.yaml");
std::fs::write(
&cfg,
"version: \"1.0\"\nsettings:\n auto_install_deps: false\n confirm_before_run: false\nprofiles:\n dev:\n agent: claude-code\n env:\n DEBUG: \"1\"\n LOG_LEVEL: debug\n args: [--model, sonnet]\n prod:\n env:\n LOG_LEVEL: info\nagents: []\n",
)
.unwrap();
let cli = crate::cli::Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
let mut app = App::from_cli(cli).unwrap();
let mut p = app.paths.clone();
p.install_dir = dir.join("agents");
p.bin_dir = dir.join("agents").join("bin");
p.log_dir = dir.join("logs");
p.state_file = dir.join("state.json");
p.probe_cache_file = dir.join("probe-cache.json");
p.config_dir = Some(dir.join("config"));
app.paths = p;
app
}
#[test]
fn apply_merges_args_and_env() {
let app = cfg_with_profiles();
let (args, env) = apply(&app, Some("dev"), &["--base".to_string()], &BTreeMap::from([("KEEP".into(), "1".into())]))
.unwrap();
assert_eq!(args, vec!["--base", "--model", "sonnet"]);
assert_eq!(env.get("DEBUG").map(String::as_str), Some("1"));
assert_eq!(env.get("KEEP").map(String::as_str), Some("1"));
}
#[test]
fn unknown_profile_lists_available() {
let app = cfg_with_profiles();
let err = resolve(&app, "staging").unwrap_err().to_string();
assert!(err.contains("unknown profile 'staging'"), "{err}");
assert!(err.contains("dev") && err.contains("prod"), "{err}");
}
#[test]
fn none_profile_is_noop() {
let app = cfg_with_profiles();
let (args, env) = apply(&app, None, &["x".to_string()], &BTreeMap::new()).unwrap();
assert_eq!(args, vec!["x"]);
assert!(env.is_empty());
}
}
+92
View File
@@ -0,0 +1,92 @@
//! projects: which agent works on which project.
use super::*;
use crate::output::{print_json, Table};
use crate::projects::{self, ProjectSummary};
pub fn run(app: &App, name: Option<&str>) -> Result<i32> {
let summaries = projects::aggregate(app);
if let Some(n) = name {
return show_one(app, n, &summaries);
}
if app.json() {
print_json(&summaries);
return Ok(0);
}
let mut table = Table::new(vec!["PROJECT", "AGENTS", "SESSIONS", "DURATION", "LAST"]);
for s in &summaries {
table.row(vec![
short_path(&s.root),
s.dominant
.clone()
.unwrap_or_else(|| "—".to_string()),
s.sessions.to_string(),
format_duration(s.total_duration_s),
s.last_activity
.as_deref()
.map(|t| t[..t.len().min(10)].to_string())
.unwrap_or_else(|| "—".to_string()),
]);
}
print!("{}", table.render());
if summaries.is_empty() {
app.log.info("no project activity recorded yet — run or start an agent inside a git repository");
}
Ok(0)
}
fn show_one(app: &App, name: &str, summaries: &[ProjectSummary]) -> Result<i32> {
let s = summaries
.iter()
.find(|s| {
s.root == name
|| s.root.ends_with(&format!("/{name}"))
|| s.root.ends_with(&format!("\\{name}"))
})
.ok_or_else(|| anyhow!("unknown project '{name}' — 'am projects' lists them"))?;
if app.json() {
print_json(s);
return Ok(0);
}
println!("project {}", s.root);
println!(" agents: {}", s.agents.join(", "));
println!(
" dominant: {}",
s.dominant.as_deref().unwrap_or("—")
);
println!(" sessions: {}", s.sessions);
println!(" launches: {}", s.launches);
println!(" duration: {}", format_duration(s.total_duration_s));
println!(
" last: {}",
s.last_activity.as_deref().unwrap_or("—")
);
Ok(0)
}
/// Keep the tail of a long path.
fn short_path(p: &str) -> String {
let s = p.trim_end_matches(['/', '\\']);
let name = s.rsplit(['/', '\\']).next().unwrap_or(s);
let mut out = format!("…/{name}");
let full = p.chars().count();
let shown = out.chars().count();
if shown >= full || full <= 30 {
return p.to_string();
}
out.truncate(30);
out
}
fn format_duration(secs: u64) -> String {
if secs == 0 {
return "—".to_string();
}
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m{:02}s", secs / 60, secs % 60)
} else {
format!("{}h{:02}m", secs / 3600, (secs % 3600) / 60)
}
}
+181 -18
View File
@@ -2,28 +2,85 @@
use super::*;
use crate::cli::StartArgs;
use crate::events::{self, Event, EventKind};
use crate::process;
use crate::sessions;
use anyhow::{bail, Context, Result};
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
/// Resolve the start target: the explicit agent/alias/group when given,
/// otherwise the default agent of the project containing cwd (issue #30).
pub fn resolve_start_target(app: &App, cwd: &std::path::Path, agent: Option<&str>) -> Result<String> {
if let Some(a) = agent.filter(|s| !s.is_empty()) {
return Ok(a.to_string());
}
crate::projects::default_agent_for_dir(app, cwd).ok_or_else(|| {
anyhow!(
"no agent given — set default_agent in this project's profile ('am init' does it) or pass an agent name"
)
})
}
pub fn start(app: &App, opts: &StartArgs) -> Result<i32> {
start_with_cwd(app, opts, None)
}
/// Apply the environment profile (issue #40): the explicit --profile wins;
/// otherwise the current project's default profile applies when launching
/// the project's default agent.
fn apply_profile(
app: &App,
explicit: Option<&str>,
project_default_agent: bool,
extra_args: &[String],
extra_env: &BTreeMap<String, String>,
) -> Result<(Vec<String>, BTreeMap<String, String>)> {
let mut profile = explicit;
if profile.is_none() && project_default_agent {
let current = std::env::current_dir().unwrap_or_default();
if let Some((_, pp)) = crate::projects::profile_for(app, &current) {
profile = pp.profile.as_deref();
}
}
crate::commands::profile_cmd::apply(app, profile, extra_args, extra_env)
}
/// Start with an explicit working directory (used by sessions resume).
pub fn start_with_cwd(app: &App, opts: &StartArgs, cwd: Option<&std::path::Path>) -> Result<i32> {
let current = std::env::current_dir().unwrap_or_default();
let explicit = opts.agent.as_deref().filter(|s| !s.is_empty());
// A profile may declare its own default agent (issue #40).
let profile_default = profile_cmd::default_agent(app, opts.profile.as_deref())?;
let target = match (explicit, profile_default) {
(Some(a), _) => a.to_string(),
(None, Some(a)) => a.to_string(),
(None, None) => resolve_start_target(app, &current, None)?,
};
let extra_env = parse_env_list(&opts.env)?;
let extra_args = parse_extra_args(&opts.args);
if let Some(group) = crate::catalog::Catalog::parse_group_selector(&opts.agent) {
let project_default_agent = explicit.is_none() && profile_default.is_none();
let (extra_args, extra_env) = apply_profile(
app,
opts.profile.as_deref(),
project_default_agent,
&extra_args,
&extra_env,
)?;
if let Some(group) = crate::catalog::Catalog::parse_group_selector(&target) {
let members = app.catalog.group_members(group);
if members.is_empty() {
bail!("unknown group '{group}'");
}
for m in members {
start_one(app, m, &extra_args, &extra_env, opts.notify, true)?;
start_one(app, m, &extra_args, &extra_env, opts.notify, true, cwd)?;
}
return Ok(0);
}
let agent = require_agent(app, &opts.agent)?;
start_one(app, agent, &extra_args, &extra_env, opts.notify, opts.background)
let agent = require_agent(app, &target)?;
start_one(app, agent, &extra_args, &extra_env, opts.notify, opts.background, cwd)
}
fn start_one(
@@ -33,12 +90,16 @@ fn start_one(
extra_env: &BTreeMap<String, String>,
notify: bool,
background: bool,
cwd: Option<&std::path::Path>,
) -> Result<i32> {
let exec = resolve_exec(app, agent, extra_args, extra_env)?;
let bin = PathBuf::from(&exec.program);
let is_managed = app.state.get(&agent.name).ok().flatten().is_some();
let ctx = crate::context::detect(&std::env::current_dir().unwrap_or_default());
let current = std::env::current_dir().unwrap_or_default();
crate::hooks::run_hooks(app, "on_start", cwd.unwrap_or(&current));
if background {
let pid = process::spawn_background(app, &agent.name, &bin, &exec.args, &exec.env)?;
let pid = process::spawn_background(app, &agent.name, &bin, &exec.args, &exec.env, cwd)?;
if app.dry_run() {
return Ok(0);
}
@@ -51,7 +112,30 @@ fn start_one(
agent.name
));
}
app.emit(
&Event::now(EventKind::Start)
.with_agent(agent.name.clone())
.with_pid(pid)
.with_session(crate::repl::session_id())
.with_cwd(cwd_string())
.with_project(ctx.root.clone())
.with_args(exec.args.clone())
.with_env_keys(events::env_keys(&exec.env)),
);
if is_managed {
let _ = app.state.touch(&agent.name);
}
let log = process::agent_log_path(app, &agent.name);
let _ = sessions::start_agent(
app,
&agent.name,
pid,
cwd_string(),
ctx.root.clone(),
exec.args.clone(),
events::env_keys(&exec.env),
Some(log.display().to_string()),
);
app.log.success(&format!(
"{} started (pid {pid}); log: {}",
agent.title(),
@@ -70,15 +154,31 @@ fn start_one(
));
return Ok(0);
}
let started = std::time::Instant::now();
let (prog, prefix) = crate::runner::resolve_program(&exec.program);
let mut full_args = prefix;
full_args.extend(exec.args.iter().cloned());
let status = Command::new(&prog)
.args(&full_args)
.envs(&exec.env)
let mut cmd = Command::new(&prog);
cmd.args(&full_args).envs(&exec.env);
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
let status = cmd
.status()
.with_context(|| format!("failed to run {}", exec.program))?;
Ok(status.code().unwrap_or(1))
let code = status.code().unwrap_or(1);
app.emit(
&Event::now(EventKind::Run)
.with_agent(agent.name.clone())
.with_cwd(cwd_string())
.with_project(ctx.root.clone())
.with_args(exec.args.clone())
.with_env_keys(events::env_keys(&exec.env))
.with_exit_code(code)
.with_duration(started.elapsed().as_secs()),
);
let _ = app.state.touch(&agent.name);
Ok(code)
}
}
@@ -123,12 +223,34 @@ fn stop_one(app: &App, agent: &AgentDef, force: bool, timeout: u64) -> Result<i3
"stale PID {pid} for '{}' (process no longer running); cleaning up",
agent.name
));
app.emit(
&Event::now(EventKind::Stop)
.with_agent(agent.name.clone())
.with_pid(pid)
.with_reason("stale"),
);
let _ = sessions::finish_agent(app, &agent.name, Some(pid), 0, true);
app.state.update_pid(&agent.name, None, None)?;
let current = std::env::current_dir().unwrap_or_default();
crate::hooks::run_hooks(app, "on_stop", &current);
return Ok(0);
}
app.log.info(&format!("stopping '{}' (pid {pid})", agent.name));
let stopped = process::stop_pid(pid, force, timeout);
let duration = seconds_since(entry.started_at.as_deref());
let mut ev = Event::now(EventKind::Stop)
.with_agent(agent.name.clone())
.with_pid(pid)
.with_reason(if force { "force" } else { "signal" })
.with_exit_code(if stopped { 0 } else { 1 });
if let Some(d) = duration {
ev = ev.with_duration(d);
}
app.emit(&ev);
let _ = sessions::finish_agent(app, &agent.name, Some(pid), if stopped { 0 } else { 1 }, stopped);
app.state.update_pid(&agent.name, None, None)?;
let current = std::env::current_dir().unwrap_or_default();
crate::hooks::run_hooks(app, "on_stop", &current);
if stopped {
app.log.success(&format!("{} stopped", agent.title()));
Ok(0)
@@ -140,7 +262,25 @@ fn stop_one(app: &App, agent: &AgentDef, force: bool, timeout: u64) -> Result<i3
pub fn restart(app: &App, opts: &StartArgs, force: bool, timeout: Option<u64>) -> Result<i32> {
let timeout = timeout.unwrap_or_else(|| app.config.settings.stop_timeout());
if let Some(group) = crate::catalog::Catalog::parse_group_selector(&opts.agent) {
let cwd = std::env::current_dir().unwrap_or_default();
let explicit = opts.agent.as_deref().filter(|s| !s.is_empty());
let profile_default = profile_cmd::default_agent(app, opts.profile.as_deref())?;
let target = match (explicit, profile_default) {
(Some(a), _) => a.to_string(),
(None, Some(a)) => a.to_string(),
(None, None) => resolve_start_target(app, &cwd, None)?,
};
let extra_env = parse_env_list(&opts.env)?;
let extra_args = parse_extra_args(&opts.args);
let project_default_agent = explicit.is_none() && profile_default.is_none();
let (extra_args, extra_env) = apply_profile(
app,
opts.profile.as_deref(),
project_default_agent,
&extra_args,
&extra_env,
)?;
if let Some(group) = crate::catalog::Catalog::parse_group_selector(&target) {
let members = app.catalog.group_members(group);
if members.is_empty() {
bail!("unknown group '{group}'");
@@ -148,18 +288,14 @@ pub fn restart(app: &App, opts: &StartArgs, force: bool, timeout: Option<u64>) -
for m in members.iter().rev() {
stop_one(app, m, force, timeout)?;
}
let extra_env = parse_env_list(&opts.env)?;
let extra_args = parse_extra_args(&opts.args);
for m in members {
start_one(app, m, &extra_args, &extra_env, opts.notify, true)?;
start_one(app, m, &extra_args, &extra_env, opts.notify, true, None)?;
}
return Ok(0);
}
let agent = require_agent(app, &opts.agent)?;
let agent = require_agent(app, &target)?;
stop_one(app, agent, force, timeout)?;
let extra_env = parse_env_list(&opts.env)?;
let extra_args = parse_extra_args(&opts.args);
start_one(app, agent, &extra_args, &extra_env, opts.notify, opts.background)
start_one(app, agent, &extra_args, &extra_env, opts.notify, opts.background, None)
}
/// run: execute the agent command directly, no process management.
@@ -175,6 +311,7 @@ pub fn run(app: &App, target: &str, extra: &[OsString]) -> Result<i32> {
));
return Ok(0);
}
let started = std::time::Instant::now();
let (prog, prefix) = crate::runner::resolve_program(&exec.program);
let mut full_args = prefix;
full_args.extend(exec.args.iter().cloned());
@@ -183,5 +320,31 @@ pub fn run(app: &App, target: &str, extra: &[OsString]) -> Result<i32> {
.envs(&exec.env)
.status()
.with_context(|| format!("failed to run {}", exec.program))?;
Ok(status.code().unwrap_or(1))
let code = status.code().unwrap_or(1);
app.emit(
&Event::now(EventKind::Run)
.with_agent(agent.name.clone())
.with_cwd(cwd_string())
.with_args(exec.args.clone())
.with_env_keys(events::env_keys(&exec.env))
.with_exit_code(code)
.with_duration(started.elapsed().as_secs()),
);
let _ = app.state.touch(&agent.name);
Ok(code)
}
/// Current directory as a string (None when unavailable).
fn cwd_string() -> Option<String> {
std::env::current_dir().ok().map(|p| p.display().to_string())
}
/// Seconds elapsed since an RFC 3339 timestamp (None when unparsable).
fn seconds_since(ts: Option<&str>) -> Option<u64> {
let t = chrono::DateTime::parse_from_rfc3339(ts?).ok()?;
let now = chrono::Utc::now();
let secs = now
.signed_duration_since(t.with_timezone(&chrono::Utc))
.num_seconds();
Some(secs.max(0) as u64)
}
+77 -19
View File
@@ -1,7 +1,7 @@
//! search: keyword search over the catalog.
use super::*;
use crate::output::{print_json, Table};
use crate::output::{boxed_table, print_json};
use serde::Serialize;
#[derive(Serialize)]
@@ -13,40 +13,98 @@ struct SearchRow {
description: Option<String>,
website: Option<String>,
tags: Vec<String>,
/// Personal tags (issue #39).
personal_tags: Vec<String>,
favorite: bool,
}
pub fn run(app: &App, keyword: &str, category: Option<&str>) -> Result<i32> {
let hits = app.catalog.search(keyword, category);
pub fn run(app: &App, keyword: Option<&str>, category: Option<&str>, tag: Option<&str>) -> Result<i32> {
let keyword = keyword.unwrap_or("");
// An empty keyword (search --tag only) starts from the whole catalog.
let mut hits: Vec<&AgentDef> = if keyword.is_empty() {
app.catalog
.agents()
.iter()
.filter(|a| !a.hidden)
.filter(|a| category.map_or(true, |c| a.category.as_deref() == Some(c)))
.collect()
} else {
app.catalog.search(keyword, category)
};
// Personal tag filter (issue #39): keep agents carrying this tag.
if let Some(t) = tag {
let tagged = crate::commands::annotations_cmd::agents_with_tag(app, t);
if tagged.is_empty() {
app.log.info(&format!(
"no agent is tagged '{t}' — 'am tags' lists every personal tag"
));
return Ok(0);
}
hits.retain(|a| tagged.contains(&a.name));
}
if hits.is_empty() && tag.is_some() {
app.log.info(&format!(
"no agent matches the keyword and the tag '{}'",
tag.unwrap_or("")
));
return Ok(0);
}
if app.json() {
let rows: Vec<SearchRow> = hits
.iter()
.map(|a| SearchRow {
name: a.name.clone(),
display_name: a.title().to_string(),
category: a.category.clone(),
installable: a.installable,
description: a.description.clone(),
website: a.website.clone(),
tags: a.tags.clone(),
.map(|a| {
let personal = crate::commands::annotations_cmd::tags_of(app, &a.name);
SearchRow {
name: a.name.clone(),
display_name: a.title().to_string(),
category: a.category.clone(),
installable: a.installable,
description: a.description.clone(),
website: a.website.clone(),
tags: a.tags.clone(),
personal_tags: personal,
favorite: crate::commands::annotations_cmd::is_favorite(app, &a.name),
}
})
.collect();
print_json(&rows);
return Ok(0);
}
let mut table = Table::new(vec!["NAME", "CATEGORY", "INSTALLABLE", "DESCRIPTION"]);
for a in hits {
let mut table_rows = Vec::new();
for a in &hits {
let desc = truncate(a.description.as_deref().unwrap_or(""), 56);
table.row(vec![
a.title().to_string(),
let star = if crate::commands::annotations_cmd::is_favorite(app, &a.name) {
"★ "
} else {
""
};
table_rows.push(vec![
format!("{star}{}", a.title()),
a.category.clone().unwrap_or_else(|| "—".to_string()),
if a.installable { "yes" } else { "no" }.to_string(),
desc,
]);
}
print!("{}", table.render());
if app.catalog.search(keyword, category).is_empty() {
app.log
.info(&format!("no agent matches '{keyword}' (categories: {})", app.catalog.categories().join(", ")));
print!(
"{}",
boxed_table(
vec!["NAME", "CATEGORY", "INSTALLABLE", "DESCRIPTION"],
table_rows,
app.theme(),
app.color(),
)
);
if hits.is_empty() && !keyword.is_empty() {
let suggestions = app.catalog.suggest(keyword);
if suggestions.is_empty() {
app.log
.info(&format!("no agent matches '{keyword}' (categories: {})", app.catalog.categories().join(", ")));
} else {
app.log.info(&format!(
"no agent matches '{keyword}' — vouliez-vous dire : {} ?",
suggestions.join(", ")
));
}
}
Ok(0)
}
+55
View File
@@ -0,0 +1,55 @@
//! secret: manage secrets in the OS keyring (values never shown or logged).
use super::*;
use crate::cli::SecretCmd;
use crate::output::print_json;
use crate::secrets::{self, SecretStore};
pub fn run(app: &App, sub: &SecretCmd) -> Result<i32> {
match sub {
SecretCmd::Set { name, agent, value } => {
if app.dry_run() {
app.log.dry(format!("would store secret {name} for {agent} in the OS keyring"));
return Ok(0);
}
let key = secrets::key_for(agent, name);
secrets::store().set(&key, value)?;
app.log.success(&format!("secret {name} stored for {agent}"));
app.log.info("use it with --env NAME=@secret (injected at start/run)");
Ok(0)
}
SecretCmd::Unset { name, agent } => {
let key = secrets::key_for(agent, name);
if app.dry_run() {
app.log.dry(format!("would remove secret {key}"));
return Ok(0);
}
let removed = secrets::store().remove(&key)?;
if removed {
app.log.success(&format!("secret {name} removed for {agent}"));
} else {
app.log.info(&format!("secret {name} does not exist for {agent}"));
}
Ok(0)
}
SecretCmd::List => {
let all = secrets::store().list().unwrap_or_default();
if app.json() {
print_json(&all);
return Ok(0);
}
if all.is_empty() {
app.log.info("no secrets stored — see: am secret set NAME --agent AGENT --value ...");
return Ok(0);
}
let mut table = crate::output::Table::new(vec!["SECRET"]);
for k in all {
if k != "__index__" {
table.row(vec![k]);
}
}
print!("{}", table.render());
Ok(0)
}
}
}
+123 -48
View File
@@ -3,7 +3,9 @@
//! configuration directories are deleted, then the executable itself.
use crate::app::{self, App};
use anyhow::{bail, Context, Result};
use anyhow::{bail, Result};
#[cfg(unix)]
use anyhow::Context;
use colored::Colorize;
use std::path::{Path, PathBuf};
@@ -54,6 +56,9 @@ pub fn run(app: &App) -> Result<i32> {
}
}
let exe = std::env::current_exe().ok();
let mut renamed: Option<PathBuf> = None;
if roots.is_empty() {
app.log
.info("nothing to remove — agent-manager left no traces on this machine");
@@ -70,6 +75,20 @@ pub fn run(app: &App) -> Result<i32> {
app.log.dry("nothing removed (dry run)");
return Ok(0);
}
// Windows allows renaming a running executable (but not deleting
// it): do it up-front so the original binary disappears immediately,
// even if the deferred deletion has to wait for this process to exit.
renamed = exe.as_deref().and_then(rename_for_deletion);
if let Some(new) = &renamed {
if let Some(old) = &exe {
println!(
"{} {} -> {}",
"renamed:".cyan(),
old.display(),
new.display()
);
}
}
// Remove everything first; any message logged *after* deleting the
// state directory would recreate the log folder, so the removal
// itself is silent and the report below goes to plain stdout.
@@ -88,38 +107,31 @@ pub fn run(app: &App) -> Result<i32> {
}
if !locked.is_empty() {
let scheduled = schedule_locked(&locked);
if scheduled.len() == locked.len() {
if !scheduled.is_empty() {
println!(
"{} {} entrée(s) verrouillée(s) — suppression planifiée au prochain redémarrage",
"locked:".yellow(),
locked.len()
);
} else {
println!(
"{} {} entrée(s) verrouillée(s), {} planifiée(s) au prochain redémarrage",
"locked:".yellow(),
locked.len(),
scheduled.len()
);
for path in &locked {
if !scheduled.contains(path) {
eprintln!(
"{} {} (supprimez-le manuellement ou relancez en administrateur)",
"kept:".red(),
path.display()
);
}
}
}
if scheduled.len() != locked.len() {
println!(
"{} {} entrée(s) verrouillée(s) — le processus différé les supprimera après la fermeture de am",
"deferred:".cyan(),
locked.len() - scheduled.len()
);
}
}
}
// 3. Delete the executable itself, then sweep the directories left
// empty behind it.
let exe = std::env::current_exe()
.with_context(|| "cannot locate the agent-manager executable")?;
let cleanup_roots: Vec<PathBuf> = roots.iter().map(|(_, p)| p.clone()).collect();
remove_self(&exe, &cleanup_roots)?;
match &exe {
Some(e) => remove_self(e, renamed.as_deref(), &cleanup_roots)?,
None => app.log
.warn("cannot locate the agent-manager executable — delete it manually"),
}
Ok(0)
}
@@ -201,7 +213,7 @@ fn schedule_locked(_locked: &[PathBuf]) -> Vec<PathBuf> {
/// runs; on Windows a hidden cmd re-tries the deletion right after this
/// process exits (the file is locked while we run). Empty directories left
/// behind under the removed roots are swept afterwards.
fn remove_self(exe: &Path, roots: &[PathBuf]) -> Result<()> {
fn remove_self(exe: &Path, renamed: Option<&Path>, roots: &[PathBuf]) -> Result<()> {
#[cfg(unix)]
{
std::fs::remove_file(exe)
@@ -227,29 +239,27 @@ fn remove_self(exe: &Path, roots: &[PathBuf]) -> Result<()> {
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
// Immediate deletion: a hidden cmd retries for a few seconds, because
// the executable is locked while we are still running.
// Once the file is gone, the now-empty directories (parent, then the
// removed roots) are swept — rd /q only ever removes empty dirs.
let mut script = format!(
"for /L %i in (1,1,20) do @if exist \"{0}\" (del /f /q \"{0}\" >nul 2>&1 & ping -n 2 127.0.0.1 >nul)",
exe.display()
);
if let Some(parent) = exe.parent() {
if roots.iter().any(|r| parent.starts_with(r)) {
script.push_str(&format!(" & rd /q \"{}\" >nul 2>&1", parent.display()));
}
// The file to delete once this process exits: the renamed copy when
// the up-front rename worked, the original path otherwise.
let target = renamed.unwrap_or(exe);
// A temporary batch file retries the deletion for a few seconds (the
// image stays locked until we exit), sweeps the directories left
// empty, then deletes itself. A .bat avoids every cmd /C command-line
// quoting pitfall.
let bat = write_cleanup_bat(exe, target, roots);
if let Some(b) = &bat {
// raw_arg passes the command line verbatim: regular .arg()
// backslash-escapes embedded quotes, which cmd.exe does not
// understand.
use std::os::windows::process::CommandExt as _;
let mut cleanup = std::process::Command::new("cmd");
cleanup.raw_arg("/C");
cleanup.raw_arg(format!("\"{}\"", b.display()));
let _ = cleanup.spawn();
}
for root in roots {
script.push_str(&format!(" & rd /q \"{}\" >nul 2>&1", root.display()));
}
let spawned = std::process::Command::new("cmd")
.args(["/C", &script])
.spawn()
.is_ok();
// Belt and braces: if something keeps the file locked, the standard
// uninstaller mechanism deletes it at the next reboot.
let wide: Vec<u16> = exe
let wide: Vec<u16> = target
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
@@ -257,17 +267,25 @@ fn remove_self(exe: &Path, roots: &[PathBuf]) -> Result<()> {
let at_reboot = unsafe {
MoveFileExW(wide.as_ptr(), std::ptr::null(), MOVEFILE_DELAY_UNTIL_REBOOT) != 0
};
if !spawned && !at_reboot {
if bat.is_none() && !at_reboot {
bail!(
"cannot schedule deletion of {} — delete it manually",
exe.display()
);
}
println!(
"{} {}",
"agent-manager removed; the executable will be deleted momentarily:".green(),
exe.display()
);
if renamed.is_some() {
println!(
"{} {}",
"agent-manager removed; the executable was renamed and will be deleted momentarily:".green(),
exe.display()
);
} else {
println!(
"{} {}",
"agent-manager removed; the executable will be deleted momentarily:".green(),
exe.display()
);
}
return Ok(());
}
#[cfg(not(any(unix, windows)))]
@@ -279,6 +297,63 @@ fn remove_self(exe: &Path, roots: &[PathBuf]) -> Result<()> {
}
}
/// Rename the running executable out of the way (Windows allows renaming a
/// running image — only deletion is refused while it executes). Returns the
/// new path on success.
#[cfg(windows)]
fn rename_for_deletion(exe: &Path) -> Option<PathBuf> {
let name = exe.file_name()?.to_string_lossy();
let new = exe.with_file_name(format!("{name}.deleteme"));
std::fs::rename(exe, &new).ok()?;
Some(new)
}
#[cfg(not(windows))]
fn rename_for_deletion(_exe: &Path) -> Option<PathBuf> {
None
}
/// Write a temporary batch file that deletes the target executable once it
/// is unlocked, sweeps the directories left empty and then removes itself.
#[cfg(windows)]
fn write_cleanup_bat(exe: &Path, target: &Path, roots: &[PathBuf]) -> Option<PathBuf> {
let crlf = "\r\n";
let q = "\"";
let mut script = String::new();
let mut line = |s: &str| {
script.push_str(s);
script.push_str(crlf);
};
line("@echo off");
line("set n=0");
line(":retry");
line(&format!("del /f /q {q}{}{q} >nul 2>&1", target.display()));
line(&format!("del /f /q {q}{}{q} >nul 2>&1", exe.display()));
line(&format!(
"if not exist {q}{}{q} (if not exist {q}{}{q} goto sweep)",
target.display(),
exe.display()
));
line("set /a n+=1");
line("if %n% GEQ 60 goto end");
line("ping -n 2 127.0.0.1 >nul");
line("goto retry");
line(":sweep");
if let Some(parent) = exe.parent() {
if roots.iter().any(|r| parent.starts_with(r)) {
line(&format!("rd /q {q}{}{q} >nul 2>&1", parent.display()));
}
}
for root in roots {
line(&format!("rd /q {q}{}{q} >nul 2>&1", root.display()));
}
line(":end");
line(&format!("del {q}%~f0{q} >nul 2>&1"));
let path = std::env::temp_dir().join(format!("am-uninstall-{}.bat", std::process::id()));
std::fs::write(&path, script).ok()?;
Some(path)
}
#[cfg(test)]
mod tests {
use super::*;
+4 -1
View File
@@ -13,7 +13,10 @@ pub fn run(app: &App, check: bool, to: Option<&Path>) -> Result<i32> {
);
};
let current = env!("CARGO_PKG_VERSION");
let release = download::latest_release(&repo)?;
let release = download::latest_release_with_base(
&repo,
app.config.settings.self_update_base_url.as_deref(),
)?;
let tag = release.tag_name.trim_start_matches('v').to_string();
if check {
+192
View File
@@ -0,0 +1,192 @@
//! sessions: the registry of every agent and REPL session.
use super::*;
use crate::output::{print_json, status_style, Table};
use crate::sessions::{self, SessionRecord};
use serde::Serialize;
#[derive(Serialize)]
struct SessionRow {
id: String,
kind: String,
agent: Option<String>,
status: String,
pid: Option<u32>,
cwd: Option<String>,
project: Option<String>,
started_at: String,
ended_at: Option<String>,
duration_s: Option<u64>,
exit_code: Option<i32>,
log: Option<String>,
}
fn row_of(r: &SessionRecord) -> SessionRow {
SessionRow {
id: r.id.clone(),
kind: r.kind.clone(),
agent: r.agent.clone(),
status: r.status.clone(),
pid: r.pid,
cwd: r.cwd.clone(),
project: r.project.clone(),
started_at: r.started_at.clone(),
ended_at: r.ended_at.clone(),
duration_s: sessions::duration_s(r),
exit_code: r.exit_code,
log: r.log.clone(),
}
}
pub fn run(
app: &App,
agent: Option<&str>,
project: Option<&str>,
status: Option<&str>,
show: Option<&str>,
resume: Option<&str>,
) -> Result<i32> {
if let Some(id) = show {
return show_one(app, id);
}
if let Some(id) = resume {
return resume_session(app, id);
}
sessions::reconcile(app)?;
let sf = sessions::load(app)?;
let mut rows: Vec<SessionRow> = sf
.sessions
.values()
.filter(|r| agent.map_or(true, |a| r.agent.as_deref() == Some(a)))
.filter(|r| project.map_or(true, |p| r.project.as_deref() == Some(p)))
.filter(|r| status.map_or(true, |s| r.status == s))
.map(row_of)
.collect();
rows.sort_by(|a, b| b.started_at.cmp(&a.started_at));
if app.json() {
print_json(&rows);
return Ok(0);
}
let color = app.color();
let theme = app.theme();
let mut table = Table::new(vec!["SESSION", "AGENT", "STATUS", "DURATION", "EXIT"]);
for r in &rows {
table.row(vec![
short_id(&r.id),
r.agent
.clone()
.unwrap_or_else(|| format!("({})", r.kind)),
status_style(&r.status, theme, color),
r.duration_s
.map(format_duration)
.unwrap_or_else(|| "—".to_string()),
r.exit_code
.map(|c| c.to_string())
.unwrap_or_else(|| "—".to_string()),
]);
}
print!("{}", table.render());
if rows.is_empty() {
app.log
.info("no sessions recorded yet — 'am start <agent> --background' or run 'am'");
}
Ok(0)
}
fn show_one(app: &App, id: &str) -> Result<i32> {
sessions::reconcile(app)?;
let sf = sessions::load(app)?;
let Some(r) = sf.sessions.get(id).cloned() else {
bail!("unknown session '{id}' — 'am sessions' lists every session");
};
if app.json() {
print_json(&row_of(&r));
return Ok(0);
}
println!("session {}", r.id);
println!(" kind: {}", r.kind);
println!(" agent: {}", r.agent.as_deref().unwrap_or("—"));
println!(" status: {}", r.status);
println!(" pid: {}", r.pid.map(|p| p.to_string()).unwrap_or_else(|| "—".to_string()));
println!(" cwd: {}", r.cwd.as_deref().unwrap_or("—"));
println!(" project: {}", r.project.as_deref().unwrap_or("—"));
println!(" started: {}", r.started_at);
println!(" ended: {}", r.ended_at.as_deref().unwrap_or("—"));
if let Some(d) = sessions::duration_s(&r) {
println!(" duration: {}", format_duration(d));
}
if let Some(c) = r.exit_code {
println!(" exit code: {c}");
}
if let Some(l) = &r.log {
let p = std::path::PathBuf::from(l);
if p.exists() {
println!(" log: {l}");
let tail = crate::process::last_lines(&p, 8);
if !tail.is_empty() {
println!(" --- log excerpt ---");
println!("{tail}");
}
} else {
println!(" log: {l} (missing)");
}
}
Ok(0)
}
/// Resume a finished session: relaunch the agent in the background with
/// its recorded arguments and working directory (issue #32).
fn resume_session(app: &App, id: &str) -> Result<i32> {
sessions::reconcile(app)?;
let sf = sessions::load(app)?;
let Some(r) = sf.sessions.get(id).cloned() else {
bail!("unknown session '{id}' — 'am sessions' lists every session");
};
if r.kind != "agent" {
bail!("session '{id}' is a REPL session — start it again with 'am'");
}
if r.status == "running" {
app.log.info(&format!("session '{id}' is still running"));
return Ok(0);
}
let Some(agent_name) = r.agent.clone() else {
bail!("session '{id}' has no agent");
};
require_agent(app, &agent_name)?;
if app.dry_run() {
app.log.dry(format!(
"would resume session {id}: start {} --background with {} arg(s)",
agent_name,
r.args.len()
));
return Ok(0);
}
let cwd = r.cwd.as_deref().map(std::path::Path::new);
let opts = crate::cli::StartArgs {
agent: Some(agent_name.clone()),
background: true,
args: r.args.clone(),
..Default::default()
};
app.log.info(&format!(
"resuming session {id}: {} ({} arg(s))",
agent_name,
r.args.len()
));
crate::commands::run_cmd::start_with_cwd(app, &opts, cwd)
}
/// "20260815_143926_a1b2c3" -> "20260815_143926"
fn short_id(id: &str) -> String {
id.chars().take(15).collect()
}
fn format_duration(secs: u64) -> String {
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m{:02}s", secs / 60, secs % 60)
} else {
format!("{}h{:02}m", secs / 3600, (secs % 3600) / 60)
}
}
+328
View File
@@ -0,0 +1,328 @@
//! stats: usage statistics computed from the event journal.
use super::*;
use crate::events::{self, EventKind};
use crate::output::{print_json, Table};
use anyhow::Context;
use serde::Serialize;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, Serialize)]
pub struct AgentStats {
pub agent: String,
pub launches: u64,
pub total_duration_s: u64,
pub avg_duration_s: u64,
pub failures: u64,
pub installs: u64,
pub updates: u64,
pub last_used: Option<String>,
}
/// Cutoff instant for a period: 7d / 30d / 90d, or None for everything.
pub fn cutoff(period: Option<&str>) -> Result<Option<chrono::DateTime<chrono::Utc>>> {
let days = match period {
None | Some("all") => return Ok(None),
Some("7d") => 7,
Some("30d") => 30,
Some("90d") => 90,
Some(other) => bail!("invalid period '{other}' — expected 7d, 30d, 90d or all"),
};
Ok(Some(chrono::Utc::now() - chrono::Duration::days(days)))
}
/// Aggregate journal events into per-agent statistics.
pub fn aggregate(app: &App, agent: Option<&str>, period: Option<&str>) -> Result<Vec<AgentStats>> {
let cut = cutoff(period)?;
let events = events::read_events(&app.events_dir(), 0);
let mut map: BTreeMap<String, AgentStats> = BTreeMap::new();
let mut dur_count: BTreeMap<String, u64> = BTreeMap::new();
for ev in events {
if let Some(c) = cut {
let Ok(ts) = chrono::DateTime::parse_from_rfc3339(&ev.ts) else {
continue;
};
if ts.with_timezone(&chrono::Utc) < c {
continue;
}
}
let Some(name) = ev.agent else {
continue;
};
if let Some(a) = agent {
if name != a {
continue;
}
}
let s = map.entry(name.clone()).or_default();
s.agent = name.clone();
match ev.kind {
EventKind::Start | EventKind::Run => s.launches += 1,
EventKind::Install => s.installs += 1,
EventKind::Update => s.updates += 1,
_ => {}
}
if let Some(d) = ev.duration_s {
s.total_duration_s += d;
*dur_count.entry(name.clone()).or_default() += 1;
}
if let Some(code) = ev.exit_code {
if code != 0 {
s.failures += 1;
}
}
s.last_used = Some(ev.ts);
}
for (name, s) in map.iter_mut() {
let n = dur_count.get(name).copied().unwrap_or(0);
s.avg_duration_s = if n > 0 { s.total_duration_s / n } else { 0 };
}
Ok(map.into_values().collect())
}
/// Top 10 agents by launch count (issue #37).
pub fn top(app: &App, period: Option<&str>) -> Result<Vec<AgentStats>> {
let mut rows = aggregate(app, None, period)?;
rows.sort_by(|a, b| b.launches.cmp(&a.launches).then(b.total_duration_s.cmp(&a.total_duration_s)));
rows.truncate(10);
Ok(rows)
}
/// Markdown weekly/monthly report over the last N days (issue #37).
pub fn report_md(app: &App, days: u32) -> String {
let cut = chrono::Utc::now() - chrono::Duration::days(days as i64);
let events = events::read_events(&app.events_dir(), 0);
let mut total_launches = 0u64;
let mut failures = 0u64;
let mut changes = 0u64;
let mut duration = 0u64;
for ev in events {
let ok = chrono::DateTime::parse_from_rfc3339(&ev.ts)
.map(|t| t.with_timezone(&chrono::Utc) >= cut)
.unwrap_or(false);
if !ok {
continue;
}
match ev.kind {
EventKind::Start | EventKind::Run => total_launches += 1,
EventKind::Install | EventKind::Update | EventKind::Uninstall => changes += 1,
_ => {}
}
if let Some(d) = ev.duration_s {
duration += d;
}
if let Some(c) = ev.exit_code {
if c != 0 {
failures += 1;
}
}
}
let sessions = crate::sessions::load(app).map(|s| s.sessions.len()).unwrap_or(0);
let top_rows = top(app, None).unwrap_or_default();
let mut md = format!(
"# Rapport agent-manager — {days} derniers jours\n\n- Lancements : {total_launches}\n- Échecs : {failures}\n- Changements (install/update/uninstall) : {changes}\n- Durée cumulée : {}s\n- Sessions enregistrées : {sessions}\n",
duration
);
if !top_rows.is_empty() {
md.push_str("\n## Top agents\n\n| Agent | Lancements | Durée | Échecs |\n|---|---|---|---|\n");
for r in &top_rows {
md.push_str(&format!(
"| {} | {} | {}s | {} |\n",
r.agent, r.launches, r.total_duration_s, r.failures
));
}
}
md
}
pub fn run(app: &App, agent: Option<&str>, period: Option<&str>) -> Result<i32> {
let rows = aggregate(app, agent, period)?;
if app.json() {
print_json(&rows);
return Ok(0);
}
let color = app.color();
let theme = app.theme();
if rows.is_empty() {
app.log.info("no usage recorded yet — run or start an agent first");
return Ok(0);
}
let max_launches = rows.iter().map(|r| r.launches).max().unwrap_or(1);
let mut table = Table::new(vec!["AGENT", "LAUNCHES", "DURATION", "AVG", "FAIL", "LAST"]);
for r in &rows {
table.row(vec![
r.agent.clone(),
format!("{} {}", r.launches, bar(r.launches, max_launches, 8)),
format_duration(r.total_duration_s),
format_duration(r.avg_duration_s),
if r.failures > 0 {
crate::output::status_style("error", theme, color)
} else {
r.failures.to_string()
},
r.last_used
.as_deref()
.map(|t| t[..t.len().min(10)].to_string())
.unwrap_or_else(|| "—".to_string()),
]);
}
print!("{}", table.render());
Ok(0)
}
/// am top : the most used agents (issue #37).
pub fn run_top(app: &App, period: Option<&str>) -> Result<i32> {
let rows = top(app, period)?;
if app.json() {
print_json(&rows);
return Ok(0);
}
let color = app.color();
let theme = app.theme();
if rows.is_empty() {
app.log.info("no usage recorded yet");
return Ok(0);
}
let max_launches = rows.iter().map(|r| r.launches).max().unwrap_or(1);
let mut table = Table::new(vec!["AGENT", "LAUNCHES", "DURATION", "FAIL"]);
for r in &rows {
table.row(vec![
r.agent.clone(),
format!("{} {}", r.launches, bar(r.launches, max_launches, 8)),
format_duration(r.total_duration_s),
if r.failures > 0 {
crate::output::status_style("error", theme, color)
} else {
r.failures.to_string()
},
]);
}
print!("{}", table.render());
Ok(0)
}
/// am report : write the markdown digest (issue #37).
pub fn run_report(
app: &App,
last_week: bool,
last_month: bool,
output: Option<&std::path::Path>,
) -> Result<i32> {
let days: u32 = if last_week { 7 } else { 30 };
let md = report_md(app, days);
let path = match output {
Some(p) => p.to_path_buf(),
None => {
let stamp = chrono::Local::now().format("%Y%m%d");
app.events_dir().join(format!("report-{stamp}.md"))
}
};
if app.dry_run() {
app.log.dry(format!("would write {}", path.display()));
return Ok(0);
}
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
std::fs::write(&path, md)
.with_context(|| format!("cannot write {}", path.display()))?;
app.log.success(&format!("report written to {}", path.display()));
Ok(0)
}
/// ASCII bar: relative weight of v against max.
fn bar(v: u64, max: u64, width: usize) -> String {
if max == 0 {
return String::new();
}
let n = ((v * width as u64) / max).max(if v > 0 { 1 } else { 0 }) as usize;
format!("{}{}", "█".repeat(n), "░".repeat(width.saturating_sub(n)))
}
fn format_duration(secs: u64) -> String {
if secs == 0 {
return "—".to_string();
}
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m{:02}s", secs / 60, secs % 60)
} else {
format!("{}h{:02}m", secs / 3600, (secs % 3600) / 60)
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
fn app_with_events(events_jsonl: &str) -> crate::app::App {
// The TempDir guard would delete the directory when this helper
// returns; leak it on purpose so the App keeps a live events dir.
let guard = tempfile::tempdir().unwrap();
let dir = guard.path().to_path_buf();
std::mem::forget(guard);
let cfg = dir.join("config.yaml");
std::fs::write(
&cfg,
concat!(
"version: \"1.0\"\n",
"settings:\n",
" auto_install_deps: false\n",
"agents: []\n",
),
)
.unwrap();
let cli = crate::cli::Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
let mut app = crate::app::App::from_cli(cli).unwrap();
let mut p = app.paths.clone();
p.install_dir = dir.join("agents");
p.bin_dir = dir.join("agents").join("bin");
p.log_dir = dir.join("logs");
p.state_file = dir.join("state.json");
p.probe_cache_file = dir.join("probe-cache.json");
p.config_dir = Some(dir.join("config"));
app.paths = p;
std::fs::write(dir.join("events-test.jsonl"), events_jsonl).unwrap();
// Rename into the right month file so read_events picks it up.
let month = chrono::Utc::now().format("%Y%m");
std::fs::rename(
dir.join("events-test.jsonl"),
dir.join(format!("events-{month}.jsonl")),
)
.unwrap();
app
}
#[test]
fn aggregates_counts_durations_and_failures() {
let lines = [
r#"{"ts":"2026-08-01T10:00:00Z","kind":"run","agent":"a","exit_code":0,"duration_s":10}"#,
r#"{"ts":"2026-08-02T10:00:00Z","kind":"run","agent":"a","exit_code":0,"duration_s":30}"#,
r#"{"ts":"2026-08-03T10:00:00Z","kind":"run","agent":"b","exit_code":1,"duration_s":5}"#,
r#"{"ts":"2026-08-03T11:00:00Z","kind":"install","agent":"a"}"#,
];
let app = app_with_events(&lines.join("\n"));
let rows = aggregate(&app, None, None).unwrap();
let a = rows.iter().find(|r| r.agent == "a").unwrap();
assert_eq!(a.launches, 2);
assert_eq!(a.total_duration_s, 40);
assert_eq!(a.avg_duration_s, 20);
assert_eq!(a.failures, 0);
assert_eq!(a.installs, 1);
let b = rows.iter().find(|r| r.agent == "b").unwrap();
assert_eq!(b.failures, 1);
assert_eq!(b.launches, 1);
}
#[test]
fn period_cuts_old_events() {
let app = app_with_events(
r#"{"ts":"2020-01-01T10:00:00Z","kind":"run","agent":"old","exit_code":0}"#,
);
assert!(aggregate(&app, None, Some("30d")).unwrap().is_empty());
assert_eq!(aggregate(&app, None, None).unwrap().len(), 1);
assert!(cutoff(Some("bad")).is_err());
}
}
+54 -20
View File
@@ -1,7 +1,7 @@
//! status: detailed state of one agent, or of every installed agent.
use super::*;
use crate::output::{print_json, status_style, Table};
use crate::output::{boxed_table, kv_table, print_json, status_style};
use crate::probe::ExternalInfo;
use crate::state::InstalledEntry;
use serde::Serialize;
@@ -39,28 +39,48 @@ fn status_one(app: &App, name: &str, probe: &ExternalInfo) -> Result<i32> {
return Ok(0);
}
let color = app.color();
println!("{} ({})", agent.title(), agent.name);
let status_line = status_style(&detail.status, color);
println!(" status: {status_line}");
println!(
" version: {}",
detail.version.as_deref().unwrap_or("unknown")
);
println!(" method: {}", detail.method.as_deref().unwrap_or("—"));
println!(" path: {}", detail.path.as_deref().unwrap_or("—"));
println!(" run: {}", detail.run.as_deref().unwrap_or("—"));
let theme = app.theme();
// Favorite star (issue #39).
let star = if crate::commands::annotations_cmd::is_favorite(app, &agent.name) {
"★ "
} else {
""
};
println!("{}{} ({})", star, agent.title(), agent.name);
let status_line = status_style(&detail.status, theme, color);
let mut rows: Vec<(String, String)> = vec![
("agent".to_string(), agent.name.clone()),
("status".to_string(), status_line),
(
"version".to_string(),
detail.version.clone().unwrap_or_else(|| "unknown".to_string()),
),
(
"method".to_string(),
detail.method.clone().unwrap_or_else(|| "—".to_string()),
),
(
"path".to_string(),
detail.path.clone().unwrap_or_else(|| "—".to_string()),
),
(
"run".to_string(),
detail.run.clone().unwrap_or_else(|| "—".to_string()),
),
];
if let Some(pid) = detail.pid {
println!(" pid: {pid}");
rows.push(("pid".to_string(), pid.to_string()));
}
if let Some(t) = &detail.started_at {
println!(" started: {t}");
rows.push(("started".to_string(), t.clone()));
}
if let Some(t) = &detail.installed_at {
println!(" installed: {t}");
rows.push(("installed".to_string(), t.clone()));
}
if let Some(l) = &detail.log {
println!(" log: {l}");
rows.push(("log".to_string(), l.clone()));
}
print!("{}", kv_table(&rows, theme, color));
Ok(0)
}
@@ -90,17 +110,31 @@ fn status_all(app: &App, probe: &ExternalInfo) -> Result<i32> {
return Ok(0);
}
let color = app.color();
let mut table = Table::new(vec!["NAME", "VERSION", "STATUS", "PID", "PATH"]);
let theme = app.theme();
let mut table_rows = Vec::new();
for r in &rows {
table.row(vec![
r.display_name.clone(),
let star = if crate::commands::annotations_cmd::is_favorite(app, &r.name) {
"★ "
} else {
""
};
table_rows.push(vec![
format!("{star}{}", r.display_name),
r.version.clone().unwrap_or_else(|| "—".to_string()),
status_style(&r.status, color),
status_style(&r.status, theme, color),
r.pid.map(|p| p.to_string()).unwrap_or_else(|| "—".to_string()),
r.path.clone().unwrap_or_else(|| "—".to_string()),
]);
}
print!("{}", table.render());
print!(
"{}",
boxed_table(
vec!["NAME", "VERSION", "STATUS", "PID", "PATH"],
table_rows,
theme,
color,
)
);
if rows.is_empty() {
app.log.info("no installed or running agents");
}

Some files were not shown because too many files have changed in this diff Show More