dsc/cli.rs
1use clap::{ArgAction, Parser, Subcommand, ValueEnum};
2use clap_complete::Shell;
3use std::path::PathBuf;
4
5#[derive(Parser)]
6#[command(name = "dsc")]
7#[command(about = "Discourse CLI", long_about = None)]
8pub struct Cli {
9 /// Path to the config file. If omitted, dsc searches standard locations.
10 #[arg(long, short = 'c')]
11 pub config: Option<PathBuf>,
12 /// Describe destructive actions without sending them. Read-only commands
13 /// ignore the flag.
14 #[arg(long, short = 'n', global = true)]
15 pub dry_run: bool,
16 #[command(subcommand)]
17 pub command: Commands,
18}
19
20#[derive(Subcommand)]
21pub enum Commands {
22 /// List configured Discourses.
23 #[command(visible_alias = "ls")]
24 List {
25 /// Output format for the listing.
26 #[arg(long, short = 'f', value_enum, default_value = "text")]
27 format: OutputFormat,
28 /// Filter by tags (comma/semicolon separated, match-any).
29 #[arg(long, value_name = "tag1,tag2")]
30 tags: Option<String>,
31 /// Open each listed Discourse base URL in a browser tab/window.
32 #[arg(long, short = 'o')]
33 open: bool,
34 /// Include empty results and verbose listing details where supported.
35 #[arg(long, short = 'v')]
36 verbose: bool,
37 #[command(subcommand)]
38 command: Option<ListCommand>,
39 },
40 /// Add one or more Discourses to the config.
41 #[command(visible_alias = "a")]
42 Add {
43 /// Comma-separated discourse names to add.
44 names: String,
45 /// Prompt for additional optional fields while adding.
46 #[arg(long, short = 'i')]
47 interactive: bool,
48 },
49 /// Import Discourses from a file or stdin.
50 #[command(visible_alias = "imp")]
51 Import {
52 /// Path to import input (text/CSV). Reads stdin when omitted.
53 path: Option<PathBuf>,
54 },
55 /// Run remote OS + Discourse update workflow for one or all Discourses.
56 #[command(visible_alias = "up")]
57 Update {
58 /// Discourse name, or 'all' to update every configured Discourse.
59 name: String,
60 /// Parallel update mode for `dsc update all`.
61 #[arg(long, short = 'p')]
62 parallel: bool,
63 /// Maximum workers when parallel mode is enabled (default: 3).
64 #[arg(long, short = 'm')]
65 max: Option<usize>,
66 /// Disable changelog posting (posting prompt is on by default).
67 #[arg(long = "no-changelog", action = ArgAction::SetFalse, default_value_t = true)]
68 post_changelog: bool,
69 /// Auto-confirm changelog posting prompt (non-interactive mode).
70 #[arg(long, short = 'y')]
71 yes: bool,
72 },
73 /// Manage custom emoji.
74 #[command(visible_alias = "em")]
75 Emoji {
76 #[command(subcommand)]
77 command: EmojiCommand,
78 },
79 /// Pull/push/sync topics as local Markdown.
80 #[command(visible_alias = "t")]
81 Topic {
82 #[command(subcommand)]
83 command: TopicCommand,
84 },
85 /// List/copy/pull/push categories.
86 #[command(visible_alias = "cat")]
87 Category {
88 #[command(subcommand)]
89 command: CategoryCommand,
90 },
91 /// List/inspect/copy groups.
92 #[command(visible_alias = "grp")]
93 Group {
94 #[command(subcommand)]
95 command: GroupCommand,
96 },
97 /// Operations that act from a user's perspective.
98 #[command(visible_alias = "usr")]
99 User {
100 #[command(subcommand)]
101 command: UserCommand,
102 },
103 /// Create/list/restore backups.
104 #[command(visible_alias = "bk")]
105 Backup {
106 #[command(subcommand)]
107 command: BackupCommand,
108 },
109 /// List/pull/push color palettes.
110 #[command(visible_alias = "pal")]
111 Palette {
112 #[command(subcommand)]
113 command: PaletteCommand,
114 },
115 /// List/install/remove plugins.
116 #[command(visible_alias = "plg")]
117 Plugin {
118 #[command(subcommand)]
119 command: PluginCommand,
120 },
121 /// List/install/remove/pull/push/duplicate themes.
122 #[command(visible_alias = "th")]
123 Theme {
124 #[command(subcommand)]
125 command: ThemeCommand,
126 },
127 /// Update site settings.
128 #[command(visible_alias = "set")]
129 Setting {
130 #[command(subcommand)]
131 command: SettingCommand,
132 },
133 /// List tags and apply/remove them on topics.
134 #[command(visible_alias = "tg")]
135 Tag {
136 #[command(subcommand)]
137 command: TagCommand,
138 },
139 /// Post-level operations: edit / delete / move.
140 #[command(visible_alias = "po")]
141 Post {
142 #[command(subcommand)]
143 command: PostCommand,
144 },
145 /// Open a Discourse in the default browser.
146 #[command(visible_alias = "o")]
147 Open {
148 /// Discourse name.
149 discourse: String,
150 },
151 /// Search topics on a Discourse.
152 #[command(visible_alias = "s")]
153 Search {
154 /// Discourse name.
155 discourse: String,
156 /// Search query (passed through verbatim, including any
157 /// Discourse filter syntax like `category:foo` or `@user`).
158 query: String,
159 /// Output format.
160 #[arg(long, short = 'f', value_enum, default_value = "text")]
161 format: ListFormat,
162 },
163 /// Upload a file. Prints the resulting upload:// short URL by default.
164 #[command(visible_alias = "u")]
165 Upload {
166 /// Discourse name.
167 discourse: String,
168 /// Path to the file to upload.
169 file: PathBuf,
170 /// Discourse upload context. Default `composer` is correct for
171 /// embedding in posts; other values include `avatar`,
172 /// `profile_background`, `card_background`, `custom_emoji`.
173 #[arg(long, short = 't', default_value = "composer")]
174 upload_type: String,
175 /// Output format. Text mode prints just the short URL.
176 #[arg(long, short = 'f', value_enum, default_value = "text")]
177 format: ListFormat,
178 },
179 /// Inspect and validate configuration.
180 #[command(visible_alias = "cfg")]
181 Config {
182 #[command(subcommand)]
183 command: ConfigCommand,
184 },
185 /// Generate shell completion scripts.
186 #[command(visible_alias = "comp")]
187 Completions {
188 /// Target shell.
189 #[arg(value_enum)]
190 shell: CompletionShell,
191 /// Output directory. Prints to stdout when omitted.
192 #[arg(long, short = 'd')]
193 dir: Option<PathBuf>,
194 },
195 /// Print the dsc version.
196 #[command(visible_alias = "ver")]
197 Version,
198}
199
200#[derive(Subcommand)]
201pub enum ConfigCommand {
202 /// Probe each configured Discourse: API auth and (optionally) SSH reachability.
203 #[command(visible_alias = "ck")]
204 Check {
205 /// Output format.
206 #[arg(long, short = 'f', value_enum, default_value = "text")]
207 format: ListFormat,
208 /// Skip the SSH reachability probe.
209 #[arg(long)]
210 skip_ssh: bool,
211 },
212}
213
214#[derive(Subcommand)]
215pub enum ListCommand {
216 /// Sort discourse entries by name and rewrite config in-place.
217 /// Also inserts placeholder values for unset template keys.
218 #[command(visible_alias = "ty")]
219 Tidy,
220}
221
222#[derive(Subcommand)]
223pub enum EmojiCommand {
224 /// Upload one emoji file, or bulk-upload from a directory.
225 #[command(visible_alias = "a")]
226 Add {
227 /// Discourse name.
228 discourse: String,
229 /// Local file or directory path.
230 emoji_path: PathBuf,
231 /// Optional emoji name (file uploads only).
232 emoji_name: Option<String>,
233 },
234
235 /// List custom emojis on a Discourse.
236 #[command(visible_alias = "ls")]
237 List {
238 /// Discourse name.
239 discourse: String,
240 /// Output format.
241 #[arg(long, short = 'f', value_enum, default_value = "text")]
242 format: ListFormat,
243 /// Include additional fields where supported.
244 #[arg(long, short = 'v')]
245 verbose: bool,
246 /// Render inline images when terminal protocol support is available.
247 #[arg(long, short = 'i')]
248 inline: bool,
249 },
250}
251
252#[derive(Subcommand)]
253pub enum TopicCommand {
254 /// Pull a topic to a local Markdown file.
255 #[command(visible_alias = "pl")]
256 Pull {
257 /// Discourse name.
258 discourse: String,
259 /// Topic ID.
260 topic_id: u64,
261 /// Destination file or directory (auto-derived when omitted).
262 local_path: Option<PathBuf>,
263 },
264 /// Push a local Markdown file to a topic.
265 #[command(visible_alias = "ps")]
266 Push {
267 /// Discourse name.
268 discourse: String,
269 /// Topic ID.
270 topic_id: u64,
271 /// Local Markdown file path.
272 local_path: PathBuf,
273 },
274 /// Sync a topic and local Markdown file using newest timestamp.
275 #[command(visible_alias = "sy")]
276 Sync {
277 /// Discourse name.
278 discourse: String,
279 /// Topic ID.
280 topic_id: u64,
281 /// Local Markdown file path.
282 local_path: PathBuf,
283 /// Skip sync confirmation prompt.
284 #[arg(long, short = 'y')]
285 yes: bool,
286 },
287 /// Reply to a topic with content from a file or stdin.
288 #[command(visible_alias = "r")]
289 Reply {
290 /// Discourse name.
291 discourse: String,
292 /// Topic ID.
293 topic_id: u64,
294 /// Input file path. Reads stdin when omitted or `-`.
295 local_path: Option<PathBuf>,
296 },
297 /// Create a new topic in a category, body from a file or stdin.
298 #[command(visible_alias = "n")]
299 New {
300 /// Discourse name.
301 discourse: String,
302 /// Target category ID.
303 category_id: u64,
304 /// Topic title.
305 #[arg(long, short = 't')]
306 title: String,
307 /// Input file path. Reads stdin when omitted or `-`.
308 local_path: Option<PathBuf>,
309 },
310}
311
312#[derive(Subcommand)]
313pub enum CategoryCommand {
314 /// List categories.
315 #[command(visible_alias = "ls")]
316 List {
317 /// Discourse name.
318 discourse: String,
319 /// Output format.
320 #[arg(long, short = 'f', value_enum, default_value = "text")]
321 format: ListFormat,
322 /// Include additional fields where supported.
323 #[arg(long, short = 'v')]
324 verbose: bool,
325 /// Show category hierarchy tree.
326 #[arg(long)]
327 tree: bool,
328 },
329 /// Copy a category to another Discourse.
330 #[command(visible_alias = "cp")]
331 Copy {
332 /// Source discourse name.
333 discourse: String,
334 /// Target discourse name (defaults to source when omitted).
335 #[arg(long, short = 't')]
336 target: Option<String>,
337 /// Category ID or slug.
338 category: String,
339 },
340 /// Pull all topics from a category into local Markdown files.
341 #[command(visible_alias = "pl")]
342 Pull {
343 /// Discourse name.
344 discourse: String,
345 /// Category ID or slug.
346 category: String,
347 /// Destination directory (auto-derived when omitted).
348 local_path: Option<PathBuf>,
349 },
350 /// Push local Markdown files into a category.
351 #[command(visible_alias = "ps")]
352 Push {
353 /// Discourse name.
354 discourse: String,
355 /// Category ID or slug.
356 category: String,
357 /// Local directory containing Markdown files.
358 local_path: PathBuf,
359 },
360}
361
362#[derive(Subcommand)]
363pub enum GroupCommand {
364 /// List groups.
365 #[command(visible_alias = "ls")]
366 List {
367 /// Discourse name.
368 discourse: String,
369 /// Output format.
370 #[arg(long, short = 'f', value_enum, default_value = "text")]
371 format: ListFormat,
372 /// Include additional fields where supported.
373 #[arg(long, short = 'v')]
374 verbose: bool,
375 },
376 /// Show group details.
377 #[command(visible_alias = "i")]
378 Info {
379 /// Discourse name.
380 discourse: String,
381 /// Group ID.
382 group: u64,
383 /// Output format.
384 #[arg(long, short = 'f', value_enum, default_value = "json")]
385 format: StructuredFormat,
386 },
387 /// List members of a group.
388 #[command(visible_alias = "m")]
389 Members {
390 /// Discourse name.
391 discourse: String,
392 /// Group ID.
393 group: u64,
394 /// Output format.
395 #[arg(long, short = 'f', value_enum, default_value = "text")]
396 format: ListFormat,
397 },
398 /// Copy a group to another Discourse.
399 #[command(visible_alias = "cp")]
400 Copy {
401 /// Source discourse name.
402 discourse: String,
403 /// Target discourse name (defaults to source when omitted).
404 #[arg(long, short = 't')]
405 target: Option<String>,
406 /// Group ID.
407 group: u64,
408 },
409 /// Bulk add members to a group from a file (or stdin) of email addresses.
410 #[command(visible_alias = "a")]
411 Add {
412 /// Discourse name.
413 discourse: String,
414 /// Group ID.
415 group: u64,
416 /// Path to a file of email addresses (one per line; blank
417 /// lines and `#` comments are ignored). Reads stdin when
418 /// omitted or `-`.
419 local_path: Option<PathBuf>,
420 /// Send Discourse notifications to added users.
421 #[arg(long)]
422 notify: bool,
423 },
424}
425
426#[derive(Subcommand)]
427pub enum BackupCommand {
428 /// Create a new backup.
429 #[command(visible_alias = "cr")]
430 Create {
431 /// Discourse name.
432 discourse: String,
433 },
434 /// List backups.
435 #[command(visible_alias = "ls")]
436 List {
437 /// Discourse name.
438 discourse: String,
439 /// Output format.
440 #[arg(long, short = 'f', value_enum, default_value = "text")]
441 format: OutputFormat,
442 /// Include additional fields where supported.
443 #[arg(long, short = 'v')]
444 verbose: bool,
445 },
446 /// Restore a backup.
447 #[command(visible_alias = "rs")]
448 Restore {
449 /// Discourse name.
450 discourse: String,
451 /// Backup filename/path on the target system.
452 backup_path: String,
453 },
454}
455
456#[derive(Subcommand)]
457pub enum PaletteCommand {
458 /// List color palettes.
459 #[command(visible_alias = "ls")]
460 List {
461 /// Discourse name.
462 discourse: String,
463 /// Output format.
464 #[arg(long, short = 'f', value_enum, default_value = "text")]
465 format: ListFormat,
466 /// Include additional fields where supported.
467 #[arg(long, short = 'v')]
468 verbose: bool,
469 },
470 /// Pull a palette to local JSON.
471 #[command(visible_alias = "pl")]
472 Pull {
473 /// Discourse name.
474 discourse: String,
475 /// Palette ID.
476 palette_id: u64,
477 /// Destination file path (auto-derived when omitted).
478 local_path: Option<PathBuf>,
479 },
480 /// Push local JSON to create or update a palette.
481 #[command(visible_alias = "ps")]
482 Push {
483 /// Discourse name.
484 discourse: String,
485 /// Local JSON file path.
486 local_path: PathBuf,
487 /// Palette ID to update (creates a new palette when omitted).
488 palette_id: Option<u64>,
489 },
490}
491
492#[derive(Subcommand)]
493pub enum PluginCommand {
494 /// List installed plugins.
495 #[command(visible_alias = "ls")]
496 List {
497 /// Discourse name.
498 discourse: String,
499 /// Output format.
500 #[arg(long, short = 'f', value_enum, default_value = "text")]
501 format: ListFormat,
502 /// Include additional fields where supported.
503 #[arg(long, short = 'v')]
504 verbose: bool,
505 },
506 /// Install a plugin from URL.
507 #[command(visible_alias = "i")]
508 Install {
509 /// Discourse name.
510 discourse: String,
511 /// Plugin repository URL.
512 url: String,
513 },
514 /// Remove a plugin by name.
515 #[command(visible_alias = "rm")]
516 Remove {
517 /// Discourse name.
518 discourse: String,
519 /// Plugin name.
520 name: String,
521 },
522}
523
524#[derive(Subcommand)]
525pub enum ThemeCommand {
526 /// List installed themes.
527 #[command(visible_alias = "ls")]
528 List {
529 /// Discourse name.
530 discourse: String,
531 /// Output format.
532 #[arg(long, short = 'f', value_enum, default_value = "text")]
533 format: ListFormat,
534 /// Include additional fields where supported.
535 #[arg(long, short = 'v')]
536 verbose: bool,
537 },
538 /// Install a theme from URL.
539 #[command(visible_alias = "i")]
540 Install {
541 /// Discourse name.
542 discourse: String,
543 /// Theme repository URL.
544 url: String,
545 },
546 /// Remove a theme by name.
547 #[command(visible_alias = "rm")]
548 Remove {
549 /// Discourse name.
550 discourse: String,
551 /// Theme name.
552 name: String,
553 },
554 /// Pull a theme to a local JSON file.
555 #[command(visible_alias = "pl")]
556 Pull {
557 /// Discourse name.
558 discourse: String,
559 /// Theme ID (from `dsc theme list`).
560 theme_id: u64,
561 /// Destination file path (auto-derived from theme name when omitted).
562 local_path: Option<PathBuf>,
563 },
564 /// Push a local JSON file to create or update a theme.
565 #[command(visible_alias = "ps")]
566 Push {
567 /// Discourse name.
568 discourse: String,
569 /// Local JSON file path.
570 local_path: PathBuf,
571 /// Theme ID to update (creates a new theme when omitted).
572 theme_id: Option<u64>,
573 },
574 /// Duplicate a theme and print the new theme ID.
575 #[command(visible_alias = "dup")]
576 Duplicate {
577 /// Discourse name.
578 discourse: String,
579 /// Theme ID to duplicate (from `dsc theme list`).
580 theme_id: u64,
581 },
582}
583
584#[derive(Subcommand)]
585pub enum UserCommand {
586 /// List users via the admin users endpoint.
587 #[command(visible_alias = "ls")]
588 List {
589 /// Discourse name.
590 discourse: String,
591 /// Listing type: active | new | staff | suspended | silenced | staged.
592 #[arg(long, short = 'l', default_value = "active")]
593 listing: String,
594 /// Page number (Discourse paginates 100 per page).
595 #[arg(long, short = 'p', default_value_t = 1)]
596 page: u32,
597 /// Output format.
598 #[arg(long, short = 'f', value_enum, default_value = "text")]
599 format: ListFormat,
600 },
601 /// Show detailed info for a user.
602 #[command(visible_alias = "i")]
603 Info {
604 /// Discourse name.
605 discourse: String,
606 /// Username.
607 username: String,
608 /// Output format.
609 #[arg(long, short = 'f', value_enum, default_value = "text")]
610 format: ListFormat,
611 },
612 /// Suspend a user.
613 #[command(visible_alias = "sus")]
614 Suspend {
615 /// Discourse name.
616 discourse: String,
617 /// Username.
618 username: String,
619 /// When the suspension ends. ISO-8601 timestamp (e.g.
620 /// `2026-12-31T00:00:00Z`) or `forever`.
621 #[arg(long, short = 'u', default_value = "forever")]
622 until: String,
623 /// Reason shown to the user and in the audit log.
624 #[arg(long, short = 'r', default_value = "")]
625 reason: String,
626 },
627 /// Remove a suspension from a user.
628 #[command(visible_alias = "uns")]
629 Unsuspend {
630 /// Discourse name.
631 discourse: String,
632 /// Username.
633 username: String,
634 },
635 /// Manage a user's group memberships.
636 #[command(visible_alias = "g")]
637 Groups {
638 #[command(subcommand)]
639 command: UserGroupsCommand,
640 },
641}
642
643#[derive(Subcommand)]
644pub enum UserGroupsCommand {
645 /// List the groups a user belongs to.
646 #[command(visible_alias = "ls")]
647 List {
648 /// Discourse name.
649 discourse: String,
650 /// Target username.
651 username: String,
652 /// Output format.
653 #[arg(long, short = 'f', value_enum, default_value = "text")]
654 format: ListFormat,
655 },
656 /// Add a user to a group.
657 #[command(visible_alias = "a")]
658 Add {
659 /// Discourse name.
660 discourse: String,
661 /// Target username.
662 username: String,
663 /// Group ID.
664 group_id: u64,
665 /// Send Discourse notification to the user.
666 #[arg(long)]
667 notify: bool,
668 },
669 /// Remove a user from a group.
670 #[command(visible_alias = "rm")]
671 Remove {
672 /// Discourse name.
673 discourse: String,
674 /// Target username.
675 username: String,
676 /// Group ID.
677 group_id: u64,
678 },
679}
680
681#[derive(Subcommand)]
682pub enum PostCommand {
683 /// Edit a post by ID. Reads the new body from file or stdin.
684 #[command(visible_alias = "e")]
685 Edit {
686 /// Discourse name.
687 discourse: String,
688 /// Post ID.
689 post_id: u64,
690 /// Input file path. Reads stdin when omitted or `-`.
691 local_path: Option<PathBuf>,
692 },
693 /// Delete a post by ID.
694 #[command(visible_alias = "rm")]
695 Delete {
696 /// Discourse name.
697 discourse: String,
698 /// Post ID.
699 post_id: u64,
700 },
701 /// Move a post to a different topic.
702 #[command(visible_alias = "mv")]
703 Move {
704 /// Discourse name.
705 discourse: String,
706 /// Post ID to move.
707 post_id: u64,
708 /// Destination topic ID.
709 #[arg(long = "to-topic", short = 't')]
710 to_topic: u64,
711 },
712}
713
714#[derive(Subcommand)]
715pub enum TagCommand {
716 /// List every tag on the Discourse.
717 #[command(visible_alias = "ls")]
718 List {
719 /// Discourse name.
720 discourse: String,
721 /// Output format.
722 #[arg(long, short = 'f', value_enum, default_value = "text")]
723 format: ListFormat,
724 },
725 /// Add a tag to a topic.
726 #[command(visible_alias = "a")]
727 Apply {
728 /// Discourse name.
729 discourse: String,
730 /// Topic ID.
731 topic_id: u64,
732 /// Tag to add.
733 tag: String,
734 },
735 /// Remove a tag from a topic.
736 #[command(visible_alias = "rm")]
737 Remove {
738 /// Discourse name.
739 discourse: String,
740 /// Topic ID.
741 topic_id: u64,
742 /// Tag to remove.
743 tag: String,
744 },
745}
746
747#[derive(Subcommand)]
748pub enum SettingCommand {
749 /// Set a site setting on a Discourse (or all tagged Discourses).
750 #[command(visible_alias = "s")]
751 Set {
752 /// Discourse name. Required when targeting a single discourse.
753 discourse: String,
754 /// Setting key.
755 setting: String,
756 /// Setting value.
757 value: String,
758 /// Optional tag filter (comma/semicolon separated, match-any). Ignored when discourse is specified.
759 #[arg(long, value_name = "tag1,tag2")]
760 tags: Option<String>,
761 },
762
763 /// Get the current value of a site setting.
764 #[command(visible_alias = "g")]
765 Get {
766 /// Discourse name.
767 discourse: String,
768 /// Setting key.
769 setting: String,
770 },
771
772 /// List all site settings.
773 #[command(visible_alias = "ls")]
774 List {
775 /// Discourse name.
776 discourse: String,
777 /// Output format.
778 #[arg(long, short = 'f', value_enum, default_value = "text")]
779 format: ListFormat,
780 /// Show output even when list is empty.
781 #[arg(long, short = 'v')]
782 verbose: bool,
783 },
784}
785
786#[derive(ValueEnum, Clone, Copy)]
787pub enum CompletionShell {
788 /// Bash shell.
789 Bash,
790 /// Zsh shell.
791 Zsh,
792 /// Fish shell.
793 Fish,
794}
795
796impl From<CompletionShell> for Shell {
797 fn from(value: CompletionShell) -> Self {
798 match value {
799 CompletionShell::Bash => Shell::Bash,
800 CompletionShell::Zsh => Shell::Zsh,
801 CompletionShell::Fish => Shell::Fish,
802 }
803 }
804}
805
806#[derive(ValueEnum, Clone)]
807pub enum OutputFormat {
808 /// Plain text.
809 #[value(alias = "plaintext")]
810 Text,
811 /// Markdown list.
812 Markdown,
813 /// Markdown table.
814 MarkdownTable,
815 /// Pretty JSON.
816 Json,
817 /// YAML.
818 #[value(alias = "yml")]
819 Yaml,
820 /// CSV.
821 Csv,
822 /// One base URL per line (pipe-friendly).
823 #[value(alias = "url")]
824 Urls,
825}
826
827#[derive(ValueEnum, Clone, Copy)]
828pub enum ListFormat {
829 /// Plain text.
830 Text,
831 /// Pretty JSON.
832 Json,
833 /// YAML.
834 #[value(alias = "yml")]
835 Yaml,
836}
837
838#[derive(ValueEnum, Clone, Copy)]
839pub enum StructuredFormat {
840 /// Pretty JSON.
841 Json,
842 /// YAML.
843 #[value(alias = "yml")]
844 Yaml,
845}