use std::time::Duration;
use anyhow::Result;
use clap::Args;
use serde::Serialize;
use systemprompt_identifiers::UserId;
use systemprompt_oauth::repository::{BridgeSessionRepository, BridgeSessionRow};
use systemprompt_runtime::AppContext;
use crate::CliConfig;
use crate::shared::CommandResult;
const DEFAULT_WITHIN_SECS: u64 = 120;
#[derive(Debug, Args)]
pub struct ListArgs {
#[arg(long, help = "Filter to a single user")]
pub user_id: Option<String>,
#[arg(
long,
default_value_t = DEFAULT_WITHIN_SECS,
help = "Treat sessions with a heartbeat newer than this many seconds as active",
)]
pub within_secs: u64,
}
#[derive(Debug, Serialize)]
pub struct BridgeListOutput {
pub within_secs: u64,
pub sessions: Vec<BridgeSessionSummary>,
}
#[derive(Debug, Serialize)]
pub struct BridgeSessionSummary {
pub session_id: String,
pub user_id: String,
pub hostname: String,
pub bridge_version: String,
pub os: String,
pub last_heartbeat_at: String,
pub last_activity_at: Option<String>,
pub forwarded_total: i64,
}
pub async fn execute(
args: ListArgs,
_config: &CliConfig,
) -> Result<CommandResult<BridgeListOutput>> {
let ctx = AppContext::new().await?;
let repo = BridgeSessionRepository::new(ctx.db_pool())?;
let within = Duration::from_secs(args.within_secs);
let rows = match args
.user_id
.as_deref()
.map(str::trim)
.filter(|s| !s.is_empty())
{
Some(user) => {
repo.list_active_for_user(&UserId::new(user.to_string()), within)
.await?
},
None => repo.list_active(within).await?,
};
let summaries = rows.into_iter().map(summary).collect::<Vec<_>>();
let title = format!(
"Active bridge sessions ({} total, last {}s)",
summaries.len(),
args.within_secs
);
let output = BridgeListOutput {
within_secs: args.within_secs,
sessions: summaries,
};
Ok(CommandResult::text(output).with_title(title))
}
fn summary(row: BridgeSessionRow) -> BridgeSessionSummary {
BridgeSessionSummary {
session_id: row.session_id.as_str().to_string(),
user_id: row.user_id.as_str().to_string(),
hostname: row.hostname,
bridge_version: row.bridge_version,
os: row.os,
last_heartbeat_at: row.last_heartbeat_at.to_rfc3339(),
last_activity_at: row.last_activity_at.map(|t| t.to_rfc3339()),
forwarded_total: row.forwarded_total,
}
}