1use crate::client::ClientType;
2use crate::rules::{Confidence, ParseEnumError, RuleSeverity, Severity};
3use clap::{Parser, ValueEnum};
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "lowercase")]
9pub enum OutputFormat {
10 #[default]
11 Terminal,
12 Json,
13 Sarif,
14 Html,
15 Markdown,
16}
17
18impl std::str::FromStr for OutputFormat {
19 type Err = ParseEnumError;
20
21 fn from_str(s: &str) -> Result<Self, Self::Err> {
22 match s.to_lowercase().as_str() {
23 "terminal" | "term" => Ok(OutputFormat::Terminal),
24 "json" => Ok(OutputFormat::Json),
25 "sarif" => Ok(OutputFormat::Sarif),
26 "html" => Ok(OutputFormat::Html),
27 "markdown" | "md" => Ok(OutputFormat::Markdown),
28 _ => Err(ParseEnumError::invalid("OutputFormat", s)),
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "lowercase")]
36pub enum BadgeFormat {
37 Url,
39 #[default]
41 Markdown,
42 Html,
44}
45
46impl std::str::FromStr for BadgeFormat {
47 type Err = ParseEnumError;
48
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
50 match s.to_lowercase().as_str() {
51 "url" => Ok(BadgeFormat::Url),
52 "markdown" | "md" => Ok(BadgeFormat::Markdown),
53 "html" => Ok(BadgeFormat::Html),
54 _ => Err(ParseEnumError::invalid("BadgeFormat", s)),
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "lowercase")]
61pub enum ScanType {
62 #[default]
63 Skill,
64 Hook,
65 Mcp,
66 Command,
67 Rules,
68 Docker,
69 Dependency,
70 Subagent,
72 Plugin,
74}
75
76impl std::str::FromStr for ScanType {
77 type Err = ParseEnumError;
78
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 match s.to_lowercase().as_str() {
81 "skill" => Ok(ScanType::Skill),
82 "hook" => Ok(ScanType::Hook),
83 "mcp" => Ok(ScanType::Mcp),
84 "command" | "cmd" => Ok(ScanType::Command),
85 "rules" => Ok(ScanType::Rules),
86 "docker" => Ok(ScanType::Docker),
87 "dependency" | "dep" | "deps" => Ok(ScanType::Dependency),
88 "subagent" | "agent" => Ok(ScanType::Subagent),
89 "plugin" => Ok(ScanType::Plugin),
90 _ => Err(ParseEnumError::invalid("ScanType", s)),
91 }
92 }
93}
94
95#[derive(Parser, Debug)]
96#[command(
97 name = "cc-audit",
98 version,
99 about = "Security auditor for Claude Code skills, hooks, and MCP servers",
100 long_about = "cc-audit scans Claude Code skills, hooks, and MCP servers for security vulnerabilities before installation."
101)]
102pub struct Cli {
103 #[arg(required_unless_present_any = ["remote", "remote_list", "awesome_claude_code", "init", "all_clients", "client"])]
105 pub paths: Vec<PathBuf>,
106
107 #[arg(long, conflicts_with_all = ["remote", "remote_list", "awesome_claude_code", "client"])]
109 pub all_clients: bool,
110
111 #[arg(long, value_enum, conflicts_with_all = ["remote", "remote_list", "awesome_claude_code", "all_clients"])]
113 pub client: Option<ClientType>,
114
115 #[arg(long, value_name = "URL")]
117 pub remote: Option<String>,
118
119 #[arg(long, default_value = "HEAD")]
121 pub git_ref: String,
122
123 #[arg(long, env = "GITHUB_TOKEN", value_name = "TOKEN")]
125 pub remote_auth: Option<String>,
126
127 #[arg(long, conflicts_with = "remote", value_name = "FILE")]
129 pub remote_list: Option<PathBuf>,
130
131 #[arg(long, conflicts_with_all = ["remote", "remote_list"])]
133 pub awesome_claude_code: bool,
134
135 #[arg(long, default_value = "4")]
137 pub parallel_clones: usize,
138
139 #[arg(long)]
141 pub badge: bool,
142
143 #[arg(long, value_enum, default_value_t = BadgeFormat::Markdown)]
145 pub badge_format: BadgeFormat,
146
147 #[arg(long)]
149 pub summary: bool,
150
151 #[arg(short, long, value_enum, default_value_t = OutputFormat::Terminal)]
153 pub format: OutputFormat,
154
155 #[arg(short, long)]
157 pub strict: bool,
158
159 #[arg(long)]
161 pub warn_only: bool,
162
163 #[arg(long, value_enum)]
165 pub min_severity: Option<Severity>,
166
167 #[arg(long, value_enum)]
169 pub min_rule_severity: Option<RuleSeverity>,
170
171 #[arg(short = 't', long = "type", value_enum, default_value_t = ScanType::Skill)]
173 pub scan_type: ScanType,
174
175 #[arg(short, long)]
177 pub recursive: bool,
178
179 #[arg(long)]
181 pub ci: bool,
182
183 #[arg(short, long)]
185 pub verbose: bool,
186
187 #[arg(long)]
189 pub include_tests: bool,
190
191 #[arg(long)]
193 pub include_node_modules: bool,
194
195 #[arg(long)]
197 pub include_vendor: bool,
198
199 #[arg(long, value_enum, default_value_t = Confidence::Tentative)]
201 pub min_confidence: Confidence,
202
203 #[arg(long)]
205 pub skip_comments: bool,
206
207 #[arg(long)]
209 pub fix_hint: bool,
210
211 #[arg(short, long)]
213 pub watch: bool,
214
215 #[arg(long)]
217 pub init_hook: bool,
218
219 #[arg(long)]
221 pub remove_hook: bool,
222
223 #[arg(long)]
225 pub malware_db: Option<PathBuf>,
226
227 #[arg(long)]
229 pub no_malware_scan: bool,
230
231 #[arg(long)]
233 pub cve_db: Option<PathBuf>,
234
235 #[arg(long)]
237 pub no_cve_scan: bool,
238
239 #[arg(long)]
241 pub custom_rules: Option<PathBuf>,
242
243 #[arg(long)]
245 pub baseline: bool,
246
247 #[arg(long)]
249 pub check_drift: bool,
250
251 #[arg(long)]
253 pub init: bool,
254
255 #[arg(short, long)]
257 pub output: Option<PathBuf>,
258
259 #[arg(long, value_name = "FILE")]
261 pub save_baseline: Option<PathBuf>,
262
263 #[arg(long, value_name = "FILE")]
265 pub baseline_file: Option<PathBuf>,
266
267 #[arg(long, num_args = 2, value_names = ["PATH1", "PATH2"])]
269 pub compare: Option<Vec<PathBuf>>,
270
271 #[arg(long)]
273 pub fix: bool,
274
275 #[arg(long)]
277 pub fix_dry_run: bool,
278
279 #[arg(long)]
281 pub mcp_server: bool,
282
283 #[arg(long)]
285 pub deep_scan: bool,
286
287 #[arg(long, value_name = "NAME")]
289 pub profile: Option<String>,
290
291 #[arg(long, value_name = "NAME")]
293 pub save_profile: Option<String>,
294}
295
296impl Default for Cli {
297 fn default() -> Self {
298 Self {
299 paths: Vec::new(),
300 all_clients: false,
301 client: None,
302 remote: None,
303 git_ref: "HEAD".to_string(),
304 remote_auth: None,
305 remote_list: None,
306 awesome_claude_code: false,
307 parallel_clones: 4,
308 badge: false,
309 badge_format: BadgeFormat::Markdown,
310 summary: false,
311 format: OutputFormat::Terminal,
312 strict: false,
313 warn_only: false,
314 min_severity: None,
315 min_rule_severity: None,
316 scan_type: ScanType::Skill,
317 recursive: false,
318 ci: false,
319 verbose: false,
320 include_tests: false,
321 include_node_modules: false,
322 include_vendor: false,
323 min_confidence: Confidence::Tentative,
324 skip_comments: false,
325 fix_hint: false,
326 watch: false,
327 init_hook: false,
328 remove_hook: false,
329 malware_db: None,
330 no_malware_scan: false,
331 cve_db: None,
332 no_cve_scan: false,
333 custom_rules: None,
334 baseline: false,
335 check_drift: false,
336 init: false,
337 output: None,
338 save_baseline: None,
339 baseline_file: None,
340 compare: None,
341 fix: false,
342 fix_dry_run: false,
343 mcp_server: false,
344 deep_scan: false,
345 profile: None,
346 save_profile: None,
347 }
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 use crate::rules::{Confidence, RuleSeverity, Severity};
355 use clap::CommandFactory;
356
357 #[test]
358 fn test_cli_valid() {
359 Cli::command().debug_assert();
360 }
361
362 #[test]
363 fn test_parse_basic_args() {
364 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
365 assert_eq!(cli.paths.len(), 1);
366 assert!(!cli.strict);
367 assert!(!cli.recursive);
368 }
369
370 #[test]
371 fn test_parse_multiple_paths() {
372 let cli = Cli::try_parse_from(["cc-audit", "./skill1/", "./skill2/"]).unwrap();
373 assert_eq!(cli.paths.len(), 2);
374 }
375
376 #[test]
377 fn test_parse_format_json() {
378 let cli = Cli::try_parse_from(["cc-audit", "--format", "json", "./skill/"]).unwrap();
379 assert!(matches!(cli.format, OutputFormat::Json));
380 }
381
382 #[test]
383 fn test_parse_strict_mode() {
384 let cli = Cli::try_parse_from(["cc-audit", "--strict", "./skill/"]).unwrap();
385 assert!(cli.strict);
386 }
387
388 #[test]
389 fn test_parse_recursive() {
390 let cli = Cli::try_parse_from(["cc-audit", "-r", "./skills/"]).unwrap();
391 assert!(cli.recursive);
392 }
393
394 #[test]
395 fn test_parse_format_sarif() {
396 let cli = Cli::try_parse_from(["cc-audit", "--format", "sarif", "./skill/"]).unwrap();
397 assert!(matches!(cli.format, OutputFormat::Sarif));
398 }
399
400 #[test]
401 fn test_parse_type_hook() {
402 let cli = Cli::try_parse_from(["cc-audit", "--type", "hook", "./settings.json"]).unwrap();
403 assert!(matches!(cli.scan_type, ScanType::Hook));
404 }
405
406 #[test]
407 fn test_parse_type_mcp() {
408 let cli = Cli::try_parse_from(["cc-audit", "--type", "mcp", "./mcp.json"]).unwrap();
409 assert!(matches!(cli.scan_type, ScanType::Mcp));
410 }
411
412 #[test]
413 fn test_parse_type_command() {
414 let cli = Cli::try_parse_from(["cc-audit", "--type", "command", "./"]).unwrap();
415 assert!(matches!(cli.scan_type, ScanType::Command));
416 }
417
418 #[test]
419 fn test_parse_type_rules() {
420 let cli = Cli::try_parse_from(["cc-audit", "--type", "rules", "./"]).unwrap();
421 assert!(matches!(cli.scan_type, ScanType::Rules));
422 }
423
424 #[test]
425 fn test_parse_type_docker() {
426 let cli = Cli::try_parse_from(["cc-audit", "--type", "docker", "./"]).unwrap();
427 assert!(matches!(cli.scan_type, ScanType::Docker));
428 }
429
430 #[test]
431 fn test_parse_type_dependency() {
432 let cli = Cli::try_parse_from(["cc-audit", "--type", "dependency", "./"]).unwrap();
433 assert!(matches!(cli.scan_type, ScanType::Dependency));
434 }
435
436 #[test]
437 fn test_parse_ci_mode() {
438 let cli = Cli::try_parse_from(["cc-audit", "--ci", "./skill/"]).unwrap();
439 assert!(cli.ci);
440 }
441
442 #[test]
443 fn test_parse_verbose() {
444 let cli = Cli::try_parse_from(["cc-audit", "-v", "./skill/"]).unwrap();
445 assert!(cli.verbose);
446 }
447
448 #[test]
449 fn test_parse_all_options() {
450 let cli = Cli::try_parse_from([
451 "cc-audit",
452 "--format",
453 "json",
454 "--strict",
455 "--type",
456 "hook",
457 "--recursive",
458 "--ci",
459 "--verbose",
460 "./path/",
461 ])
462 .unwrap();
463 assert!(matches!(cli.format, OutputFormat::Json));
464 assert!(cli.strict);
465 assert!(matches!(cli.scan_type, ScanType::Hook));
466 assert!(cli.recursive);
467 assert!(cli.ci);
468 assert!(cli.verbose);
469 }
470
471 #[test]
472 fn test_default_values() {
473 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
474 assert!(matches!(cli.format, OutputFormat::Terminal));
475 assert!(matches!(cli.scan_type, ScanType::Skill));
476 assert!(!cli.strict);
477 assert!(!cli.recursive);
478 assert!(!cli.ci);
479 assert!(!cli.verbose);
480 assert!(!cli.include_tests);
481 assert!(!cli.include_node_modules);
482 assert!(!cli.include_vendor);
483 assert!(matches!(cli.min_confidence, Confidence::Tentative));
484 }
485
486 #[test]
487 fn test_parse_include_tests() {
488 let cli = Cli::try_parse_from(["cc-audit", "--include-tests", "./skill/"]).unwrap();
489 assert!(cli.include_tests);
490 }
491
492 #[test]
493 fn test_parse_include_node_modules() {
494 let cli = Cli::try_parse_from(["cc-audit", "--include-node-modules", "./skill/"]).unwrap();
495 assert!(cli.include_node_modules);
496 }
497
498 #[test]
499 fn test_parse_include_vendor() {
500 let cli = Cli::try_parse_from(["cc-audit", "--include-vendor", "./skill/"]).unwrap();
501 assert!(cli.include_vendor);
502 }
503
504 #[test]
505 fn test_parse_all_include_options() {
506 let cli = Cli::try_parse_from([
507 "cc-audit",
508 "--include-tests",
509 "--include-node-modules",
510 "--include-vendor",
511 "./skill/",
512 ])
513 .unwrap();
514 assert!(cli.include_tests);
515 assert!(cli.include_node_modules);
516 assert!(cli.include_vendor);
517 }
518
519 #[test]
520 fn test_parse_min_confidence_tentative() {
521 let cli =
522 Cli::try_parse_from(["cc-audit", "--min-confidence", "tentative", "./skill/"]).unwrap();
523 assert!(matches!(cli.min_confidence, Confidence::Tentative));
524 }
525
526 #[test]
527 fn test_parse_min_confidence_firm() {
528 let cli =
529 Cli::try_parse_from(["cc-audit", "--min-confidence", "firm", "./skill/"]).unwrap();
530 assert!(matches!(cli.min_confidence, Confidence::Firm));
531 }
532
533 #[test]
534 fn test_parse_min_confidence_certain() {
535 let cli =
536 Cli::try_parse_from(["cc-audit", "--min-confidence", "certain", "./skill/"]).unwrap();
537 assert!(matches!(cli.min_confidence, Confidence::Certain));
538 }
539
540 #[test]
541 fn test_parse_skip_comments() {
542 let cli = Cli::try_parse_from(["cc-audit", "--skip-comments", "./skill/"]).unwrap();
543 assert!(cli.skip_comments);
544 }
545
546 #[test]
547 fn test_default_skip_comments_false() {
548 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
549 assert!(!cli.skip_comments);
550 }
551
552 #[test]
553 fn test_parse_fix_hint() {
554 let cli = Cli::try_parse_from(["cc-audit", "--fix-hint", "./skill/"]).unwrap();
555 assert!(cli.fix_hint);
556 }
557
558 #[test]
559 fn test_default_fix_hint_false() {
560 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
561 assert!(!cli.fix_hint);
562 }
563
564 #[test]
565 fn test_parse_watch() {
566 let cli = Cli::try_parse_from(["cc-audit", "--watch", "./skill/"]).unwrap();
567 assert!(cli.watch);
568 }
569
570 #[test]
571 fn test_parse_watch_short() {
572 let cli = Cli::try_parse_from(["cc-audit", "-w", "./skill/"]).unwrap();
573 assert!(cli.watch);
574 }
575
576 #[test]
577 fn test_default_watch_false() {
578 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
579 assert!(!cli.watch);
580 }
581
582 #[test]
583 fn test_parse_init_hook() {
584 let cli = Cli::try_parse_from(["cc-audit", "--init-hook", "./repo/"]).unwrap();
585 assert!(cli.init_hook);
586 }
587
588 #[test]
589 fn test_parse_remove_hook() {
590 let cli = Cli::try_parse_from(["cc-audit", "--remove-hook", "./repo/"]).unwrap();
591 assert!(cli.remove_hook);
592 }
593
594 #[test]
595 fn test_default_init_hook_false() {
596 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
597 assert!(!cli.init_hook);
598 }
599
600 #[test]
601 fn test_default_remove_hook_false() {
602 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
603 assert!(!cli.remove_hook);
604 }
605
606 #[test]
607 fn test_parse_malware_db() {
608 let cli =
609 Cli::try_parse_from(["cc-audit", "--malware-db", "./custom.json", "./skill/"]).unwrap();
610 assert!(cli.malware_db.is_some());
611 assert_eq!(cli.malware_db.unwrap().to_str().unwrap(), "./custom.json");
612 }
613
614 #[test]
615 fn test_default_malware_db_none() {
616 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
617 assert!(cli.malware_db.is_none());
618 }
619
620 #[test]
621 fn test_parse_no_malware_scan() {
622 let cli = Cli::try_parse_from(["cc-audit", "--no-malware-scan", "./skill/"]).unwrap();
623 assert!(cli.no_malware_scan);
624 }
625
626 #[test]
627 fn test_default_no_malware_scan_false() {
628 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
629 assert!(!cli.no_malware_scan);
630 }
631
632 #[test]
633 fn test_parse_custom_rules() {
634 let cli = Cli::try_parse_from(["cc-audit", "--custom-rules", "./rules.yaml", "./skill/"])
635 .unwrap();
636 assert!(cli.custom_rules.is_some());
637 assert_eq!(cli.custom_rules.unwrap().to_str().unwrap(), "./rules.yaml");
638 }
639
640 #[test]
641 fn test_default_custom_rules_none() {
642 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
643 assert!(cli.custom_rules.is_none());
644 }
645
646 #[test]
647 fn test_parse_init() {
648 let cli = Cli::try_parse_from(["cc-audit", "--init", "./"]).unwrap();
649 assert!(cli.init);
650 }
651
652 #[test]
653 fn test_default_init_false() {
654 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
655 assert!(!cli.init);
656 }
657
658 #[test]
659 fn test_parse_warn_only() {
660 let cli = Cli::try_parse_from(["cc-audit", "--warn-only", "./skill/"]).unwrap();
661 assert!(cli.warn_only);
662 }
663
664 #[test]
665 fn test_default_warn_only_false() {
666 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
667 assert!(!cli.warn_only);
668 }
669
670 #[test]
671 fn test_parse_min_severity_critical() {
672 let cli =
673 Cli::try_parse_from(["cc-audit", "--min-severity", "critical", "./skill/"]).unwrap();
674 assert_eq!(cli.min_severity, Some(Severity::Critical));
675 }
676
677 #[test]
678 fn test_parse_min_severity_high() {
679 let cli = Cli::try_parse_from(["cc-audit", "--min-severity", "high", "./skill/"]).unwrap();
680 assert_eq!(cli.min_severity, Some(Severity::High));
681 }
682
683 #[test]
684 fn test_parse_min_severity_medium() {
685 let cli =
686 Cli::try_parse_from(["cc-audit", "--min-severity", "medium", "./skill/"]).unwrap();
687 assert_eq!(cli.min_severity, Some(Severity::Medium));
688 }
689
690 #[test]
691 fn test_parse_min_severity_low() {
692 let cli = Cli::try_parse_from(["cc-audit", "--min-severity", "low", "./skill/"]).unwrap();
693 assert_eq!(cli.min_severity, Some(Severity::Low));
694 }
695
696 #[test]
697 fn test_default_min_severity_none() {
698 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
699 assert!(cli.min_severity.is_none());
700 }
701
702 #[test]
703 fn test_parse_min_rule_severity_error() {
704 let cli =
705 Cli::try_parse_from(["cc-audit", "--min-rule-severity", "error", "./skill/"]).unwrap();
706 assert_eq!(cli.min_rule_severity, Some(RuleSeverity::Error));
707 }
708
709 #[test]
710 fn test_parse_min_rule_severity_warn() {
711 let cli =
712 Cli::try_parse_from(["cc-audit", "--min-rule-severity", "warn", "./skill/"]).unwrap();
713 assert_eq!(cli.min_rule_severity, Some(RuleSeverity::Warn));
714 }
715
716 #[test]
717 fn test_default_min_rule_severity_none() {
718 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
719 assert!(cli.min_rule_severity.is_none());
720 }
721
722 #[test]
723 fn test_warn_only_with_strict_conflict() {
724 let cli = Cli::try_parse_from(["cc-audit", "--warn-only", "--strict", "./skill/"]).unwrap();
726 assert!(cli.warn_only);
727 assert!(cli.strict);
728 }
729
730 #[test]
731 fn test_parse_all_clients() {
732 let cli = Cli::try_parse_from(["cc-audit", "--all-clients"]).unwrap();
733 assert!(cli.all_clients);
734 assert!(cli.paths.is_empty());
735 }
736
737 #[test]
738 fn test_parse_client_claude() {
739 let cli = Cli::try_parse_from(["cc-audit", "--client", "claude"]).unwrap();
740 assert_eq!(cli.client, Some(ClientType::Claude));
741 assert!(cli.paths.is_empty());
742 }
743
744 #[test]
745 fn test_parse_client_cursor() {
746 let cli = Cli::try_parse_from(["cc-audit", "--client", "cursor"]).unwrap();
747 assert_eq!(cli.client, Some(ClientType::Cursor));
748 }
749
750 #[test]
751 fn test_parse_client_windsurf() {
752 let cli = Cli::try_parse_from(["cc-audit", "--client", "windsurf"]).unwrap();
753 assert_eq!(cli.client, Some(ClientType::Windsurf));
754 }
755
756 #[test]
757 fn test_parse_client_vscode() {
758 let cli = Cli::try_parse_from(["cc-audit", "--client", "vscode"]).unwrap();
759 assert_eq!(cli.client, Some(ClientType::Vscode));
760 }
761
762 #[test]
763 fn test_all_clients_conflicts_with_client() {
764 let result = Cli::try_parse_from(["cc-audit", "--all-clients", "--client", "claude"]);
765 assert!(result.is_err());
766 }
767
768 #[test]
769 fn test_all_clients_conflicts_with_remote() {
770 let result = Cli::try_parse_from([
771 "cc-audit",
772 "--all-clients",
773 "--remote",
774 "https://github.com/x/y",
775 ]);
776 assert!(result.is_err());
777 }
778
779 #[test]
780 fn test_default_client_none() {
781 let cli = Cli::try_parse_from(["cc-audit", "./skill/"]).unwrap();
782 assert!(cli.client.is_none());
783 assert!(!cli.all_clients);
784 }
785}