Files
agent-manager/src/commands/projects_cmd.rs
T

93 lines
2.7 KiB
Rust

//! projects: which agent works on which project.
use super::*;
use crate::output::{print_json, Table};
use crate::projects::{self, ProjectSummary};
pub fn run(app: &App, name: Option<&str>) -> Result<i32> {
let summaries = projects::aggregate(app);
if let Some(n) = name {
return show_one(app, n, &summaries);
}
if app.json() {
print_json(&summaries);
return Ok(0);
}
let mut table = Table::new(vec!["PROJECT", "AGENTS", "SESSIONS", "DURATION", "LAST"]);
for s in &summaries {
table.row(vec![
short_path(&s.root),
s.dominant
.clone()
.unwrap_or_else(|| "—".to_string()),
s.sessions.to_string(),
format_duration(s.total_duration_s),
s.last_activity
.as_deref()
.map(|t| t[..t.len().min(10)].to_string())
.unwrap_or_else(|| "—".to_string()),
]);
}
print!("{}", table.render());
if summaries.is_empty() {
app.log.info("no project activity recorded yet — run or start an agent inside a git repository");
}
Ok(0)
}
fn show_one(app: &App, name: &str, summaries: &[ProjectSummary]) -> Result<i32> {
let s = summaries
.iter()
.find(|s| {
s.root == name
|| s.root.ends_with(&format!("/{name}"))
|| s.root.ends_with(&format!("\\{name}"))
})
.ok_or_else(|| anyhow!("unknown project '{name}' — 'am projects' lists them"))?;
if app.json() {
print_json(s);
return Ok(0);
}
println!("project {}", s.root);
println!(" agents: {}", s.agents.join(", "));
println!(
" dominant: {}",
s.dominant.as_deref().unwrap_or("—")
);
println!(" sessions: {}", s.sessions);
println!(" launches: {}", s.launches);
println!(" duration: {}", format_duration(s.total_duration_s));
println!(
" last: {}",
s.last_activity.as_deref().unwrap_or("—")
);
Ok(0)
}
/// Keep the tail of a long path.
fn short_path(p: &str) -> String {
let s = p.trim_end_matches(['/', '\\']);
let name = s.rsplit(['/', '\\']).next().unwrap_or(s);
let mut out = format!("…/{name}");
let full = p.chars().count();
let shown = out.chars().count();
if shown >= full || full <= 30 {
return p.to_string();
}
out.truncate(30);
out
}
fn format_duration(secs: u64) -> String {
if secs == 0 {
return "—".to_string();
}
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m{:02}s", secs / 60, secs % 60)
} else {
format!("{}h{:02}m", secs / 3600, (secs % 3600) / 60)
}
}