//! 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 { 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 { 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) } }