bzr 0.4.4

A CLI for Bugzilla, inspired by gh
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
use clap::{Args, Subcommand};

/// Shared `--fields` / `--exclude-fields` selection, flattened into the bug
/// query subcommands (`list`, `view`, `search`, `my`) so the pair is defined
/// once instead of repeated per variant.
#[derive(Args, Debug, Clone)]
pub struct FieldArgs {
    /// Fields to request from the server (comma-separated). In table output,
    /// selects which columns (or detail rows) to show, in order; under --json,
    /// the object contains only the selected fields (id only if requested).
    /// Built-in fields and Bugzilla custom fields named `cf_*` are valid.
    #[arg(long)]
    pub fields: Option<String>,
    /// Fields to drop from the server request (comma-separated). In table
    /// output, removes those columns/rows; under --json, the object omits the
    /// dropped fields (including id, if excluded).
    #[arg(long)]
    pub exclude_fields: Option<String>,
}

#[derive(Subcommand)]
#[expect(
    clippy::doc_markdown,
    reason = "doc examples are literal shell commands; wrapping URLs in <> or identifiers in backticks would degrade copy-paste UX"
)]
pub enum BugAction {
    /// List bugs that match the given filters.
    ///
    /// Filter flags (`--product`, `--component`, `--status`,
    /// `--assignee`, `--creator`, `--priority`, `--severity`) are
    /// repeatable for OR semantics within a category and AND across
    /// categories. Prefix any filter value with `!` to invert it
    /// (e.g. `--status '!CLOSED'`).
    ///
    /// `--summary` matches a substring against the bug's Summary field
    /// across all bug states (open and closed). It is the structured
    /// counterpart to `bzr bug search`, which uses Bugzilla's
    /// quicksearch syntax and defaults to open bugs only.
    ///
    /// `--limit` defaults to 50; raise it for broader scans, but very
    /// large values may exceed the server's max-results setting and
    /// return a truncated list. `--fields` and `--exclude-fields` control
    /// which fields are requested from the server; in table output they
    /// select and remove columns (in the given order). Under `--json` the
    /// output object is trimmed to the selected fields (gh-style):
    /// `--fields summary` returns `{"summary": ...}` with no `id` unless you
    /// ask for it, and `--exclude-fields id` drops `id`. Built-in fields and
    /// Bugzilla custom fields named `cf_*` are valid; custom fields are shown
    /// when requested and returned by the server. Unknown non-custom fields
    /// are skipped with a warning, or rejected if nothing known remains.
    ///
    /// `--created-since` / `--changed-since` filter by Bugzilla's
    /// `creation_time` / `last_change_time` fields. Both accept ISO
    /// 8601 (`YYYY-MM-DDTHH:MM:SSZ`) or a bare `YYYY-MM-DD` (treated
    /// as 00:00:00 UTC). Malformed input exits 7 before any network
    /// call.
    ///
    /// Eight additional field filters from bzl-parity issue #158:
    /// `--whiteboard`, `--target-milestone`, `--version`, `--op-sys`,
    /// `--platform`, `--resolution`, `--qa-contact`, `--url`. All
    /// repeatable for OR within a field; AND across fields. Prefix
    /// with `!` to invert. `--whiteboard` and `--url` are substring
    /// matches (negation uses `notsubstring`); the other six are
    /// exact match (negation uses `notequals`).
    ///
    /// Examples:
    ///
    ///   bzr bug list --product Firefox --status NEW --limit 25
    ///   bzr bug list --assignee me@example.com --status '!CLOSED'
    ///   bzr bug list --summary "kernel panic" --product Kernel
    ///   bzr bug list --id 100,101,102
    ///   bzr bug list --product Firefox --changed-since 2026-04-01
    ///
    /// See bzr-bug-search(1) for free-text search, bzr-bug-my(1) for
    /// caller-relative views, and bzr-query(1) for saving a filter
    /// combination by name.
    #[command(verbatim_doc_comment)]
    List {
        /// Filter by product (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        product: Vec<String>,
        /// Filter by component (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        component: Vec<String>,
        /// Filter by status (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        status: Vec<String>,
        /// Filter by assignee (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        assignee: Vec<String>,
        /// Filter by creator (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        creator: Vec<String>,
        /// Filter by priority (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        priority: Vec<String>,
        /// Filter by severity (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        severity: Vec<String>,
        /// Filter by bug IDs
        #[arg(long)]
        id: Vec<u64>,
        /// Filter by alias
        #[arg(long)]
        alias: Option<String>,
        /// Substring match on the Summary field (matches across all bug states)
        #[arg(long)]
        summary: Option<String>,
        /// Max number of results
        #[arg(long, default_value = "50")]
        limit: u32,
        #[command(flatten)]
        field_args: FieldArgs,
        /// Filter to bugs created at or after this date.
        ///
        /// Accepts `YYYY-MM-DD` (interpreted as 00:00:00 UTC),
        /// `YYYY-MM-DDTHH:MM:SS`, `YYYY-MM-DDTHH:MM:SSZ`, or
        /// `YYYY-MM-DDTHH:MM:SS±HH:MM`. Malformed input exits 7.
        #[arg(long, value_name = "DATE")]
        created_since: Option<String>,
        /// Filter to bugs last modified at or after this date.
        ///
        /// Same accepted forms as `--created-since`.
        #[arg(long, value_name = "DATE")]
        changed_since: Option<String>,
        /// Filter by Status Whiteboard substring (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        whiteboard: Vec<String>,
        /// Filter by Target Milestone (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        target_milestone: Vec<String>,
        /// Filter by Version (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        version: Vec<String>,
        /// Filter by Operating System (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        op_sys: Vec<String>,
        /// Filter by Platform (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        platform: Vec<String>,
        /// Filter by Resolution (repeatable for OR; prefix with ! to exclude); empty matches open bugs
        #[arg(long)]
        resolution: Vec<String>,
        /// Filter by QA Contact login (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        qa_contact: Vec<String>,
        /// Filter by URL field substring (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        url: Vec<String>,
    },
    /// View one or more bugs by ID or alias.
    ///
    /// Single-ID: prints the bug's full record (summary, status,
    /// assignee, priority, CC list, depends-on, blocks, and the most
    /// recent comments) as a formatted detail block or JSON object.
    ///
    /// Multi-ID: emits one detail block per bug separated by a
    /// horizontal divider, in argument order. Aliases and numeric IDs
    /// may be mixed. JSON output for multi-ID becomes a wrapped
    /// `{"bugs": [...], "failed": [...]}` object — `failed` is always
    /// present (empty array when no failures) so `jq` consumers can
    /// rely on `.bugs[]`. Single-ID JSON output is unchanged
    /// (a bare `Bug` object).
    ///
    /// `--permissive` (multi-ID only) suppresses per-bug access
    /// failures: inaccessible bugs are surfaced as inline
    /// `Bug #N — UNAVAILABLE` placeholder blocks instead of bailing
    /// the whole call. Exit code is 0 even when some bugs fail.
    /// Session-wide failures (transport, auth, security) still bail.
    ///
    /// Use `--fields` to fetch only specific fields (faster on large
    /// bugs over REST); `--exclude-fields` is the inverse. Under `--json`
    /// the returned object is trimmed to the selected fields (gh-style) on
    /// every transport, since trimming happens client-side after the fetch.
    /// Built-in fields and Bugzilla custom fields named `cf_*` are valid.
    /// On XML-RPC servers the full bug is fetched regardless of the field
    /// list, so there the selection only controls which detail rows (table)
    /// or object keys (JSON) appear, not what is sent over the wire.
    ///
    /// Under `--json`, `bug view` stays lenient when the selection resolves
    /// to nothing known: an unknown or mistyped `--fields`, or an
    /// `--exclude-fields` covering every field, yields an empty `{}` object
    /// and exits 0, with a one-line warning on stderr. `bug list`, `my`,
    /// `search`, and `query run` instead exit 7 in that case. So a `{}`
    /// object plus a zero exit can mean a field name was misspelled — check
    /// stderr.
    ///
    /// Examples:
    ///
    ///   bzr bug view 12345
    ///   bzr bug view 12345 12346 12347
    ///   bzr bug view 12345 my-alias 12347 --permissive
    ///   bzr bug view 12345 --json | jq .summary
    ///   bzr bug view my-alias --fields id,summary,status
    ///
    /// See bzr-bug-history(1) for the change log and
    /// bzr-comment-list(1) for the full comment thread.
    #[command(verbatim_doc_comment)]
    View {
        /// Bug ID(s) or alias(es). Aliases and numeric IDs may be mixed.
        #[arg(required = true, num_args = 1..)]
        ids: Vec<String>,
        /// Continue past per-bug failures (multi-ID only).
        ///
        /// When set, inaccessible bugs (NotFound or Bug.get fault
        /// codes 100/101/102) are reported as inline placeholder
        /// rows and the command exits 0. Without `--permissive`,
        /// the first per-bug failure aborts the whole call. Has
        /// no effect on session-wide errors (transport, auth,
        /// security) — those always bail.
        #[arg(long)]
        permissive: bool,
        #[command(flatten)]
        field_args: FieldArgs,
    },
    /// Search bugs using Bugzilla quicksearch or by parsing a Bugzilla URL.
    ///
    /// The positional `query` is passed verbatim to the server's
    /// quicksearch engine, which searches summary, description, and
    /// comments and understands operators (`@user` for assignee,
    /// `:product` for product, etc.). Mutually exclusive with
    /// `--from-url`, which parses a Bugzilla `buglist.cgi` URL and
    /// reproduces the same filter set against the configured server.
    /// Unrecognized URL parameters are passed through verbatim.
    ///
    /// Important: quicksearch defaults to OPEN bugs only. To include
    /// closed/resolved bugs, prepend the bare token `ALL` to the
    /// query (e.g. `ALL kernel panic`). For a Summary-field-only
    /// substring match across all bug states with no quicksearch
    /// tokenization or status defaults at play, use
    /// `bzr bug list --summary <text>`.
    ///
    /// `--save-as` (only valid with `--from-url`) saves the parsed
    /// query for reuse; if no name is given it defaults to the URL's
    /// `known_name` parameter when present, otherwise an
    /// auto-generated name. Saved queries are managed with
    /// `bzr query`.
    ///
    /// Examples:
    ///
    ///   bzr bug search "kernel panic" --limit 10
    ///   bzr bug search "ALL kernel panic" --limit 10      # all states, summary+description+comments
    ///   bzr bug list --summary "kernel panic" --limit 10  # all states, summary only
    ///   bzr bug search --from-url 'https://bz/buglist.cgi?product=Firefox'
    ///   bzr bug search --from-url '...' --save-as firefox-bugs
    ///
    /// See bzr-bug-list(1) for filter-flag based listing and
    /// bzr-query(1) for managing saved queries directly.
    #[command(verbatim_doc_comment)]
    Search {
        /// Quicksearch query (mutually exclusive with `--from-url`).
        ///
        /// Passed to the server's quicksearch engine, which searches
        /// summary, description, and comments and DEFAULTS TO OPEN
        /// BUGS ONLY. Prepend the bare token `ALL` to include closed
        /// bugs (`ALL kernel panic`); for a Summary-field-only match
        /// across all states, use `bzr bug list --summary <text>`.
        #[arg(conflicts_with = "from_url")]
        query: Option<String>,
        /// Execute a search from a Bugzilla `buglist.cgi` URL.
        ///
        /// Parses the URL's query parameters into known filters
        /// where possible; unrecognized parameters are passed
        /// through to the API verbatim. Pair with `--save-as` to
        /// persist the parsed query as a named entry usable by
        /// `bzr query run`.
        #[arg(long)]
        from_url: Option<String>,
        /// Save the parsed `--from-url` query for future reuse.
        ///
        /// Only valid with `--from-url`. If a name is provided,
        /// the query is stored under that name. If `--save-as` is
        /// given without a value, the URL's `known_name` query
        /// parameter is used as the name; if neither is present,
        /// the command fails with input-validation (exit code 7).
        /// Saved queries are managed via `bzr query`.
        #[arg(long, requires = "from_url", num_args = 0..=1, default_missing_value = "")]
        save_as: Option<String>,
        /// Max number of results (default: 50)
        #[arg(long)]
        limit: Option<u32>,
        #[command(flatten)]
        field_args: FieldArgs,
    },
    /// Show the change history for a single bug.
    ///
    /// Prints every recorded change to the bug's fields (status,
    /// assignee, comments added, attachments, etc.) in chronological
    /// order, including the user who made each change. Use `--since`
    /// (ISO 8601 date or datetime) to limit the output to changes
    /// after a given point.
    ///
    /// Examples:
    ///
    ///   bzr bug history 12345
    ///   bzr bug history 12345 --since 2026-01-01
    ///   bzr bug history 12345 --since 2026-04-15T00:00:00Z --json
    ///
    /// See bzr-bug-view(1) for the current state of a bug.
    #[command(verbatim_doc_comment)]
    History {
        /// Bug ID
        id: u64,
        /// Only show changes after this date (ISO 8601)
        #[arg(long)]
        since: Option<String>,
    },
    /// Create a new bug under a product and component.
    ///
    /// `--product` and `--component` are required unless a saved
    /// template (`--template`) supplies them; CLI flags override
    /// template values. Some Bugzilla installations also require
    /// `--op-sys` and `--rep-platform` -- the API call fails with
    /// exit code 4 (Api) when the server demands a field that
    /// wasn't provided.
    ///
    /// Description sources, highest priority first:
    ///
    ///   1. `--description "text"` (literal)
    ///   2. `--description-file PATH` (UTF-8 file contents)
    ///   3. piped stdin (when stdin is not a TTY)
    ///   4. `$EDITOR` (when stdin is a TTY and none of the above)
    ///
    /// `--description` and `--description-file` are mutually
    /// exclusive. When the editor flow is active, `--summary` is
    /// optional: the first non-empty line of the buffer becomes
    /// the summary and the rest becomes the description. A
    /// `git commit -v`-style sentinel divider separates editable
    /// content from informational field reminders.
    ///
    /// On success, prints the new bug ID, alias (if assigned), and
    /// URL to stdout. With `--json`, the same fields are emitted as
    /// a JSON object suitable for piping into scripts.
    ///
    /// Examples:
    ///
    ///   bzr bug create --product Fedora --component kernel \
    ///     --summary "Boot failure on 6.x" \
    ///     --description "System hangs at initramfs"
    ///   bzr bug create --product Fedora --component kernel \
    ///     --description-file /tmp/desc.txt --summary "Boot failure"
    ///   bzr bug create --product Fedora --component kernel
    ///     # opens $EDITOR; first non-empty line of the buffer
    ///     # becomes the summary
    ///   bzr bug create --template security-bug --summary "XSS in login"
    ///
    /// Exit codes: 0 on success, 4 on Bugzilla API error, 7 on
    /// input validation (missing --summary outside the editor flow,
    /// empty editor buffer, missing or non-UTF-8 --description-file,
    /// $EDITOR exited non-zero), 9 on auth failure.
    ///
    /// See bzr-bug-clone(1) for cloning an existing bug,
    /// bzr-template(1) for managing templates, and bzr-field(1) for
    /// discovering valid `--priority`, `--severity`, and `--status`
    /// values.
    #[command(verbatim_doc_comment)]
    Create {
        /// Use a saved template for default field values.
        ///
        /// References a named template from `bzr template list`.
        /// When set, fields stored in the template (product,
        /// component, version, priority, severity, assignee,
        /// op-sys, rep-platform, description) are used as defaults
        /// for this `create` invocation; CLI flags override
        /// template values.
        #[arg(long)]
        template: Option<String>,
        /// Product name (required unless supplied by `--template`).
        ///
        /// Required unless the chosen template provides a product.
        /// When both are set, this CLI value wins.
        #[arg(long)]
        product: Option<String>,
        /// Component name (required unless supplied by `--template`).
        ///
        /// Required unless the chosen template provides a
        /// component. When both are set, this CLI value wins. The
        /// component must exist on the chosen product -- discover
        /// valid names via `bzr product view <product>`.
        #[arg(long)]
        component: Option<String>,
        /// Bug summary (required unless the editor flow is active)
        #[arg(long)]
        summary: Option<String>,
        /// Version
        #[arg(long)]
        version: Option<String>,
        /// Bug description
        #[arg(long, conflicts_with = "description_file")]
        description: Option<String>,
        /// Read the bug description from a UTF-8 file.
        ///
        /// Mutually exclusive with `--description`. The file path
        /// must exist and be readable; non-existent paths or
        /// non-UTF-8 contents fail with exit code 7.
        #[arg(long, value_name = "PATH", conflicts_with = "description")]
        description_file: Option<std::path::PathBuf>,
        /// Priority
        #[arg(long)]
        priority: Option<String>,
        /// Severity
        #[arg(long)]
        severity: Option<String>,
        /// Assignee
        #[arg(long)]
        assignee: Option<String>,
        /// Operating system (required by some Bugzilla installations)
        #[arg(long)]
        op_sys: Option<String>,
        /// Hardware platform (required by some Bugzilla installations)
        #[arg(long)]
        rep_platform: Option<String>,
        /// Bug IDs that this bug blocks (comma-separated)
        #[arg(long, value_delimiter = ',')]
        blocks: Vec<u64>,
        /// Bug IDs that this bug depends on (comma-separated)
        #[arg(long, value_delimiter = ',')]
        depends_on: Vec<u64>,
    },
    /// Show bugs related to the authenticated user.
    ///
    /// Default view: bugs assigned to the caller. `--created`
    /// switches to bugs the caller filed; `--cc` switches to bugs
    /// the caller is CC'd on; `--all` shows all three categories
    /// at once (assigned, created, CC'd) and conflicts with the
    /// other two flags.
    ///
    /// `--limit` is per category, not total -- with `--all` and
    /// `--limit 50`, up to 150 rows may be returned. `--status`
    /// filters across whichever category is active, with the same
    /// repeatability and `!`-prefix semantics as `bzr bug list`.
    ///
    /// Examples:
    ///
    ///   bzr bug my
    ///   bzr bug my --created --status NEW
    ///   bzr bug my --all --limit 25
    ///
    /// See bzr-bug-list(1) for filter-driven listing without the
    /// caller-relative shortcuts and bzr-whoami(1) to confirm which
    /// account `my` resolves to.
    #[command(verbatim_doc_comment)]
    My {
        /// Show bugs I created (instead of assigned to me).
        ///
        /// Mutually exclusive with `--all`.
        #[arg(long)]
        created: bool,
        /// Show bugs I'm CC'd on (instead of assigned to me).
        ///
        /// Mutually exclusive with `--all`.
        #[arg(long)]
        cc: bool,
        /// Show all bugs related to me (assigned + created + CC'd).
        ///
        /// Mutually exclusive with `--created` and `--cc`. Output
        /// is grouped into the three categories; `--limit` applies
        /// per category, so the total can be up to 3x the limit.
        #[arg(long, conflicts_with_all = ["created", "cc"])]
        all: bool,
        /// Filter by status (repeatable for OR; prefix with ! to exclude)
        #[arg(long)]
        status: Vec<String>,
        /// Max results per category (assigned/created/cc).
        ///
        /// With `--all`, the limit applies independently to each of
        /// the three categories, so up to 3x this value may be
        /// returned. With `--created`, `--cc`, or no view flag,
        /// the limit applies to the single active category.
        #[arg(long, default_value = "50")]
        limit: u32,
        #[command(flatten)]
        field_args: FieldArgs,
    },
    /// Clone an existing bug, optionally overriding fields.
    ///
    /// Copies the source bug's product, component, version, summary,
    /// description, priority, severity, assignee, op-sys,
    /// rep-platform, CC list, and keywords into a new bug. Pass any
    /// of the override flags (`--summary`, `--product`, ...) to
    /// change values for the clone; unspecified fields inherit from
    /// the source.
    ///
    /// By default the new bug gets a "Cloned from bug #N" comment;
    /// disable with `--no-comment`. Use `--add-depends-on` to link
    /// the new bug as a dependency of the source, or `--add-blocks`
    /// to make it block the source. `--no-cc` and `--no-keywords`
    /// skip copying those lists.
    ///
    /// Examples:
    ///
    ///   bzr bug clone 12345
    ///   bzr bug clone 12345 --summary "Backport to RHEL 9" \
    ///     --version "RHEL 9.4" --add-depends-on
    ///
    /// See bzr-bug-create(1) for filing a brand-new bug.
    #[command(verbatim_doc_comment)]
    Clone {
        /// Source bug ID or alias
        id: String,
        /// Override summary
        #[arg(long)]
        summary: Option<String>,
        /// Override product
        #[arg(long)]
        product: Option<String>,
        /// Override component
        #[arg(long)]
        component: Option<String>,
        /// Override version
        #[arg(long)]
        version: Option<String>,
        /// Override description
        #[arg(long)]
        description: Option<String>,
        /// Override priority
        #[arg(long)]
        priority: Option<String>,
        /// Override severity
        #[arg(long)]
        severity: Option<String>,
        /// Override assignee
        #[arg(long)]
        assignee: Option<String>,
        /// Override operating system
        #[arg(long)]
        op_sys: Option<String>,
        /// Override hardware platform
        #[arg(long)]
        rep_platform: Option<String>,
        /// Skip adding "Cloned from bug #N" comment
        #[arg(long)]
        no_comment: bool,
        /// Make the new bug depend on the source bug
        #[arg(long)]
        add_depends_on: bool,
        /// Make the new bug block the source bug
        #[arg(long)]
        add_blocks: bool,
        /// Don't copy the CC list from the source bug
        #[arg(long)]
        no_cc: bool,
        /// Don't copy keywords from the source bug
        #[arg(long)]
        no_keywords: bool,
    },
    /// Update one or more bugs with the same set of changes.
    ///
    /// Accepts one or more bug IDs as positional args. All field
    /// changes (`--status`, `--resolution`, `--dupe-of`, `--assignee`,
    /// `--priority`, `--severity`, `--summary`, `--whiteboard`) are
    /// applied to every bug in the list. When closing a bug, both
    /// `--status` and `--resolution` typically need to be set
    /// together (e.g. `--status RESOLVED --resolution FIXED`).
    ///
    /// `--dupe-of <ID>` marks the bug as a duplicate. Bugzilla sets
    /// status/resolution automatically; do not set status/resolution
    /// just to make the duplicate transition.
    ///
    /// `--flag` accepts Bugzilla flag syntax: `name?`, `name+`,
    /// `name-`, `name?(user@example.com)`, or `name?,!` to clear.
    /// Repeatable.
    ///
    /// `--comment <BODY>` (or `--comment-file <PATH>`) posts a comment
    /// atomically with the field changes — a single `Bug.update`
    /// round-trip rather than a separate `bzr comment add` call.
    /// `--comment-private` marks it private. Empty / whitespace-only
    /// bodies are rejected (exit 7).
    ///
    /// List-typed fields support `*-add` / `*-remove` pairs for
    /// incremental edits: `--blocks`, `--depends-on`, `--keywords`,
    /// `--cc`, `--groups`, and `--see-also`. The first five accept
    /// comma-separated values. `--see-also-add` / `--see-also-remove`
    /// do not split on commas — repeat the flag to pass multiple URLs.
    /// Additional update fields include `--alias`, `--deadline`,
    /// `--estimated-time`, `--remaining-time`, `--work-time`,
    /// `--reset-assigned-to`, and `--reset-qa-contact`.
    ///
    /// On batch updates, partial failures (some bugs updated,
    /// others rejected) exit with code 11 (BatchPartialFailure) and
    /// the JSON output enumerates per-bug results.
    ///
    /// Examples:
    ///
    ///   bzr bug update 100 --status RESOLVED --resolution FIXED
    ///   bzr bug update 100 --dupe-of 200
    ///   bzr bug update 100 200 300 --priority high --flag review+
    ///   bzr bug update 100 --status RESOLVED --resolution FIXED \
    ///     --comment "Fixed by patch in #200"
    ///   bzr bug update 100 --blocks-add 200,201 \
    ///     --depends-on-remove 99
    ///   bzr bug update 100 --keywords-add fix-needed,regression \
    ///     --cc-add alice@example.com \
    ///     --see-also-add <https://example.com/issue/42>
    ///
    /// See bzr-bug-create(1) for new bugs, bzr-bug-clone(1) for
    /// cloning, and bzr-comment-add(1) for adding a comment as part
    /// of a status change.
    #[command(verbatim_doc_comment)]
    Update {
        /// Bug ID(s).
        ///
        /// One or more IDs. When more than one is supplied, the
        /// same field changes are applied to every bug; partial
        /// failures (some bugs updated, others rejected) exit with
        /// code 11 and the JSON output enumerates per-bug results.
        #[arg(required = true, num_args = 1..)]
        ids: Vec<u64>,
        /// New status (e.g. `NEW`, `ASSIGNED`, `RESOLVED`, `CLOSED`).
        ///
        /// When closing a bug, `--resolution` must usually be set
        /// in the same call. Discover valid values via
        /// `bzr field list status`.
        #[arg(long, conflicts_with = "dupe_of")]
        status: Option<String>,
        /// Resolution to set when closing a bug.
        ///
        /// Required by most workflows when `--status` transitions
        /// to a closed state (e.g. `RESOLVED`, `VERIFIED`).
        /// Discover valid values via `bzr field list resolution`.
        #[arg(long, conflicts_with = "dupe_of")]
        resolution: Option<String>,
        /// Mark this bug as a duplicate of another bug.
        ///
        /// Forwards Bugzilla's `dupe_of` field. Bugzilla handles the
        /// status/resolution transition to RESOLVED/DUPLICATE.
        #[arg(long, value_name = "ID")]
        dupe_of: Option<u64>,
        /// Set this bug's alias.
        ///
        /// Bugzilla only allows alias updates for a single bug at a time.
        #[arg(long, value_name = "ALIAS")]
        alias: Option<String>,
        /// Set the deadline date (`YYYY-MM-DD`).
        #[arg(long, value_name = "DATE")]
        deadline: Option<String>,
        /// Set the total estimated time in hours.
        #[arg(long, value_name = "HOURS")]
        estimated_time: Option<f64>,
        /// Set the remaining time in hours.
        #[arg(long, value_name = "HOURS")]
        remaining_time: Option<f64>,
        /// Add work time in hours for this update.
        #[arg(long, value_name = "HOURS")]
        work_time: Option<f64>,
        /// Reset assignee to the component default.
        #[arg(long)]
        reset_assigned_to: bool,
        /// Reset QA contact to the component default.
        #[arg(long)]
        reset_qa_contact: bool,
        /// Reassign
        #[arg(long)]
        assignee: Option<String>,
        /// Priority
        #[arg(long)]
        priority: Option<String>,
        /// Severity
        #[arg(long)]
        severity: Option<String>,
        /// Summary
        #[arg(long)]
        summary: Option<String>,
        /// Whiteboard
        #[arg(long)]
        whiteboard: Option<String>,
        /// Post a comment atomically with the field changes.
        ///
        /// Mutually exclusive with `--comment-file`. Use
        /// `--comment-private` to mark the comment private. Empty /
        /// whitespace-only bodies are rejected (exit 7).
        #[arg(long, value_name = "BODY", conflicts_with = "comment_file")]
        comment: Option<String>,
        /// Read the comment body from a UTF-8 file.
        ///
        /// Mutually exclusive with `--comment`. The file must exist
        /// and be readable; non-existent paths or non-UTF-8 contents
        /// fail with exit code 7. Empty / whitespace-only contents
        /// are also rejected.
        #[arg(long, value_name = "PATH", conflicts_with = "comment")]
        comment_file: Option<std::path::PathBuf>,
        /// Mark the comment private (visible only to users with
        /// elevated permissions on the server).
        ///
        /// Requires `--comment` or `--comment-file`; using
        /// `--comment-private` alone is a usage error (exit 7).
        #[arg(long)]
        comment_private: bool,
        /// Set, request, or clear a flag using Bugzilla flag syntax.
        ///
        /// Repeatable. Accepted forms:
        /// `name+` (granted), `name-` (denied), `name?` (request),
        /// `name?(user@example.com)` (request a specific user), or
        /// `name?,!` to clear an existing flag.
        #[arg(long)]
        flag: Vec<String>,
        /// Add bug IDs to the blocks list (comma-separated).
        ///
        /// Combine with `--blocks-remove` for incremental edits.
        /// To replace the list entirely, the bug must be edited
        /// through the Bugzilla web UI.
        #[arg(long, value_delimiter = ',')]
        blocks_add: Vec<u64>,
        /// Remove bug IDs from the blocks list (comma-separated).
        #[arg(long, value_delimiter = ',')]
        blocks_remove: Vec<u64>,
        /// Add bug IDs to the depends-on list (comma-separated).
        ///
        /// Combine with `--depends-on-remove` for incremental
        /// edits.
        #[arg(long, value_delimiter = ',')]
        depends_on_add: Vec<u64>,
        /// Remove bug IDs from the depends-on list (comma-separated).
        #[arg(long, value_delimiter = ',')]
        depends_on_remove: Vec<u64>,
        /// Add keywords (comma-separated).
        ///
        /// Combine with `--keywords-remove` for incremental edits.
        #[arg(long, value_delimiter = ',')]
        keywords_add: Vec<String>,
        /// Remove keywords (comma-separated).
        #[arg(long, value_delimiter = ',')]
        keywords_remove: Vec<String>,
        /// Add CC entries (comma-separated).
        ///
        /// Accepts usernames or email addresses; format is
        /// server-defined.
        #[arg(long, value_delimiter = ',')]
        cc_add: Vec<String>,
        /// Remove CC entries (comma-separated).
        #[arg(long, value_delimiter = ',')]
        cc_remove: Vec<String>,
        /// Add groups (comma-separated).
        ///
        /// Group operations require permission; failures surface
        /// from the server.
        #[arg(long, value_delimiter = ',')]
        groups_add: Vec<String>,
        /// Remove groups (comma-separated).
        #[arg(long, value_delimiter = ',')]
        groups_remove: Vec<String>,
        /// Add a see-also URL.
        ///
        /// Repeat the flag to add multiple URLs (URLs may contain
        /// commas, so no comma-list parsing is performed).
        #[arg(long)]
        see_also_add: Vec<String>,
        /// Remove a see-also URL.
        ///
        /// Repeat the flag to remove multiple URLs.
        #[arg(long)]
        see_also_remove: Vec<String>,
    },
}