1use anyhow::Result;
8
9use crate::app_store::{AppStore, SqliteAppStore};
10use crate::config::Config;
11
12pub async fn run_stats(config: &Config) -> Result<()> {
14 let store = SqliteAppStore::connect(config).await?;
15 let stats = store.stats().await?;
16
17 println!("Context Harness — Database Stats");
18 println!("================================");
19 println!();
20 println!(" Database: {}", config.db.path.display());
21 println!(" Size: {}", format_bytes(stats.db_size_bytes));
22 println!();
23 println!(" Documents: {}", stats.total_docs);
24 println!(" Chunks: {}", stats.total_chunks);
25 println!(
26 " Embedded: {} / {} ({}%)",
27 stats.total_embedded,
28 stats.total_chunks,
29 if stats.total_chunks > 0 {
30 (stats.total_embedded * 100) / stats.total_chunks
31 } else {
32 0
33 }
34 );
35
36 if !stats.sources.is_empty() {
37 println!();
38 println!(" By source:");
39 println!(
40 " {:<24} {:>6} {:>8} {:>10} LAST SYNC",
41 "SOURCE", "DOCS", "CHUNKS", "EMBEDDED"
42 );
43 println!(" {}", "-".repeat(76));
44
45 for s in &stats.sources {
46 let sync_display = match s.last_sync_ts {
47 Some(ts) => format_ts_relative(ts),
48 None => "never".to_string(),
49 };
50 println!(
51 " {:<24} {:>6} {:>8} {:>10} {}",
52 s.source, s.doc_count, s.chunk_count, s.embedded_count, sync_display
53 );
54 }
55 }
56
57 println!();
58
59 store.close().await;
60 Ok(())
61}
62
63fn format_bytes(bytes: u64) -> String {
65 if bytes < 1024 {
66 format!("{} B", bytes)
67 } else if bytes < 1024 * 1024 {
68 format!("{:.1} KB", bytes as f64 / 1024.0)
69 } else if bytes < 1024 * 1024 * 1024 {
70 format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
71 } else {
72 format!("{:.2} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
73 }
74}
75
76fn format_ts_relative(ts: i64) -> String {
78 let now = chrono::Utc::now().timestamp();
79 let delta = now - ts;
80
81 if delta < 0 {
82 return format_ts_iso(ts);
83 }
84
85 if delta < 60 {
86 "just now".to_string()
87 } else if delta < 3600 {
88 let mins = delta / 60;
89 format!("{} min{} ago", mins, if mins == 1 { "" } else { "s" })
90 } else if delta < 86400 {
91 let hours = delta / 3600;
92 format!("{} hour{} ago", hours, if hours == 1 { "" } else { "s" })
93 } else if delta < 86400 * 30 {
94 let days = delta / 86400;
95 format!("{} day{} ago", days, if days == 1 { "" } else { "s" })
96 } else {
97 format_ts_iso(ts)
98 }
99}
100
101fn format_ts_iso(ts: i64) -> String {
102 chrono::DateTime::from_timestamp(ts, 0)
103 .map(|dt| dt.format("%Y-%m-%d %H:%M").to_string())
104 .unwrap_or_else(|| ts.to_string())
105}