feat: hints de configuration post-install (setup_hints) — commandes manuelles affichees apres install avec substitution {model}/{provider}/{base_url} (picoclaw onboard/model/auth)
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "agent-manager"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
edition = "2021"
|
||||
description = "Manage local AI coding agents: list, install, start, stop, update — with automatic dependency handling and a YAML-driven catalog."
|
||||
license = "MIT"
|
||||
|
||||
@@ -92,6 +92,14 @@
|
||||
> enregistré existant » (les branches non-installé/binaire manquant
|
||||
> étaient déjà correctes). Test de régression
|
||||
> `resolve_exec_installed_agent_does_not_leak_run_token`.
|
||||
>
|
||||
> 🔧 **Maintenance v1.1.4 (2026-08-21)** : hints de configuration
|
||||
> post-install — nouveau champ `setup_hints` dans la définition d'un agent
|
||||
> (config.yaml) : commandes de configuration manuelle affichées après
|
||||
> l'install quand l'agent gère sa propre config (ex. picoclaw
|
||||
> onboard/model/auth), avec substitution des placeholders `{model}`,
|
||||
> `{provider}` et `{base_url}` depuis le provider résolu. picoclaw est le
|
||||
> premier agent du catalogue à les déclarer.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1025,6 +1025,12 @@ agents:
|
||||
repo: sipeed/picoclaw
|
||||
binary: picoclaw
|
||||
run: picoclaw
|
||||
# PicoClaw gère sa config par sous-commandes (pas de fichier à écrire) :
|
||||
# ces commandes sont affichées après l'install avec {model} substitué.
|
||||
setup_hints:
|
||||
- "picoclaw onboard"
|
||||
- "picoclaw model {model}"
|
||||
- "picoclaw auth login"
|
||||
tags: [go, tiny]
|
||||
|
||||
- name: ironclaw
|
||||
|
||||
@@ -111,6 +111,46 @@ pub fn apply_env_map_resolved(
|
||||
}
|
||||
}
|
||||
|
||||
/// Substitute the {model}, {provider} and {base_url} placeholders of a
|
||||
/// setup hint with the resolved provider configuration (v1.1.4). When no
|
||||
/// provider is resolved, the hint is returned unchanged.
|
||||
pub fn render_hint(
|
||||
hint: &str,
|
||||
resolved: &Option<(String, Option<String>)>,
|
||||
config: &Config,
|
||||
) -> String {
|
||||
let Some((pname, model)) = resolved else {
|
||||
return hint.to_string();
|
||||
};
|
||||
let base_url = crate::providers::get(config, pname)
|
||||
.map(|d| d.base_url.clone())
|
||||
.unwrap_or_default();
|
||||
hint.replace("{provider}", pname)
|
||||
.replace(
|
||||
"{model}",
|
||||
model.as_deref().unwrap_or("{model}"),
|
||||
)
|
||||
.replace("{base_url}", &base_url)
|
||||
}
|
||||
|
||||
/// Print the manual setup commands of an agent (agents that manage their
|
||||
/// own configuration, e.g. picoclaw onboard/model/auth). No-op when the
|
||||
/// agent declares none (v1.1.4).
|
||||
fn print_hints(
|
||||
app: &App,
|
||||
agent: &AgentDef,
|
||||
resolved: &Option<(String, Option<String>)>,
|
||||
) {
|
||||
if agent.setup_hints.is_empty() {
|
||||
return;
|
||||
}
|
||||
app.log
|
||||
.success(crate::i18n::tr("Configuration manuelle requise — à exécuter :"));
|
||||
for hint in &agent.setup_hints {
|
||||
app.log.info(&format!(" {}", render_hint(hint, resolved, &app.config)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the agent configuration files after a successful install
|
||||
/// (issue #91). The caller skips this on `--no-config`, on non-configurable
|
||||
/// agents, and in dry-run (never reached). Never writes a token in clear:
|
||||
@@ -124,6 +164,7 @@ pub fn apply_post_install(
|
||||
app.log.info(crate::i18n::tr(
|
||||
"agent sans adaptateur de config — voir sa doc",
|
||||
));
|
||||
print_hints(app, agent, resolved);
|
||||
return Ok(());
|
||||
};
|
||||
if cfg.files.is_empty() {
|
||||
@@ -136,12 +177,14 @@ pub fn apply_post_install(
|
||||
"config par variables d'env — injectée au démarrage",
|
||||
));
|
||||
}
|
||||
print_hints(app, agent, resolved);
|
||||
return Ok(());
|
||||
}
|
||||
let Some((pname, model)) = resolved else {
|
||||
app.log.warn(crate::i18n::tr(
|
||||
"aucun provider résolu — fichiers de config non écrits",
|
||||
));
|
||||
print_hints(app, agent, resolved);
|
||||
return Ok(());
|
||||
};
|
||||
let Some(def) = crate::providers::get(&app.config, pname) else {
|
||||
@@ -418,6 +461,7 @@ mod tests {
|
||||
provider,
|
||||
model: None,
|
||||
config: Some(config),
|
||||
setup_hints: vec![],
|
||||
sandbox: None,
|
||||
tags: vec![],
|
||||
installable: false,
|
||||
@@ -515,4 +559,64 @@ mod tests {
|
||||
);
|
||||
assert_eq!(provider_pref(&agent2), Some("openai"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_hint_substitutes_resolved_values() {
|
||||
let c: Config = serde_yaml::from_str(
|
||||
"version: \"1.0\"\nsettings:\n default_provider: openrouter\n providers:\n openrouter:\n base_url: https://openrouter.ai/api/v1\n default_model: deepseek/deepseek-chat-v3-0324\n models: [deepseek/deepseek-chat-v3-0324]\n",
|
||||
)
|
||||
.unwrap();
|
||||
let resolved = Some(("openrouter".to_string(), Some("deepseek/deepseek-chat-v3-0324".to_string())));
|
||||
assert_eq!(
|
||||
render_hint("picoclaw model {model}", &resolved, &c),
|
||||
"picoclaw model deepseek/deepseek-chat-v3-0324"
|
||||
);
|
||||
assert_eq!(render_hint("picoclaw onboard", &resolved, &c), "picoclaw onboard");
|
||||
assert_eq!(
|
||||
render_hint("cli --provider {provider} --url {base_url}", &resolved, &c),
|
||||
"cli --provider openrouter --url https://openrouter.ai/api/v1"
|
||||
);
|
||||
// No resolved provider: the hint is left untouched.
|
||||
assert_eq!(
|
||||
render_hint("picoclaw model {model}", &None, &c),
|
||||
"picoclaw model {model}"
|
||||
);
|
||||
// Resolved provider without a model: {model} stays visible.
|
||||
let no_model = Some(("openrouter".to_string(), None));
|
||||
assert_eq!(
|
||||
render_hint("picoclaw model {model}", &no_model, &c),
|
||||
"picoclaw model {model}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_post_install_with_setup_hints_is_ok() {
|
||||
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\nagents: []\n",
|
||||
)
|
||||
.unwrap();
|
||||
let cli = crate::cli::Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
|
||||
let app = crate::app::App::from_cli(cli).unwrap();
|
||||
let mut agent = agent_with_config(
|
||||
AgentConfig {
|
||||
env_map: BTreeMap::new(),
|
||||
files: vec![],
|
||||
provider_default: None,
|
||||
},
|
||||
None,
|
||||
);
|
||||
agent.config = None;
|
||||
agent.setup_hints = vec![
|
||||
"picoclaw onboard".to_string(),
|
||||
"picoclaw model {model}".to_string(),
|
||||
];
|
||||
// No provider resolved: hints printed with placeholders, no crash.
|
||||
assert!(apply_post_install(&app, &agent, &None).is_ok());
|
||||
assert!(apply_post_install(&app, &agent, &Some(("openrouter".to_string(), None))).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,6 +669,7 @@ mod tests {
|
||||
files: vec![],
|
||||
provider_default: None,
|
||||
}),
|
||||
setup_hints: vec![],
|
||||
sandbox: None,
|
||||
tags: vec![],
|
||||
installable: false,
|
||||
|
||||
@@ -200,6 +200,7 @@ mod tests {
|
||||
provider: None,
|
||||
model: None,
|
||||
config: None,
|
||||
setup_hints: vec![],
|
||||
sandbox: None,
|
||||
tags: tags.iter().map(|s| s.to_string()).collect(),
|
||||
installable: false,
|
||||
|
||||
@@ -467,6 +467,12 @@ pub struct AgentDef {
|
||||
/// Post-install provider configuration (issue #91).
|
||||
#[serde(default)]
|
||||
pub config: Option<AgentConfig>,
|
||||
/// Manual setup commands shown after install when the agent manages its
|
||||
/// own configuration (onboard/model/auth...). Placeholders {model},
|
||||
/// {provider} and {base_url} are substituted with the resolved values
|
||||
/// (v1.1.4).
|
||||
#[serde(default)]
|
||||
pub setup_hints: Vec<String>,
|
||||
/// Sandbox profile (issue #79): allowed commands, working directories
|
||||
/// and network policy. Disabled by default — the unsandboxed mode
|
||||
/// stays available and documented.
|
||||
|
||||
@@ -214,6 +214,7 @@ pub const CATALOG: &[(&str, &str)] = &[
|
||||
("provider {} (modèle {}) au lancement de {}", "provider {} (model {}) when launching {}"),
|
||||
("agent sans adaptateur de config — voir sa doc", "agent without a config adapter — see its docs"),
|
||||
("config par variables d'env — injectée au démarrage", "env-var configuration — injected at start"),
|
||||
("Configuration manuelle requise — à exécuter :", "Manual setup required — run:"),
|
||||
("aucun provider résolu — fichiers de config non écrits", "no provider resolved — config files not written"),
|
||||
("provider '{}' introuvable — fichiers de config non écrits", "provider '{}' not found — config files not written"),
|
||||
("format inconnu pour {} — adaptateurs: toml, yaml, json, key=value", "unknown format for {} — adapters: toml, yaml, json, key=value"),
|
||||
|
||||
Reference in New Issue
Block a user