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