1use crate::core::config;
5use crate::shell::cli::{AgentScanStats, scan_all_agents_with_stats};
6use crate::store::Store;
7use anyhow::Result;
8use serde::Serialize;
9use std::path::{Path, PathBuf};
10
11#[derive(Serialize)]
12struct LoadJson {
13 workspace_count: usize,
14 totals: AgentScanStats,
15 workspaces: Vec<LoadWorkspaceJson>,
16}
17
18#[derive(Serialize)]
19struct LoadWorkspaceJson {
20 workspace: String,
21 #[serde(flatten)]
22 stats: AgentScanStats,
23}
24
25pub fn cmd_load(workspace: Option<&Path>, json: bool) -> Result<()> {
26 print!("{}", load_text(workspace, json)?);
27 Ok(())
28}
29
30pub fn load_text(workspace: Option<&Path>, json: bool) -> Result<String> {
31 let roots = load_roots(workspace)?;
32 let mut totals = AgentScanStats::default();
33 let mut rows = Vec::new();
34 for root in roots {
35 let stats = load_one(&root)?;
36 totals.merge(&stats);
37 rows.push(LoadWorkspaceJson {
38 workspace: root.to_string_lossy().to_string(),
39 stats,
40 });
41 }
42 render_load(rows, totals, json)
43}
44
45fn load_roots(workspace: Option<&Path>) -> Result<Vec<PathBuf>> {
46 if let Some(path) = workspace {
47 return Ok(vec![crate::core::paths::canonical(path)]);
48 }
49 let roots = crate::core::workspace::machine_workspaces(None)?;
50 if roots.is_empty() {
51 return crate::core::workspace::resolve(None).map(|p| vec![p]);
52 }
53 Ok(roots)
54}
55
56fn load_one(workspace: &Path) -> Result<AgentScanStats> {
57 let cfg = config::load(workspace)?;
58 let store = Store::open(&crate::core::workspace::db_path(workspace)?)?;
59 let ws = workspace.to_string_lossy().to_string();
60 scan_all_agents_with_stats(workspace, &cfg, &ws, &store)
61}
62
63fn render_load(rows: Vec<LoadWorkspaceJson>, totals: AgentScanStats, json: bool) -> Result<String> {
64 if json {
65 return Ok(format!(
66 "{}\n",
67 serde_json::to_string_pretty(&LoadJson {
68 workspace_count: rows.len(),
69 totals,
70 workspaces: rows,
71 })?
72 ));
73 }
74 Ok(render_text(&rows, &totals))
75}
76
77fn render_text(rows: &[LoadWorkspaceJson], totals: &AgentScanStats) -> String {
78 use std::fmt::Write;
79 let mut out = String::new();
80 writeln!(&mut out, "Loaded {} workspace(s)", rows.len()).unwrap();
81 for row in rows {
82 writeln!(
83 &mut out,
84 "{}: sessions {} events {} agents {}",
85 row.workspace,
86 row.stats.sessions_upserted,
87 row.stats.events_upserted,
88 agents_label(&row.stats),
89 )
90 .unwrap();
91 }
92 writeln!(
93 &mut out,
94 "Total: sessions {} events {} agents {}",
95 totals.sessions_upserted,
96 totals.events_upserted,
97 agents_label(totals),
98 )
99 .unwrap();
100 out
101}
102
103fn agents_label(stats: &AgentScanStats) -> String {
104 if stats.agents.is_empty() {
105 "-".into()
106 } else {
107 stats.agents.iter().cloned().collect::<Vec<_>>().join(",")
108 }
109}