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, anyhow};
use clap::Args;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use systemprompt_runtime::AppContext;
use systemprompt_users::UserService;

use crate::CliConfig;
use crate::commands::admin::users::types::BulkDeleteOutput;
use crate::shared::CommandResult;

#[derive(Debug, Args)]
pub struct DeleteArgs {
    #[arg(long, help = "Filter by role (e.g., 'anonymous')")]
    pub role: Option<String>,

    #[arg(long, help = "Filter by status (e.g., 'inactive')")]
    pub status: Option<String>,

    #[arg(long, help = "Filter by age: users older than N days")]
    pub older_than: Option<i64>,

    #[arg(
        long,
        default_value = "100",
        help = "Maximum number of users to delete"
    )]
    pub limit: i64,

    #[arg(
        long,
        help = "Dry run - show what would be deleted without actually deleting"
    )]
    pub dry_run: bool,

    #[arg(short = 'y', long)]
    pub yes: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DryRunOutput {
    pub dry_run: bool,
    pub would_delete: usize,
    pub message: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum DeleteResult {
    DryRun(DryRunOutput),
    Executed(BulkDeleteOutput),
}

pub async fn execute(args: DeleteArgs, _config: &CliConfig) -> Result<CommandResult<DeleteResult>> {
    if !args.yes && !args.dry_run {
        return Err(anyhow!(
            "This will permanently delete users. Use --yes to confirm or --dry-run to preview."
        ));
    }

    if args.role.is_none() && args.status.is_none() && args.older_than.is_none() {
        return Err(anyhow!(
            "At least one filter is required: --role, --status, or --older-than"
        ));
    }

    let ctx = AppContext::new().await?;
    let user_service = UserService::new(ctx.db_pool())?;

    let users = user_service
        .list_by_filter(
            args.status.as_deref(),
            args.role.as_deref(),
            args.older_than,
            args.limit,
        )
        .await?;

    if users.is_empty() {
        let output = BulkDeleteOutput {
            deleted: 0,
            message: "No users match the specified filters".to_string(),
        };
        return Ok(CommandResult::text(DeleteResult::Executed(output)).with_title("Bulk Delete"));
    }

    if args.dry_run {
        let output = DryRunOutput {
            dry_run: true,
            would_delete: users.len(),
            message: format!("Would delete {} user(s)", users.len()),
        };
        return Ok(
            CommandResult::text(DeleteResult::DryRun(output)).with_title("Bulk Delete (Dry Run)")
        );
    }

    let user_ids: Vec<_> = users.iter().map(|u| u.id.clone()).collect();
    let deleted = user_service.bulk_delete(&user_ids).await?;

    let output = BulkDeleteOutput {
        deleted,
        message: format!("Deleted {} user(s)", deleted),
    };

    Ok(CommandResult::text(DeleteResult::Executed(output)).with_title("Bulk Delete"))
}