systemprompt-cli 0.2.1

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
use anyhow::{Context, Result};
use clap::Args;
use systemprompt_agent::repository::context::ContextRepository;
use systemprompt_database::DbPool;
use systemprompt_logging::CliService;
use systemprompt_runtime::AppContext;
use tabled::{Table, Tabled};

use super::types::{ContextListOutput, ContextSummary};
use crate::cli_settings::CliConfig;
use crate::session::get_or_create_session;
use crate::shared::{CommandResult, truncate_with_ellipsis};

#[derive(Debug, Clone, Copy, Args)]
pub struct ListArgs;

#[derive(Tabled)]
struct ContextRow {
    #[tabled(rename = "ID")]
    id: String,
    #[tabled(rename = "Name")]
    name: String,
    #[tabled(rename = "Tasks")]
    task_count: i64,
    #[tabled(rename = "Messages")]
    message_count: i64,
    #[tabled(rename = "Updated")]
    updated_at: String,
    #[tabled(rename = "Active")]
    active: String,
}

pub async fn execute(
    _args: ListArgs,
    config: &CliConfig,
) -> Result<CommandResult<ContextListOutput>> {
    let session_ctx = get_or_create_session(config).await?;
    let ctx = AppContext::new().await?;
    execute_with_pool(&session_ctx.session, ctx.db_pool(), config).await
}

pub async fn execute_with_pool(
    session: &systemprompt_cloud::CliSession,
    pool: &DbPool,
    config: &CliConfig,
) -> Result<CommandResult<ContextListOutput>> {
    let repo = ContextRepository::new(pool)?;

    let contexts = repo
        .list_contexts_with_stats(&session.user_id)
        .await
        .context("Failed to list contexts")?;

    let active_context_id = session.context_id.clone();

    let summaries: Vec<ContextSummary> = contexts
        .into_iter()
        .map(|c| ContextSummary {
            id: c.context_id.clone(),
            name: c.name,
            task_count: c.task_count,
            message_count: c.message_count,
            created_at: c.created_at,
            updated_at: c.updated_at,
            last_message_at: c.last_message_at,
            is_active: c.context_id == active_context_id,
        })
        .collect();

    let total = summaries.len();

    let output = ContextListOutput {
        contexts: summaries.clone(),
        total,
        active_context_id: Some(active_context_id),
    };

    if !config.is_json_output() {
        CliService::section("Contexts");

        if summaries.is_empty() {
            CliService::info("No contexts found");
        } else {
            let rows: Vec<ContextRow> = summaries
                .iter()
                .map(|c| ContextRow {
                    id: c.id.as_str()[..8].to_string(),
                    name: truncate_with_ellipsis(&c.name, 40),
                    task_count: c.task_count,
                    message_count: c.message_count,
                    updated_at: c.updated_at.format("%Y-%m-%d %H:%M").to_string(),
                    active: if c.is_active {
                        "*".to_string()
                    } else {
                        String::new()
                    },
                })
                .collect();

            let table = Table::new(rows).to_string();
            CliService::output(&table);

            CliService::info(&format!("Showing {} context(s)", total));
        }
    }

    Ok(CommandResult::table(output)
        .with_title("Contexts")
        .with_columns(vec![
            "id".to_string(),
            "name".to_string(),
            "task_count".to_string(),
            "message_count".to_string(),
            "updated_at".to_string(),
            "is_active".to_string(),
        ]))
}