Skip to main content

systemprompt_cli/commands/analytics/conversations/
mod.rs

1//! Conversation analytics: aggregate stats, time-series trends, and listings.
2//!
3//! Defines the [`ConversationsCommands`] subcommand tree and the typed output
4//! shapes ([`ConversationStatsOutput`], [`ConversationTrendsOutput`],
5//! [`ConversationListOutput`]) rendered by the `analytics conversations`
6//! commands.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11mod list;
12mod stats;
13mod trends;
14
15use anyhow::Result;
16use clap::Subcommand;
17use schemars::JsonSchema;
18use serde::{Deserialize, Serialize};
19use systemprompt_identifiers::UserId;
20
21use crate::context::CommandContext;
22use crate::shared::render_result;
23
24#[derive(Debug, Subcommand)]
25pub enum ConversationsCommands {
26    #[command(about = "Conversation statistics")]
27    Stats(stats::StatsArgs),
28
29    #[command(about = "Conversation trends over time")]
30    Trends(trends::TrendsArgs),
31
32    #[command(about = "List conversations")]
33    List(list::ListArgs),
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
37pub struct ConversationStatsOutput {
38    pub period: String,
39    pub total_contexts: i64,
40    pub total_tasks: i64,
41    pub total_messages: i64,
42    pub avg_messages_per_task: f64,
43    pub avg_task_duration_ms: i64,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
47pub struct ConversationTrendPoint {
48    pub timestamp: String,
49    pub context_count: i64,
50    pub task_count: i64,
51    pub message_count: i64,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
55pub struct ConversationTrendsOutput {
56    pub period: String,
57    pub group_by: String,
58    pub points: Vec<ConversationTrendPoint>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
62pub struct ConversationListRow {
63    #[serde(rename = "context_id")]
64    pub context: String,
65    pub source: String,
66    pub user_id: UserId,
67    pub name: Option<String>,
68    pub task_count: i64,
69    pub message_count: i64,
70    pub created_at: String,
71    pub updated_at: String,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
75pub struct ConversationListOutput {
76    pub conversations: Vec<ConversationListRow>,
77    pub total: i64,
78}
79
80pub async fn execute(command: ConversationsCommands, ctx: &CommandContext) -> Result<()> {
81    let db_ctx = ctx.database().await?;
82    match command {
83        ConversationsCommands::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        ConversationsCommands::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        ConversationsCommands::List(args) => {
94            let result = list::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
95            render_result(&result, &ctx.cli);
96            Ok(())
97        },
98    }
99}