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,113 @@
|
||||
//! uninstall: stop the agent if needed, remove its files, bins and state
|
||||
//! entry; --purge also removes logs and the user-config definition.
|
||||
|
||||
use super::*;
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn run(app: &App, agent_name: &str, purge: bool) -> Result<i32> {
|
||||
let agent = require_agent(app, agent_name)?;
|
||||
let entry = app.state.get(&agent.name).ok().flatten();
|
||||
let Some(entry) = entry else {
|
||||
app.log.info(&format!(
|
||||
"agent '{}' is not managed by agent-manager — nothing to uninstall",
|
||||
agent.name
|
||||
));
|
||||
return Ok(0);
|
||||
};
|
||||
|
||||
// Stop it first when running.
|
||||
if let Some(pid) = entry.pid {
|
||||
if crate::process::is_running(pid) {
|
||||
app.log.info(&format!("stopping running agent (pid {pid})"));
|
||||
if app.dry_run() {
|
||||
app.log.dry(format!("would stop pid {pid}"));
|
||||
} else {
|
||||
crate::process::stop_pid(pid, false, app.config.settings.stop_timeout());
|
||||
app.state.update_pid(&agent.name, None, None)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dir = PathBuf::from(&entry.install_dir);
|
||||
if app.dry_run() {
|
||||
app.log.dry(format!("would remove {}", dir.display()));
|
||||
} else {
|
||||
if dir.exists() {
|
||||
std::fs::remove_dir_all(&dir)
|
||||
.with_context(|| format!("cannot remove {}", dir.display()))?;
|
||||
}
|
||||
for b in &entry.bins {
|
||||
let p = PathBuf::from(b);
|
||||
if p.is_file() {
|
||||
std::fs::remove_file(&p)
|
||||
.with_context(|| format!("cannot remove {}", p.display()))?;
|
||||
}
|
||||
}
|
||||
app.state.remove(&agent.name)?;
|
||||
}
|
||||
|
||||
if purge {
|
||||
let log = crate::process::agent_log_path(app, &agent.name);
|
||||
if app.dry_run() {
|
||||
app.log.dry(format!("would remove log {}", log.display()));
|
||||
} else if log.exists() {
|
||||
std::fs::remove_file(&log).ok();
|
||||
}
|
||||
remove_from_user_config(app, &agent.name)?;
|
||||
}
|
||||
|
||||
app.log.success(&format!(
|
||||
"uninstalled {} (files removed{})",
|
||||
agent.title(),
|
||||
if purge { ", config purged" } else { "" }
|
||||
));
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Remove the agent entry from the user config file (--purge).
|
||||
fn remove_from_user_config(app: &App, name: &str) -> Result<()> {
|
||||
let Some(path) = &app.config_origin else {
|
||||
app.log.verbose("no user config file — nothing to purge");
|
||||
return Ok(());
|
||||
};
|
||||
let is_json = path.extension().map(|e| e == "json").unwrap_or(false);
|
||||
if is_json {
|
||||
app.log
|
||||
.warn("config purge is not supported for JSON config files");
|
||||
return Ok(());
|
||||
}
|
||||
let text = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("cannot read {}", path.display()))?;
|
||||
let mut value: serde_yaml::Value = serde_yaml::from_str(&text)
|
||||
.with_context(|| format!("cannot parse {}", path.display()))?;
|
||||
let mut removed = false;
|
||||
if let serde_yaml::Value::Mapping(map) = &mut value {
|
||||
if let Some(serde_yaml::Value::Sequence(seq)) = map.get_mut("agents") {
|
||||
let before = seq.len();
|
||||
seq.retain(|v| {
|
||||
v.get("name")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|n| n != name)
|
||||
.unwrap_or(true)
|
||||
});
|
||||
removed = seq.len() != before;
|
||||
}
|
||||
}
|
||||
if !removed {
|
||||
app.log.verbose(&format!("'{name}' not defined in {}", path.display()));
|
||||
return Ok(());
|
||||
}
|
||||
let backup = path.with_extension("yaml.bak");
|
||||
std::fs::copy(path, &backup)
|
||||
.with_context(|| format!("cannot create backup {}", backup.display()))?;
|
||||
std::fs::write(path, serde_yaml::to_string(&value)?)
|
||||
.with_context(|| format!("cannot write {}", path.display()))?;
|
||||
app.log.info(&format!(
|
||||
"removed '{name}' from {} (backup: {})",
|
||||
path.display(),
|
||||
backup.display()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user