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