From f8323886dbe6d7f444ee1bd10ed8a109702dc562 Mon Sep 17 00:00:00 2001 From: bjay kamwa watanabe Date: Sun, 22 Feb 2026 16:13:41 +0100 Subject: [PATCH 1/2] bump open-api --- src/cli.rs | 2 - src/commands/asset/mod.rs | 3 - src/commands/asset/preprocess.rs | 84 -- src/commands/entity/create.rs | 283 ------ src/commands/entity/mod.rs | 17 - src/commands/group/create.rs | 64 -- src/commands/group/mod.rs | 3 - src/commands/mod.rs | 1 - src/commands/upload/dry_run.rs | 2 +- src/commands/upload/main.rs | 46 +- src/commands/upload/mod.rs | 2 +- src/logger.rs | 2 + src/main.rs | 22 - src/output.rs | 10 +- .../openapi.tellers_public_api.yaml | 911 ++++++++++++++---- 15 files changed, 777 insertions(+), 675 deletions(-) delete mode 100644 src/commands/asset/preprocess.rs delete mode 100644 src/commands/entity/create.rs delete mode 100644 src/commands/entity/mod.rs delete mode 100644 src/commands/group/create.rs diff --git a/src/cli.rs b/src/cli.rs index 12bc23a..2cae383 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,6 @@ use clap::{Parser, Subcommand}; use crate::commands::asset::AssetArgs; -use crate::commands::entity::EntityArgs; use crate::commands::group::GroupArgs; use crate::commands::upload::UploadArgs; @@ -24,7 +23,6 @@ pub struct Cli { #[derive(Subcommand, Debug)] pub enum Command { Asset(AssetArgs), - Entity(EntityArgs), Group(GroupArgs), Upload(UploadArgs), } diff --git a/src/commands/asset/mod.rs b/src/commands/asset/mod.rs index f3a0165..44f907a 100644 --- a/src/commands/asset/mod.rs +++ b/src/commands/asset/mod.rs @@ -1,10 +1,8 @@ use clap::{Args, Subcommand}; mod list; -mod preprocess; pub use list::{run as list_run, ListArgs}; -pub use preprocess::{run as preprocess_run, PreprocessArgs}; #[derive(Args, Debug)] pub struct AssetArgs { @@ -15,6 +13,5 @@ pub struct AssetArgs { #[derive(Subcommand, Debug)] pub enum AssetCommand { List(ListArgs), - Preprocess(PreprocessArgs), } diff --git a/src/commands/asset/preprocess.rs b/src/commands/asset/preprocess.rs deleted file mode 100644 index 7729508..0000000 --- a/src/commands/asset/preprocess.rs +++ /dev/null @@ -1,84 +0,0 @@ -use clap::Args; -use tellers_api_client::apis::accepts_api_key_api as api; -use tellers_api_client::models::{AssetUploadResponse, ProcessAssetsRequest}; - -use crate::commands::api_config; - -#[derive(Args, Debug)] -pub struct PreprocessArgs { - #[arg(required = true, num_args = 1..)] - pub ids: Vec, - - #[arg(long, default_value_t = false)] - pub disable_description_generation: bool, - - #[arg(long, env = "TELLERS_API_KEY")] - pub api_key: Option, - - #[arg(long, env = "TELLERS_AUTH_BEARER")] - pub auth_bearer: Option, -} - -pub fn run(args: PreprocessArgs) -> Result<(), String> { - let cfg = api_config::create_config(); - let api_key = api_config::get_api_key(args.api_key)?; - let bearer_header = api_config::get_bearer_header(args.auth_bearer); - - tokio::runtime::Runtime::new() - .map_err(|e| format!("failed to start runtime: {}", e))? - .block_on(async move { - let assets: Vec = args - .ids - .into_iter() - .map(|asset_id| AssetUploadResponse::new(String::new(), String::new(), asset_id)) - .collect(); - - let mut preproc_req = ProcessAssetsRequest::new( - assets.clone(), - None::, - ); - preproc_req.generate_time_based_media_description = - Some(!args.disable_description_generation); - - println!("Triggering preprocessing for {} asset(s)...", preproc_req.assets.len()); - - let preproc_tasks = api::process_assets_users_assets_preprocess_post( - &cfg, - preproc_req, - None, - Some(&api_key), - bearer_header.as_deref(), - ) - .await - .map_err(|e| { - let mut m = format!("failed to trigger preprocess: {}", e); - match &e { - tellers_api_client::apis::Error::Reqwest(req_err) => { - if let Some(status) = req_err.status() { - m.push_str(&format!("; http_status: {}", status)); - } - } - tellers_api_client::apis::Error::ResponseError(resp) => { - m.push_str(&format!("; http_status: {}", resp.status)); - if !resp.content.is_empty() { - m.push_str(&format!("; response_body: {}", resp.content)); - } - } - _ => {} - } - m - })?; - - println!("Preprocess tasks queued: {}", preproc_tasks.len()); - for task in preproc_tasks { - if let Some(Some(ref error_msg)) = task.error { - eprintln!("Task {} error: {}", task.task_id, error_msg); - } else { - println!("Task {} queued successfully", task.task_id); - } - } - - Ok(()) - }) -} - diff --git a/src/commands/entity/create.rs b/src/commands/entity/create.rs deleted file mode 100644 index 8b866a8..0000000 --- a/src/commands/entity/create.rs +++ /dev/null @@ -1,283 +0,0 @@ -use clap::Args; -use std::path::PathBuf; -use tellers_api_client::apis::accepts_api_key_api as api; -use tellers_api_client::models::{ - AssetUploadRequest, AssetUploadResponse, CreateEntityRequest, ProcessEntityRequest, - SourceFileInfo, -}; -use uuid::Uuid; - -use crate::auth; -use crate::commands::api_config; -use crate::media::metadata::extract_media_metadata; -use crate::output; -use crate::uploads_tracking; - -#[derive(Args, Debug)] -pub struct CreateArgs { - #[arg(long)] - pub group_id: String, - - #[arg(long)] - pub name: String, - - #[arg(long)] - pub asset_id: Option, - - #[arg(long)] - pub filepath: Option, - - #[arg(long, default_value = "")] - pub description: String, - - #[arg(long, env = "TELLERS_API_KEY")] - pub api_key: Option, - - #[arg(long, env = "TELLERS_AUTH_BEARER")] - pub auth_bearer: Option, -} - -pub fn run(args: CreateArgs) -> Result<(), String> { - if args.asset_id.is_some() && args.filepath.is_some() { - return Err("Cannot specify both --asset-id and --filepath. Use only one.".to_string()); - } - - let cfg = api_config::create_config(); - let api_key = api_config::get_api_key(args.api_key)?; - let bearer_header = api_config::get_bearer_header(args.auth_bearer.clone()); - - let user_id = auth::get_user_id_from_bearer(bearer_header.as_deref()); - - let asset_id = if let Some(ref filepath) = args.filepath { - let file_path = PathBuf::from(filepath); - if !file_path.exists() { - return Err(format!("File not found: {}", filepath)); - } - - output::info(format!("Uploading file {}...", filepath)); - Some(upload_file_and_get_asset_id( - &file_path, - &cfg, - &api_key, - bearer_header.as_deref(), - &user_id, - )?) - } else { - args.asset_id - }; - - tokio::runtime::Runtime::new() - .map_err(|e| format!("failed to start runtime: {}", e))? - .block_on(async move { - let create_req = CreateEntityRequest::new( - args.group_id.clone(), - args.name.clone(), - args.description.clone(), - ); - - output::info(format!("Creating entity: name={}, group_id={}", args.name, args.group_id)); - - let create_resp = api::create_entity_users_entity_create_post( - &cfg, - create_req, - Some(&api_key), - bearer_header.as_deref(), - ) - .await - .map_err(|e| { - let mut m = format!("failed to create entity: {}", e); - match &e { - tellers_api_client::apis::Error::Reqwest(req_err) => { - if let Some(status) = req_err.status() { - m.push_str(&format!("; http_status: {}", status)); - } - } - tellers_api_client::apis::Error::ResponseError(resp) => { - m.push_str(&format!("; http_status: {}", resp.status)); - if !resp.content.is_empty() { - m.push_str(&format!("; response_body: {}", resp.content)); - } - } - _ => {} - } - m - })?; - - let entity_id = create_resp.entity_id; - output::success(format!("Entity created successfully: {}", entity_id)); - - if let Some(asset_id) = asset_id { - output::info(format!("Associating asset {} with entity {}", asset_id, entity_id)); - - let asset = AssetUploadResponse::new( - String::new(), - String::new(), - asset_id.clone(), - ); - - let process_req = ProcessEntityRequest::new(entity_id.clone(), vec![asset]); - - let process_resp = api::process_entity_users_entity_preprocess_post( - &cfg, - process_req, - None, - Some(&api_key), - bearer_header.as_deref(), - ) - .await - .map_err(|e| { - let mut m = format!("failed to process entity with asset: {}", e); - match &e { - tellers_api_client::apis::Error::Reqwest(req_err) => { - if let Some(status) = req_err.status() { - m.push_str(&format!("; http_status: {}", status)); - } - } - tellers_api_client::apis::Error::ResponseError(resp) => { - m.push_str(&format!("; http_status: {}", resp.status)); - if !resp.content.is_empty() { - m.push_str(&format!("; response_body: {}", resp.content)); - } - } - _ => {} - } - m - })?; - - output::success(format!( - "Asset {} associated with entity {} (task_id: {})", - asset_id, entity_id, process_resp.task_id - )); - } - - Ok(()) - }) -} - -fn upload_file_and_get_asset_id( - file_path: &PathBuf, - cfg: &tellers_api_client::apis::configuration::Configuration, - api_key: &str, - bearer_header: Option<&str>, - user_id: &str, -) -> Result { - tokio::runtime::Runtime::new() - .map_err(|e| format!("failed to start runtime: {}", e))? - .block_on(async move { - let content_length = std::fs::metadata(file_path) - .map_err(|e| format!("failed to stat {}: {}", file_path.display(), e))? - .len(); - - let upload_id = Uuid::new_v4().to_string(); - let upload_request_id = Uuid::new_v4().to_string(); - - let file_name_str = file_path - .file_name() - .unwrap_or_default() - .to_string_lossy() - .to_string(); - - let in_app_path = file_name_str.clone(); - - let now_secs = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as i32; - - let umid = extract_media_metadata(file_path).ok(); - let mut source_info = SourceFileInfo::new( - "__user_upload__".to_string(), - None, - None, - vec!["__current_user__".to_string()], - Some(now_secs), - now_secs, - vec![in_app_path.clone()], - Some(file_name_str), - None, - vec![], - ); - - if let Some(metadata) = umid { - if let Some(umid_value) = metadata.material_package_umid { - source_info.capture_device_umid = Some(Some(umid_value)); - } - if let Some(first_umid) = metadata.file_package_umids.first() { - source_info.umid = Some(Some(first_umid.clone())); - } - } - - let upload_req = AssetUploadRequest::new( - i32::try_from(content_length).unwrap_or(i32::MAX), - upload_id.clone(), - source_info, - ); - - output::info(format!("Requesting presigned URL for {}", file_path.display())); - - let mut responses = api::create_upload_urls_users_assets_upload_urls_post( - cfg, - vec![upload_req], - Some(api_key), - bearer_header, - ) - .await - .map_err(|e| { - let mut m = format!("failed to get upload url: {}", e); - match &e { - tellers_api_client::apis::Error::Reqwest(req_err) => { - if let Some(status) = req_err.status() { - m.push_str(&format!("; http_status: {}", status)); - } - } - tellers_api_client::apis::Error::ResponseError(resp) => { - m.push_str(&format!("; http_status: {}", resp.status)); - if !resp.content.is_empty() { - m.push_str(&format!("; response_body: {}", resp.content)); - } - } - _ => {} - } - m - })?; - - if responses.is_empty() { - return Err("No upload response received".to_string()); - } - - let upload_resp = responses.remove(0); - let asset_id = upload_resp.asset_id.clone(); - - output::info(format!("Uploading file to presigned URL...")); - - let http = reqwest::Client::builder() - .timeout(std::time::Duration::from_secs(60)) - .build() - .map_err(|e| format!("failed to build http client: {}", e))?; - - crate::commands::upload::upload_file_to_presigned( - file_path, - &upload_resp, - &http, - cfg, - api_key, - bearer_header, - ) - .await?; - - if let Err(e) = uploads_tracking::record_upload( - user_id, - file_path.as_path(), - &in_app_path, - &asset_id, - &upload_request_id, - ) { - output::warning(format!("Failed to record upload in tracking file: {}", e)); - } - - output::success(format!("File uploaded successfully, asset_id: {}", asset_id)); - - Ok(asset_id) - }) -} - diff --git a/src/commands/entity/mod.rs b/src/commands/entity/mod.rs deleted file mode 100644 index a4d4734..0000000 --- a/src/commands/entity/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -use clap::{Args, Subcommand}; - -mod create; - -pub use create::{run as create_run, CreateArgs}; - -#[derive(Args, Debug)] -pub struct EntityArgs { - #[command(subcommand)] - pub command: EntityCommand, -} - -#[derive(Subcommand, Debug)] -pub enum EntityCommand { - Create(CreateArgs), -} - diff --git a/src/commands/group/create.rs b/src/commands/group/create.rs deleted file mode 100644 index 31c02e9..0000000 --- a/src/commands/group/create.rs +++ /dev/null @@ -1,64 +0,0 @@ -use clap::Args; -use tellers_api_client::apis::accepts_api_key_api as api; -use tellers_api_client::models::CreateGroupRequest; - -use crate::commands::api_config; -use crate::output; - -#[derive(Args, Debug)] -pub struct CreateArgs { - #[arg(long)] - pub name: String, - - #[arg(long, env = "TELLERS_API_KEY")] - pub api_key: Option, - - #[arg(long, env = "TELLERS_AUTH_BEARER")] - pub auth_bearer: Option, -} - -pub fn run(args: CreateArgs) -> Result<(), String> { - let cfg = api_config::create_config(); - let api_key = api_config::get_api_key(args.api_key)?; - let bearer_header = api_config::get_bearer_header(args.auth_bearer); - - tokio::runtime::Runtime::new() - .map_err(|e| format!("failed to start runtime: {}", e))? - .block_on(async move { - let create_req = CreateGroupRequest::new(args.name.clone()); - - output::info(format!("Creating group: name={}", args.name)); - - let create_resp = api::create_group_group_create_post( - &cfg, - create_req, - Some(&api_key), - bearer_header.as_deref(), - ) - .await - .map_err(|e| { - let mut m = format!("failed to create group: {}", e); - match &e { - tellers_api_client::apis::Error::Reqwest(req_err) => { - if let Some(status) = req_err.status() { - m.push_str(&format!("; http_status: {}", status)); - } - } - tellers_api_client::apis::Error::ResponseError(resp) => { - m.push_str(&format!("; http_status: {}", resp.status)); - if !resp.content.is_empty() { - m.push_str(&format!("; response_body: {}", resp.content)); - } - } - _ => {} - } - m - })?; - - output::success("Group created successfully"); - println!("{}", serde_json::to_string_pretty(&create_resp).unwrap_or_default()); - - Ok(()) - }) -} - diff --git a/src/commands/group/mod.rs b/src/commands/group/mod.rs index 9f3c04d..1ebe163 100644 --- a/src/commands/group/mod.rs +++ b/src/commands/group/mod.rs @@ -1,9 +1,7 @@ use clap::{Args, Subcommand}; -mod create; mod list; -pub use create::{run as create_run, CreateArgs}; pub use list::{run as list_run, ListArgs}; #[derive(Args, Debug)] @@ -15,6 +13,5 @@ pub struct GroupArgs { #[derive(Subcommand, Debug)] pub enum GroupCommand { List(ListArgs), - Create(CreateArgs), } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index ab6afdd..c033d54 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,6 +1,5 @@ pub mod api_config; pub mod asset; -pub mod entity; pub mod group; pub mod prompt; pub mod upload; diff --git a/src/commands/upload/dry_run.rs b/src/commands/upload/dry_run.rs index 76b1957..2719b6d 100644 --- a/src/commands/upload/dry_run.rs +++ b/src/commands/upload/dry_run.rs @@ -186,7 +186,7 @@ pub fn run_dry_run( } } - let elapsed = task_start.elapsed(); + let _elapsed = task_start.elapsed(); let _ = progress_handle.finish_task(i, true); } diff --git a/src/commands/upload/main.rs b/src/commands/upload/main.rs index e85eaa6..10220ef 100644 --- a/src/commands/upload/main.rs +++ b/src/commands/upload/main.rs @@ -1,4 +1,4 @@ -use clap::Args; +use clap::{Args, Subcommand}; use regex::Regex; use std::collections::HashMap; use std::fs::File; @@ -34,6 +34,31 @@ use tellers_api_client::models::{ #[derive(Args, Debug)] pub struct UploadArgs { + #[command(subcommand)] + pub command: UploadCommand, +} + +#[derive(Subcommand, Debug)] +pub enum UploadCommand { + /// Upload files from a path + Upload(UploadCmdArgs), + /// Recreate filesystem from a path + RecreateFilesystem(RecreateFilesystemArgs), +} + +#[derive(Args, Debug, Clone)] +pub struct RecreateFilesystemArgs { + #[arg(long)] + pub in_app_path: Option, + + #[arg(long, default_value_t = false)] + pub dry_run: bool, + + pub path: String, +} + +#[derive(Args, Debug, Clone)] +pub struct UploadCmdArgs { #[arg(long, default_value_t = false)] pub local_encoding: bool, @@ -109,6 +134,18 @@ fn matches_regex(file_path: &PathBuf, regex_patterns: &[Regex]) -> bool { } pub fn run(args: UploadArgs) -> Result<(), String> { + match args.command { + UploadCommand::Upload(cmd_args) => run_upload(cmd_args), + UploadCommand::RecreateFilesystem(cmd_args) => run_recreate_filesystem(cmd_args), + } +} + +fn run_recreate_filesystem(_args: RecreateFilesystemArgs) -> Result<(), String> { + // TODO: implement recreate filesystem API call + Ok(()) +} + +fn run_upload(args: UploadCmdArgs) -> Result<(), String> { let base_dir = PathBuf::from(&args.path); if !base_dir.exists() { return Err(format!("path not found: {}", base_dir.display())); @@ -526,7 +563,7 @@ fn build_downscale_work(original_files: &[PathBuf]) -> Result fn run_two_queue_pipeline( work_items: Vec, base_dir: &PathBuf, - args: &UploadArgs, + args: &UploadCmdArgs, cfg: &Configuration, api_key: &str, bearer_opt: Option<&str>, @@ -911,7 +948,10 @@ async fn upload_to_presigned_urls( } fn single_put_url(resp: &AssetUploadResponse) -> String { - resp.presigned_put_url.clone() + resp.presigned_put_url + .clone() + .flatten() + .unwrap_or_else(String::new) } pub async fn upload_file_to_presigned( diff --git a/src/commands/upload/mod.rs b/src/commands/upload/mod.rs index ebf8683..74363fe 100644 --- a/src/commands/upload/mod.rs +++ b/src/commands/upload/mod.rs @@ -2,6 +2,6 @@ mod dry_run; mod main; mod utils; -pub use main::{run, upload_file_to_presigned, UploadArgs}; +pub use main::{run, UploadArgs}; diff --git a/src/logger.rs b/src/logger.rs index d019eea..a5480e3 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -79,6 +79,7 @@ impl Logger { Ok(()) } + #[allow(dead_code)] fn get_log_path() -> Option { LOGGER.inner.lock().unwrap().log_path.clone() } @@ -141,6 +142,7 @@ pub fn init() -> Result<(), String> { /// /// Returns `Some(PathBuf)` with the path to the log file if the logger is initialized, /// or `None` if not yet initialized. +#[allow(dead_code)] pub fn get_log_path() -> Option { Logger::get_log_path() } diff --git a/src/main.rs b/src/main.rs index 105dcc7..60a4d75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,22 +25,6 @@ fn main() { std::process::exit(1); } } - commands::asset::AssetCommand::Preprocess(preprocess_args) => { - if let Err(error) = commands::asset::preprocess_run(preprocess_args) { - eprintln!("error: {}", error); - std::process::exit(1); - } - } - } - } - Some(cli::Command::Entity(entity_args)) => { - match entity_args.command { - commands::entity::EntityCommand::Create(create_args) => { - if let Err(error) = commands::entity::create_run(create_args) { - eprintln!("error: {}", error); - std::process::exit(1); - } - } } } Some(cli::Command::Group(group_args)) => { @@ -51,12 +35,6 @@ fn main() { std::process::exit(1); } } - commands::group::GroupCommand::Create(create_args) => { - if let Err(error) = commands::group::create_run(create_args) { - eprintln!("error: {}", error); - std::process::exit(1); - } - } } } Some(cli::Command::Upload(upload_args)) => { diff --git a/src/output.rs b/src/output.rs index 4aaabce..1817750 100644 --- a/src/output.rs +++ b/src/output.rs @@ -3,8 +3,10 @@ use crossterm::terminal; static INFO_ICON: Emoji<'_, '_> = Emoji("ℹ ", ""); static WARNING_ICON: Emoji<'_, '_> = Emoji("⚠ ", ""); +#[allow(dead_code)] static ERROR_ICON: Emoji<'_, '_> = Emoji("✗ ", ""); static SUCCESS_ICON: Emoji<'_, '_> = Emoji("✓ ", ""); +#[allow(dead_code)] static ARROW: Emoji<'_, '_> = Emoji("→ ", ""); const DEFAULT_MAX_WIDTH: usize = 120; @@ -43,6 +45,7 @@ pub fn warning(msg: impl std::fmt::Display) { println!("{} {}", style(WARNING_ICON).yellow(), style(formatted).yellow()); } +#[allow(dead_code)] pub fn error(msg: impl std::fmt::Display) { let formatted = format_message(&msg); log::error!("{}", msg); @@ -55,6 +58,7 @@ pub fn success(msg: impl std::fmt::Display) { println!("{} {}", style(SUCCESS_ICON).green(), style(formatted).green().bold()); } +#[allow(dead_code)] pub fn step(msg: impl std::fmt::Display) { let formatted = format_message(&msg); log::info!("→ {}", msg); @@ -73,14 +77,16 @@ pub fn item(msg: impl std::fmt::Display) { println!(" {}", formatted); } +#[allow(dead_code)] pub fn debug(msg: impl std::fmt::Display) { - let formatted = format_message(&msg); + let _formatted = format_message(&msg); log::debug!("{}", msg); // Debug messages not printed to terminal by default } +#[allow(dead_code)] pub fn trace(msg: impl std::fmt::Display) { - let formatted = format_message(&msg); + let _formatted = format_message(&msg); log::trace!("{}", msg); // Trace messages not printed to terminal by default } diff --git a/src/tellers_api/openapi.tellers_public_api.yaml b/src/tellers_api/openapi.tellers_public_api.yaml index 56eea06..33c87e0 100644 --- a/src/tellers_api/openapi.tellers_public_api.yaml +++ b/src/tellers_api/openapi.tellers_public_api.yaml @@ -35,13 +35,14 @@ paths: type: boolean default: false title: Folder On Top - - name: with_public_assets + - name: group_name in: query required: false schema: - type: boolean - default: false - title: With Public Assets + anyOf: + - type: string + - type: 'null' + title: Group Name - name: x-api-key in: header required: false @@ -75,12 +76,14 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /users/assets/upload_urls: + /users/assets/multipart/complete: post: tags: - accepts-api-key - summary: Create Upload Urls - operationId: create_upload_urls_users_assets_upload_urls_post + summary: Complete Multipart Asset Upload + description: Complete a multipart upload. Client must supply ETags returned + from each part upload. + operationId: complete_multipart_asset_upload_users_assets_multipart_complete_post parameters: - name: x-api-key in: header @@ -103,39 +106,67 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/AssetUploadRequest' - title: Assets + $ref: '#/components/schemas/MultipartCompleteRequest' responses: '200': description: Successful Response content: application/json: - schema: - type: array - items: - $ref: '#/components/schemas/AssetUploadResponse' - title: Response Create Upload Urls Users Assets Upload Urls Post + schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /users/assets/preprocess: + /users/assets/multipart/abort: post: tags: - accepts-api-key - summary: Process Assets - operationId: process_assets_users_assets_preprocess_post + summary: Abort Multipart Asset Upload + operationId: abort_multipart_asset_upload_users_assets_multipart_abort_post parameters: - - name: priority - in: query + - name: x-api-key + in: header required: false schema: - $ref: '#/components/schemas/TaskPriority' - default: highest + anyOf: + - type: string + - type: 'null' + title: X-Api-Key + - name: authorization + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Authorization + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MultipartAbortRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: {} + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /users/assets/upload_urls: + post: + tags: + - accepts-api-key + summary: Create Upload Urls + operationId: create_upload_urls_users_assets_upload_urls_post + parameters: - name: x-api-key in: header required: false @@ -157,7 +188,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ProcessAssetsRequest' + type: array + items: + $ref: '#/components/schemas/AssetUploadRequest' + title: Assets responses: '200': description: Successful Response @@ -166,27 +200,27 @@ paths: schema: type: array items: - $ref: '#/components/schemas/RequestTaskResponse' - title: Response Process Assets Users Assets Preprocess Post + $ref: '#/components/schemas/AssetUploadResponse' + title: Response Create Upload Urls Users Assets Upload Urls Post '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /users/assets/generate_description: + /users/assets/preprocess: post: tags: - accepts-api-key - summary: Generate Description - operationId: generate_description_users_assets_generate_description_post + summary: Process Assets + operationId: process_assets_users_assets_preprocess_post parameters: - - name: asset_id + - name: priority in: query - required: true + required: false schema: - type: string - title: Asset Id + $ref: '#/components/schemas/TaskPriority' + default: highest - name: x-api-key in: header required: false @@ -208,43 +242,40 @@ paths: content: application/json: schema: - type: array - items: - type: string - title: Entity Ids + $ref: '#/components/schemas/ProcessAssetsRequest' responses: '200': description: Successful Response content: application/json: - schema: {} + schema: + type: array + items: + $ref: '#/components/schemas/RequestTaskResponse' + title: Response Process Assets Users Assets Preprocess Post '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /users/entity/list: + /users/tasks: get: tags: - accepts-api-key - summary: List Entities - operationId: list_entities_users_entity_list_get + summary: Get Tasks + description: "Get pending tasks and recently finished tasks for the user.\n\n\ + Args:\n finish_before_seconds: Number of seconds to look back for finished\ + \ tasks. Default is 300 (5 minutes)." + operationId: get_tasks_users_tasks_get parameters: - - name: offset + - name: finish_before_seconds in: query required: false schema: type: integer - default: 0 - title: Offset - - name: limit - in: query - required: false - schema: - type: integer - default: 20 - title: Limit + default: 300 + title: Finish Before Seconds - name: x-api-key in: header required: false @@ -269,21 +300,60 @@ paths: schema: type: array items: - $ref: '#/components/schemas/EntityResponse' - title: Response List Entities Users Entity List Get + $ref: '#/components/schemas/TaskResponse' + title: Response Get Tasks Users Tasks Get '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /users/entity/create: - post: + /asset/most_recent: + get: tags: - accepts-api-key - summary: Create Entity - operationId: create_entity_users_entity_create_post + summary: Get Most Recent Files + description: 'Return the most recently added file(s) the user has read access + to. + + Uses default group(s) unless group_id is set (user must have read access to + that group). + + before: only files with last_edit < before (older); use the smallest last_edit + from a previous page to get the next page.' + operationId: get_most_recent_files_asset_most_recent_get parameters: + - name: path + in: query + required: false + schema: + type: string + default: / + title: Path + - name: limit + in: query + required: false + schema: + type: integer + default: 1 + title: Limit + - name: group_id + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Group Id + - name: before + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Before - name: x-api-key in: header required: false @@ -300,38 +370,33 @@ paths: - type: string - type: 'null' title: Authorization - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateEntityRequest' responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/CreateEntityResponse' + type: array + items: + $ref: '#/components/schemas/FileReference' + title: Response Get Most Recent Files Asset Most Recent Get '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /users/entity/preprocess: + /asset/folder: post: tags: - accepts-api-key - summary: Process Entity - operationId: process_entity_users_entity_preprocess_post + summary: Create Folder + description: 'Create a folder at the given path. Ensures the folder and all + ancestor folders exist for the user''s default group. + + Callable with API key (X-Api-Key header) or Bearer token.' + operationId: create_folder_asset_folder_post parameters: - - name: priority - in: query - required: false - schema: - $ref: '#/components/schemas/TaskPriority' - default: highest - name: x-api-key in: header required: false @@ -353,14 +418,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ProcessEntityRequest' + $ref: '#/components/schemas/CreateFolderRequest' responses: - '200': + '201': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/RequestTaskResponse' + $ref: '#/components/schemas/CreateFolderResponse' '422': description: Validation Error content: @@ -607,50 +672,300 @@ paths: schema: type: array items: - $ref: '#/components/schemas/ChatHistoryResponse' - title: Response List Chats Agent Chats List Get + $ref: '#/components/schemas/ChatHistoryResponse' + title: Response List Chats Agent Chats List Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /agent/chat/{chat_id}: + get: + tags: + - accepts-api-key + summary: Get Chat Info + operationId: get_chat_info_agent_chat__chat_id__get + parameters: + - name: chat_id + in: path + required: true + schema: + type: string + title: Chat Id + - name: x-api-key + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Api-Key + - name: authorization + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Authorization + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/ChatHistoryResponse' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + delete: + tags: + - accepts-api-key + summary: Delete Chat + operationId: delete_chat_agent_chat__chat_id__delete + parameters: + - name: chat_id + in: path + required: true + schema: + type: string + title: Chat Id + - name: cancel_running + in: query + required: false + schema: + type: boolean + default: false + title: Cancel Running + - name: authorization + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Authorization + responses: + '200': + description: Successful Response + content: + application/json: + schema: {} + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /agent/chat/{chat_id}/rename: + patch: + tags: + - accepts-api-key + summary: Rename Chat + operationId: rename_chat_agent_chat__chat_id__rename_patch + parameters: + - name: chat_id + in: path + required: true + schema: + type: string + title: Chat Id + - name: name + in: query + required: true + schema: + type: string + title: Name + - name: x-api-key + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Api-Key + - name: authorization + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Authorization + responses: + '200': + description: Successful Response + content: + application/json: + schema: {} + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /agent/chat/{chat_id}/messages: + get: + tags: + - accepts-api-key + summary: Get Chat + operationId: get_chat_agent_chat__chat_id__messages_get + parameters: + - name: chat_id + in: path + required: true + schema: + type: string + title: Chat Id + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: before + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Before + - name: x-api-key + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Api-Key + - name: authorization + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Authorization + responses: + '200': + description: Successful Response + content: + application/json: + schema: + type: array + items: + anyOf: + - $ref: '#/components/schemas/ChatMessageUserResponse' + - $ref: '#/components/schemas/ChatMessageAgentResponse' + title: Response Get Chat Agent Chat Chat Id Messages Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /group/create: + post: + tags: + - accepts-api-key + summary: Create Group + operationId: create_group_group_create_post + parameters: + - name: x-api-key + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Api-Key + - name: authorization + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Authorization + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateGroupRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/CreateGroupResponse' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /group/list: + get: + tags: + - accepts-api-key + summary: List Groups + operationId: list_groups_group_list_get + parameters: + - name: x-api-key + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Api-Key + - name: authorization + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Authorization + responses: + '200': + description: Successful Response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/GroupResponse' + title: Response List Groups Group List Get '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /agent/chat/{chat_id}/messages: - get: + /group/update: + post: tags: - accepts-api-key - summary: Get Chat - operationId: get_chat_agent_chat__chat_id__messages_get + summary: Update Group + operationId: update_group_group_update_post parameters: - - name: chat_id - in: path - required: true - schema: - type: string - title: Chat Id - - name: offset - in: query - required: false - schema: - type: integer - default: 0 - title: Offset - - name: limit - in: query - required: false - schema: - type: integer - default: 20 - title: Limit - - name: before - in: query - required: false - schema: - anyOf: - - type: string - format: date-time - - type: 'null' - title: Before - name: x-api-key in: header required: false @@ -667,30 +982,30 @@ paths: - type: string - type: 'null' title: Authorization + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateGroupRequest' responses: '200': description: Successful Response content: application/json: - schema: - type: array - items: - anyOf: - - $ref: '#/components/schemas/ChatMessageUserResponse' - - $ref: '#/components/schemas/ChatMessageAgentResponse' - title: Response Get Chat Agent Chat Chat Id Messages Get + schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /group/create: + /group/users/add: post: tags: - accepts-api-key - summary: Create Group - operationId: create_group_group_create_post + summary: Add Group Users + operationId: add_group_users_group_users_add_post parameters: - name: x-api-key in: header @@ -713,26 +1028,26 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateGroupRequest' + $ref: '#/components/schemas/GroupUsersRequest' responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/CreateGroupResponse' + $ref: '#/components/schemas/GroupUsersResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /group/list: - get: + /group/users/remove: + post: tags: - accepts-api-key - summary: List Groups - operationId: list_groups_group_list_get + summary: Remove Group Users + operationId: remove_group_users_group_users_remove_post parameters: - name: x-api-key in: header @@ -750,16 +1065,19 @@ paths: - type: string - type: 'null' title: Authorization + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupUsersRemoveRequest' responses: '200': description: Successful Response content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/GroupResponse' - title: Response List Groups Group List Get + $ref: '#/components/schemas/GroupUsersResponse' '422': description: Validation Error content: @@ -801,10 +1119,12 @@ components: anyOf: - type: string enum: + - none - minimal - low - medium - high + - xhigh - type: 'null' title: Llm Reasoning Effort description: 'Controls reasoning effort: higher values yield more precise @@ -834,6 +1154,13 @@ components: title: Custom Agent Id description: The ID of the custom agent to use to generate the response. If not provided, the default custom agent will be used. + parallel_tool_calls: + type: boolean + title: Parallel Tool Calls + description: Whether to allow parallel tool calls. If True, the tool calls + will be executed in parallel. If False, the tool calls will be executed + sequentially. + default: false additionalProperties: false type: object required: @@ -898,6 +1225,22 @@ components: title: Upload Id source_file: $ref: '#/components/schemas/SourceFileInfo' + file_type: + $ref: '#/components/schemas/FileType' + default: video + multipart: + type: boolean + title: Multipart + description: If true, returns presigned multipart upload URLs instead of + a single PUT URL. + default: false + multipart_part_size: + anyOf: + - type: integer + - type: 'null' + title: Multipart Part Size + description: Optional multipart part size in bytes (min 5 MiB). If omitted, + a default is used. type: object required: - content_length @@ -909,16 +1252,37 @@ components: upload_id: type: string title: Upload Id - presigned_put_url: - type: string - title: Presigned Put Url asset_id: type: string title: Asset Id + presigned_put_url: + anyOf: + - type: string + - type: 'null' + title: Presigned Put Url + presigned_put_urls: + items: + type: string + type: array + title: Presigned Put Urls + multipart_upload_id: + anyOf: + - type: string + - type: 'null' + title: Multipart Upload Id + multipart_part_size: + anyOf: + - type: integer + - type: 'null' + title: Multipart Part Size + multipart_part_count: + anyOf: + - type: integer + - type: 'null' + title: Multipart Part Count type: object required: - upload_id - - presigned_put_url - asset_id title: AssetUploadResponse ChatHistoryResponse: @@ -942,6 +1306,12 @@ components: - type: string - type: 'null' title: Custom Agent Id + total_tokens: + type: number + title: Total Tokens + is_running: + type: boolean + title: Is Running type: object required: - id @@ -949,6 +1319,8 @@ components: - created_at - updated_at - custom_agent_id + - total_tokens + - is_running title: ChatHistoryResponse ChatMessageAgentResponse: properties: @@ -1016,32 +1388,24 @@ components: - context - created_at title: ChatMessageUserResponse - CreateEntityRequest: + CreateFolderRequest: properties: - group_id: + path: type: string - title: Group Id - name: - type: string - title: Name - description: - type: string - title: Description + title: Path type: object required: - - group_id - - name - - description - title: CreateEntityRequest - CreateEntityResponse: + - path + title: CreateFolderRequest + CreateFolderResponse: properties: - entity_id: + path: type: string - title: Entity Id + title: Path type: object required: - - entity_id - title: CreateEntityResponse + - path + title: CreateFolderResponse CreateGroupRequest: properties: name: @@ -1060,27 +1424,6 @@ components: required: - id title: CreateGroupResponse - EntityResponse: - properties: - id: - type: string - title: Id - name: - type: string - title: Name - description: - type: string - title: Description - group_id: - type: string - title: Group Id - type: object - required: - - id - - name - - description - - group_id - title: EntityResponse FileReference: properties: file_name: @@ -1104,13 +1447,31 @@ components: - type: number - type: 'null' title: Duration Seconds + last_edit: + type: string + format: date-time + title: Last Edit type: object required: - file_name - is_folder - file_id - file_type + - last_edit title: FileReference + FileType: + type: string + enum: + - video + - audio + - image + - placeholder + - group_clip + - folder + - project + - entity + - teller_config + title: FileType GroupResponse: properties: id: @@ -1124,6 +1485,75 @@ components: - id - name title: GroupResponse + GroupRights: + type: string + enum: + - ADMIN + - READ + - WRITE + title: GroupRights + GroupUsersRemoveRequest: + properties: + group_id: + type: string + title: Group Id + user_ids: + items: + type: string + type: array + title: User Ids + default: [] + emails: + items: + type: string + type: array + title: Emails + default: [] + type: object + required: + - group_id + title: GroupUsersRemoveRequest + GroupUsersRequest: + properties: + group_id: + type: string + title: Group Id + user_ids: + items: + type: string + type: array + title: User Ids + default: [] + emails: + items: + type: string + type: array + title: Emails + default: [] + right: + $ref: '#/components/schemas/GroupRights' + default: READ + type: object + required: + - group_id + title: GroupUsersRequest + GroupUsersResponse: + properties: + user_ids: + items: + type: string + type: array + title: User Ids + not_found: + items: + type: string + type: array + title: Not Found + type: object + required: + - user_ids + - not_found + title: GroupUsersResponse HTTPValidationError: properties: detail: @@ -1133,6 +1563,51 @@ components: title: Detail type: object title: HTTPValidationError + MultipartAbortRequest: + properties: + asset_id: + type: string + title: Asset Id + multipart_upload_id: + type: string + title: Multipart Upload Id + type: object + required: + - asset_id + - multipart_upload_id + title: MultipartAbortRequest + MultipartCompletePart: + properties: + part_number: + type: integer + title: Part Number + etag: + type: string + title: Etag + type: object + required: + - part_number + - etag + title: MultipartCompletePart + MultipartCompleteRequest: + properties: + asset_id: + type: string + title: Asset Id + multipart_upload_id: + type: string + title: Multipart Upload Id + parts: + items: + $ref: '#/components/schemas/MultipartCompletePart' + type: array + title: Parts + type: object + required: + - asset_id + - multipart_upload_id + - parts + title: MultipartCompleteRequest ProcessAssetsRequest: properties: assets: @@ -1152,26 +1627,18 @@ components: type: boolean title: Generate Time Based Media Description default: false + override_entity_ids: + anyOf: + - items: + type: string + type: array + - type: 'null' + title: Override Entity Ids type: object required: - assets - version title: ProcessAssetsRequest - ProcessEntityRequest: - properties: - entity_id: - type: string - title: Entity Id - assets: - items: - $ref: '#/components/schemas/AssetUploadResponse' - type: array - title: Assets - type: object - required: - - entity_id - - assets - title: ProcessEntityRequest RequestTaskResponse: properties: task_id: @@ -1298,6 +1765,72 @@ components: - lowest title: TaskPriority description: Task priority is used to prioritize tasks in the queue. + TaskResponse: + properties: + id: + type: string + title: Id + task_type: + type: string + title: Task Type + status: + type: string + title: Status + progress: + type: number + title: Progress + asset_ids: + items: + type: string + type: array + title: Asset Ids + created_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Created At + ended_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Ended At + error_message: + anyOf: + - type: string + - type: 'null' + title: Error Message + type: object + required: + - id + - task_type + - status + - progress + - asset_ids + - created_at + - ended_at + - error_message + title: TaskResponse + UpdateGroupRequest: + properties: + group_id: + type: string + title: Group Id + name: + anyOf: + - type: string + - type: 'null' + title: Name + is_public_read_access: + anyOf: + - type: boolean + - type: 'null' + title: Is Public Read Access + type: object + required: + - group_id + title: UpdateGroupRequest UseAgentToolRequest: properties: tool_id: From 13bb1016424a7c9eafbbb14fd6602673d47c32ba Mon Sep 17 00:00:00 2001 From: bjay kamwa watanabe Date: Sun, 22 Feb 2026 19:05:09 +0100 Subject: [PATCH 2/2] add recreate file system --- src/commands/upload/main.rs | 98 +++++++++++++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/src/commands/upload/main.rs b/src/commands/upload/main.rs index 10220ef..4a370ac 100644 --- a/src/commands/upload/main.rs +++ b/src/commands/upload/main.rs @@ -29,7 +29,8 @@ use tokio::sync::mpsc as tokio_mpsc; use tellers_api_client::apis::accepts_api_key_api as api; use tellers_api_client::apis::configuration::Configuration; use tellers_api_client::models::{ - AssetUploadRequest, AssetUploadResponse, ProcessAssetsRequest, SourceFileInfo, + AssetUploadRequest, AssetUploadResponse, CreateFolderRequest, ProcessAssetsRequest, + SourceFileInfo, }; #[derive(Args, Debug)] @@ -140,8 +141,99 @@ pub fn run(args: UploadArgs) -> Result<(), String> { } } -fn run_recreate_filesystem(_args: RecreateFilesystemArgs) -> Result<(), String> { - // TODO: implement recreate filesystem API call +fn run_recreate_filesystem(args: RecreateFilesystemArgs) -> Result<(), String> { + let base_dir = PathBuf::from(&args.path); + if !base_dir.exists() { + return Err(format!("path not found: {}", base_dir.display())); + } + if base_dir.is_file() { + return Err("path must be a directory".to_string()); + } + + // Collect all directories (root and every subfolder), with relative path for in-app mapping. + // By default exclude any path that has a segment starting with '.' (e.g. .git, .fingerprint). + let base_dir = base_dir.canonicalize().map_err(|e| format!("{}", e))?; + let mut dirs: Vec = Vec::new(); + for entry in WalkDir::new(&base_dir).into_iter().filter_map(Result::ok) { + if entry.file_type().is_dir() { + let p = entry.path(); + let rel = match p.strip_prefix(&base_dir) { + Ok(r) => r, + Err(_) => continue, + }; + let has_dot_component = rel.components().any(|c| { + c.as_os_str() + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false) + }); + if !has_dot_component { + dirs.push(p.to_path_buf()); + } + } + } + + // Build in-app path for each dir: in_app_path + relative_path, or relative path only + let mut in_app_paths: Vec = dirs + .iter() + .map(|p| { + let rel = p + .strip_prefix(&base_dir) + .unwrap_or(p) + .to_string_lossy() + .replace('\\', "/"); + let rel_trimmed = rel.trim_start_matches('/'); + match &args.in_app_path { + Some(prefix) => { + let prefix = prefix.trim_end_matches('/'); + if rel_trimmed.is_empty() { + prefix.to_string() + } else { + format!("{}/{}", prefix, rel_trimmed) + } + } + None => { + if rel_trimmed.is_empty() { + ".".to_string() + } else { + rel_trimmed.to_string() + } + } + } + }) + .collect(); + + // Don't create a folder named "." on the server when root maps to it + in_app_paths.retain(|p| p != "."); + + // Sort by path depth so parents are created before children + in_app_paths.sort_by_key(|p| p.matches('/').count()); + + if args.dry_run { + for p in &in_app_paths { + output::info(format!("[dry run] would create folder: {}", p)); + } + return Ok(()); + } + + let cfg = api_config::create_config(); + let api_key = api_config::get_api_key(None)?; + let bearer_header = api_config::get_bearer_header(None); + + let rt = tokio::runtime::Runtime::new() + .map_err(|e| format!("failed to start runtime: {}", e))?; + + for folder_path in in_app_paths { + let req = CreateFolderRequest::new(folder_path.clone()); + let response = rt.block_on(api::create_folder_asset_folder_post( + &cfg, + req, + Some(api_key.as_str()), + bearer_header.as_deref(), + )) + .map_err(|e| e.to_string())?; + println!("{}", response.path); + } Ok(()) }