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:
2026-08-15 11:26:34 -04:00
commit 8ee3ddf3db
79 changed files with 15724 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
//! Application context: configuration, catalog, state, paths, logger and
//! shared flags.
use crate::catalog::Catalog;
use crate::cli::Cli;
use crate::config::{self, Config, Settings};
use crate::output::Logger;
use crate::state::StateStore;
use anyhow::{Context, Result};
use std::io::{IsTerminal, Write};
use std::path::PathBuf;
#[derive(Debug)]
pub struct App {
pub cli: Cli,
pub config: Config,
pub config_origin: Option<PathBuf>,
pub paths: Paths,
pub catalog: Catalog,
pub state: StateStore,
pub log: Logger,
}
#[derive(Debug, Clone)]
pub struct Paths {
pub install_dir: PathBuf,
pub bin_dir: PathBuf,
pub log_dir: PathBuf,
pub state_file: PathBuf,
pub probe_cache_file: PathBuf,
pub config_dir: Option<PathBuf>,
}
impl Paths {
pub fn from_settings(settings: &Settings) -> Self {
let install_dir = settings
.install_dir
.as_deref()
.map(config::expand_path)
.unwrap_or_else(|| data_dir().join("agents"));
let log_dir = settings
.log_dir
.as_deref()
.map(config::expand_path)
.unwrap_or_else(|| state_dir().join("logs"));
let bin_dir = install_dir.join("bin");
let state_file = state_dir().join("state.json");
let probe_cache_file = state_dir().join("probe-cache.json");
let config_dir = config::user_config_dir();
Self {
install_dir,
bin_dir,
log_dir,
state_file,
probe_cache_file,
config_dir,
}
}
/// Create every directory the manager needs.
pub fn ensure(&self) -> Result<()> {
std::fs::create_dir_all(&self.install_dir)
.with_context(|| format!("cannot create {}", self.install_dir.display()))?;
std::fs::create_dir_all(&self.bin_dir)?;
std::fs::create_dir_all(&self.log_dir)?;
Ok(())
}
}
impl App {
pub fn from_cli(cli: Cli) -> Result<Self> {
let loaded = config::load(&cli)?;
let paths = Paths::from_settings(&loaded.config.settings);
let log = Logger::new(paths.log_dir.clone(), cli.verbose, cli.quiet, cli.no_color);
let state = StateStore::new(paths.state_file.clone());
let catalog = Catalog::new(
loaded.config.agents.clone(),
loaded.config.aliases.clone(),
loaded.config.groups.clone(),
);
Ok(Self {
cli,
config: loaded.config,
config_origin: loaded.origin,
paths,
catalog,
state,
log,
})
}
pub fn dry_run(&self) -> bool {
self.cli.dry_run
}
pub fn json(&self) -> bool {
self.cli.json
}
pub fn yes(&self) -> bool {
self.cli.yes
}
pub fn color(&self) -> bool {
!self.cli.no_color && std::io::stdout().is_terminal()
}
/// Ask the user for confirmation on stderr. Accepts y/yes/o/oui.
pub fn confirm(&self, question: &str) -> Result<bool> {
if self.cli.yes {
return Ok(true);
}
eprint!("{question} [y/N] ");
std::io::stderr().flush().ok();
let mut line = String::new();
std::io::stdin().read_line(&mut line).ok();
let answer = line.trim().to_lowercase();
Ok(matches!(answer.as_str(), "y" | "yes" | "o" | "oui" | "true"))
}
}
/// Root directory for agent data (overridable with AGENT_MANAGER_DATA,
/// mostly used by tests).
pub fn data_dir() -> PathBuf {
if let Some(d) = std::env::var_os("AGENT_MANAGER_DATA") {
return PathBuf::from(d);
}
if cfg!(windows) {
let base = std::env::var_os("LOCALAPPDATA")
.map(PathBuf::from)
.or_else(|| config::home_dir().map(|h| h.join(".local").join("share")));
return base
.map(|b| b.join("agent-manager"))
.unwrap_or_else(|| PathBuf::from(".agent-manager"));
}
if cfg!(target_os = "macos") {
return config::home_dir()
.map(|h| h.join("Library").join("Application Support").join("agent-manager"))
.unwrap_or_else(|| PathBuf::from(".agent-manager"));
}
let base = std::env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.or_else(|| config::home_dir().map(|h| h.join(".local").join("share")));
base.map(|b| b.join("agent-manager"))
.unwrap_or_else(|| PathBuf::from(".agent-manager"))
}
/// Root directory for runtime state (logs, state file).
pub fn state_dir() -> PathBuf {
if let Some(d) = std::env::var_os("AGENT_MANAGER_STATE") {
return PathBuf::from(d);
}
if cfg!(target_os = "linux") {
let base = std::env::var_os("XDG_STATE_HOME")
.map(PathBuf::from)
.or_else(|| config::home_dir().map(|h| h.join(".local").join("state")));
if let Some(b) = base {
return b.join("agent-manager");
}
}
data_dir()
}