v0.2.5 : info enrichi (statut, versions, dependances verifiees) + self-uninstall tolerant (fichiers verrouilles / lecture seule, balayage des dossiers vides) + agent deepseek-harness au catalogue

This commit is contained in:
2026-08-15 21:10:14 -04:00
parent 98aa75d185
commit 61f4909bc6
7 changed files with 344 additions and 71 deletions
+134 -48
View File
@@ -2,6 +2,7 @@
use super::*;
use crate::output::print_json;
use colored::Colorize;
use serde::Serialize;
#[derive(Serialize)]
@@ -101,51 +102,102 @@ pub fn run(app: &App, name: &str) -> Result<i32> {
return Ok(0);
}
println!("{}", agent.title());
if agent.name != agent.title() {
println!(" slug: {}", agent.name);
}
if let Some(d) = &agent.description {
println!(" description: {d}");
}
let color = app.color();
let dim = |s: &str| -> String {
if color {
s.dimmed().to_string()
} else {
s.to_string()
}
};
let bold = |s: &str| -> String {
if color {
s.bold().to_string()
} else {
s.to_string()
}
};
// --- Header -------------------------------------------------------------
println!("{}", bold(agent.title()));
let mut sub: Vec<String> = vec![agent.name.clone()];
if let Some(c) = &agent.category {
println!(" category: {c}");
sub.push(c.clone());
}
println!(" {}", dim(&sub.join(" · ")));
if let Some(d) = &agent.description {
println!();
println!(" {d}");
}
if let Some(w) = &agent.website {
println!(" website: {w}");
}
if !agent.tags.is_empty() {
println!(" tags: {}", agent.tags.join(", "));
}
if !agent.platforms.is_empty() {
println!(" platforms: {}", agent.platforms.join(", "));
println!();
println!(" {} {}", dim("website"), w);
}
println!();
// Status block
let status = if running {
format!("running (pid {})", entry.as_ref().and_then(|e| e.pid).unwrap_or(0))
// --- Status --------------------------------------------------------------
let external = external_path(agent);
let (status_word, detail): (&str, String) = if running {
let pid = entry.as_ref().and_then(|e| e.pid).unwrap_or(0);
("running", format!(" (pid {pid})"))
} else if entry.is_some() {
format!("installed (version {})", entry.as_ref().and_then(|e| e.version.clone()).unwrap_or_else(|| "unknown".to_string()))
let v = entry
.as_ref()
.and_then(|e| e.version.clone())
.map(|v| format!(" — v{v}"))
.unwrap_or_default();
("installed", v)
} else if !agent.installable {
"not installable locally".to_string()
} else if external_path(agent).is_some() {
"found on PATH (external — not managed)".to_string()
("not-installable", String::new())
} else if external.is_some() {
("external", " (found on PATH — not managed)".to_string())
} else {
"not installed".to_string()
("not-installed", String::new())
};
println!(" status: {status}");
let word = crate::output::status_style(status_word, color);
println!(" {} {}", dim("● status"), format!("{word}{detail}"));
// Version: recorded for managed installs, detected for external ones.
let version_display: Option<String> = entry
.as_ref()
.and_then(|e| e.version.clone())
.or_else(|| external.as_ref().and_then(|p| crate::probe::detect_external_version(p)));
if let Some(v) = version_display {
println!(" {} {}", dim("● version"), v);
}
if let Some(t) = entry.as_ref().map(|e| e.installed_at.clone()) {
println!(" {} {}", dim("● installed"), t);
}
if let Some(t) = entry.as_ref().and_then(|e| e.updated_at.clone()) {
println!(" {} {}", dim("● updated"), t);
}
if let Some(p) = entry
.as_ref()
.map(|e| e.install_dir.clone())
.or_else(|| external.clone().map(|p| p.display().to_string()))
{
println!(" {} {}", dim("● path"), p);
}
if let Some(e) = &entry {
if let Some(bin) = e.bins.first() {
println!(" {} {}", dim("● bin"), bin);
}
}
// --- How to run it -------------------------------------------------------
if let Some(run) = &agent.run {
let args = if agent.args.is_empty() {
String::new()
} else {
format!(" {}", agent.args.join(" "))
};
println!(" run: {run}{args}");
println!();
println!(" {} {}", dim("▶ run"), format!("{run}{args}"));
}
if !agent.env.is_empty() {
println!(
" env: {}",
" {} {}",
dim("⚙ env"),
agent
.env
.iter()
@@ -155,38 +207,72 @@ pub fn run(app: &App, name: &str) -> Result<i32> {
);
}
// --- Installation ---------------------------------------------------------
if let Some(spec) = &agent.install {
println!(" install methods:");
println!();
println!(" {}", bold("install"));
for (i, m) in spec.all_methods().iter().enumerate() {
let note = m
.note
.as_deref()
.map(|n| format!(" ({n})"))
.unwrap_or_default();
println!(" {}) {}{}", i + 1, describe_method(m), note);
println!(" {}) {}", i + 1, describe_method(m));
if let Some(n) = &m.note {
println!(" {}", dim(n));
}
}
if !spec.post_install.is_empty() {
println!(" post-install: {}", spec.post_install.join(" && "));
println!(
" {}",
dim(&format!("post-install: {}", spec.post_install.join(" && ")))
);
}
}
// --- Dependencies, checked live ------------------------------------------
if !agent.dependencies.is_empty() {
println!(" dependencies:");
for d in &agent.dependencies {
let min = d
.min_version
.as_deref()
.map(|m| format!(" >= {m}"))
.unwrap_or_default();
let hint = d
.install_hint
.as_deref()
.map(|h| format!(" — {h}"))
.unwrap_or_default();
println!(" - {}{}{}", d.name, min, hint);
println!();
println!(" {}", bold("dependencies"));
let runner = crate::runner::SystemRunner::new(false, app.cli.verbose, &app.log);
let checks = crate::deps::check_agent(agent, &runner);
for c in &checks {
let mark = if c.ok { "✓" } else { "✗" };
let mark = if color {
if c.ok {
mark.green().to_string()
} else {
mark.red().to_string()
}
} else {
mark.to_string()
};
let mut line = format!(" {mark} {}", c.name);
if let Some(v) = &c.version {
line.push_str(&format!(" {v}"));
}
if let Some(min) = &c.min {
line.push_str(&format!(" (min {min})"));
}
if !c.ok {
if let Some(hint) = agent
.dependencies
.iter()
.find(|d| d.name == c.name)
.and_then(|d| d.install_hint.clone())
{
line.push_str(&format!(" — {hint}"));
} else if !c.detail.is_empty() && c.detail != "not found on PATH" {
line.push_str(&format!(" — {}", c.detail));
}
}
println!("{line}");
}
}
// --- Tags and note ---------------------------------------------------------
if !agent.tags.is_empty() {
println!();
println!(" {}", dim(&format!("tags {}", agent.tags.join(" · "))));
}
if let Some(n) = &agent.note {
println!(" note: {n}");
println!();
println!(" {}", dim(&format!("note {n}")));
}
Ok(0)
}