@@ -8,7 +8,7 @@ mod output;
88mod tui;
99mod utils;
1010
11- use clap:: { ArgAction , Parser , Subcommand } ;
11+ use clap:: { ArgAction , Args , Parser , Subcommand } ;
1212use config:: Config ;
1313use error:: Result ;
1414use 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) ]
13081386mod 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