feat: epique v1.1.0 — onboarding, copilot & roles aichat (am setup)
release / release (push) Successful in 13m21s
release / macos (push) Canceled after 0s

F1 — Wizard d'onboarding (src/setup.rs) :
- am setup : provider (anthropic, openai, deepseek, google, ollama, custom),
  token masque (rpassword) -> keyring OS (jamais en clair), modele par defaut
  (liste declaree + champ libre), ping API non-bloquant
- Declencheurs non-bloquants : am ai / am ask et banner REPL proposent
  am setup quand aucun provider n'est configure ; re-executable (mode revoir)
- settings.default_provider + providers.* ecrits via persist_setting

F2 — Integration aichat :
- Etape wizard 'Installer le moteur IA (aichat) ? [Y/n]' -> am install aichat
- am ai sans aichat -> 'aichat est manquant. Installer ? [Y/n]'
- install.ps1/install.sh enchainent sur am setup si stdin interactif

F3 — Roles copilot aichat (src/roles.rs) :
- 6 agents generes dans ~/.config/aichat/agents (ou %APPDATA%\aichat\agents) :
  am-copilot, am-operator, am-dev, am-do, am-analyst, am-orchestrator
- Prompts bilingues (FR defaut) privilegiant les contrats --json ; le prompt
  am-copilot embarque la liste des commandes am
- am ai --role <nom> (valide le fichier genere, passe --agent a aichat),
  completion REPL ; config aichat creee seulement si absente
- Câblage complet : cli, dispatch, help, REPL, tip, man (am-setup.1), i18n

Tests : 450 verts (roles + setup unitaires, ai_test ajustes locale-agnostic)
v1.1.0 ; ROADMAP axe 14 + maintenance note ; README Premier lancement
This commit is contained in:
2026-08-20 23:09:21 -04:00
parent 50f515e406
commit d71c3dd34d
20 changed files with 870 additions and 12 deletions
+57 -1
View File
@@ -11,10 +11,38 @@ use crate::cli::AiArgs;
use crate::commands::{require_agent, resolve_exec};
use crate::events::{Event, EventKind};
use crate::shell_ai::{Decision, SafetyMode};
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use std::process::Command;
pub fn run(app: &App, args: &AiArgs) -> Result<i32> {
// Onboarding hint (v1.1.0 F1) — non-blocking: the AI commands propose
// `am setup` when no provider is configured, then continue anyway.
if crate::setup::needs_setup(app) {
app.log.info(crate::i18n::tr_in(
app.lang(),
"am n'est pas configuré — lancez am setup (provider + token)",
));
}
// F2 (v1.1.0): aichat must be INSTALLED (not only in the catalog) to
// power am ai. Offer the installation when missing.
if !aichat_installed(app) {
let ask_install = !app.cli.yes
&& app.confirm(crate::i18n::tr_in(
app.lang(),
"aichat est manquant. Installer ?",
))?;
if app.cli.yes || ask_install {
app.log.info(crate::i18n::tr_in(app.lang(), "Installation d'aichat…"));
crate::commands::install_cmd::run(app, "aichat", None, false, None, None, false)?;
} else {
bail!(crate::i18n::tr_in(
app.lang(),
"installez aichat: am install aichat — puis réessayez"
));
}
}
let prompt = args.prompt.join(" ");
// Context files: explicit --files wins, else the current directory
// (issue #96: `aichat -f . "<prompt>"`).
@@ -39,6 +67,31 @@ pub fn run(app: &App, args: &AiArgs) -> Result<i32> {
exec_mode(app, &prompt, &files, args, yes, dry_run)
}
/// True when the aichat binary is available (installed or on PATH).
/// Uses a direct PATH lookup — the probe cache would mask a shim added
/// mid-test and is meant for agent inventories, not runtime checks.
fn aichat_installed(app: &App) -> bool {
app.state.get("aichat").ok().flatten().is_some()
|| which::which("aichat").is_ok()
}
/// Validate the --role flag: the file must exist among the generated
/// am-* roles (v1.1.0 F3). Returns the aichat --agent arguments.
fn role_args(app: &App, role: Option<&str>) -> Result<Vec<String>> {
let Some(role) = role else {
return Ok(Vec::new());
};
let dir = crate::roles::aichat_agents_dir();
let path = dir.join(format!("{role}.md"));
if !path.exists() {
bail!(crate::i18n::tr_in(
app.lang(),
"rôle '{}' inconnu — générez les rôles avec: am setup --roles (am-copilot, am-operator, am-dev, am-do, am-analyst, am-orchestrator)"
).replace("{}", role));
}
Ok(vec!["--agent".to_string(), role.to_string()])
}
/// Conversational mode: aichat -f <ctx> "<prompt>" in the foreground, with
/// the provider/model environment applied when requested.
fn chat_mode(
@@ -50,6 +103,7 @@ fn chat_mode(
dry_run: bool,
) -> Result<i32> {
let mut extra: Vec<String> = Vec::new();
extra.extend(role_args(app, args.role.as_deref())?);
for f in files {
extra.push("-f".to_string());
extra.push(f.clone());
@@ -102,12 +156,14 @@ fn exec_mode(
"would generate & classify via aichat (exec) — commande non exécutée"
));
}
let role = role_args(app, args.role.as_deref())?;
let cmd = crate::shell_ai::generate(
app,
prompt,
files,
args.provider.as_deref(),
args.model.as_deref(),
&role,
)?;
let settings = app
.config
+7
View File
@@ -108,6 +108,13 @@ pub fn execute_command(app: &App, cmd: &Command) -> Result<i32> {
Command::Suggest { words } => suggest_cmd::run(app, &words.join(" ")),
Command::Ask { query, yes } => crate::ask::run(app, &query.join(" "), *yes),
Command::Ai(args) => ai_cmd::run(app, args),
Command::Setup { roles } => {
if *roles {
crate::setup::run_roles(app)
} else {
crate::setup::run(app)
}
}
Command::Start(a) => run_cmd::start(app, a),
Command::Stop {
agent,
+11 -1
View File
@@ -110,17 +110,27 @@ pub static SECTIONS: &[TipSection] = &[
example: "ask installe claude et lance-le",
},
TipEntry {
usage: "ai <demande> [--exec] [--files <path>]",
usage: "ai <demande> [--exec] [--files <path>] [--role <rôle>]",
about: "langage naturel → action shell via AIChat (issues #96 #97) : génère avec aichat, classe safe/risky, dry-run par défaut",
options: &[
("--exec", "génère PUIS exécute la commande (après la politique de sécurité)"),
("-f, --files", "fichiers/dossiers en contexte (défaut: dossier courant)"),
("--role", "rôle copilot aichat (am-copilot, am-operator, am-dev, am-do, am-analyst, am-orchestrator)"),
("--provider", "provider du registre (clé du keyring + modèle)"),
("--model", "forcer un modèle"),
("--yes", "exécute sans confirmation"),
],
example: "ai --exec \"compresse les fichiers JSON en un zip\"",
},
TipEntry {
usage: "setup [--roles]",
about: "wizard d'onboarding : provider, token (trousseau OS), modèle par défaut, installation d'aichat, rôles copilot am-* (v1.1.0)",
options: &[
("--roles", "régénérer uniquement les rôles copilot aichat"),
("--yes", "non-interactif (valeurs actuelles)"),
],
example: "setup",
},
TipEntry {
usage: "models",
about: "inventaire des modèles locaux (ollama, llama.cpp, LM Studio)",