feat: phase 2 — modèles locaux + catalogue distant + suggest (closes #70 #71 #73 #60 #67 #61)

Axe 11 (modèles) :
- am models : inventaire des runtimes locaux (ollama list --format json,
  llama.cpp .gguf sur disque, LM Studio API locale) avec cache façon
  probe.rs (TTL 30s) et absence de runtime signalée sans erreur fatale
- am run <agent> --model <m> : résolution dans l'inventaire, transmission
  via model_env/model_arg de l'agent (défaut AM_MODEL), tracé en session
- am models --prune : candidats inutilisés depuis settings.models_prune_days
  (défaut 30), :latest toujours protégé, dry-run + confirmation + événement
  EventKind::Model journalisé

Axes 7/9 (catalogue) :
- am catalog update : fetch du catalogue officiel, diff (ajoutés/retirés/
  modifiés), confirmation, enregistrement en include
- am catalog add <url> : validation du format (parse + validate), cache
  catalog-cache.json (TTL 3600s), événement EventKind::Catalog journalisé
- include par URL (http/https/file) dans config.yaml, résolu au chargement
  avec cache + TTL ; override local toujours prioritaire (issue #67)
- am suggest <requête> : réutilise le scoring fuzzy du catalogue, boost par
  usage réel (lancements + durée depuis le journal), top 5 justifié, --json

REPL : commandes models/catalog/suggest (parse, complétion, descriptions),
help specs, tip cheat sheet, man pages régénérées (49 pages, dont 3 nouvelles).
Version bump 0.4.5, ROADMAP cases cochées, 266 tests verts.
This commit is contained in:
2026-08-18 09:06:16 -04:00
parent 890ad953e5
commit 22bc529818
31 changed files with 2122 additions and 40 deletions
+40 -1
View File
@@ -56,6 +56,8 @@ pub struct AmCompleter {
groups: Vec<String>,
/// 'profile' subcommands.
profile_sub: Vec<&'static str>,
/// 'catalog' subcommands (issues #60 #67).
catalog_sub: Vec<&'static str>,
/// Short descriptions shown next to candidates in the Tab menu
/// (Nushell-style).
descriptions: BTreeMap<String, String>,
@@ -104,6 +106,9 @@ const COMMAND_DESCRIPTIONS: &[(&str, &str)] = &[
("tags", "list personal tags"),
("profile", "manage environment profiles"),
("man", "readable man page in the terminal"),
("models", "list local models (ollama, llama.cpp, LM Studio)"),
("catalog", "manage remote catalogs"),
("suggest", "recommend an agent for a request"),
("shell", "show or switch the system shell"),
("theme", "show or switch the color theme"),
("tip", "cheat sheet of the most useful commands"),
@@ -217,11 +222,12 @@ impl AmCompleter {
"start", "stop", "restart", "run", "doctor", "config", "completion",
"self-update", "self-uninstall", "export", "import", "shell", "theme",
"tip", "dashboard", "favorite", "unfavorite", "note", "tag", "untag", "tags",
"profile", "man",
"profile", "man", "models", "catalog", "suggest",
"ls", "dir", "cd", "ps", "where", "get", "help", "version", "exit",
],
config_sub: vec!["show", "path", "edit", "validate", "add"],
profile_sub: vec!["list", "show"],
catalog_sub: vec!["update", "add", "list"],
shells: vec!["bash", "zsh", "fish", "powershell", "elvish"],
gateway_shells: vec![
"list", "bash", "zsh", "fish", "powershell", "pwsh", "cmd", "sh", "nu",
@@ -265,6 +271,9 @@ impl AmCompleter {
"profile" => {
pool = self.profile_sub.iter().map(|s| s.to_string()).collect();
}
"catalog" => {
pool = self.catalog_sub.iter().map(|s| s.to_string()).collect();
}
"completion" => {
pool = self.shells.iter().map(|s| s.to_string()).collect();
}
@@ -759,6 +768,9 @@ pub fn banner_box(
rows.push(inner(
" config alias · secret · profile · config · doctor · completion · man · tip".to_string(),
));
rows.push(inner(
" models models · models --prune · catalog · suggest".to_string(),
));
rows.push(inner(
" system self-update · self-uninstall · export · import".to_string(),
));
@@ -1416,6 +1428,32 @@ fn handle_line(
return Ok(false);
}
},
"models" => Command::Models(crate::cli::ModelsArgs {
prune: flag("--prune"),
dry_run: flag("--dry-run"),
yes: flag("--yes") || app.yes(),
days: opt_value("--days").and_then(|v| v.parse().ok()),
}),
"catalog" => match rest.first().map(|s| s.as_str()) {
Some("update") | None => Command::Catalog(crate::cli::CatalogCmd::Update),
Some("add") if rest.len() >= 2 => Command::Catalog(crate::cli::CatalogCmd::Add {
url: rest[1].clone(),
}),
Some("list") => Command::Catalog(crate::cli::CatalogCmd::List),
_ => {
app.log.error("usage: catalog update | catalog add <url> | catalog list");
return Ok(false);
}
},
"suggest" => {
if rest.is_empty() {
app.log.error("usage: suggest <requête> — ex: suggest un agent pour du Python");
return Ok(false);
}
Command::Suggest {
words: rest.to_vec(),
}
},
"alias" => match rest.first().map(|s| s.as_str()) {
Some("add") if rest.len() >= 3 => Command::Alias(crate::cli::AliasCmd::Add {
name: rest[1].clone(),
@@ -1583,6 +1621,7 @@ fn handle_line(
let agent = need("agent name")?;
Command::Run {
agent,
model: opt_value("--model"),
args: rest[1..].iter().map(|s| s.as_str().into()).collect(),
}
}