feat: epique v1.1.0 — onboarding, copilot & roles aichat (am setup)
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:
+57
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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)",
|
||||
|
||||
Reference in New Issue
Block a user