projects : am projects (agregats par depot depuis le journal) + profils de projet dans la config (issue #29) - agent dominant, sessions, durees, cache projects.json rafraichi par doctor --fix
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
//! 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user