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,197 @@
|
||||
//! info: everything known about one agent.
|
||||
|
||||
use super::*;
|
||||
use crate::output::print_json;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct InfoJson {
|
||||
name: String,
|
||||
display_name: String,
|
||||
description: Option<String>,
|
||||
category: Option<String>,
|
||||
website: Option<String>,
|
||||
tags: Vec<String>,
|
||||
status: String,
|
||||
version: Option<String>,
|
||||
managed: bool,
|
||||
running: bool,
|
||||
pid: Option<u32>,
|
||||
installable: bool,
|
||||
note: Option<String>,
|
||||
platforms: Vec<String>,
|
||||
run: Option<String>,
|
||||
args: Vec<String>,
|
||||
env: std::collections::BTreeMap<String, String>,
|
||||
install_methods: Vec<String>,
|
||||
post_install: Vec<String>,
|
||||
dependencies: Vec<DepJson>,
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DepJson {
|
||||
name: String,
|
||||
min_version: Option<String>,
|
||||
install_hint: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(app: &App, name: &str) -> Result<i32> {
|
||||
let agent = require_agent(app, name)?;
|
||||
let entry = app.state.get(&agent.name).ok().flatten();
|
||||
let running = entry
|
||||
.as_ref()
|
||||
.and_then(|e| e.pid)
|
||||
.map(crate::process::is_running)
|
||||
.unwrap_or(false);
|
||||
let methods: Vec<String> = agent
|
||||
.install
|
||||
.as_ref()
|
||||
.map(|spec| spec.all_methods().iter().map(|m| describe_method(m)).collect())
|
||||
.unwrap_or_default();
|
||||
let deps: Vec<DepJson> = agent
|
||||
.dependencies
|
||||
.iter()
|
||||
.map(|d| DepJson {
|
||||
name: d.name.clone(),
|
||||
min_version: d.min_version.clone(),
|
||||
install_hint: d.install_hint.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if app.json() {
|
||||
print_json(&InfoJson {
|
||||
name: agent.name.clone(),
|
||||
display_name: agent.title().to_string(),
|
||||
description: agent.description.clone(),
|
||||
category: agent.category.clone(),
|
||||
website: agent.website.clone(),
|
||||
tags: agent.tags.clone(),
|
||||
status: if running {
|
||||
"running".to_string()
|
||||
} else if entry.is_some() {
|
||||
"installed".to_string()
|
||||
} else if !agent.installable {
|
||||
"not-installable".to_string()
|
||||
} else {
|
||||
"not-installed".to_string()
|
||||
},
|
||||
version: entry.as_ref().and_then(|e| e.version.clone()),
|
||||
managed: entry.is_some(),
|
||||
running,
|
||||
pid: if running { entry.as_ref().and_then(|e| e.pid) } else { None },
|
||||
installable: agent.installable,
|
||||
note: agent.note.clone(),
|
||||
platforms: agent.platforms.clone(),
|
||||
run: agent.run.clone(),
|
||||
args: agent.args.clone(),
|
||||
env: agent.env.clone(),
|
||||
install_methods: methods,
|
||||
post_install: agent
|
||||
.install
|
||||
.as_ref()
|
||||
.map(|s| s.post_install.clone())
|
||||
.unwrap_or_default(),
|
||||
dependencies: deps,
|
||||
path: entry
|
||||
.as_ref()
|
||||
.map(|e| e.install_dir.clone())
|
||||
.or_else(|| external_path(agent).map(|p| p.display().to_string())),
|
||||
});
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
println!("{}", agent.title());
|
||||
if agent.name != agent.title() {
|
||||
println!(" slug: {}", agent.name);
|
||||
}
|
||||
if let Some(d) = &agent.description {
|
||||
println!(" description: {d}");
|
||||
}
|
||||
if let Some(c) = &agent.category {
|
||||
println!(" category: {c}");
|
||||
}
|
||||
if let Some(w) = &agent.website {
|
||||
println!(" website: {w}");
|
||||
}
|
||||
if !agent.tags.is_empty() {
|
||||
println!(" tags: {}", agent.tags.join(", "));
|
||||
}
|
||||
if !agent.platforms.is_empty() {
|
||||
println!(" platforms: {}", agent.platforms.join(", "));
|
||||
}
|
||||
|
||||
// Status block
|
||||
let status = if running {
|
||||
format!("running (pid {})", entry.as_ref().and_then(|e| e.pid).unwrap_or(0))
|
||||
} else if entry.is_some() {
|
||||
format!("installed (version {})", entry.as_ref().and_then(|e| e.version.clone()).unwrap_or_else(|| "unknown".to_string()))
|
||||
} else if !agent.installable {
|
||||
"not installable locally".to_string()
|
||||
} else if external_path(agent).is_some() {
|
||||
"found on PATH (external — not managed)".to_string()
|
||||
} else {
|
||||
"not installed".to_string()
|
||||
};
|
||||
println!(" status: {status}");
|
||||
|
||||
if let Some(run) = &agent.run {
|
||||
let args = if agent.args.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" {}", agent.args.join(" "))
|
||||
};
|
||||
println!(" run: {run}{args}");
|
||||
}
|
||||
if !agent.env.is_empty() {
|
||||
println!(
|
||||
" env: {}",
|
||||
agent
|
||||
.env
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k}={v}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(spec) = &agent.install {
|
||||
println!(" install methods:");
|
||||
for (i, m) in spec.all_methods().iter().enumerate() {
|
||||
let note = m
|
||||
.note
|
||||
.as_deref()
|
||||
.map(|n| format!(" ({n})"))
|
||||
.unwrap_or_default();
|
||||
println!(" {}) {}{}", i + 1, describe_method(m), note);
|
||||
}
|
||||
if !spec.post_install.is_empty() {
|
||||
println!(" post-install: {}", spec.post_install.join(" && "));
|
||||
}
|
||||
}
|
||||
if !agent.dependencies.is_empty() {
|
||||
println!(" dependencies:");
|
||||
for d in &agent.dependencies {
|
||||
let min = d
|
||||
.min_version
|
||||
.as_deref()
|
||||
.map(|m| format!(" >= {m}"))
|
||||
.unwrap_or_default();
|
||||
let hint = d
|
||||
.install_hint
|
||||
.as_deref()
|
||||
.map(|h| format!(" — {h}"))
|
||||
.unwrap_or_default();
|
||||
println!(" - {}{}{}", d.name, min, hint);
|
||||
}
|
||||
}
|
||||
if let Some(n) = &agent.note {
|
||||
println!(" note: {n}");
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn external_path(agent: &AgentDef) -> Option<std::path::PathBuf> {
|
||||
let first = agent.first_token()?;
|
||||
which::which(first).ok()
|
||||
}
|
||||
Reference in New Issue
Block a user