Skip to main content

starweaver_cli/
args.rs

1//! CLI argument parsing.
2
3use std::ffi::OsString;
4
5use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
6use clap_complete::Shell;
7use serde::{Deserialize, Serialize};
8
9use crate::{CliError, CliResult};
10
11/// Top-level CLI.
12#[derive(Clone, Debug, Parser)]
13#[command(name = "starweaver-cli", version, about = "Starweaver local CLI")]
14pub struct Cli {
15    /// Prompt shorthand for headless runs.
16    #[arg(short = 'p', long = "prompt", global = false)]
17    pub prompt: Option<String>,
18    /// Append a run to the selected session.
19    #[arg(
20        short = 's',
21        long,
22        global = false,
23        conflicts_with_all = ["new_session", "continue_session"]
24    )]
25    pub session: Option<String>,
26    /// Continue the latest local session.
27    #[arg(long = "continue", global = false, conflicts_with = "new_session")]
28    pub continue_session: bool,
29    /// Create a fresh session.
30    #[arg(long, global = false)]
31    pub new_session: bool,
32    /// Restore from a specific run before appending a run.
33    #[arg(long, global = false)]
34    pub run: Option<String>,
35    /// Branch from a specific run before appending a run.
36    #[arg(long, global = false, conflicts_with = "run")]
37    pub branch_from: Option<String>,
38    /// Agent profile name or YAML path.
39    #[arg(long, global = false)]
40    pub profile: Option<String>,
41    /// Enable worker mode or set an optional worker label.
42    #[arg(long, global = false, num_args = 0..=1, default_missing_value = "true")]
43    pub worker: Option<String>,
44    /// Explicit worker label.
45    #[arg(long = "worker-label", global = false)]
46    pub worker_label: Option<String>,
47    /// Enable a git worktree or set an optional worktree name/path.
48    #[arg(
49        short = 'w',
50        long,
51        global = false,
52        num_args = 0..=1,
53        default_missing_value = "true"
54    )]
55    pub worktree: Option<String>,
56    /// Explicit worktree name/path.
57    #[arg(long = "worktree-name", global = false)]
58    pub worktree_name: Option<String>,
59    /// Git branch for worktree metadata.
60    #[arg(long, global = false)]
61    pub branch: Option<String>,
62    /// Output mode.
63    #[arg(long, global = false)]
64    pub output: Option<OutputMode>,
65    /// Headless human-in-the-loop policy for prompt shorthand.
66    #[arg(long, global = false)]
67    pub hitl: Option<HitlPolicy>,
68    /// Override local store database path.
69    #[arg(long, global = true)]
70    pub store: Option<String>,
71    /// Optional subcommand.
72    #[command(subcommand)]
73    pub command: Option<CliCommand>,
74}
75
76/// CLI command families.
77#[allow(clippy::large_enum_variant)]
78#[derive(Clone, Debug, Subcommand)]
79pub enum CliCommand {
80    /// Print SDK identity.
81    Version,
82    /// Run a prompt.
83    Run(RunCommand),
84    /// Manage local sessions.
85    Session {
86        /// Session subcommand.
87        #[command(subcommand)]
88        command: SessionCommand,
89    },
90    /// Manage agent profiles.
91    Profile {
92        /// Profile subcommand.
93        #[command(subcommand)]
94        command: ProfileCommand,
95    },
96    /// Initialize local CLI configuration and catalogs.
97    Setup(SetupCommand),
98    /// Inspect OAuth-backed provider authentication.
99    Auth {
100        /// Auth subcommand.
101        #[command(subcommand)]
102        command: AuthCommand,
103    },
104    /// Inspect configured skills.
105    Skill {
106        /// Skill subcommand.
107        #[command(subcommand)]
108        command: CatalogCommand,
109    },
110    /// Inspect configured subagents.
111    Subagent {
112        /// Subagent subcommand.
113        #[command(subcommand)]
114        command: CatalogCommand,
115    },
116    /// Inspect configured MCP servers.
117    Mcp {
118        /// MCP subcommand.
119        #[command(subcommand)]
120        command: CatalogCommand,
121    },
122    /// Inspect default CLI tool catalog and policy.
123    Tools {
124        /// Tools subcommand.
125        #[command(subcommand)]
126        command: ToolsCommand,
127    },
128    /// Render a retained terminal UI from local session display messages.
129    Tui(TuiCommand),
130    /// Manage persisted approval requests.
131    Approval {
132        /// Approval subcommand.
133        #[command(subcommand)]
134        command: ApprovalCommand,
135    },
136    /// Manage deferred tool calls.
137    Deferred {
138        /// Deferred subcommand.
139        #[command(subcommand)]
140        command: DeferredCommand,
141    },
142    /// Resume a waiting session by appending a continuation run.
143    Resume(ResumeCommand),
144    /// Remove runtime session state while preserving configuration.
145    Reset(ResetCommand),
146    /// Print diagnostics.
147    Diagnostics,
148    /// Print replay-check guidance.
149    ReplayCheck,
150    /// Update installed Starweaver components.
151    Update(UpdateCommand),
152    /// Get or set configuration values.
153    Config {
154        /// Config subcommand.
155        #[command(subcommand)]
156        command: ConfigCommand,
157    },
158    /// Generate shell completion scripts.
159    Completion {
160        /// Target shell.
161        shell: Shell,
162    },
163}
164
165/// Prompt run command.
166#[derive(Clone, Debug, Args)]
167pub struct RunCommand {
168    /// Prompt text.
169    #[arg(short = 'p', long = "prompt")]
170    pub prompt: Option<String>,
171    /// Positional prompt text.
172    pub prompt_parts: Vec<String>,
173    /// Append a run to the selected session.
174    #[arg(short = 's', conflicts_with_all = ["new_session", "continue_session"], long)]
175    pub session: Option<String>,
176    /// Continue the latest local session.
177    #[arg(long = "continue", conflicts_with = "new_session")]
178    pub continue_session: bool,
179    /// Create a fresh session.
180    #[arg(long)]
181    pub new_session: bool,
182    /// Restore from a specific run before appending a run.
183    #[arg(long)]
184    pub run: Option<String>,
185    /// Branch from a specific run before appending a run.
186    #[arg(long, conflicts_with = "run")]
187    pub branch_from: Option<String>,
188    /// Agent profile name or YAML path.
189    #[arg(long)]
190    pub profile: Option<String>,
191    /// Enable worker mode or set an optional worker label.
192    #[arg(long, num_args = 0..=1, default_missing_value = "true")]
193    pub worker: Option<String>,
194    /// Explicit worker label.
195    #[arg(long = "worker-label")]
196    pub worker_label: Option<String>,
197    /// Enable a git worktree or set an optional worktree name/path.
198    #[arg(short = 'w', long, num_args = 0..=1, default_missing_value = "true")]
199    pub worktree: Option<String>,
200    /// Explicit worktree name/path.
201    #[arg(long = "worktree-name")]
202    pub worktree_name: Option<String>,
203    /// Git branch for worktree metadata.
204    #[arg(long)]
205    pub branch: Option<String>,
206    /// Output mode.
207    #[arg(long)]
208    pub output: Option<OutputMode>,
209    /// Headless human-in-the-loop policy.
210    #[arg(long)]
211    pub hitl: Option<HitlPolicy>,
212    /// Internal runtime goal-mode options.
213    #[arg(skip)]
214    pub goal: Option<GoalCommandOptions>,
215    /// Internal stable provider-routing affinity id.
216    #[arg(skip)]
217    pub session_affinity_id: Option<String>,
218    /// Internal host RPC environment attachments.
219    #[arg(skip)]
220    pub environment_attachments: Vec<starweaver_rpc_core::EnvironmentAttachmentRef>,
221}
222
223/// Internal goal-mode options attached by product surfaces such as the TUI.
224#[derive(Clone, Debug, Eq, PartialEq)]
225pub struct GoalCommandOptions {
226    /// Goal objective.
227    pub objective: String,
228    /// Maximum runtime goal retry iterations.
229    pub max_iterations: usize,
230}
231
232impl RunCommand {
233    /// Return prompt text.
234    pub fn prompt_text(&self) -> CliResult<String> {
235        let prompt = self
236            .prompt
237            .clone()
238            .unwrap_or_else(|| self.prompt_parts.join(" "));
239        if prompt.trim().is_empty() {
240            Err(CliError::Usage(
241                "usage: starweaver-cli run -p <prompt>".to_string(),
242            ))
243        } else {
244            Ok(prompt)
245        }
246    }
247}
248
249/// Compact session commands.
250#[derive(Clone, Debug, Subcommand)]
251pub enum SessionCommand {
252    /// List local sessions.
253    List(SessionListCommand),
254    /// Search local sessions by metadata and approved text projections.
255    Search(SessionSearchCommand),
256    /// Show one session with recent runs.
257    Show(SessionShowCommand),
258    /// Replay stored display messages.
259    Replay(SessionReplayCommand),
260    /// Delete one local session and its retained evidence.
261    Delete(SessionDeleteCommand),
262    /// Trim retained run evidence.
263    Trim(SessionTrimCommand),
264}
265
266/// Session list command.
267#[derive(Clone, Debug, Args)]
268pub struct SessionListCommand {
269    /// Output mode.
270    #[arg(long, default_value = "display-jsonl")]
271    pub output: OutputMode,
272    /// Maximum sessions to show.
273    #[arg(long, default_value_t = 50)]
274    pub limit: usize,
275}
276
277/// Session search command.
278#[derive(Clone, Debug, Args)]
279pub struct SessionSearchCommand {
280    /// Optional case-insensitive literal text. Omit it to browse metadata.
281    pub text: Option<String>,
282    /// Exact session status.
283    #[arg(long)]
284    pub status: Option<SessionSearchStatusArg>,
285    /// Exact profile name.
286    #[arg(long)]
287    pub profile: Option<String>,
288    /// Exact workspace display value.
289    #[arg(long)]
290    pub workspace: Option<String>,
291    /// Search source; repeat to select multiple sources.
292    #[arg(long = "source", value_enum)]
293    pub sources: Vec<SessionSearchSourceArg>,
294    /// Result grouping level.
295    #[arg(long, value_enum, default_value = "session")]
296    pub granularity: SessionSearchGranularityArg,
297    /// Maximum hits to return.
298    #[arg(long, default_value_t = 20)]
299    pub limit: u32,
300    /// Opaque next-page cursor.
301    #[arg(long = "after")]
302    pub cursor: Option<String>,
303    /// Output mode.
304    #[arg(long, default_value = "text")]
305    pub output: OutputMode,
306}
307
308/// Session status accepted by search.
309#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
310#[value(rename_all = "snake_case")]
311pub enum SessionSearchStatusArg {
312    /// Active session.
313    Active,
314    /// Archived session.
315    Archived,
316    /// Failed session.
317    Failed,
318}
319
320/// Search source accepted by the CLI.
321#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
322#[value(rename_all = "snake_case")]
323pub enum SessionSearchSourceArg {
324    /// Session title and approved metadata.
325    SessionMetadata,
326    /// Canonical text input.
327    RunInput,
328    /// Bounded run output preview.
329    RunOutputPreview,
330    /// User-visible display messages.
331    #[value(alias = "display")]
332    DisplayMessage,
333}
334
335/// Search grouping accepted by the CLI.
336#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
337#[value(rename_all = "snake_case")]
338pub enum SessionSearchGranularityArg {
339    /// One hit per session.
340    Session,
341    /// One hit per session/run pair.
342    Run,
343    /// Every stable projected occurrence.
344    Occurrence,
345}
346
347/// Session show command.
348#[derive(Clone, Debug, Args)]
349pub struct SessionShowCommand {
350    /// Session id.
351    pub session_id: String,
352    /// Output mode.
353    #[arg(long, default_value = "display-jsonl")]
354    pub output: OutputMode,
355    /// Recent run limit.
356    #[arg(long, default_value_t = 20)]
357    pub runs: usize,
358}
359
360/// Session replay command.
361#[derive(Clone, Debug, Args)]
362pub struct SessionReplayCommand {
363    /// Session id.
364    pub session_id: String,
365    /// Optional run id.
366    #[arg(long)]
367    pub run: Option<String>,
368    /// Cursor sequence to replay after.
369    #[arg(long)]
370    pub after: Option<usize>,
371    /// Output mode.
372    #[arg(long, default_value = "display-jsonl")]
373    pub output: OutputMode,
374}
375
376/// Session delete command.
377#[derive(Clone, Debug, Args)]
378pub struct SessionDeleteCommand {
379    /// Session id or unique prefix.
380    pub session_id: String,
381    /// Confirm deletion.
382    #[arg(long)]
383    pub yes: bool,
384    /// Output mode.
385    #[arg(long, default_value = "text")]
386    pub output: OutputMode,
387}
388
389/// Session trim command.
390#[derive(Clone, Debug, Args)]
391pub struct SessionTrimCommand {
392    /// Trim current session.
393    #[arg(long)]
394    pub current: bool,
395    /// Trim all sessions.
396    #[arg(long)]
397    pub all: bool,
398    /// Trim a selected session.
399    #[arg(long)]
400    pub session: Option<String>,
401    /// Retain this many recent runs per session.
402    #[arg(long, default_value_t = 20)]
403    pub keep_runs: usize,
404    /// Trim runs older than a duration such as 7d, 24h, or 3600s.
405    #[arg(long)]
406    pub older_than: Option<String>,
407    /// Preview trim results.
408    #[arg(long)]
409    pub dry_run: bool,
410    /// Output mode.
411    #[arg(long, default_value = "display-jsonl")]
412    pub output: OutputMode,
413}
414
415/// Profile commands.
416#[derive(Clone, Debug, Subcommand)]
417pub enum ProfileCommand {
418    /// List built-in and configured profiles.
419    List,
420    /// Show one built-in or configured profile.
421    Show { name: String },
422}
423
424/// Setup command.
425#[derive(Clone, Debug, Args)]
426pub struct SetupCommand {
427    /// Initialize global configuration only.
428    #[arg(long, conflicts_with = "project")]
429    pub global: bool,
430    /// Initialize project configuration only.
431    #[arg(long)]
432    pub project: bool,
433    /// Replace existing generated files.
434    #[arg(long)]
435    pub force: bool,
436}
437
438/// Auth commands.
439#[derive(Clone, Debug, Subcommand)]
440pub enum AuthCommand {
441    /// Log in to an OAuth provider.
442    Login(AuthProviderCommand),
443    /// Print provider auth status.
444    Status(AuthStatusCommand),
445    /// Refresh provider credentials.
446    Refresh(AuthProviderCommand),
447    /// Remove provider credentials from the local auth store.
448    Logout(AuthLogoutCommand),
449    /// Inspect OAuth store health without printing tokens.
450    Doctor(AuthDoctorCommand),
451}
452
453/// Provider-scoped auth command.
454#[derive(Clone, Debug, Args)]
455pub struct AuthProviderCommand {
456    /// Provider name.
457    #[arg(default_value = "codex", value_parser = ["codex"])]
458    pub provider: String,
459    /// Auth file path. Defaults to ~/.starweaver/auth.json.
460    #[arg(long = "auth-file")]
461    pub auth_file: Option<String>,
462    /// Device authorization timeout in seconds.
463    #[arg(long, default_value_t = 15 * 60)]
464    pub timeout_seconds: u64,
465}
466
467/// Auth status command.
468#[derive(Clone, Debug, Args)]
469pub struct AuthStatusCommand {
470    /// Provider name.
471    #[arg(default_value = "codex", value_parser = ["codex"])]
472    pub provider: Option<String>,
473    /// Auth file path. Defaults to ~/.starweaver/auth.json.
474    #[arg(long = "auth-file")]
475    pub auth_file: Option<String>,
476}
477
478/// Auth logout command.
479#[derive(Clone, Debug, Args)]
480pub struct AuthLogoutCommand {
481    /// Provider name.
482    #[arg(default_value = "codex", value_parser = ["codex"])]
483    pub provider: String,
484    /// Auth file path. Defaults to ~/.starweaver/auth.json.
485    #[arg(long = "auth-file")]
486    pub auth_file: Option<String>,
487    /// Revoke provider tokens before deleting local credentials.
488    #[arg(long, default_value_t = true, action = clap::ArgAction::Set)]
489    pub revoke: bool,
490}
491
492/// Auth doctor command.
493#[derive(Clone, Debug, Args)]
494pub struct AuthDoctorCommand {
495    /// Auth file path. Defaults to ~/.starweaver/auth.json.
496    #[arg(long = "auth-file")]
497    pub auth_file: Option<String>,
498}
499
500/// Catalog inspection commands.
501#[derive(Clone, Debug, Subcommand)]
502pub enum CatalogCommand {
503    /// List configured entries.
504    List,
505    /// Show one configured entry.
506    Show { name: String },
507    /// Validate configured entries and print findings.
508    Doctor,
509}
510
511/// Tool catalog commands.
512#[derive(Clone, Debug, Subcommand)]
513pub enum ToolsCommand {
514    /// List default first-party tools.
515    List,
516    /// Validate tool policy and default catalog.
517    Doctor,
518}
519
520/// TUI command.
521#[derive(Clone, Debug, Args)]
522pub struct TuiCommand {
523    /// Session id to render. Omit for a clean welcome screen.
524    #[arg(long)]
525    pub session: Option<String>,
526    /// Optional run id to render.
527    #[arg(long)]
528    pub run: Option<String>,
529    /// Render only messages after this display cursor.
530    #[arg(long)]
531    pub after: Option<usize>,
532    /// Force interactive terminal UI when stdout is a TTY.
533    #[arg(long)]
534    pub interactive: bool,
535    /// Force deterministic snapshot output for scripts and tests.
536    #[arg(long, conflicts_with = "interactive")]
537    pub snapshot: bool,
538    /// Output mode for non-interactive TUI snapshots.
539    #[arg(long, default_value = "text")]
540    pub output: OutputMode,
541    /// Transcript rendering mode for interactive TUI.
542    #[arg(long = "render-mode")]
543    pub render_mode: Option<TuiRenderMode>,
544}
545
546/// Approval commands.
547#[derive(Clone, Debug, Subcommand)]
548pub enum ApprovalCommand {
549    /// List persisted approval records.
550    List(ApprovalListCommand),
551    /// Show one approval record.
552    Show { approval_id: String },
553    /// Approve one pending approval record.
554    Approve(ApprovalDecisionCommand),
555    /// Reject one pending approval record.
556    Reject(ApprovalDecisionCommand),
557}
558
559/// Approval list command.
560#[derive(Clone, Debug, Args)]
561pub struct ApprovalListCommand {
562    /// Filter by session id.
563    #[arg(long)]
564    pub session: Option<String>,
565    /// Filter by run id.
566    #[arg(long)]
567    pub run: Option<String>,
568    /// Output mode.
569    #[arg(long, default_value = "display-jsonl")]
570    pub output: OutputMode,
571}
572
573/// Approval decision command.
574#[derive(Clone, Debug, Args)]
575pub struct ApprovalDecisionCommand {
576    /// Approval id.
577    pub approval_id: String,
578    /// Decision reason.
579    #[arg(long)]
580    pub reason: Option<String>,
581    /// Output mode.
582    #[arg(long, default_value = "text")]
583    pub output: OutputMode,
584}
585
586/// Deferred tool commands.
587#[derive(Clone, Debug, Subcommand)]
588pub enum DeferredCommand {
589    /// List persisted deferred tool records.
590    List(DeferredListCommand),
591    /// Show one deferred tool record.
592    Show { deferred_id: String },
593    /// Complete one deferred tool record with a JSON result payload.
594    Complete(DeferredCompleteCommand),
595    /// Fail one deferred tool record with an error message.
596    Fail(DeferredFailCommand),
597}
598
599/// Deferred list command.
600#[derive(Clone, Debug, Args)]
601pub struct DeferredListCommand {
602    /// Filter by session id.
603    #[arg(long)]
604    pub session: Option<String>,
605    /// Filter by run id.
606    #[arg(long)]
607    pub run: Option<String>,
608    /// Output mode.
609    #[arg(long, default_value = "display-jsonl")]
610    pub output: OutputMode,
611}
612
613/// Deferred complete command.
614#[derive(Clone, Debug, Args)]
615pub struct DeferredCompleteCommand {
616    /// Deferred id.
617    pub deferred_id: String,
618    /// JSON result payload.
619    #[arg(long)]
620    pub result: String,
621    /// Output mode.
622    #[arg(long, default_value = "text")]
623    pub output: OutputMode,
624}
625
626/// Deferred failure command.
627#[derive(Clone, Debug, Args)]
628pub struct DeferredFailCommand {
629    /// Deferred id.
630    pub deferred_id: String,
631    /// Error message.
632    #[arg(long)]
633    pub error: String,
634    /// Output mode.
635    #[arg(long, default_value = "text")]
636    pub output: OutputMode,
637}
638
639/// Resume command.
640#[derive(Clone, Debug, Args)]
641pub struct ResumeCommand {
642    /// Session id to resume. Defaults to current or latest session.
643    #[arg(long)]
644    pub session: Option<String>,
645    /// Run id to resume from. Defaults to the session active or head run.
646    #[arg(long)]
647    pub run: Option<String>,
648    /// Prompt to append for the continuation run.
649    #[arg(short = 'p', long = "prompt", default_value = "resume waiting run")]
650    pub prompt: String,
651    /// Output mode.
652    #[arg(long)]
653    pub output: Option<OutputMode>,
654    /// Headless human-in-the-loop policy.
655    #[arg(long)]
656    pub hitl: Option<HitlPolicy>,
657}
658
659/// Reset command.
660#[derive(Clone, Debug, Args)]
661pub struct ResetCommand {
662    /// Confirm runtime state removal.
663    #[arg(long)]
664    pub yes: bool,
665    /// Output mode.
666    #[arg(long, default_value = "text")]
667    pub output: OutputMode,
668}
669
670/// Update command.
671#[derive(Clone, Debug, Args)]
672pub struct UpdateCommand {
673    /// Update target, defaults to cli.
674    #[arg(default_value = "cli")]
675    pub target: String,
676    /// Print the update plan without downloading or installing.
677    #[arg(long)]
678    pub dry_run: bool,
679    /// Reinstall even when the selected release matches the current version.
680    #[arg(long, short = 'f')]
681    pub force: bool,
682}
683
684/// Config commands.
685#[derive(Clone, Debug, Subcommand)]
686pub enum ConfigCommand {
687    /// Initialize a Starweaver config file.
688    Init {
689        /// Write the global config file.
690        #[arg(long, conflicts_with = "project")]
691        global: bool,
692        /// Write the project config file.
693        #[arg(long)]
694        project: bool,
695        /// Replace an existing config file.
696        #[arg(long)]
697        force: bool,
698    },
699    /// Get a resolved config value.
700    Get { key: String },
701    /// Set a config value.
702    Set {
703        /// Write the global config file.
704        #[arg(long, conflicts_with = "project")]
705        global: bool,
706        /// Write the project config file.
707        #[arg(long)]
708        project: bool,
709        /// Config key.
710        key: String,
711        /// Config value.
712        value: String,
713    },
714}
715
716/// TUI transcript render mode.
717#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
718#[serde(rename_all = "kebab-case")]
719pub enum TuiRenderMode {
720    /// Show assistant text, reasoning, tool calls, and tool returns.
721    #[default]
722    Normal,
723    /// Hide ordinary tool calls from transcript while keeping high-level events.
724    Concise,
725    /// Show detailed diagnostic rendering.
726    Debug,
727}
728
729/// Output mode.
730#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
731#[serde(rename_all = "kebab-case")]
732pub enum OutputMode {
733    /// Human-readable text.
734    Text,
735    /// Starweaver durable `DisplayMessage` JSON lines.
736    #[default]
737    DisplayJsonl,
738    /// Starweaver/AGUI top-level event JSON lines.
739    AguiJsonl,
740    /// Compact JSON command result.
741    Json,
742    /// Persist and print compact status.
743    Silent,
744}
745
746/// HITL policy.
747#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
748#[serde(rename_all = "snake_case")]
749pub enum HitlPolicy {
750    /// Deny approvals.
751    Deny,
752    /// Defer approvals.
753    Defer,
754    /// Fail on approvals.
755    Fail,
756    /// Prompt interactively.
757    #[default]
758    Prompt,
759}
760
761/// Build the clap command schema.
762#[must_use]
763pub fn command() -> clap::Command {
764    Cli::command()
765}
766
767/// Parse CLI arguments.
768pub fn parse(args: impl IntoIterator<Item = String>) -> CliResult<Cli> {
769    Cli::try_parse_from(args).map_err(|error| clap_error(&error))
770}
771
772/// Parse CLI arguments from OS strings.
773#[allow(dead_code)]
774pub fn parse_os(args: impl IntoIterator<Item = OsString>) -> CliResult<Cli> {
775    Cli::try_parse_from(args).map_err(|error| clap_error(&error))
776}
777
778fn clap_error(error: &clap::Error) -> CliError {
779    match error.kind() {
780        clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
781            CliError::Display(error.to_string())
782        }
783        _ => CliError::Usage(error.to_string()),
784    }
785}