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