Files
agent-manager/src/commands/status_cmd.rs
T
bruno 14f873bfdd v0.2.14 : aide Nushell, themes de couleurs et cadres blancs
- Aide facon Nushell : help <cmd>, <cmd> -h/--help, help, help commands,
  help --find <texte>, help <agent> (fiche info) - sections Usage/Flags/
  Command Type/Parameters/Input-output/Examples avec couleurs
- Commande version : tableau Nushell (commit, build, rustc, theme...) + --json ;
  build.rs pour commit/branche git ; -V reste la version courte
- Themes : --theme <nom> (default, ocean, sunset, forest, dracula, mono)
  + settings.theme dans la config ; activation VT Windows pour les couleurs
- Cadres blancs Nushell partages : list, search, status, doctor, ls/ps/
  where/get et menu d autocompletion Tab encadre (colonnes name/description)
2026-08-16 12:52:12 -04:00

203 lines
6.4 KiB
Rust

//! status: detailed state of one agent, or of every installed agent.
use super::*;
use crate::output::{boxed_table, kv_table, print_json, status_style};
use crate::probe::ExternalInfo;
use crate::state::InstalledEntry;
use serde::Serialize;
#[derive(Serialize)]
struct StatusDetail {
name: String,
display_name: String,
status: String,
version: Option<String>,
method: Option<String>,
path: Option<String>,
run: Option<String>,
pid: Option<u32>,
started_at: Option<String>,
installed_at: Option<String>,
log: Option<String>,
managed: bool,
}
pub fn run(app: &App, agent_name: Option<&str>) -> Result<i32> {
let probe = crate::probe::detect_externals(app);
match agent_name {
Some(name) => status_one(app, name, &probe),
None => status_all(app, &probe),
}
}
fn status_one(app: &App, name: &str, probe: &ExternalInfo) -> Result<i32> {
let agent = require_agent(app, name)?;
let entry: Option<InstalledEntry> = app.state.get(&agent.name).ok().flatten();
let detail = detail_for(app, agent, entry.as_ref(), probe);
if app.json() {
print_json(&detail);
return Ok(0);
}
let color = app.color();
let theme = app.theme();
println!("{} ({})", agent.title(), agent.name);
let status_line = status_style(&detail.status, theme, color);
let mut rows: Vec<(String, String)> = vec![
("agent".to_string(), agent.name.clone()),
("status".to_string(), status_line),
(
"version".to_string(),
detail.version.clone().unwrap_or_else(|| "unknown".to_string()),
),
(
"method".to_string(),
detail.method.clone().unwrap_or_else(|| "—".to_string()),
),
(
"path".to_string(),
detail.path.clone().unwrap_or_else(|| "—".to_string()),
),
(
"run".to_string(),
detail.run.clone().unwrap_or_else(|| "—".to_string()),
),
];
if let Some(pid) = detail.pid {
rows.push(("pid".to_string(), pid.to_string()));
}
if let Some(t) = &detail.started_at {
rows.push(("started".to_string(), t.clone()));
}
if let Some(t) = &detail.installed_at {
rows.push(("installed".to_string(), t.clone()));
}
if let Some(l) = &detail.log {
rows.push(("log".to_string(), l.clone()));
}
print!("{}", kv_table(&rows, theme, color));
Ok(0)
}
fn status_all(app: &App, probe: &ExternalInfo) -> Result<i32> {
let entries = app.state.all().unwrap_or_default();
let mut rows: Vec<AgentRow> = Vec::new();
for agent in app.catalog.agents() {
if agent.hidden {
continue;
}
let entry = entries.get(&agent.name);
let d = detail_for(app, agent, entry, probe);
if d.status == "running" || d.status == "installed" || d.status == "external" {
rows.push(AgentRow {
name: agent.name.clone(),
display_name: agent.title().to_string(),
status: d.status,
version: d.version,
pid: d.pid,
path: d.path,
});
}
}
rows.sort_by(|a, b| a.name.cmp(&b.name));
if app.json() {
print_json(&rows);
return Ok(0);
}
let color = app.color();
let theme = app.theme();
let mut table_rows = Vec::new();
for r in &rows {
table_rows.push(vec![
r.display_name.clone(),
r.version.clone().unwrap_or_else(|| "—".to_string()),
status_style(&r.status, theme, color),
r.pid.map(|p| p.to_string()).unwrap_or_else(|| "—".to_string()),
r.path.clone().unwrap_or_else(|| "—".to_string()),
]);
}
print!(
"{}",
boxed_table(
vec!["NAME", "VERSION", "STATUS", "PID", "PATH"],
table_rows,
theme,
color,
)
);
if rows.is_empty() {
app.log.info("no installed or running agents");
}
Ok(0)
}
#[derive(Serialize)]
struct AgentRow {
name: String,
display_name: String,
status: String,
version: Option<String>,
pid: Option<u32>,
path: Option<String>,
}
fn detail_for(
app: &App,
agent: &AgentDef,
entry: Option<&InstalledEntry>,
probe: &ExternalInfo,
) -> StatusDetail {
match entry {
Some(e) => {
let running = e.pid.map(crate::process::is_running).unwrap_or(false);
let status = if running { "running" } else { "installed" };
let path = e
.bins
.first()
.filter(|b| std::path::Path::new(b).exists())
.cloned()
.or_else(|| Some(e.install_dir.clone()));
StatusDetail {
name: agent.name.clone(),
display_name: agent.title().to_string(),
status: status.to_string(),
version: e.version.clone(),
method: Some(e.method.clone()),
path,
run: Some(e.run.clone()),
pid: if running { e.pid } else { None },
started_at: if running { e.started_at.clone() } else { None },
installed_at: Some(e.installed_at.clone()),
log: Some(crate::process::agent_log_path(app, &agent.name).display().to_string()),
managed: true,
}
}
None => {
let external = probe.path_of(&agent.name).cloned();
let version = external
.as_ref()
.and_then(|_p| probe.version_of(&agent.name).cloned().flatten())
.or_else(|| agent.version.clone());
StatusDetail {
name: agent.name.clone(),
display_name: agent.title().to_string(),
status: if external.is_some() {
"external".to_string()
} else if agent.installable {
"not-installed".to_string()
} else {
"not-installable".to_string()
},
version,
method: None,
path: external.map(|p| p.display().to_string()),
run: agent.run.clone(),
pid: None,
started_at: None,
installed_at: None,
log: None,
managed: false,
}
}
}
}