Skip to main content

chub_cli/commands/
stats.rs

1use clap::Args;
2use owo_colors::OwoColorize;
3
4use chub_core::team::analytics;
5
6#[derive(Args)]
7pub struct StatsArgs {
8    /// Number of days to show stats for (default 30)
9    #[arg(long, default_value = "30")]
10    days: u64,
11}
12
13pub fn run(args: StatsArgs, json: bool) {
14    let stats = analytics::get_stats(args.days);
15
16    if json {
17        println!(
18            "{}",
19            serde_json::to_string_pretty(&stats).unwrap_or_default()
20        );
21    } else {
22        eprintln!(
23            "{}\n",
24            format!("Usage stats (last {} days):", stats.period_days).bold()
25        );
26
27        eprintln!(
28            "  Events: {}  (fetches: {}, searches: {}, builds: {}, MCP calls: {}, annotations: {}, feedback: {})",
29            stats.total_events,
30            stats.total_fetches,
31            stats.total_searches,
32            stats.total_builds,
33            stats.total_mcp_calls,
34            stats.total_annotations,
35            stats.total_feedback,
36        );
37
38        if stats.total_events == 0 {
39            eprintln!("\n{}", "No events recorded.".dimmed());
40            return;
41        }
42
43        // Most fetched docs
44        if !stats.most_fetched.is_empty() {
45            eprintln!("\n{}", "Most fetched docs:".bold());
46            for (i, (id, count)) in stats.most_fetched.iter().take(10).enumerate() {
47                eprintln!("  {}. {}  — {} fetches", i + 1, id.bold(), count);
48            }
49        }
50
51        // Top search queries
52        if !stats.top_queries.is_empty() {
53            eprintln!("\n{}", "Top search queries:".bold());
54            for (i, (query, count)) in stats.top_queries.iter().take(10).enumerate() {
55                eprintln!("  {}. \"{}\"  — {} times", i + 1, query, count);
56            }
57            if stats.total_searches > 0 {
58                eprintln!("  Avg results per search: {:.1}", stats.avg_search_results);
59            }
60        }
61
62        // Top MCP tools
63        if !stats.top_mcp_tools.is_empty() {
64            eprintln!("\n{}", "Top MCP tools:".bold());
65            for (tool, count) in stats.top_mcp_tools.iter().take(10) {
66                eprintln!("  {}  — {} calls", tool, count);
67            }
68        }
69
70        // Agents
71        if !stats.agents.is_empty() {
72            eprintln!("\n{}", "Agents:".bold());
73            for (agent, count) in &stats.agents {
74                eprintln!("  {}  — {} events", agent, count);
75            }
76        }
77
78        // Never-fetched pins
79        if !stats.never_fetched_pins.is_empty() {
80            eprintln!("\n{}", "Never fetched (pinned but unused):".yellow());
81            for id in &stats.never_fetched_pins {
82                eprintln!("  - {}", id.dimmed());
83            }
84            eprintln!(
85                "\n{}",
86                "Suggestion: unpin unused docs to reduce noise.".dimmed()
87            );
88        }
89
90        // Journal info
91        let size = analytics::journal_size_bytes();
92        if size > 0 {
93            eprintln!("\n  Journal: {:.1} KB", size as f64 / 1024.0);
94        }
95    }
96}