- 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
136 lines
4.5 KiB
Rust
136 lines
4.5 KiB
Rust
//! export/import: back up the configuration and installation state.
|
|
|
|
use super::*;
|
|
use crate::config::{Config, Settings};
|
|
use crate::state::StateFile;
|
|
use crate::output::print_json;
|
|
use anyhow::{anyhow, Context, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::Path;
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct ExportFile {
|
|
tool: String,
|
|
version: String,
|
|
generated_at: String,
|
|
config: Config,
|
|
state: StateFile,
|
|
}
|
|
|
|
pub fn export_cmd(app: &App, output: &Path) -> Result<i32> {
|
|
let export = ExportFile {
|
|
tool: "agent-manager".to_string(),
|
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
|
generated_at: crate::installers::now_rfc3339(),
|
|
config: app.config.clone(),
|
|
state: app.state.load()?,
|
|
};
|
|
if app.dry_run() {
|
|
app.log
|
|
.dry(format!("would write {}", output.display()));
|
|
return Ok(0);
|
|
}
|
|
let text = serde_json::to_string_pretty(&export)?;
|
|
std::fs::write(output, text)
|
|
.with_context(|| format!("cannot write {}", output.display()))?;
|
|
app.log
|
|
.success(&format!("exported config + state to {}", output.display()));
|
|
Ok(0)
|
|
}
|
|
|
|
pub fn import_cmd(app: &App, file: &Path) -> Result<i32> {
|
|
let text = std::fs::read_to_string(file)
|
|
.with_context(|| format!("cannot read {}", file.display()))?;
|
|
let export: ExportFile = serde_json::from_str(&text)
|
|
.with_context(|| format!("{} is not a valid agent-manager export", file.display()))?;
|
|
if app.dry_run() {
|
|
app.log
|
|
.dry(format!("would import from {}", file.display()));
|
|
return Ok(0);
|
|
}
|
|
|
|
// State: merge entries (existing ones win).
|
|
let mut sf = app.state.load()?;
|
|
let mut imported_state = 0;
|
|
for (name, entry) in export.state.installed {
|
|
if !sf.installed.contains_key(&name) {
|
|
sf.installed.insert(name, entry);
|
|
imported_state += 1;
|
|
}
|
|
}
|
|
app.state.save(&sf)?;
|
|
|
|
// Config: write the exported agents to a file and include it.
|
|
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 ts = chrono::Local::now().format("%Y%m%d-%H%M%S");
|
|
let fname = format!("imported-{ts}.yaml");
|
|
let dest = dir.join(&fname);
|
|
let cfg = Config {
|
|
version: "1.0".to_string(),
|
|
settings: Settings::default(),
|
|
aliases: Default::default(),
|
|
groups: Default::default(),
|
|
include: vec![],
|
|
agents: export.config.agents.clone(),
|
|
};
|
|
std::fs::write(&dest, serde_yaml::to_string(&cfg)?)
|
|
.with_context(|| format!("cannot write {}", dest.display()))?;
|
|
|
|
let cfg_path = dir.join(crate::config::CONFIG_FILE_NAME);
|
|
if !cfg_path.exists() {
|
|
std::fs::write(
|
|
&cfg_path,
|
|
format!("version: \"1.0\"\ninclude:\n - {fname}\n"),
|
|
)?;
|
|
} else {
|
|
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);
|
|
if let serde_yaml::Value::Mapping(map) = &mut value {
|
|
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(fname.clone());
|
|
if !seq.contains(&entry) {
|
|
seq.push(entry);
|
|
}
|
|
}
|
|
_ => {
|
|
map.insert(
|
|
key,
|
|
serde_yaml::Value::Sequence(vec![serde_yaml::Value::String(
|
|
fname.clone(),
|
|
)]),
|
|
);
|
|
}
|
|
}
|
|
std::fs::write(&cfg_path, serde_yaml::to_string(&value)?)?;
|
|
}
|
|
}
|
|
|
|
if app.json() {
|
|
print_json(&serde_json::json!({
|
|
"agents_imported": export.config.agents.len(),
|
|
"state_entries_imported": imported_state,
|
|
"config_file": dest.display().to_string(),
|
|
}));
|
|
} else {
|
|
app.log.success(&format!(
|
|
"imported {} agent definition(s) and {} state entrie(s) from {}",
|
|
export.config.agents.len(),
|
|
imported_state,
|
|
file.display()
|
|
));
|
|
app.log.info(&format!(
|
|
"definitions written to {}",
|
|
dest.display()
|
|
));
|
|
}
|
|
Ok(0)
|
|
}
|