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::Result;
use clap::Args;
use systemprompt_database::DbPool;
use systemprompt_files::FileService;
use systemprompt_identifiers::{FileId, UserId};
use systemprompt_runtime::AppContext;

use super::types::{FileListOutput, FileSummary};
use crate::CliConfig;
use crate::shared::CommandResult;

#[derive(Debug, Clone, Args)]
pub struct ListArgs {
    #[arg(long, default_value = "20")]
    pub limit: i64,

    #[arg(long, default_value = "0")]
    pub offset: i64,

    #[arg(long, help = "Filter by user ID")]
    pub user: Option<String>,

    #[arg(long, visible_alias = "type", help = "Filter by MIME type pattern")]
    pub mime: Option<String>,
}

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

pub async fn execute_with_pool(
    args: ListArgs,
    pool: &DbPool,
    _config: &CliConfig,
) -> Result<CommandResult<FileListOutput>> {
    let service = FileService::new(pool)?;

    let files = match &args.user {
        Some(user_id) => {
            let user_id = UserId::new(user_id.clone());
            service
                .list_by_user(&user_id, args.limit, args.offset)
                .await?
        },
        None => service.list_all(args.limit, args.offset).await?,
    };

    let files: Vec<FileSummary> = files
        .into_iter()
        .filter(|f| {
            args.mime
                .as_ref()
                .is_none_or(|pattern| matches_mime_pattern(&f.mime_type, pattern))
        })
        .map(|f| FileSummary {
            id: FileId::new(f.id.to_string()),
            path: f.path,
            public_url: f.public_url,
            mime_type: f.mime_type,
            size_bytes: f.size_bytes,
            ai_content: f.ai_content,
            created_at: f.created_at,
        })
        .collect();

    let total = files.len() as i64;

    let output = FileListOutput {
        files,
        total,
        limit: args.limit,
        offset: args.offset,
    };

    Ok(CommandResult::table(output)
        .with_title("Files")
        .with_columns(vec![
            "id".to_string(),
            "path".to_string(),
            "mime_type".to_string(),
            "size_bytes".to_string(),
            "ai_content".to_string(),
            "created_at".to_string(),
        ]))
}

fn matches_mime_pattern(mime_type: &str, pattern: &str) -> bool {
    if pattern.ends_with("/*") {
        let prefix = pattern.trim_end_matches("/*");
        mime_type.starts_with(prefix)
    } else {
        mime_type == pattern
    }
}