Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91253b79c5 | ||
|
|
3c8bb93b8a | ||
|
|
363d5e3286 | ||
|
|
4c60ab5bcc | ||
|
|
e24e518846 | ||
|
|
2c079b0ccd | ||
|
|
e6e85e2a9e | ||
|
|
56732d672b | ||
|
|
4f4cd034b2 | ||
|
|
aeca797ae0 | ||
|
|
354c914c43 | ||
|
|
c57d3a333d | ||
|
|
16926ffb00 | ||
|
|
f14b59e63b | ||
|
|
8d93eece1e | ||
|
|
2a1fcfeed6 | ||
|
|
18784ba9f7 | ||
|
|
3220a323ef | ||
|
|
30cc49de3c | ||
|
|
314583ceee | ||
|
|
ed795c522f | ||
|
|
bbc2bb889d | ||
|
|
904e340cdd | ||
|
|
5f8b973789 | ||
|
|
463fa6b937 | ||
|
|
dc7372f277 | ||
|
|
a23bc6b5ae | ||
|
|
81c69db17b | ||
|
|
9e6c8aca08 | ||
|
|
4ef89cde72 | ||
|
|
2eaafe6e2a | ||
|
|
deeb489b4e | ||
|
|
fc243d2f21 | ||
|
|
6ecfa0d21c | ||
|
|
3902bbcb37 | ||
|
|
d4a6aeae1d | ||
|
|
38d0729cb3 | ||
|
|
85d44f9e56 | ||
|
|
48e85555f7 | ||
|
|
c9e2543f50 | ||
|
|
9f4a13eae2 | ||
|
|
33748bea36 | ||
|
|
19e77fd261 | ||
|
|
38b7a3a915 | ||
|
|
3af0834bae | ||
|
|
c6518bcfd5 | ||
|
|
a672880e34 | ||
|
|
310da96a63 | ||
|
|
ba901df953 | ||
|
|
e50e8d3e41 | ||
|
|
498af684c9 | ||
|
|
2282f71829 | ||
|
|
f968d7d0b0 | ||
|
|
b3437b0601 | ||
|
|
843c3e1278 | ||
|
|
4db567eebe | ||
|
|
2bbe45e508 | ||
|
|
4b0f02a7c4 | ||
|
|
79143e197d | ||
|
|
14f873bfdd | ||
|
|
53f3fa34e7 | ||
|
|
5841926b25 | ||
|
|
979875fa63 | ||
|
|
93c32b60ba | ||
|
|
0f4ba71219 | ||
|
|
e7cb136c38 | ||
|
|
57234b7a38 | ||
|
|
3e1b66809a | ||
|
|
d886de322c | ||
|
|
bc38cd59e5 |
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+10
-2
@@ -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
@@ -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')
|
||||
|
||||
@@ -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
@@ -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
@@ -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).*
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+713
-126
File diff suppressed because it is too large
Load Diff
+1274
-545
File diff suppressed because it is too large
Load Diff
+678
-96
@@ -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
@@ -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
@@ -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]
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
@@ -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()))?;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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, ¤t) {
|
||||
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, ¤t, 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(¤t));
|
||||
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", ¤t);
|
||||
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", ¤t);
|
||||
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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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::*;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user