brazen 0.0.4

A stateless, swiss-army-knife adapter for every LLM provider and protocol.
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
# brazen

**`brazen`** (the **`bz`** command) — a stateless, swiss-army-knife adapter for every LLM
provider and protocol. Pipe a request in, stream a normalized response out.

One small Rust binary that speaks OpenAI `chat/completions`, OpenAI `responses`,
Anthropic `messages`, and Google `generative-ai` across providers (OpenAI, Anthropic,
Mistral, Google, local Ollama, …), handling API-key and OAuth/SSO auth. It is a low-level
building block for agents.

> The *brazen head* was a brass automaton that answered any question put to it. Pipe in a
> question; it speaks the answer.

## Install

```sh
cargo install brazen            # builds and installs the `bz` command
```

Or download a prebuilt `bz` for your platform from the [latest release][releases] — no Rust
toolchain required. Building from source needs Rust 1.85+.

[releases]: https://github.com/mudbungie/brazen/releases/latest

### Corporate roots / TLS-inspecting proxies — the `native-certs` feature

By default `bz` trusts a **bundled Mozilla root set** (compiled in via `rustls` +
`webpki-roots`), so a single static binary verifies public-CA certificates with no OS
trust store. That is the secure default, but it means a **private/corporate root CA** — or
a TLS-inspecting proxy's MITM root, which lives only in your OS trust store — is **not
trusted**, and such a connection fails the handshake (the error now names the cause, e.g.
`HTTP transport: io: invalid peer certificate: UnknownIssuer`). If you are behind one, build
from source with the **`native-certs`** feature, which trusts your OS certificate store
instead:

```sh
cargo install brazen --features native-certs
```

It is a build-time property (no runtime flag), OFF by default so the shipped binary's trust
set never silently widens to a host's.

## Quickstart

```sh
# one-shot: key on the env, model picked by --model (which prefix-routes to its provider)
ANTHROPIC_API_KEY=sk-ant-... bz --model claude-sonnet-4-6 "What is the capital of France?"
```

Set a default model once and the prompt is all you need — the brazen head speaks the answer:

```sh
export ANTHROPIC_API_KEY=sk-ant-...     # or BRAZEN_API_KEY; or `bz --login` for OAuth/SSO
export BRAZEN_MODEL=claude-sonnet-4-6
bz "What is the capital of France?"
bz "Summarize this: $(cat notes.txt)"     # feed data via the prompt (a positional prompt
                                          # overrides stdin; pipe a canonical JSON request with no arg)
```

With **nothing** specified — no `--provider`, no `--model`, no `BRAZEN_MODEL` — `bz` falls
back to the **first provider you declare** in the config (the first `[[provider]]` block,
top of file) and, for that provider, the **model you last used** with it — falling back to
its **first cached model** if you never have:

```sh
bz --list-models        # once: populate the default provider's model cache
bz "What is the capital of France?"   # zero-config: first-declared provider, last-used model
```

The cache also **learns from success**: any `2xx` records which model it used (that is the
last-used above), and if the cache couldn't yet place that model it is appended. So a single
`bz --provider X --model some-model "hi"` seeds the cache, and the next bare `bz "…"` defaults
to it — even for a provider whose `--list-models` endpoint is broken or you never ran. (It
records only the model *you* chose and the provider accepted; it never lists behind your back.)

The default is the first row *you* write, not the alphabetically-first — the built-in
providers brazen ships (anthropic, openai, …) sit below your rows, so they never hijack the
default. (`--model` and `--provider` are pure overrides; name a model and it routes by its
family, name a provider and it wins outright.)

More:

```sh
bz --login --provider openai-chatgpt --browser   # OAuth / Sign in with ChatGPT — no API key
bz --provider openai --model gpt-5 "explain monads in one line"
bz --list-providers                              # every provider bz can route to, and whether it can reach it
bz --list-models --provider anthropic            # discover the model ids a provider serves
bz --list-models --provider google --json        # …with provider-reported metadata (context_window etc.) where served
bz --json "..."                                  # canonical NDJSON event stream instead of text
bz --skill                                       # the fuller skill doc (worked examples) — richer than --help
```

## What works today

**Early implementation** — we design first (specifications in [`specs/`](specs/)), implement
second — but the core vertical slice is in and tested end-to-end:

- **Protocols** — OpenAI `chat/completions`, OpenAI `responses` (ChatGPT/Codex), Anthropic
  `messages`, Google `generative-ai`, Ollama (NDJSON), and the Claude Code CLI's
  `stream-json` (`specs/claude-code.md` — a subprocess, not HTTP), all normalized to one
  canonical request + `Event` stream. An executable single-source-of-truth test proves the
  five HTTP basic fixtures decode to the *same* `Vec<Event>`.
- **Providers** — OpenAI, Anthropic, Mistral, Google, local Ollama, and `claude-code`
  (the installed `claude` CLI driven as a pure model pass-through — an Anthropic-family
  path with **no API key**: `bz --provider claude-code -m sonnet "hi"` rides claude's own
  OAuth), added as config rows. Mistral is the severability floor: **one row, zero Rust**
  (it reuses the OpenAI dialect verbatim).
- **Auth** — API key (`x-api-key` or `Authorization: Bearer`, chosen by row data), keyless
  (`none`, for local Ollama), and OAuth2 / SSO with silent refresh, including **Sign in with
  ChatGPT** via `bz --login`.
- **Routing** — a model owns its provider by an exact alias or a prefix family (`claude-`,
  `gpt-`, …), so `--provider` is droppable for a model some row claims. Rows are a
  priority list in config order and the first owner wins. A model **no** row claims falls
  through to the first row whose **cached model list** matches it (`bz --model 5.5` skips
  the provider that has no 5.5) — a local read, never a probe, and a claim always outranks
  a cache match; missing/unknown providers surface as a clean config error.
- **Output** — streamed text (default), `--thinking`, `--json` (canonical NDJSON events), and
  `--raw` (lossless passthrough). `--raw` is **directional**: bare `--raw` (= `--raw=both`) is
  verbatim in **and** out; `--raw=in` sends the request verbatim but emits canonical events;
  `--raw=out` builds the request from `bz`'s ergonomics (prompt, `-f`, config, model cache,
  auth) and streams the provider's **exact wire bytes** back. A full sysexits-style exit table
  (0 / 64 / 66 / 69 / 70 / 77 / 78) and `BrokenPipe` -> 141.
- **Config** — one schema folded **flags > env > file > built-in defaults**, merged
  per-provider-row and per-field: declaring your own `[[provider]]` rows **patches** the
  built-in table, it never replaces it, so a row a later brazen ships still reaches you
  unedited. `--dump-config` prints that merge **minus the built-in floor** (dumping it
  would pin today's defaults in your file forever), secrets redacted — so a dump listing
  only your rows is the delta, not the effective table; `--list-providers` is the read of
  that effective table (name / protocol / auth / credential, in routing-priority order,
  built-ins included, zero round-trips). `--base-url <url>` / `BRAZEN_BASE_URL`
  points a run at a custom endpoint (local proxy, mock, vLLM, tenant gateway) — same
  provider, different host — with no temp config file.
- **Provider + model discovery**`bz --list-providers` (offline: the effective row table)
  and `bz --list-models` (one GET, over a lazy live-probe cache).
- **Ingress (masquerade)**`bz --serve` runs an OpenAI-compatible AND an
  Anthropic-compatible HTTP endpoint in front of ANY configured provider: a harness that only
  speaks `chat/completions` — or an Anthropic SDK POSTing `/v1/messages` — points its
  `base_url` at brazen and reaches Anthropic, Google, Ollama, OpenAI, … The path picks the
  dialect (`POST /v1/chat/completions` vs `POST /v1/messages`; no extra config);
  `GET /v1/models` serves the local model cache plus every row's `model_aliases` keys.
  `bz --in openai_chat` (or `anthropic_messages`) is the same capability as a one-shot POSIX
  filter: one dialect request on stdin, the dialect response on stdout (SSE if the request
  says `stream:true`). A
  fail-open replay stash carries opaque reasoning payloads (thinking signatures,
  `encrypted_content`) across turns the client's dialect cannot; a stash miss degrades the
  turn and is exposed as a named adaptation (`"brazen":{"adaptations":[…]}` / an SSE comment),
  or rejected via `[ingress] lossy_overrides`.
- **Token counting**`bz --count-tokens` returns a provider-accurate `input_tokens` for a
  request (one round-trip to the provider's count endpoint; Anthropic + Google, others decline
  with a config error rather than fabricate an estimate).
- **Transport** — a blocking, rustls-backed `ureq` client (no OpenSSL, no async runtime) with a
  single config-driven `--timeout` (the silence budget: abort when the upstream sends no bytes for
  N seconds, applied per phase — connect / headers / between chunks; not a wall-clock total).
  A row can also **hand its whole HTTP round-trip to a program you supply**
  (`specs/transport.md`) — because no header or config edit can make ureq/rustls look on the
  wire like a client built on another runtime, and brazen ships no impersonation profile:

  ```toml
  [[provider]]
  name = "my-adapter"
  base_url = "https://api.example.com"
  protocol = "anthropic_messages"
  auth = "api_key"
  api_header = { name = "x-api-key", scheme = "raw" }

    [provider.transport]
    program = "/opt/my-adapter/http-relay"    # yours; brazen never inspects it
  ```

  Your program gets one whole HTTP/1.1 request message on stdin (absolute-form target, brazen's
  headers verbatim, body framed by EOF) and answers with one whole response message on stdout,
  streamed. It owns the generated headers, framing, ALPN and TLS ClientHello; brazen keeps
  encode/auth/decode, the exit codes, `retry-after`, and the one-request-no-retry guarantee.
  Credentials ride the pipe only — never argv, env or a temp file. See
  [`examples/stdio_transport.rs`]examples/stdio_transport.rs for a working delegate in ~100
  lines. Rows without the block are untouched.

The pure library is held at **100% line coverage**; the data plane is smoke-tested live against
Anthropic and OpenAI. The full design lives in [`specs/architecture.md`]specs/architecture.md.

## Serving the masquerade (`--serve` / `--in`)

Point an OpenAI-only harness at any provider brazen speaks. Config:

```toml
[ingress]                      # the deliberate opt-in; the route path picks the codec
# listen = "127.0.0.1:4891"    # default; non-loopback REFUSES to start without `token`
# token  = "..."               # optional bearer; set -> requests without it get 401

[[provider]]
name = "anthropic"
model_aliases = { "gpt-4o" = "claude-sonnet-4-6" }   # routes AND substitutes
```

The built-in `openai` row also claims `gpt-4o` (by its `gpt-` prefix), but your row is
declared first and the **first owner in config order wins** — so this one line diverts
`gpt-4o` to Claude while `openai` keeps serving every other `gpt-…`. Order decides, and
nothing warns you when it decides against you: `--dump-config` prints **your** rows in
order (the built-in ones stay in `data/defaults.toml`, beneath), `bz --list-providers`
prints the merged table in the order routing reads it, and `--provider` overrides
routing outright.

Then `bz --serve` — the harness sets `base_url = "http://127.0.0.1:4891/v1"` and keeps
sending `gpt-4o`; brazen decodes the request at the edge, runs the ordinary pipeline
against the routed provider (row auth, model cache, everything), and re-encodes the
canonical events as `chat.completion(.chunk)` — the client's `stream` field picks SSE vs
one JSON body, independently of the upstream's own streaming. The same listener also
answers the **native Anthropic route**: an Anthropic SDK sets
`base_url = "http://127.0.0.1:4891"` and its `POST /v1/messages` selects the
`anthropic_messages` codec by path — no config change; both ecosystems are served at
once, and errors on that surface wear Anthropic's `{"type":"error",…}` envelope.
`GET /v1/models` answers from the local model cache plus every row's alias keys (refresh
with `bz --list-models`), always in OpenAI's list shape; upstream errors come back in the
client dialect's error envelope with the real status. SIGINT/SIGTERM stops the listener.
The same edge works without a listener as a POSIX filter — no `[ingress]` table needed:

```sh
echo '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}' | bz --in openai_chat
```

Multi-turn reasoning survives the dialect: opaque replay payloads (Anthropic thinking
signatures, `encrypted_content`, Google `thoughtSignature`) park in a fail-open XDG stash
(`$XDG_CACHE_HOME/brazen/replay/`) and are re-injected when the client echoes the turn
back. A stash miss never breaks the turn — brazen omits thinking for that replay turn and
says so (`"brazen":{"adaptations":["thinking_replay"]}` on aggregates, a `: brazen
adaptation=…` SSE comment on streams); set `[ingress] lossy_overrides = { thinking_replay
= "reject" }` to refuse instead. Full design: [`specs/ingress.md`](specs/ingress.md).

## Sign in with ChatGPT (OpenAI SSO)

`bz` can authenticate against a ChatGPT subscription using the same OAuth flow the Codex CLI uses.
There is no built-in OpenAI OAuth row (the core ships no vendor login policy — auth §7); paste this
row into your `config.toml` (`$XDG_CONFIG_HOME/brazen/config.toml` or `$BRAZEN_CONFIG`), then run
`bz --login --provider openai-chatgpt --browser`.

`bz --login --provider <id>` has two flows: the **default** is the headless **device flow** (it prints a
short code to enter on another device — needs no local browser, ideal over SSH); **`--browser`**
runs the loopback browser flow (it opens the authorize URL and captures the redirect) when the
provider's registered redirect is a loopback URL, as the ChatGPT row above is. Both end in one
stored credential. Run `bz --login --help` for the synopsis.

For the ChatGPT row's loopback redirect, use `--browser`:

```toml
[[provider]]
name       = "openai-chatgpt"
base_url   = "https://chatgpt.com/backend-api/codex"
protocol   = "openai_responses"
auth       = "oauth2"
api_header = { name = "Authorization", scheme = "bearer" }

[provider.oauth]
authorize_url    = "https://auth.openai.com/oauth/authorize"
token_url        = "https://auth.openai.com/oauth/token"
client_id        = "app_EMoamEEZ73f0CkXaXp7hrann"
scope            = "openid profile email offline_access api.connectors.read api.connectors.invoke"
redirect         = { host = "localhost", port = 1455, path = "/auth/callback" }
authorize_params = [["id_token_add_organizations", "true"], ["codex_cli_simplified_flow", "true"], ["originator", "codex_cli_rs"]]
account_header   = "ChatGPT-Account-ID"
beta_headers     = [["originator", "codex_cli_rs"]]

[provider.body_defaults]   # request-body fields this backend always needs
store  = false             # the Codex backend 400s unless store:false

# Canonical request-body fields this backend REJECTS — the inverse of body_defaults.
# brazen strips each before encoding, so a stray --temperature/--top-p/--max-tokens
# never reaches the wire (the Codex backend 400s on all three; see specs/config.md §4.1.1).
unsupported_body_keys = ["max_tokens", "temperature", "top_p"]
```

`[provider.body_defaults]` pins request-body fields a backend always requires so you don't
hand-craft them every call: `store = false` here makes
`bz --provider openai-chatgpt --model gpt-5.4 --system "…" "hi"` just work. (The Codex backend
also 400s unless `stream:true`, but that needs no pin — brazen's stream-native global default is
`true`, so the mandate is satisfied by default; a row that wanted to FORCE it could still pin
`body_defaults = { stream = true }`, and `--no-stream` against this backend honestly surfaces the
provider's 400 rather than silently reverting — see `specs/config.md` §4.2.)
A `body_defaults`
value is a per-row default at the lowest precedence — an explicit flag or request field beats it.
A row that *requires* a token cap (standard providers) sets `body_defaults = { max_tokens = … }`;
the Codex row deliberately pins none (its backend rejects `max_output_tokens`). See
[`specs/config.md` §4.1](specs/config.md).

`unsupported_body_keys` is the **inverse** of `body_defaults`: where `body_defaults` *fills* a
field the backend always needs, `unsupported_body_keys` *strips* a field the backend cannot accept.
The Codex backend 400s on `temperature`, `top_p`, and `max_output_tokens` with
`{"detail":"Unsupported parameter: …"}` (validated live 2026-06-17) — `bz` renames
`max_tokens`→`max_output_tokens` per the Responses spec, but this one backend forbids the standard
sampling/length params. With the three keys listed above, `bz` silently drops them before encoding
(naming **canonical** fields — `max_tokens`, not the wire `max_output_tokens` — so the rename stays
owned by the encoder), so passing `--max-tokens`/`--temperature`/`--top-p` (or the same keys in the
input JSON) against this row no longer 400s — the value is normalized away, the request streams
normally. brazen now supplies or normalizes every one of this backend's quirks (`instructions`,
`store:false`, `stream:true`, and the three rejected params); none is left for the operator to honor
by hand.

The flow, the verified Codex wire facts behind each field, and the empirical risks still to confirm
end-to-end (e.g. the data-plane request shape against the `codex` backend) are documented in
[`specs/auth.md` §10](specs/auth.md).

## OAuth providers in general (and a note on Anthropic)

The OAuth machinery is **vendor-blind** and reachable by config: a provider row with
`auth = "oauth2"` resolves like any other, given a `[provider.oauth]` block of operator-supplied
values. Nothing about any specific vendor is built in — brazen ships **no** OAuth row and bakes in
**no** vendor login policy ([`specs/auth.md` §7](specs/auth.md),
[architecture.md §13](specs/architecture.md) item 3). The fields the row understands, all data:

```toml
[[provider]]
name       = "my-oauth"        # an ALTERNATE row; claims no model_prefixes ⇒ reach it via --provider
base_url   = "https://…"
protocol   = "anthropic_messages"   # or openai_responses / openai_chat / …
generation_query = [["preview", "true"]] # optional generation-POST query; protocol still owns the path
auth       = "oauth2"          # silent refresh for brazen-owned credentials
api_header = { name = "Authorization", scheme = "bearer" }

[provider.oauth]
authorize_url   = "https://…/authorize"   # operator-supplied; nothing vendor-specific is compiled in
token_url       = "https://…/token"       # token exchange AND silent refresh
client_id       = "…"
scope           = "…"
beta_headers    = [["…", "…"]]            # auth-mode-DEPENDENT headers, sent ONLY under OAuth (auth §4)
system_preamble = "…"                     # text the request's system must LEAD with, prepended in resolution (auth §4.1)
```

A row may also carry an `ambient` block to **borrow** a credential another tool already wrote
(see [`specs/auth.md`](specs/auth.md) §5.5, *Ambient credential discovery*). A fresh borrowed
OAuth token authenticates the request; an expired one returns 77 and must be refreshed by its
owner — brazen never refreshes or copies foreign state into its store. `bz --login --provider <id>
--browser` runs the loopback flow for credentials brazen owns when the vendor's registered redirect
is a loopback URL. See
[`specs/auth.md`](specs/auth.md) §4–§7 for the full mechanism.

**Anthropic, specifically.** A Claude **subscription** OAuth token (an `sk-ant-oat01…` rather than
an `sk-ant-api…` key) is intended for Anthropic's own tools; Anthropic's terms restrict third-party
use of it. brazen does not configure that path for you, and we don't ship a recipe for it — the
generic `oauth2` mechanism above exists, but supplying the endpoints, client id, scope, and any
required system lead is **your** decision and **your** responsibility under those terms. A normal
`sk-ant-api…` key needs none of this; it just works through the built-in `anthropic` row.

## Principles

- **Stateless.** A pure `stdin → stdout` filter. The only disk it touches is XDG-standard
  config and credentials.
- **Single source of truth.** One canonical model; every protocol maps to and from it.
- **Deep, narrow interface.** Adding a provider / protocol / auth model is *data*, not a new
  core code path.
- **Strict POSIX.** Predictable streaming, exit codes, and signal handling.
- **100% test coverage**, enforced by the pre-commit hook. Code files capped at 300 lines.

## Layout

One crate, **`brazen`** — `cargo install brazen` builds the **`bz`** command (the `balls`->`bl`
pattern). The pure, network-free core is the library (`src/lib.rs`); the impure native shim — the
only `ureq`/`libc` user — is the `bz` bin (`src/main.rs`) and [`src/native/`](src/native/). Now that
it is one crate, [`tests/purity.rs`](tests/purity.rs) keeps the library network-free (it fails if a
library module imports `ureq`/`libc`/`std::net`).

- [`specs/`]specs/ — design specifications (living documents). Start at
  [`specs/README.md`]specs/README.md.
- [`SKILL.md`]SKILL.md — the agent-facing skill card `bz --skill` prints (compiled into
  the binary via `include_str!`). Read it directly, or drop it into an agent's context as-is.
- `Makefile` — build / test / coverage / lint targets (`make help`).
- `.githooks/pre-commit` — runs the full `make check` gate (fmt + clippy + 100% coverage)
  + the 300-line code-file cap, on commit and on `bl close`.
- `.githooks/reference-transaction` — whenever local `main` advances (the gate having
  passed), pushes it to origin and installs `bz` from that tip (`make install`, detached,
  from a worktree at the new ref). See [`AGENTS.md`]AGENTS.md "Close gates".
- [`.github/workflows/ci.yml`].github/workflows/ci.yml — the `make check` gate (run once,
  it is platform-independent) plus the portability matrix.
- [`.github/workflows/release-plz.yml`].github/workflows/release-plz.yml — release-plz versioning + publish;
  [`release-binaries.yml`].github/workflows/release-binaries.yml attaches prebuilt `bz` binaries.

## Embedding (shelling out vs. linking the library)

A harness has two ways to reach a provider through brazen, and they cost differently.

- **Shell out to `bz`** — the simple, fully-supported default. Each call is one process:
  a spawn, a fresh TLS handshake, a re-parse of the embedded defaults, a config-file read,
  and (on generation) a model-cache read — none of it reused between calls, because a
  subprocess has nowhere to keep a connection pool, so HTTP keep-alive is *structurally
  unavailable*. Against a multi-second generation this overhead is noise; it only matters at
  high call frequency with short completions. For concurrency you spawn N processes — brazen
  never fans out in-process (that is the harness's job to schedule and reap).
- **Link the library** (`brazen` as a crate dependency) — the sanctioned path when the
  per-call overhead actually bites. Construct one `HttpTransport` (`HttpTransport::new`) and
  **hold it across calls**: the lone `ureq::Agent` — connection pool and all — lives on that
  struct, so calling `generate` repeatedly through the same transport reuses the kept-alive
  connection, plus the already-parsed config and the warm model cache, all in-process. You
  consume the typed `Event` stream directly instead of parsing bytes.

There is **no call-mechanics daemon**, by design — improving call mechanics belongs to a
library embedder, not a long-running server inside `bz` (which would grow the stateful,
connection-owning surface the stateless model deliberately refuses). `bz --serve` is not
that daemon: it exists to translate *dialects* (the masquerade edge above), each request an
independent stateless pipeline, not to amortize per-call overhead. The library API is not yet
a stability contract (pin an exact version); see [`specs/architecture.md`](specs/architecture.md)
§12 for the full cost accounting.

## Build

```sh
make hooks   # one-time per clone: enable the pre-commit gate + main-advance hook
make check   # fmt + clippy + 100% coverage gate
make install # install this tree's bz into ~/.cargo/bin (the hook runs this on every merge to main)
make smoke   # live request per provider (real keys; skips providers whose key is unset)
```

## Live conformance suite

`make smoke` (`scripts/smoke.sh`) asks shallow questions — *did each provider with a key
return exit 0 + non-empty output on a good key (keeping `--json`/`--raw` output-mode shape),
and a correct non-zero exit + a non-empty surfaced provider error on a bad one?* It also probes the
**OAuth2 / SSO data plane** (bl-61a6): the real `AuthId::OAuth2` path via a stored `bz --login
--provider openai-chatgpt` cred, and the anthropic Max OAuth token (`sk-ant-oat01…`) through a bearer +
`anthropic-beta` oauth `--config` override — the token taken from `$ANTHROPIC_OAUTH_TOKEN`, else a
Claude Code login (`~/.claude/.credentials.json`) when `jq` is present; each SSO row SKIPs when its
credential is absent. The **live conformance suite**
(`tests/live_conformance.rs`) asks the real one: *does one canonical request
produce the same NORMALIZED event grammar across every provider this box can
authenticate to?* That is the whole point of brazen, so this is the test that
proves it end-to-end against live endpoints.

For the same proof **without** real keys — and so **in CI, on every platform** —
`tests/sim_conformance.rs` runs the real `bz` binary over the real HTTP transport
against a tiny loopback server (`FakeProvider`) that replays the golden wire
fixtures. It asserts the normalized grammar for all five providers and that an HTTP
`401` maps to exit 77, catching `ureq`-round-trip and status-mapping defects the
in-process `MockTransport` cannot. Not `#[ignore]`d — it runs in plain `cargo test`.

It is **opt-in** and never part of `make check`/CI: it is `#[ignore]`d (run only
under `--ignored`) **and** env-gated on `BRAZEN_LIVE`, and the whole `bz` crate is
excluded from the coverage gate — so it adds no coverage obligation. Run it:

```sh
BRAZEN_LIVE=1 \
  BRAZEN_LIVE_OLLAMA_MODEL=llama3.2 \   # point each row at a model this box has
  OPENAI_API_KEY=sk-… \                 # any provider key you want exercised
  cargo test -p brazen --test live_conformance -- --ignored --nocapture
```

**Providers are discovered at runtime.** For each row the harness looks for a
usable credential — a reachable keyless endpoint (Ollama), a stored `Cred` from
`bz --login --provider <id>` (e.g. OpenAI "Sign in with ChatGPT"), or one of the row's
API-key env vars — and **skips, never fails,** any provider with none, printing
the reason (no silent truncation). A box with zero credentials is a clean no-op.

**Per authed provider it asserts the canonical surface:** the streamed-text event
grammar over `--json` (`message_start` → text `content_start` → `text_delta` →
`usage` → `finish` → terminal `end`), the `--text` projection, system/instructions
(every request carries a non-empty `system`), a tool round-trip where the row
supports it (a `tool_use` `content_start` + streamed `json_delta` arguments), and
error mapping (a deliberately bad model → exit 69), and the `--raw` projection
(lossless passthrough → exit 0 + non-empty native wire bytes; `--raw` sends stdin
verbatim, so the harness feeds each row its *native*-shaped body — the canonical
messages shape for messages-dialects, `contents` for Google, `/api/chat` for
Ollama — declared per row as `raw: RawBody::…`, bl-5f6e). The raw path is
implemented and **offline-tested** (`run_cache.rs`, `seams_protocol.rs`, the sim
suite); the live harness now exercises it on the wire too.

**Adding a provider is one `Row`** in the `TABLE` (no code branches — quirks are
DATA): set `provider`/`model`/`model_env`, the `auth` discovery strategy
(`Keyless { probe }` or `Keyed { env }`), and the per-row knobs (`max_tokens:
None` to omit it, `store_false`, `tools`, the `raw` body shape). The harness drives the same assertions
for every row. (The codex backend's quirks — no `max_output_tokens`, explicit
`store:false`, required `instructions` — live entirely in its row as data,
validated live 2026-06-16.)

### OpenAI ChatGPT-SSO fuzz

Where the conformance suite drives the *one* happy path, `tests/live_fuzz_openai.rs`
(**bl-b72f**) drives a *wide range of request shapes* at the live `openai-chatgpt`
codex backend — surfacing where brazen mis-encodes or mis-maps errors. It reuses the
conformance harness leaves (`live_support/exec.rs`, `…/grammar.rs`) verbatim, so it is
the same black-box, `#[ignore]`d, `BRAZEN_LIVE`-gated, coverage-excluded shape, and
skips (printed reason) without a `bz --login --provider openai-chatgpt` cred. Two families:

- **Error-conformance matrix** — the fully-valid codex body *minus one required field*
  (no `instructions` / no `store` / `stream:false`) and the unsupported `gpt-5-codex`
  model. Each must 400 → exit 69 **and** surface the service's own message — `"Instructions
  are required"`, `"Store must be set to false"`, `"Stream must be set to true"`, `"…not
  supported…"` — asserted end-to-end (the codex `{"detail":…}` body reaching the
  `CanonicalError` is what **bl-5fe6** fixed; an empty message here is a regression). These
  400 before generation, so they are ~free.
- **Request-shape acceptance** — well-formed variations (unicode/emoji content,
  multi-turn role ordering, a tool round-trip) that must return exit 0 + the canonical
  grammar. These GENERATE, so they are behind a SECOND opt-in, `BRAZEN_LIVE_FUZZ_SPEND=1`,
  and the run prints what ran vs was capped.

```sh
BRAZEN_LIVE=1 BRAZEN_LIVE_FUZZ_SPEND=1 \
  cargo test -p brazen --test live_fuzz_openai -- --ignored --nocapture
```

(Raw-SSE golden capture for offline-decoder replay is intentionally *not* duplicated
here: the offline `response.*` decoder is already exhaustively fixture-tested in the
in-crate `tests::responses_fixtures` / `tests::responses_decode_errors` modules
(`src/tests/`, arch §9.8), so this suite is the request/error conformance the offline
path structurally cannot reach.)

### OpenAI ChatGPT-SSO OAuth circuit

`tests/live_oauth_openai.rs` (**bl-0272**) covers the *auth* half the fuzz suite
scoped but left out: it manipulates the stored credential to drive brazen's three
OAuth circuits (auth §6) against the live `openai-chatgpt` codex backend. Same
`#[ignore]`d, `BRAZEN_LIVE`-gated, coverage-excluded shape; skips (printed) without a
`bz --login --provider openai-chatgpt` cred.

- **`revoked-access` → 77** — a fresh-expiry cred with a bad *access* token: brazen
  skips refresh and sends the bad bearer → codex `401``from_http_status(401)=Auth`
  → exit **77** (the `401/403→Auth` mapping the fuzz suite's all-`400` matrix never
  reached live).
- **`revoked-refresh` → 77** — an expired cred with a bad *refresh* token: brazen
  refreshes → the token endpoint answers `invalid_grant` → exit **77**.
- **`silent-refresh` → 0** — an expired cred with the *real* refresh token: brazen
  mints a new access token over the token endpoint, persists it, and completes `200`;
  the test asserts the persisted token changed and its `expires_at` is in the future
  (the codex `jwt_exp` no-`expires_in` path, auth §10.3).

The two revoked circuits run on a **throwaway temp `XDG_DATA_HOME`** with synthetic
tokens, so the real refresh token is never sent — near-free (rejected before
generation). `silent-refresh` *must* send the real refresh token (OpenAI **rotates**
it on use), so it forces refresh on the **real** store and keeps brazen's persisted
result — a normal early refresh, leaving the credential valid — and is therefore both
token-costing and behind the second opt-in, `BRAZEN_LIVE_FUZZ_SPEND=1`.

```sh
BRAZEN_LIVE=1 BRAZEN_LIVE_FUZZ_SPEND=1 \
  cargo test -p brazen --test live_oauth_openai -- --ignored --nocapture
```

## Platform support

CI builds **and tests** the workspace on every target on a native runner — no
cross-emulation, so portability is proven by execution. The one exception is
`x86_64-apple-darwin`†, cross-built (build-verified) on the Apple-Silicon runner
because GitHub no longer offers a working Intel-mac runner.

| OS | x86_64 | aarch64 | static |
|---|---|---|---|
| Linux | `x86_64-unknown-linux-gnu` | `aarch64-unknown-linux-gnu` | `x86_64-unknown-linux-musl` |
| macOS | `x86_64-apple-darwin`| `aarch64-apple-darwin` ||
| Windows | `x86_64-pc-windows-msvc` | `aarch64-pc-windows-msvc` ||

† Built and shipped as a prebuilt binary, but not natively tested in CI (no
GitHub-hosted Intel-mac runner executes) — covered by the natively-tested
Apple-Silicon target plus the pure, portable codebase.

The matrix stays green because the native surface is deliberately tiny: **no system
C library, no OpenSSL, no `pkg-config`** to discover — that is what keeps
cross-compilation clean. (TLS is `rustls`; its `ring` crypto provider compiles and
*statically vendors* its own C/assembly, and brazen's own code has no build script,
C, or codegen.) There is no async runtime, and the `brazen`
lib has **zero platform-specific code** — the one OS branch (browser-launch argv)
lives behind the `BrowserLauncher` seam in the `bz` shim and is tested as data for
all three OSes. The single conditional dependency (`libc`, for restoring the Unix
SIGPIPE disposition) is `bz`-only and `[target.'cfg(unix)']`-gated.

### Windows secret-at-rest: documented limitation

Stored credentials are one JSON file per provider, written atomically (temp-file +
rename). On **Unix** the file is forced to mode **`0600`** at write time. On
**Windows** the file simply **inherits the user-profile directory ACL** — there is
**no DPAPI encryption and no explicit ACL hardening**. This is a deliberate v0.1
trade-off, *not* a code branch: adding DPAPI would pull in a Windows-specific
*system* C dependency and break the no-system-C, single-binary portability story
above. Treat
secrets on a shared or improperly-permissioned Windows profile as readable by other
accounts on that machine. (See architecture spec §6.4 / §10.)

## Releasing (publishing to crates.io)

brazen is **one crate** — `cargo install brazen` builds the `bz` command (the
`balls`→`bl` pattern) — and releasing is automated with
[release-plz](https://release-plz.dev) (`.github/workflows/release-plz.yml`):

- Every push to `main` refreshes a **release PR** that bumps the version in
  `Cargo.toml` and stages the next `CHANGELOG.md` entry. This repo's commit history
  is **not** conventional-commits, so `CHANGELOG.md` is **hand-curated** — you write
  the prose for each release (see the file's header); release-plz prepends the
  version bump. Pushing work never publishes — it only stages the next release.
- **Merging the release PR publishes — automatically, on a green build.** The merge
  triggers CI; when CI concludes successfully on `main`, the publish job (gated on
  that `workflow_run` success) ships the new version to crates.io, tags it
  `v<version>`, and cuts a GitHub Release. That Release triggers
  `release-binaries.yml`, which builds the `bz` binary for every supported target
  and attaches the archives — so users without a Rust toolchain can grab a prebuilt
  `bz` (`bz-<target>.tar.gz` / `.zip`) instead of `cargo install`.

So the pipeline is **hands-off and build-gated**: nothing reaches crates.io unless
CI is green, and merging the release PR is the only human step. (*Actions →
Release-plz → Run workflow* remains a manual override.) `CARGO_REGISTRY_TOKEN` is
the enable switch — until it's set, the publish job has nothing it can ship; setting
it arms auto-publish, and the **first** release (`0.0.1`, already staged on `main`)
ships on the next green build.

The `make check` gate (fmt + clippy + 100% coverage) plus the cross-platform matrix
are part of that CI, so a published version is always gated, tested code.

**One-time setup:**

- **Let the release PR be opened.** *Settings → Actions → General → Workflow
  permissions* → enable **"Allow GitHub Actions to create and approve pull
  requests."** Without this (or a `RELEASE_PLZ_TOKEN` below), the `release-pr` job
  fails with `403 GitHub Actions is not permitted to create or approve pull
  requests` — the default `GITHUB_TOKEN` can't open PRs until you flip this.
- **`CARGO_REGISTRY_TOKEN`** (*Settings → Secrets and variables → Actions*) — a
  crates.io API token (publish scope) owned by the crate owner. Required to publish.
- **`RELEASE_PLZ_TOKEN`** — recommended: a fine-grained PAT (or GitHub App token) so
  the release PR's commits re-trigger CI (and which also satisfies the PR-creation
  permission above); falls back to the default `GITHUB_TOKEN` when unset.

## License

MIT — see [`LICENSE`](LICENSE).