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:
+162
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user