bzr 0.3.0

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
mod attachment;
mod bug;
mod classification;
mod comment;
mod component;
mod config;
mod field;
mod group;
mod product;
mod query;
mod server;
mod template;
mod user;
mod whoami;

pub use attachment::AttachmentAction;
pub use bug::BugAction;
pub use classification::ClassificationAction;
pub use comment::CommentAction;
pub use component::ComponentAction;
pub use config::ConfigAction;
pub use field::FieldAction;
pub use group::GroupAction;
pub use product::ProductAction;
pub use query::QueryAction;
pub use server::ServerAction;
pub use template::TemplateAction;
pub use user::UserAction;
pub use whoami::WhoamiAction;

use clap::{Parser, Subcommand};

use crate::types::{ApiMode, OutputFormat};

/// A command-line client for Bugzilla REST API servers.
///
/// bzr provides scriptable access to bugs, comments, attachments,
/// products, components, users, and groups across one or more named
/// Bugzilla servers. It is modeled on the GitHub CLI (`gh`): every
/// resource is a top-level subcommand, every action is a verb under
/// that resource, and `--json` is supported on read paths so output
/// can be consumed by downstream tools.
///
/// Configuration lives in `~/.config/bzr/config.toml` and stores one
/// or more named servers. Use `bzr config set-server` to add a server
/// and `bzr config set-default` to pick the one used when `--server`
/// is omitted. API keys can be stored inline, in an environment
/// variable, or in the OS keychain (`bzr config set-keyring`).
///
/// Output defaults to a colored table at a TTY and to JSON when
/// stdout is piped. Use `--json` or `BZR_OUTPUT=json` to force JSON.
/// Exit codes are stable; the most common are 0 (success), 2 (not
/// found or bad args), 4 (Bugzilla API error), 9 (auth), and 13 (TLS
/// pin mismatch). The full table is in `docs/bzr-cli.md`.
///
/// Examples:
///
///   bzr config set-server prod --url https://bugzilla.example.com \
///     --api-key-env BZR_API_KEY
///   bzr bug list --product Firefox --status NEW --limit 25
///   bzr bug view 12345 --json | jq .summary
///
/// See bzr-bug(1), bzr-config(1), bzr-comment(1), bzr-attachment(1),
/// bzr-product(1), bzr-user(1), bzr-group(1), bzr-template(1),
/// bzr-query(1), bzr-server(1), bzr-classification(1),
/// bzr-component(1), bzr-field(1), and bzr-whoami(1) for the
/// per-resource reference pages.
#[derive(Parser)]
#[command(name = "bzr", version, verbatim_doc_comment)]
#[expect(
    clippy::doc_markdown,
    reason = "doc examples are literal shell commands; wrapping URLs in <> or identifiers in backticks would degrade copy-paste UX"
)]
pub struct Cli {
    /// Server name from config (uses default if not set).
    #[arg(long, global = true)]
    pub server: Option<String>,

    /// Output format: `table` (default at a TTY) or `json`.
    ///
    /// When stdout is piped, the default flips to `json` unless this
    /// flag or `BZR_OUTPUT` overrides it. Precedence:
    /// `--json` > `--output` > `BZR_OUTPUT` > auto-detect.
    #[arg(long, global = true)]
    pub output: Option<OutputFormat>,

    /// Shorthand for `--output json`.
    ///
    /// Equivalent to `--output json` and takes precedence over both
    /// `--output` and the `BZR_OUTPUT` environment variable.
    #[arg(long, global = true)]
    pub json: bool,

    /// Disable colored output.
    ///
    /// bzr also honors the `NO_COLOR` and `CLICOLOR=0` environment
    /// variables, plus `CLICOLOR_FORCE=1` to re-enable. This flag
    /// takes precedence over all of them.
    #[arg(long, global = true)]
    pub no_color: bool,

    /// Suppress all stdout output.
    ///
    /// Redirects stdout to /dev/null. Stderr (errors, warnings,
    /// `tracing` logs) is unaffected. Useful for scripts that only
    /// care about exit codes.
    #[arg(long, global = true)]
    pub quiet: bool,

    /// Override API transport: `rest` (default), `xmlrpc`, or `hybrid`.
    ///
    /// `rest` uses Bugzilla's REST API exclusively. `xmlrpc` uses
    /// XML-RPC for every call. `hybrid` uses REST where possible
    /// and falls back to XML-RPC for endpoints REST can't express
    /// reliably (e.g. `bzr user create` on Bugzilla 5.3+ when
    /// `use_email_as_login` is disabled). Most users won't need
    /// this -- bzr probes on first use and caches the working
    /// transport.
    #[arg(long, global = true)]
    pub api: Option<ApiMode>,

    /// Set log verbosity (default: warnings only, -v=info, -vv=debug, -vvv=trace; `RUST_LOG` overrides)
    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
    pub verbose: u8,

    #[command(subcommand)]
    pub command: Commands,
}

#[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 Commands {
    /// Operate on bugs: list, view, search, create, clone, update, history.
    ///
    /// The `bug` group is the most commonly used part of bzr. Read paths
    /// (`list`, `view`, `search`, `history`, `my`) work without any
    /// authentication beyond what the configured server requires; write
    /// paths (`create`, `clone`, `update`) require credentials and the
    /// caller must have appropriate Bugzilla permissions on the target
    /// product.
    ///
    /// Filter flags (`--product`, `--component`, `--status`, `--assignee`,
    /// `--creator`, `--priority`, `--severity`) on `list` and `my` are
    /// repeatable for OR semantics and accept a `!` prefix for NOT
    /// (e.g. `--status '!CLOSED'`).
    ///
    /// Examples:
    ///
    ///   bzr bug list --product Firefox --status NEW --limit 25
    ///   bzr bug view 12345
    ///   bzr bug create --product P --component C --summary "Crash"
    ///   bzr bug update 100 200 --status RESOLVED --resolution FIXED
    ///
    /// See bzr-bug-list(1), bzr-bug-view(1), bzr-bug-search(1),
    /// bzr-bug-history(1), bzr-bug-my(1), bzr-bug-create(1),
    /// bzr-bug-clone(1), and bzr-bug-update(1) for per-action detail.
    #[command(verbatim_doc_comment)]
    Bug {
        #[command(subcommand)]
        action: BugAction,
    },

    /// List, add, and tag comments on bugs.
    ///
    /// Read paths (`list`, `search-tags`) work with whatever access the
    /// configured server grants to the bug; write paths (`add`, `tag`)
    /// require credentials. Comments are immutable once posted -- tags
    /// can be added or removed, but the body cannot be edited.
    ///
    /// `comment add` reads its body from `--body`, from `$EDITOR` when
    /// neither `--body` nor stdin is provided, or from stdin when piped.
    ///
    /// Examples:
    ///
    ///   bzr comment list 12345
    ///   bzr comment add 12345 --body "Reproduced on RHEL 9.4"
    ///   echo "see #6789" | bzr comment add 12345
    ///
    /// See bzr-comment-list(1), bzr-comment-add(1), bzr-comment-tag(1),
    /// and bzr-comment-search-tags(1) for per-action detail.
    #[command(verbatim_doc_comment)]
    Comment {
        #[command(subcommand)]
        action: CommentAction,
    },

    /// List, download, upload, and update bug attachments.
    ///
    /// Read paths (`list`, `download`) work with whatever access the
    /// configured server grants to the bug; write paths (`upload`,
    /// `update`) require credentials. MIME types on upload are
    /// auto-detected from the file extension and may be overridden with
    /// `--content-type`. Base64 encoding/decoding for the Bugzilla REST
    /// API is handled transparently -- paths in `--out` and the file
    /// argument to `upload` are plain on-disk files.
    ///
    /// Examples:
    ///
    ///   bzr attachment list 12345
    ///   bzr attachment download 9876 --out patch.diff
    ///   bzr attachment upload 12345 patch.diff --summary "Fix crash"
    ///
    /// See bzr-attachment-list(1), bzr-attachment-download(1),
    /// bzr-attachment-upload(1), and bzr-attachment-update(1) for
    /// per-action detail.
    #[command(verbatim_doc_comment)]
    Attachment {
        #[command(subcommand)]
        action: AttachmentAction,
    },

    /// Manage local bzr configuration: servers, default, keychain credentials.
    ///
    /// All `config` actions are local file I/O against
    /// `~/.config/bzr/config.toml` (or `$XDG_CONFIG_HOME/bzr/config.toml`).
    /// They make no network calls and do not require a server to be
    /// reachable. Auth credentials may be stored inline (`--api-key`),
    /// in an environment variable (`--api-key-env`), or in the OS
    /// keychain (`set-keyring`).
    ///
    /// `config show` redacts API keys and prints credential indirection
    /// (env-var name or keychain entry) without reading the secret.
    ///
    /// Examples:
    ///
    ///   bzr config set-server prod --url https://bz.example.com \
    ///     --api-key-env BZR_API_KEY
    ///   bzr config set-default prod
    ///   bzr config set-keyring prod
    ///   bzr config show
    ///
    /// See bzr-config-set-server(1), bzr-config-set-default(1),
    /// bzr-config-show(1), bzr-config-set-keyring(1),
    /// bzr-config-unset-keyring(1), and bzr-config-migrate-to-keyring(1)
    /// for per-action detail.
    #[command(verbatim_doc_comment)]
    Config {
        #[command(subcommand)]
        action: ConfigAction,
    },

    /// List, view, create, and update Bugzilla products.
    ///
    /// Read paths (`list`, `view`) work with whatever access the
    /// configured server grants; write paths (`create`, `update`)
    /// require Bugzilla admin permissions. `list --type` selects the
    /// listing scope: `accessible` (default), `selectable`, or
    /// `enterable`.
    ///
    /// Examples:
    ///
    ///   bzr product list
    ///   bzr product view Firefox
    ///   bzr product create --name MyProduct --description "..."
    ///
    /// See bzr-product-list(1), bzr-product-view(1),
    /// bzr-product-create(1), and bzr-product-update(1) for per-action
    /// detail.
    #[command(verbatim_doc_comment)]
    Product {
        #[command(subcommand)]
        action: ProductAction,
    },

    /// Discover valid values for Bugzilla bug fields (status, priority, etc.).
    ///
    /// Used to find the legal values for fields like `--status`,
    /// `--priority`, `--severity`, `--resolution`, and `--op-sys` before
    /// passing them to `bug create` or `bug update`. Field-name aliases
    /// (e.g. `severity` for `bug_severity`) are listed by `field aliases`.
    ///
    /// Examples:
    ///
    ///   bzr field aliases
    ///   bzr field list status
    ///   bzr field list priority --json
    ///
    /// See bzr-field-aliases(1) and bzr-field-list(1) for per-action
    /// detail.
    #[command(verbatim_doc_comment)]
    Field {
        #[command(subcommand)]
        action: FieldAction,
    },

    /// Search, create, and update Bugzilla user accounts.
    ///
    /// `user search` works with whatever access the configured server
    /// grants (typically all authenticated users); `user create` and
    /// `user update` require Bugzilla admin permissions. Some Bugzilla
    /// installations disable `use_email_as_login` -- on those servers,
    /// `--login` is required separately from `--email`.
    ///
    /// Examples:
    ///
    ///   bzr user search alice
    ///   bzr user create --email alice@example.com --name "Alice" \
    ///     --password '...'
    ///
    /// See bzr-user-search(1), bzr-user-create(1), and bzr-user-update(1)
    /// for per-action detail.
    #[command(verbatim_doc_comment)]
    User {
        #[command(subcommand)]
        action: UserAction,
    },

    /// Manage Bugzilla group membership and group definitions.
    ///
    /// All actions in this group require Bugzilla admin permissions.
    /// Group membership changes are immediate; viewing a group lists
    /// its members and metadata. On Bugzilla 5.3+, membership operations
    /// require POST (handled automatically by bzr).
    ///
    /// Examples:
    ///
    ///   bzr group list-users --group editbugs
    ///   bzr group add-user --group editbugs --user alice@example.com
    ///   bzr group view editbugs
    ///
    /// See bzr-group-add-user(1), bzr-group-remove-user(1),
    /// bzr-group-list-users(1), bzr-group-view(1), bzr-group-create(1),
    /// and bzr-group-update(1) for per-action detail.
    #[command(verbatim_doc_comment)]
    Group {
        #[command(subcommand)]
        action: GroupAction,
    },

    /// Show the currently authenticated user.
    ///
    /// Useful as a quick auth smoke test: prints the login name,
    /// real name, and email of the user the configured API key
    /// resolves to. Both `bzr whoami` and `bzr whoami show` are
    /// equivalent -- `show` is the only action and may be omitted.
    ///
    /// Examples:
    ///
    ///   bzr whoami
    ///   bzr whoami show --json
    ///   bzr --server staging whoami
    ///
    /// See bzr-whoami-show(1) for action detail.
    #[command(verbatim_doc_comment)]
    Whoami {
        #[command(subcommand)]
        action: Option<WhoamiAction>,
    },

    /// Inspect the configured Bugzilla server: version, extensions, capabilities.
    ///
    /// Useful for confirming connectivity and detecting feature support
    /// before invoking commands that depend on a specific server version
    /// or extension. The output includes the Bugzilla version string,
    /// a list of installed extensions, and the active API transport
    /// (REST, XML-RPC, or hybrid).
    ///
    /// Examples:
    ///
    ///   bzr server info
    ///   bzr --server staging server info --json
    ///
    /// See bzr-server-info(1) for action detail.
    #[command(verbatim_doc_comment)]
    Server {
        #[command(subcommand)]
        action: ServerAction,
    },

    /// View Bugzilla classifications (the grouping above products).
    ///
    /// Classifications are an optional Bugzilla feature that groups
    /// related products under a shared umbrella (e.g. "Components",
    /// "Services"). Visibility depends on server configuration --
    /// installations with classifications disabled return a single
    /// "Unclassified" entry.
    ///
    /// Examples:
    ///
    ///   bzr classification view Unclassified
    ///   bzr classification view "Red Hat" --json
    ///
    /// See bzr-classification-view(1) for action detail.
    #[command(verbatim_doc_comment)]
    Classification {
        #[command(subcommand)]
        action: ClassificationAction,
    },

    /// Create and update components within a Bugzilla product.
    ///
    /// Both actions require Bugzilla admin permissions on the target
    /// product. Components belong to exactly one product; renaming or
    /// reassigning across products is not supported by the Bugzilla
    /// REST API. Use `bzr product view <name>` to list a product's
    /// existing components.
    ///
    /// Examples:
    ///
    ///   bzr component create --product MyProduct --name Backend \
    ///     --description "Backend services" \
    ///     --default-assignee dev@example.com
    ///   bzr component update --product MyProduct --name Backend \
    ///     --description "Updated description"
    ///
    /// See bzr-component-create(1) and bzr-component-update(1) for
    /// per-action detail.
    #[command(verbatim_doc_comment)]
    Component {
        #[command(subcommand)]
        action: ComponentAction,
    },

    /// Manage local bug-creation templates (saved field defaults).
    ///
    /// Templates store reusable bug-field defaults (product, component,
    /// version, priority, severity, assignee, op-sys, rep-platform,
    /// description) under a name. They are local to bzr -- saved in
    /// `~/.config/bzr/config.toml` -- and never sent to the server.
    /// Apply a template at bug-creation time with `bzr bug create
    /// --template <name>`; CLI flags override template values.
    ///
    /// Contrast with `bzr query` (saved searches), which also stores
    /// data locally but executes against the server when run.
    ///
    /// Examples:
    ///
    ///   bzr template save security-bug --product Security \
    ///     --component Vulnerabilities --severity critical
    ///   bzr template list
    ///   bzr bug create --template security-bug --summary "XSS in login"
    ///
    /// See bzr-template-save(1), bzr-template-list(1),
    /// bzr-template-show(1), and bzr-template-delete(1) for per-action
    /// detail.
    #[command(verbatim_doc_comment)]
    Template {
        #[command(subcommand)]
        action: TemplateAction,
    },

    /// Save and run reusable Bugzilla searches.
    ///
    /// Saved queries pair a set of filter flags (or a parsed
    /// `buglist.cgi` URL) with a name, stored locally in
    /// `~/.config/bzr/config.toml`. `query save` and `query list`/`show`/
    /// `delete` are local-only; `query run` executes the saved filters
    /// against a Bugzilla server (the saved server, or `--server` to
    /// override). Filter flags use the same syntax as `bzr bug list`,
    /// including repeatability for OR and `!` prefix for NOT.
    ///
    /// `query save --from-url` parses a Bugzilla `buglist.cgi` URL and
    /// extracts known filter parameters; unrecognized parameters are
    /// preserved verbatim and passed through to the API.
    ///
    /// Examples:
    ///
    ///   bzr query save firefox-new --product Firefox --status NEW
    ///   bzr query save my-saved --from-url 'https://bz/buglist.cgi?...'
    ///   bzr query run firefox-new --limit 10
    ///   bzr query run firefox-new --server staging
    ///
    /// See bzr-query-save(1), bzr-query-list(1), bzr-query-show(1),
    /// bzr-query-delete(1), and bzr-query-run(1) for per-action detail.
    #[command(verbatim_doc_comment)]
    Query {
        #[command(subcommand)]
        action: QueryAction,
    },
}

#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;