Skip to main content

systemprompt_cli/commands/analytics/sessions/
mod.rs

1//! Session analytics: aggregate stats, trends, and a real-time live view.
2//!
3//! Defines the [`SessionsCommands`] subcommand tree and the typed output shapes
4//! ([`SessionStatsOutput`], [`SessionTrendsOutput`], [`LiveSessionsOutput`])
5//! rendered by the `analytics sessions` commands.
6
7mod live;
8mod stats;
9mod trends;
10
11use anyhow::Result;
12use clap::Subcommand;
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16use crate::context::CommandContext;
17use crate::shared::render_result;
18
19#[derive(Debug, Subcommand)]
20pub enum SessionsCommands {
21    #[command(about = "Session statistics", alias = "list")]
22    Stats(stats::StatsArgs),
23
24    #[command(about = "Session trends over time")]
25    Trends(trends::TrendsArgs),
26
27    #[command(about = "Real-time active sessions")]
28    Live(live::LiveArgs),
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
32pub struct SessionStatsOutput {
33    pub period: String,
34    #[serde(rename = "sessions_created_in_period")]
35    pub total_sessions: i64,
36    #[serde(rename = "sessions_currently_active")]
37    pub active_sessions: i64,
38    pub unique_users: i64,
39    pub avg_duration_seconds: i64,
40    pub avg_requests_per_session: f64,
41    pub conversion_rate: f64,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
45pub struct SessionTrendPoint {
46    pub timestamp: String,
47    pub session_count: i64,
48    pub active_users: i64,
49    pub avg_duration_seconds: i64,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
53pub struct SessionTrendsOutput {
54    pub period: String,
55    pub group_by: String,
56    pub points: Vec<SessionTrendPoint>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60pub struct ActiveSessionRow {
61    #[serde(rename = "session_id")]
62    pub session: String,
63    pub user_type: String,
64    pub started_at: String,
65    pub duration_seconds: i64,
66    pub request_count: i64,
67    pub last_activity: String,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
71pub struct LiveSessionsOutput {
72    pub active_count: i64,
73    pub sessions: Vec<ActiveSessionRow>,
74    pub timestamp: String,
75}
76
77pub async fn execute(command: SessionsCommands, ctx: &CommandContext) -> Result<()> {
78    let db_ctx = ctx.database().await?;
79    match command {
80        SessionsCommands::Stats(args) => {
81            let result = stats::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
82            render_result(&result, &ctx.cli);
83            Ok(())
84        },
85        SessionsCommands::Trends(args) => {
86            let result = trends::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
87            render_result(&result, &ctx.cli);
88            Ok(())
89        },
90        SessionsCommands::Live(mut args) => {
91            if ctx.is_database_scoped() {
92                args.no_refresh = true;
93            }
94            let result = live::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
95            render_result(&result, &ctx.cli);
96            Ok(())
97        },
98    }
99}