systemprompt_cli/commands/analytics/
mod.rs1pub mod agents;
13pub mod content;
14pub mod conversations;
15pub mod costs;
16pub mod overview;
17pub mod projection;
18pub mod requests;
19pub mod sessions;
20pub mod shared;
21pub mod tools;
22pub mod traffic;
23
24use anyhow::{Context, Result};
25use clap::Subcommand;
26
27use crate::context::CommandContext;
28use crate::shared::render_result;
29
30#[derive(Debug, Subcommand)]
31pub enum AnalyticsCommands {
32 #[command(subcommand, about = "Reporting projection status and recovery")]
33 Projection(projection::ProjectionCommands),
34 #[command(about = "Dashboard overview of all analytics")]
35 Overview(overview::OverviewArgs),
36
37 #[command(subcommand, about = "Conversation analytics")]
38 Conversations(conversations::ConversationsCommands),
39
40 #[command(subcommand, about = "Agent performance analytics")]
41 Agents(agents::AgentsCommands),
42
43 #[command(subcommand, about = "Tool usage analytics")]
44 Tools(tools::ToolsCommands),
45
46 #[command(about = "AI request analytics; defaults to the stats aggregate")]
47 Requests(requests::RequestsArgs),
48
49 #[command(subcommand, about = "Session analytics")]
50 Sessions(sessions::SessionsCommands),
51
52 #[command(subcommand, about = "Content performance analytics")]
53 Content(content::ContentCommands),
54
55 #[command(subcommand, about = "Traffic analytics")]
56 Traffic(traffic::TrafficCommands),
57
58 #[command(about = "Cost analytics; defaults to the spend summary")]
59 Costs(costs::CostsArgs),
60}
61
62pub async fn execute(command: AnalyticsCommands, ctx: &CommandContext) -> Result<()> {
63 if !matches!(&command, AnalyticsCommands::Projection(_)) {
64 let database = ctx.database().await?;
65 let status = systemprompt_runtime::reporting::status(database.db_pool())
66 .await
67 .context("Reporting schema unavailable; apply database migrations first")?;
68 if !status.initialized {
69 match status.rebuild_source.as_deref() {
70 Some(source) => anyhow::bail!(
71 "Reporting baseline rebuild in progress: {source} ({} rows written, last heartbeat {}); retry shortly",
72 status.rebuild_rows,
73 status
74 .rebuild_heartbeat_at
75 .map_or_else(|| "never".to_owned(), |at| at.to_rfc3339())
76 ),
77 None => anyhow::bail!(
78 "Reporting baseline is not initialized; the server builds it at startup, or run analytics projection rebuild"
79 ),
80 }
81 }
82 }
83 match command {
84 AnalyticsCommands::Projection(command) => projection::execute(command, ctx).await,
85 AnalyticsCommands::Overview(args) => {
86 let result =
87 overview::execute_with_pool(args, &ctx.database().await?, &ctx.cli).await?;
88 render_result(&result, &ctx.cli);
89 Ok(())
90 },
91 AnalyticsCommands::Conversations(cmd) => conversations::execute(cmd, ctx).await,
92 AnalyticsCommands::Agents(cmd) => agents::execute(cmd, ctx).await,
93 AnalyticsCommands::Tools(cmd) => tools::execute(cmd, ctx).await,
94 AnalyticsCommands::Requests(cmd) => requests::execute(cmd, ctx).await,
95 AnalyticsCommands::Sessions(cmd) => sessions::execute(cmd, ctx).await,
96 AnalyticsCommands::Content(cmd) => content::execute(cmd, ctx).await,
97 AnalyticsCommands::Traffic(cmd) => traffic::execute(cmd, ctx).await,
98 AnalyticsCommands::Costs(cmd) => costs::execute(cmd, ctx).await,
99 }
100}