systemprompt_cli/commands/core/files/
upload.rs1use std::path::{Path, PathBuf};
7
8use anyhow::{Result, anyhow};
9use base64::Engine;
10use base64::engine::general_purpose::STANDARD;
11use clap::Args;
12use sha2::{Digest, Sha256};
13use systemprompt_files::{FileUploadRequest, FileUploadService, FilesConfig};
14use systemprompt_identifiers::{ContextId, SessionId, UserId};
15use tokio::fs;
16
17use super::types::FileUploadOutput;
18use crate::context::CommandContext;
19use crate::shared::CommandOutput;
20
21#[derive(Debug, Clone, Args)]
22pub struct UploadArgs {
23 #[arg(help = "Path to file to upload")]
24 pub file_path: PathBuf,
25
26 #[arg(long, help = "Context ID (required)")]
27 pub context: String,
28
29 #[arg(long, help = "User ID")]
30 pub user: Option<String>,
31
32 #[arg(long, help = "Session ID")]
33 pub session: Option<String>,
34
35 #[arg(long, help = "Mark as AI-generated content")]
36 pub ai: bool,
37}
38
39pub async fn execute(args: UploadArgs, ctx: &CommandContext) -> Result<CommandOutput> {
40 let app = ctx.app_context().await?;
41 let files_config = FilesConfig::get()?;
42 let service = FileUploadService::new(
43 (**app.file_repository()).clone(),
44 files_config.clone(),
45 std::sync::Arc::clone(app.file_storage()),
46 );
47
48 if !service.is_enabled() {
49 return Err(anyhow!("File uploads are disabled in configuration"));
50 }
51
52 let file_path = args
53 .file_path
54 .canonicalize()
55 .map_err(|e| anyhow!("File not found: {} - {}", args.file_path.display(), e))?;
56
57 let bytes = fs::read(&file_path).await?;
58 let bytes_base64 = STANDARD.encode(&bytes);
59 let digest = Sha256::digest(&bytes);
60 let checksum_sha256 = digest.iter().fold(String::with_capacity(64), |mut acc, b| {
61 acc.push_str(&format!("{b:02x}"));
62 acc
63 });
64 let size_bytes = bytes.len() as i64;
65
66 let mime_type = detect_mime_type(&file_path);
67 let filename = file_path
68 .file_name()
69 .and_then(|n| n.to_str())
70 .map(String::from);
71
72 let context_id = ContextId::new_unchecked(args.context);
73
74 let request = FileUploadRequest {
75 name: filename,
76 mime_type: mime_type.clone(),
77 bytes_base64,
78 context_id,
79 user_id: args.user.map(UserId::new),
80 session_id: args.session.map(SessionId::new),
81 trace_id: None,
82 };
83
84 let result = service.upload_file(request).await?;
85
86 let output = FileUploadOutput {
87 file_id: result.file_id,
88 path: result.path,
89 public_url: result.public_url,
90 size_bytes,
91 mime_type,
92 checksum_sha256,
93 };
94
95 Ok(CommandOutput::card_value("File Uploaded", &output))
96}
97
98pub fn detect_mime_type(path: &Path) -> String {
99 systemprompt_models::mime::from_path(path).to_owned()
100}