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,130 @@
|
||||
//! pip and uv installers: a private virtualenv per agent keeps everything
|
||||
//! self-contained (no --user, no system pollution).
|
||||
|
||||
use super::*;
|
||||
|
||||
fn find_python(runner: &dyn Runner) -> Result<PathBuf> {
|
||||
runner
|
||||
.which("python3")
|
||||
.or_else(|| runner.which("python"))
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"neither 'python3' nor 'python' found on PATH — install Python first (https://python.org)"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn venv_bin_dir(venv: &Path) -> PathBuf {
|
||||
if cfg!(windows) {
|
||||
venv.join("Scripts")
|
||||
} else {
|
||||
venv.join("bin")
|
||||
}
|
||||
}
|
||||
|
||||
fn venv_python(venv: &Path) -> PathBuf {
|
||||
venv_bin_dir(venv).join(if cfg!(windows) {
|
||||
"python.exe"
|
||||
} else {
|
||||
"python"
|
||||
})
|
||||
}
|
||||
|
||||
/// Shared implementation for pip (use_uv=false) and uv (use_uv=true).
|
||||
pub fn install(
|
||||
app: &App,
|
||||
agent: &AgentDef,
|
||||
method: &MethodSpec,
|
||||
runner: &dyn Runner,
|
||||
use_uv: bool,
|
||||
) -> Result<InstallOutcome> {
|
||||
let pkg = method
|
||||
.package
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow!("pip/uv install requires 'package'"))?;
|
||||
let python = find_python(runner)?;
|
||||
let root = install_root(app, &agent.name);
|
||||
let venv = root.join("venv");
|
||||
ensure_dir(app, &root)?;
|
||||
if !app.dry_run() && venv.exists() {
|
||||
std::fs::remove_dir_all(&venv)
|
||||
.with_context(|| format!("cannot remove {}", venv.display()))?;
|
||||
}
|
||||
let empty = BTreeMap::new();
|
||||
sh(
|
||||
runner,
|
||||
&python.display().to_string(),
|
||||
&[
|
||||
"-m".to_string(),
|
||||
"venv".to_string(),
|
||||
venv.display().to_string(),
|
||||
],
|
||||
&empty,
|
||||
None,
|
||||
)?;
|
||||
let vp = venv_python(&venv);
|
||||
let vbin = venv_bin_dir(&venv);
|
||||
if use_uv {
|
||||
let uv = require_tool(
|
||||
runner,
|
||||
"uv",
|
||||
"https://docs.astral.sh/uv — e.g. 'pip install uv'",
|
||||
)?;
|
||||
let mut args: Vec<String> = vec![
|
||||
"pip".into(),
|
||||
"install".into(),
|
||||
"--python".into(),
|
||||
vp.display().to_string(),
|
||||
pkg.to_string(),
|
||||
];
|
||||
args.extend(method.args.iter().cloned());
|
||||
sh(runner, &uv.display().to_string(), &args, &method.env, None)?;
|
||||
} else {
|
||||
let mut args: Vec<String> = vec![
|
||||
"-m".into(),
|
||||
"pip".into(),
|
||||
"install".into(),
|
||||
"--upgrade".into(),
|
||||
pkg.to_string(),
|
||||
];
|
||||
args.extend(method.args.iter().cloned());
|
||||
sh(runner, &vp.display().to_string(), &args, &method.env, None)?;
|
||||
}
|
||||
let expected = expected_bin_names(agent, method);
|
||||
let mut bins = find_bins(&vbin, &expected);
|
||||
if bins.is_empty() {
|
||||
bins = find_bins(&bin_dir(app), &expected);
|
||||
}
|
||||
let version = bins.first().and_then(|b| detect_version(runner, b));
|
||||
Ok(InstallOutcome { bins, version })
|
||||
}
|
||||
|
||||
/// Latest published version from the PyPI JSON API.
|
||||
pub fn latest(
|
||||
_app: &App,
|
||||
_agent: &AgentDef,
|
||||
method: &MethodSpec,
|
||||
_runner: &dyn Runner,
|
||||
) -> Result<Option<String>> {
|
||||
let Some(pkg) = method.package.as_deref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let url = format!("https://pypi.org/pypi/{pkg}/json");
|
||||
let resp = match ureq::get(&url)
|
||||
.set("User-Agent", crate::download::USER_AGENT)
|
||||
.call()
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
let Ok(text) = resp.into_string() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(v.get("info")
|
||||
.and_then(|i| i.get("version"))
|
||||
.and_then(|x| x.as_str())
|
||||
.map(|s| s.to_string()))
|
||||
}
|
||||
Reference in New Issue
Block a user