Skip to main content

systemprompt_cli/commands/core/files/
delete.rs

1//! `core files delete` command.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use anyhow::{Result, anyhow};
7use clap::Args;
8use systemprompt_database::DbPool;
9use systemprompt_files::FileRepository;
10use systemprompt_identifiers::FileId;
11use systemprompt_runtime::AppContext;
12
13use super::types::FileDeleteOutput;
14use crate::CliConfig;
15use crate::interactive::Prompter;
16use crate::shared::CommandOutput;
17
18#[derive(Debug, Clone, Args)]
19pub struct DeleteArgs {
20    #[arg(value_name = "FILE_ID", help = "File ID (UUID format)")]
21    pub file: String,
22
23    #[arg(
24        short = 'y',
25        long,
26        help = "Skip confirmation (required in non-interactive mode)"
27    )]
28    pub yes: bool,
29
30    #[arg(long, help = "Preview deletion without executing")]
31    pub dry_run: bool,
32}
33
34pub(super) async fn execute(
35    args: DeleteArgs,
36    prompter: &dyn Prompter,
37    config: &CliConfig,
38) -> Result<CommandOutput> {
39    let ctx = AppContext::new().await?;
40    execute_with_pool(args, prompter, ctx.db_pool(), config).await
41}
42
43pub async fn execute_with_pool(
44    args: DeleteArgs,
45    prompter: &dyn Prompter,
46    pool: &DbPool,
47    config: &CliConfig,
48) -> Result<CommandOutput> {
49    let file_id = parse_file_id(&args.file)?;
50
51    let service = FileRepository::new(pool)?;
52
53    let file = service
54        .find_by_id(&file_id)
55        .await?
56        .ok_or_else(|| anyhow!("File not found: {}", args.file))?;
57
58    if args.dry_run {
59        let output = FileDeleteOutput {
60            file_id,
61            message: format!(
62                "[DRY-RUN] Would delete file '{}' ({})",
63                file.path, args.file
64            ),
65        };
66        return Ok(CommandOutput::card_value("File Delete (Dry Run)", &output));
67    }
68
69    if !args.yes {
70        if config.is_interactive() {
71            let confirmed = prompter.confirm(
72                &format!(
73                    "Delete file '{}' ({})? This action cannot be undone.",
74                    file.path, args.file
75                ),
76                false,
77            )?;
78
79            if !confirmed {
80                return Err(anyhow!("Deletion cancelled by user"));
81            }
82        } else {
83            return Err(anyhow!(
84                "--yes is required to delete files in non-interactive mode"
85            ));
86        }
87    }
88
89    service.delete(&file_id).await?;
90
91    let output = FileDeleteOutput {
92        file_id,
93        message: format!("File '{}' deleted successfully", file.path),
94    };
95
96    Ok(CommandOutput::card_value("File Deleted", &output))
97}
98
99fn parse_file_id(id: &str) -> Result<FileId> {
100    uuid::Uuid::parse_str(id).map_err(|_e| {
101        anyhow!(
102            "Invalid file ID format. Expected UUID like 'b75940ac-c50f-4d46-9fdd-ebb4970b2a7d', \
103             got '{}'",
104            id
105        )
106    })?;
107    Ok(FileId::new(id.to_owned()))
108}