agent-manager v0.2.0 : gestionnaire d'agents IA locaux
- CLI Rust (am) : list/install/uninstall/update/start/stop/restart/run/status/search/info/config/doctor/completion/export/import/self-update + shell interactif (Tab, historique) - 9 methodes d'installation (npm, pip, uv, cargo, go, bun, curl, binary, git) - catalogue de 72 agents, dependances, processus, securite (dry-run, checksums) - sondes de version paralleles + cache (am list ~25 ms a chaud) - installateurs one-liner : install.sh, install.ps1
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
//! config subcommands: show, path, edit, validate, add.
|
||||
|
||||
use super::*;
|
||||
use crate::cli::ConfigCmd;
|
||||
use crate::config;
|
||||
use crate::output::print_json;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
const USER_CONFIG_TEMPLATE: &str = "# User configuration for agent-manager.
|
||||
# This file overrides/completes the embedded default catalog.
|
||||
# Run 'am config show' to see the effective configuration.
|
||||
version: \"1.0\"
|
||||
|
||||
agents: []
|
||||
";
|
||||
|
||||
pub fn run(app: &App, cmd: &ConfigCmd) -> Result<i32> {
|
||||
match cmd {
|
||||
ConfigCmd::Show => {
|
||||
if app.json() {
|
||||
print_json(&app.config);
|
||||
} else {
|
||||
println!("{}", serde_yaml::to_string(&app.config)?);
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
ConfigCmd::Path => {
|
||||
if app.json() {
|
||||
print_json(&serde_json::json!({
|
||||
"origin": app.config_origin.as_ref().map(|p| p.display().to_string()),
|
||||
"user_dir": app.paths.config_dir.as_ref().map(|p| p.display().to_string()),
|
||||
"user_file": user_config_path_if_any(app).map(|p| p.display().to_string()),
|
||||
"local_file": PathBuf::from(config::LOCAL_CONFIG_NAME).display().to_string(),
|
||||
}));
|
||||
} else {
|
||||
match &app.config_origin {
|
||||
Some(p) => println!("active config: {}", p.display()),
|
||||
None => println!("no user config found — using the embedded default catalog"),
|
||||
}
|
||||
if let Some(d) = &app.paths.config_dir {
|
||||
println!("user config directory: {}", d.display());
|
||||
println!(
|
||||
"user config file: {}",
|
||||
d.join(config::CONFIG_FILE_NAME).display()
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"local config file: {} (current directory)",
|
||||
PathBuf::from(config::LOCAL_CONFIG_NAME).display()
|
||||
);
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
ConfigCmd::Validate => {
|
||||
let problems = config::validate(&app.config);
|
||||
if app.json() {
|
||||
print_json(&serde_json::json!({
|
||||
"valid": problems.is_empty(),
|
||||
"problems": problems,
|
||||
}));
|
||||
return Ok(if problems.is_empty() { 0 } else { 1 });
|
||||
}
|
||||
if problems.is_empty() {
|
||||
app.log
|
||||
.success(&format!("configuration is valid ({} agents)", app.catalog.len()));
|
||||
Ok(0)
|
||||
} else {
|
||||
app.log.error(&format!(
|
||||
"configuration problems:
|
||||
- {}",
|
||||
problems.join("
|
||||
- ")
|
||||
));
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
ConfigCmd::Edit => {
|
||||
let path = user_config_path(app)?;
|
||||
let editor = std::env::var("VISUAL")
|
||||
.or_else(|_| std::env::var("EDITOR"))
|
||||
.unwrap_or_else(|_| {
|
||||
if cfg!(windows) {
|
||||
"notepad".to_string()
|
||||
} else {
|
||||
"vi".to_string()
|
||||
}
|
||||
});
|
||||
let status = Command::new(&editor)
|
||||
.arg(&path)
|
||||
.status()
|
||||
.with_context(|| format!("cannot launch editor '{editor}'"))?;
|
||||
Ok(status.code().unwrap_or(1))
|
||||
}
|
||||
ConfigCmd::Add { file } => add_file(app, file),
|
||||
}
|
||||
}
|
||||
|
||||
fn user_config_path_if_any(app: &App) -> Option<PathBuf> {
|
||||
app.paths
|
||||
.config_dir
|
||||
.as_ref()
|
||||
.map(|d| d.join(config::CONFIG_FILE_NAME))
|
||||
}
|
||||
|
||||
/// Ensure the user config file exists (creating a template when needed).
|
||||
fn user_config_path(app: &App) -> Result<PathBuf> {
|
||||
let dir = app
|
||||
.paths
|
||||
.config_dir
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("cannot determine the user config directory (no HOME set)"))?;
|
||||
std::fs::create_dir_all(&dir)
|
||||
.with_context(|| format!("cannot create {}", dir.display()))?;
|
||||
let path = dir.join(config::CONFIG_FILE_NAME);
|
||||
if !path.exists() {
|
||||
std::fs::write(&path, USER_CONFIG_TEMPLATE)
|
||||
.with_context(|| format!("cannot create {}", path.display()))?;
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// config add: copy an agent definition file into the config dir and
|
||||
/// register it via an include entry in the user config file.
|
||||
fn add_file(app: &App, file: &Path) -> Result<i32> {
|
||||
config::parse_file(file)?;
|
||||
let dir = app
|
||||
.paths
|
||||
.config_dir
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("cannot determine the user config directory (no HOME set)"))?;
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
let base = file
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("agents.yaml")
|
||||
.to_string();
|
||||
let dest = dir.join(&base);
|
||||
std::fs::copy(file, &dest)
|
||||
.with_context(|| format!("cannot copy {} to {}", file.display(), dest.display()))?;
|
||||
|
||||
let cfg_path = user_config_path(app)?;
|
||||
let text = std::fs::read_to_string(&cfg_path)?;
|
||||
let mut value: serde_yaml::Value = serde_yaml::from_str(&text)
|
||||
.unwrap_or_else(|_| serde_yaml::Value::Null);
|
||||
match &mut value {
|
||||
serde_yaml::Value::Mapping(map) => {
|
||||
let key = serde_yaml::Value::String("include".to_string());
|
||||
match map.get_mut(&key) {
|
||||
Some(serde_yaml::Value::Sequence(seq)) => {
|
||||
let entry = serde_yaml::Value::String(base.clone());
|
||||
if !seq.contains(&entry) {
|
||||
seq.push(entry);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
map.insert(
|
||||
key,
|
||||
serde_yaml::Value::Sequence(vec![serde_yaml::Value::String(
|
||||
base.clone(),
|
||||
)]),
|
||||
);
|
||||
}
|
||||
}
|
||||
std::fs::write(&cfg_path, serde_yaml::to_string(&value)?)
|
||||
.with_context(|| format!("cannot write {}", cfg_path.display()))?;
|
||||
}
|
||||
_ => {
|
||||
std::fs::write(
|
||||
&cfg_path,
|
||||
format!("version: \"1.0\"\ninclude:\n - {base}\n"),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
app.log.success(&format!(
|
||||
"added {} and registered it in {}",
|
||||
dest.display(),
|
||||
cfg_path.display()
|
||||
));
|
||||
Ok(0)
|
||||
}
|
||||
Reference in New Issue
Block a user