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, ValueEnum};
use systemprompt_database::DbPool;
use systemprompt_runtime::AppContext;
use systemprompt_users::{UserRole, UserService, UserStatus};

use super::types::{UserListOutput, UserSummary};
use crate::CliConfig;
use crate::shared::CommandResult;

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum RoleFilter {
    Admin,
    User,
    Anonymous,
}

impl From<RoleFilter> for UserRole {
    fn from(filter: RoleFilter) -> Self {
        match filter {
            RoleFilter::Admin => Self::Admin,
            RoleFilter::User => Self::User,
            RoleFilter::Anonymous => Self::Anonymous,
        }
    }
}

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum StatusFilter {
    Active,
    Inactive,
    Suspended,
    Pending,
    Deleted,
    Temporary,
}

impl From<StatusFilter> for UserStatus {
    fn from(filter: StatusFilter) -> Self {
        match filter {
            StatusFilter::Active => Self::Active,
            StatusFilter::Inactive => Self::Inactive,
            StatusFilter::Suspended => Self::Suspended,
            StatusFilter::Pending => Self::Pending,
            StatusFilter::Deleted => Self::Deleted,
            StatusFilter::Temporary => Self::Temporary,
        }
    }
}

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

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

    #[arg(long, value_enum)]
    pub role: Option<RoleFilter>,

    #[arg(long, value_enum)]
    pub status: Option<StatusFilter>,
}

pub async fn execute(args: ListArgs, config: &CliConfig) -> Result<CommandResult<UserListOutput>> {
    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<UserListOutput>> {
    let user_service = UserService::new(pool)?;

    let users = if let Some(role_filter) = args.role {
        let role: UserRole = role_filter.into();
        user_service.find_by_role(role).await?
    } else {
        user_service.list(args.limit, args.offset).await?
    };

    let users: Vec<_> = if let Some(status_filter) = args.status {
        let status: UserStatus = status_filter.into();
        let status_str = status.as_str();
        users
            .into_iter()
            .filter(|u| u.status.as_deref() == Some(status_str))
            .collect()
    } else {
        users
    };

    let total = users.len() as i64;

    let output = UserListOutput {
        users: users
            .iter()
            .map(|u| UserSummary {
                id: u.id.clone(),
                name: u.name.clone(),
                email: u.email.clone(),
                status: u.status.clone(),
                roles: u.roles.clone(),
                created_at: u.created_at,
            })
            .collect(),
        total,
        limit: args.limit,
        offset: args.offset,
    };

    Ok(CommandResult::table(output)
        .with_title("Users")
        .with_columns(vec![
            "id".to_string(),
            "name".to_string(),
            "email".to_string(),
            "status".to_string(),
            "roles".to_string(),
        ]))
}