176 lines
4.9 KiB
Rust
176 lines
4.9 KiB
Rust
//! list: installed agents by default (managed, running and external), or
|
|
//! the whole catalog with --all.
|
|
|
|
use super::*;
|
|
use crate::output::{boxed_table, print_json, status_style};
|
|
use crate::probe::ExternalInfo;
|
|
use crate::state::InstalledEntry;
|
|
use serde::Serialize;
|
|
|
|
#[derive(Serialize)]
|
|
struct AgentRow {
|
|
name: String,
|
|
display_name: String,
|
|
category: Option<String>,
|
|
description: Option<String>,
|
|
status: String,
|
|
version: Option<String>,
|
|
source: String,
|
|
method: Option<String>,
|
|
path: Option<String>,
|
|
running: bool,
|
|
pid: Option<u32>,
|
|
installable: bool,
|
|
note: Option<String>,
|
|
}
|
|
|
|
/// run: installed agents only, unless all is set (whole catalog).
|
|
pub fn run(
|
|
app: &App,
|
|
all: bool,
|
|
category: Option<&str>,
|
|
running: bool,
|
|
sort: Option<&str>,
|
|
) -> Result<i32> {
|
|
let entries = app.state.all().unwrap_or_default();
|
|
let probe = crate::probe::detect_externals(app);
|
|
let mut rows: Vec<AgentRow> = Vec::new();
|
|
for agent in app.catalog.agents() {
|
|
if agent.hidden {
|
|
continue;
|
|
}
|
|
if let Some(cat) = category {
|
|
if agent.category.as_deref() != Some(cat) {
|
|
continue;
|
|
}
|
|
}
|
|
rows.push(row_for(agent, entries.get(&agent.name), &probe));
|
|
}
|
|
if !all {
|
|
rows.retain(|r| r.status == "running" || r.status == "installed" || r.status == "external");
|
|
}
|
|
if running {
|
|
rows.retain(|r| r.status == "running");
|
|
}
|
|
match sort {
|
|
Some("version") => rows.sort_by(|a, b| b.version.cmp(&a.version)),
|
|
Some("status") => rows.sort_by(|a, b| a.status.cmp(&b.status)),
|
|
Some(other) => bail!("invalid sort key '{other}' — expected name, version or status"),
|
|
None => 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.name.clone(),
|
|
r.version.clone().unwrap_or_else(|| "—".to_string()),
|
|
status_style(&r.status, theme, color),
|
|
r.source.clone(),
|
|
r.path.clone().unwrap_or_else(|| "—".to_string()),
|
|
]);
|
|
}
|
|
print!(
|
|
"{}",
|
|
boxed_table(
|
|
vec!["NAME", "AGENT", "VERSION", "STATUS", "SOURCE", "PATH"],
|
|
table_rows,
|
|
theme,
|
|
color,
|
|
)
|
|
);
|
|
if rows.is_empty() {
|
|
app.log.info(if all {
|
|
"no agents found"
|
|
} else {
|
|
"no installed agents found — run 'am list --all' for the full catalog"
|
|
});
|
|
}
|
|
Ok(0)
|
|
}
|
|
|
|
fn row_for(agent: &AgentDef, entry: Option<&InstalledEntry>, probe: &ExternalInfo) -> AgentRow {
|
|
let (status, source, path, version, running, pid, method) = if let Some(e) = entry {
|
|
let running = e.pid.map(crate::process::is_running).unwrap_or(false);
|
|
let status = if running { "running" } else { "installed" };
|
|
let path = if e
|
|
.bins
|
|
.first()
|
|
.map(|b| std::path::Path::new(b).exists())
|
|
.unwrap_or(false)
|
|
{
|
|
Some(e.bins[0].clone())
|
|
} else {
|
|
Some(e.install_dir.clone())
|
|
};
|
|
(
|
|
status.to_string(),
|
|
"managed".to_string(),
|
|
path,
|
|
e.version.clone(),
|
|
running,
|
|
e.pid,
|
|
Some(e.method.clone()),
|
|
)
|
|
} else if !agent.installable {
|
|
(
|
|
"not-installable".to_string(),
|
|
"-".to_string(),
|
|
None,
|
|
None,
|
|
false,
|
|
None,
|
|
None,
|
|
)
|
|
} else {
|
|
match probe.path_of(&agent.name) {
|
|
Some(p) => {
|
|
let version = probe
|
|
.version_of(&agent.name)
|
|
.cloned()
|
|
.flatten()
|
|
.or_else(|| agent.version.clone());
|
|
(
|
|
"external".to_string(),
|
|
"external".to_string(),
|
|
Some(p.display().to_string()),
|
|
version,
|
|
false,
|
|
None,
|
|
None,
|
|
)
|
|
}
|
|
None => (
|
|
"not-installed".to_string(),
|
|
"-".to_string(),
|
|
None,
|
|
None,
|
|
false,
|
|
None,
|
|
None,
|
|
),
|
|
}
|
|
};
|
|
AgentRow {
|
|
name: agent.name.clone(),
|
|
display_name: agent.title().to_string(),
|
|
category: agent.category.clone(),
|
|
description: agent.description.clone(),
|
|
status,
|
|
version,
|
|
source,
|
|
method,
|
|
path,
|
|
running,
|
|
pid,
|
|
installable: agent.installable,
|
|
note: agent.note.clone(),
|
|
}
|
|
}
|