Skip to content

Commit d1ab09d

Browse files
authored
fix(cli): scope upload-only options (volcengine#2073)
1 parent 57339f2 commit d1ab09d

4 files changed

Lines changed: 240 additions & 28 deletions

File tree

crates/ov_cli/src/client.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -578,13 +578,24 @@ impl HttpClient {
578578
data: &str,
579579
wait: bool,
580580
timeout: Option<f64>,
581+
show_progress: bool,
582+
verbose: bool,
581583
) -> Result<serde_json::Value> {
582584
let path_obj = Path::new(data);
583585

584586
if path_obj.exists() {
585587
if path_obj.is_dir() {
586-
let zip_file = self.zip_directory(path_obj, None)?;
587-
let temp_file_id = self.upload_temp_file(zip_file.path()).await?;
588+
let zip_file = if show_progress {
589+
self.zip_directory_with_progress(path_obj, verbose, None)?
590+
} else {
591+
self.zip_directory(path_obj, None)?
592+
};
593+
let temp_file_id = if show_progress {
594+
self.upload_temp_file_with_progress(zip_file.path(), verbose)
595+
.await?
596+
} else {
597+
self.upload_temp_file(zip_file.path()).await?
598+
};
588599

589600
let body = serde_json::json!({
590601
"temp_file_id": temp_file_id,
@@ -597,7 +608,12 @@ impl HttpClient {
597608
.post_with_timeout("/api/v1/skills", &body, dynamic_timeout)
598609
.await
599610
} else if path_obj.is_file() {
600-
let temp_file_id = self.upload_temp_file(path_obj).await?;
611+
let temp_file_id = if show_progress {
612+
self.upload_temp_file_with_progress(path_obj, verbose)
613+
.await?
614+
} else {
615+
self.upload_temp_file(path_obj).await?
616+
};
601617

602618
let body = serde_json::json!({
603619
"temp_file_id": temp_file_id,

crates/ov_cli/src/commands/resources.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,14 @@ pub async fn add_skill(
5858
data: &str,
5959
wait: bool,
6060
timeout: Option<f64>,
61+
show_progress: bool,
62+
verbose: bool,
6163
format: OutputFormat,
6264
compact: bool,
6365
) -> Result<()> {
64-
let result = client.add_skill(data, wait, timeout).await?;
66+
let result = client
67+
.add_skill(data, wait, timeout, show_progress, verbose)
68+
.await?;
6569

6670
if !wait && matches!(format, OutputFormat::Table) {
6771
eprintln!("Note: Skill is being processed in the background.");

crates/ov_cli/src/handlers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ pub async fn handle_add_skill(
134134
&data,
135135
wait,
136136
timeout,
137+
ctx.should_show_progress(),
138+
ctx.is_verbose(),
137139
ctx.output_format,
138140
ctx.compact,
139141
)

crates/ov_cli/src/main.rs

Lines changed: 214 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mod output;
88
mod tui;
99
mod utils;
1010

11-
use clap::{ArgAction, Parser, Subcommand};
11+
use clap::{ArgAction, Args, Parser, Subcommand};
1212
use config::Config;
1313
use error::Result;
1414
use output::OutputFormat;
@@ -140,26 +140,77 @@ struct Cli {
140140
#[arg(long = "agent-id", global = true)]
141141
agent_id: Option<String>,
142142

143-
/// Use root API key for admin privileges
144-
#[arg(long, global = true)]
143+
/// Use root API key for admin commands
144+
#[arg(long)]
145145
sudo: bool,
146146

147-
/// Show upload progress (overrides config file)
148-
#[arg(long, global = true)]
147+
/// Show upload progress (legacy pre-command placement; prefer command-local --progress)
148+
#[arg(long, hide = true)]
149149
progress: bool,
150150

151-
/// Disable upload progress (overrides config file)
152-
#[arg(long = "no-progress", global = true, conflicts_with = "progress")]
151+
/// Disable upload progress (legacy pre-command placement; prefer command-local --no-progress)
152+
#[arg(long = "no-progress", hide = true, conflicts_with = "progress")]
153153
no_progress: bool,
154154

155-
/// Enable verbose output (overrides config file)
156-
#[arg(short, long, global = true)]
155+
/// Enable upload diagnostics (legacy pre-command placement; prefer command-local --verbose)
156+
#[arg(short, long, hide = true)]
157157
verbose: bool,
158158

159159
#[command(subcommand)]
160160
command: Commands,
161161
}
162162

163+
#[derive(Args, Debug, Clone, Copy, Default)]
164+
struct UploadCliOptions {
165+
/// Show local file upload progress (overrides config file)
166+
#[arg(long, conflicts_with = "no_progress")]
167+
progress: bool,
168+
169+
/// Disable local file upload progress (overrides config file)
170+
#[arg(long = "no-progress", conflicts_with = "progress")]
171+
no_progress: bool,
172+
173+
/// Print extra diagnostics during local file upload
174+
#[arg(short, long)]
175+
verbose: bool,
176+
}
177+
178+
impl UploadCliOptions {
179+
fn is_set(self) -> bool {
180+
self.progress || self.no_progress || self.verbose
181+
}
182+
183+
fn merged_with_legacy(self, legacy: Self) -> Self {
184+
Self {
185+
progress: self.progress || (!self.no_progress && legacy.progress),
186+
no_progress: self.no_progress || (!self.progress && legacy.no_progress),
187+
verbose: self.verbose || legacy.verbose,
188+
}
189+
}
190+
191+
fn show_progress_override(self) -> Option<bool> {
192+
if self.progress {
193+
Some(true)
194+
} else if self.no_progress {
195+
Some(false)
196+
} else {
197+
None
198+
}
199+
}
200+
201+
fn verbose_override(self) -> Option<bool> {
202+
if self.verbose { Some(true) } else { None }
203+
}
204+
}
205+
206+
impl CliContext {
207+
fn with_upload_options(mut self, options: UploadCliOptions) -> Self {
208+
self.show_progress = options.show_progress_override();
209+
self.verbose = options.verbose_override();
210+
self
211+
}
212+
}
213+
163214
// Commands are organized with category tags in their doc comments.
164215
//
165216
// # Command Tagging System
@@ -218,6 +269,8 @@ enum Commands {
218269
/// Watch interval in minutes for automatic resource monitoring (0 = no monitoring)
219270
#[arg(long, default_value = "0")]
220271
watch_interval: f64,
272+
#[command(flatten)]
273+
upload_options: UploadCliOptions,
221274
},
222275
/// [Data] Add a skill into OpenViking
223276
AddSkill {
@@ -229,6 +282,8 @@ enum Commands {
229282
/// Wait timeout in seconds
230283
#[arg(long)]
231284
timeout: Option<f64>,
285+
#[command(flatten)]
286+
upload_options: UploadCliOptions,
232287
},
233288
/// [Data] List directory contents
234289
#[command(alias = "list")]
@@ -625,6 +680,23 @@ impl Commands {
625680
_ => false,
626681
}
627682
}
683+
684+
fn supports_upload_options(&self) -> bool {
685+
matches!(self, Self::AddResource { .. } | Self::AddSkill { .. })
686+
}
687+
}
688+
689+
fn legacy_upload_option_error(
690+
options: UploadCliOptions,
691+
command: &Commands,
692+
) -> Option<&'static str> {
693+
if options.is_set() && !command.supports_upload_options() {
694+
Some(
695+
"--progress, --no-progress, and --verbose are only supported for add-resource and add-skill",
696+
)
697+
} else {
698+
None
699+
}
628700
}
629701

630702
#[derive(Subcommand)]
@@ -1005,17 +1077,15 @@ async fn main() {
10051077

10061078
let output_format = cli.output;
10071079
let compact = cli.compact;
1008-
1009-
// Determine show_progress override:
1010-
let show_progress = if cli.progress {
1011-
Some(true)
1012-
} else if cli.no_progress {
1013-
Some(false)
1014-
} else {
1015-
None
1080+
let legacy_upload_options = UploadCliOptions {
1081+
progress: cli.progress,
1082+
no_progress: cli.no_progress,
1083+
verbose: cli.verbose,
10161084
};
1017-
// Determine verbose override:
1018-
let verbose = if cli.verbose { Some(true) } else { None };
1085+
if let Some(message) = legacy_upload_option_error(legacy_upload_options, &cli.command) {
1086+
eprintln!("Error: {}", message);
1087+
std::process::exit(2);
1088+
}
10191089

10201090
let ctx = match CliContext::new(
10211091
output_format,
@@ -1024,8 +1094,8 @@ async fn main() {
10241094
cli.user.clone(),
10251095
cli.agent_id.clone(),
10261096
cli.sudo,
1027-
show_progress,
1028-
verbose,
1097+
None,
1098+
None,
10291099
) {
10301100
Ok(ctx) => ctx,
10311101
Err(e) => {
@@ -1064,7 +1134,10 @@ async fn main() {
10641134
exclude,
10651135
no_directly_upload_media,
10661136
watch_interval,
1137+
upload_options,
10671138
} => {
1139+
let ctx =
1140+
ctx.with_upload_options(upload_options.merged_with_legacy(legacy_upload_options));
10681141
handlers::handle_add_resource(
10691142
path,
10701143
to,
@@ -1088,7 +1161,12 @@ async fn main() {
10881161
data,
10891162
wait,
10901163
timeout,
1091-
} => handlers::handle_add_skill(data, wait, timeout, ctx).await,
1164+
upload_options,
1165+
} => {
1166+
let ctx =
1167+
ctx.with_upload_options(upload_options.merged_with_legacy(legacy_upload_options));
1168+
handlers::handle_add_skill(data, wait, timeout, ctx).await
1169+
}
10921170
Commands::Relations { uri } => handlers::handle_relations(uri, ctx).await,
10931171
Commands::Link {
10941172
from_uri,
@@ -1306,11 +1384,14 @@ async fn main() {
13061384

13071385
#[cfg(test)]
13081386
mod tests {
1309-
use super::{Cli, CliContext, Commands, PrivacyCommands, preprocess_privacy_args};
1387+
use super::{
1388+
Cli, CliContext, Commands, PrivacyCommands, UploadCliOptions, legacy_upload_option_error,
1389+
preprocess_privacy_args,
1390+
};
13101391
use crate::config::Config;
13111392
use crate::handlers;
13121393
use crate::output::OutputFormat;
1313-
use clap::Parser;
1394+
use clap::{CommandFactory, Parser};
13141395
use std::ffi::OsString;
13151396

13161397
#[test]
@@ -1332,6 +1413,115 @@ mod tests {
13321413
assert_eq!(cli.agent_id.as_deref(), Some("assistant-1"));
13331414
}
13341415

1416+
#[test]
1417+
fn cli_tree_help_hides_upload_and_admin_only_flags() {
1418+
let err = Cli::command()
1419+
.try_get_matches_from(["ov", "tree", "--help"])
1420+
.expect_err("help should exit through clap error");
1421+
let help = err.to_string();
1422+
1423+
assert!(!help.contains("--progress"));
1424+
assert!(!help.contains("--no-progress"));
1425+
assert!(!help.contains("--verbose"));
1426+
assert!(!help.contains("--sudo"));
1427+
}
1428+
1429+
#[test]
1430+
fn cli_add_resource_help_shows_upload_flags() {
1431+
let err = Cli::command()
1432+
.try_get_matches_from(["ov", "add-resource", "--help"])
1433+
.expect_err("help should exit through clap error");
1434+
let help = err.to_string();
1435+
1436+
assert!(help.contains("--progress"));
1437+
assert!(help.contains("--no-progress"));
1438+
assert!(help.contains("--verbose"));
1439+
}
1440+
1441+
#[test]
1442+
fn cli_add_skill_help_shows_upload_flags() {
1443+
let err = Cli::command()
1444+
.try_get_matches_from(["ov", "add-skill", "--help"])
1445+
.expect_err("help should exit through clap error");
1446+
let help = err.to_string();
1447+
1448+
assert!(help.contains("--progress"));
1449+
assert!(help.contains("--no-progress"));
1450+
assert!(help.contains("--verbose"));
1451+
}
1452+
1453+
#[test]
1454+
fn cli_tree_rejects_upload_and_admin_only_flags_after_subcommand() {
1455+
assert!(Cli::try_parse_from(["ov", "tree", "viking://", "--progress"]).is_err());
1456+
assert!(Cli::try_parse_from(["ov", "tree", "viking://", "--no-progress"]).is_err());
1457+
assert!(Cli::try_parse_from(["ov", "tree", "viking://", "--verbose"]).is_err());
1458+
assert!(Cli::try_parse_from(["ov", "tree", "viking://", "--sudo"]).is_err());
1459+
}
1460+
1461+
#[test]
1462+
fn cli_parses_upload_flags_on_upload_commands() {
1463+
let add_resource =
1464+
Cli::try_parse_from(["ov", "add-resource", "./README.md", "--progress", "--verbose"])
1465+
.expect("add-resource upload flags should parse");
1466+
match add_resource.command {
1467+
Commands::AddResource { upload_options, .. } => {
1468+
assert!(upload_options.progress);
1469+
assert!(upload_options.verbose);
1470+
}
1471+
_ => panic!("expected add-resource command"),
1472+
}
1473+
1474+
let add_skill = Cli::try_parse_from(["ov", "add-skill", "./skill", "--no-progress"])
1475+
.expect("add-skill upload flags should parse");
1476+
match add_skill.command {
1477+
Commands::AddSkill { upload_options, .. } => {
1478+
assert!(upload_options.no_progress);
1479+
}
1480+
_ => panic!("expected add-skill command"),
1481+
}
1482+
}
1483+
1484+
#[test]
1485+
fn cli_keeps_legacy_pre_command_upload_flags() {
1486+
let cli = Cli::try_parse_from([
1487+
"ov",
1488+
"--progress",
1489+
"--verbose",
1490+
"add-resource",
1491+
"./README.md",
1492+
])
1493+
.expect("legacy pre-command upload flags should still parse");
1494+
1495+
assert!(cli.progress);
1496+
assert!(cli.verbose);
1497+
}
1498+
1499+
#[test]
1500+
fn legacy_pre_command_upload_flags_only_allow_upload_commands() {
1501+
let upload_options = UploadCliOptions {
1502+
progress: true,
1503+
no_progress: false,
1504+
verbose: false,
1505+
};
1506+
1507+
let tree = Cli::try_parse_from(["ov", "--progress", "tree", "viking://"])
1508+
.expect("hidden legacy flag still parses before runtime validation");
1509+
assert!(legacy_upload_option_error(upload_options, &tree.command).is_some());
1510+
1511+
let add_resource =
1512+
Cli::try_parse_from(["ov", "--progress", "add-resource", "./README.md"])
1513+
.expect("legacy pre-command upload flags should parse for add-resource");
1514+
assert!(legacy_upload_option_error(upload_options, &add_resource.command).is_none());
1515+
}
1516+
1517+
#[test]
1518+
fn cli_parses_sudo_before_admin_command() {
1519+
let cli = Cli::try_parse_from(["ov", "--sudo", "admin", "list-accounts"])
1520+
.expect("pre-command sudo should parse");
1521+
1522+
assert!(cli.sudo);
1523+
}
1524+
13351525
#[test]
13361526
fn cli_context_overrides_identity_from_cli_flags() {
13371527
let config = Config {

0 commit comments

Comments
 (0)