oc-codex-multi-auth 6.23.0 → 6.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +795 -729
  3. package/assets/icon.svg +7 -7
  4. package/assets/opencode-logo-ornate-dark.svg +18 -18
  5. package/assets/readme-hero.svg +31 -31
  6. package/config/README.md +197 -197
  7. package/config/minimal-opencode.json +14 -14
  8. package/config/opencode-legacy.json +1346 -1346
  9. package/config/opencode-modern.json +466 -466
  10. package/dist/index.d.ts +7 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +61 -57
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/accounts/persistence.d.ts +6 -6
  15. package/dist/lib/accounts/persistence.d.ts.map +1 -1
  16. package/dist/lib/accounts/persistence.js +60 -6
  17. package/dist/lib/accounts/persistence.js.map +1 -1
  18. package/dist/lib/accounts/recovery.d.ts +5 -1
  19. package/dist/lib/accounts/recovery.d.ts.map +1 -1
  20. package/dist/lib/accounts/recovery.js +26 -1
  21. package/dist/lib/accounts/recovery.js.map +1 -1
  22. package/dist/lib/accounts/state.d.ts +18 -0
  23. package/dist/lib/accounts/state.d.ts.map +1 -1
  24. package/dist/lib/accounts/state.js +26 -2
  25. package/dist/lib/accounts/state.js.map +1 -1
  26. package/dist/lib/accounts.d.ts +2 -0
  27. package/dist/lib/accounts.d.ts.map +1 -1
  28. package/dist/lib/accounts.js +7 -1
  29. package/dist/lib/accounts.js.map +1 -1
  30. package/dist/lib/auth/login-runner.d.ts.map +1 -1
  31. package/dist/lib/auth/login-runner.js +8 -0
  32. package/dist/lib/auth/login-runner.js.map +1 -1
  33. package/dist/lib/auth/plan-tier.d.ts.map +1 -1
  34. package/dist/lib/auth/plan-tier.js +2 -1
  35. package/dist/lib/auth/plan-tier.js.map +1 -1
  36. package/dist/lib/codex-usage.d.ts +84 -0
  37. package/dist/lib/codex-usage.d.ts.map +1 -1
  38. package/dist/lib/codex-usage.js +186 -5
  39. package/dist/lib/codex-usage.js.map +1 -1
  40. package/dist/lib/config.d.ts +16 -3
  41. package/dist/lib/config.d.ts.map +1 -1
  42. package/dist/lib/config.js +14 -0
  43. package/dist/lib/config.js.map +1 -1
  44. package/dist/lib/context-overflow.js +10 -10
  45. package/dist/lib/desktop-notifications.js +6 -6
  46. package/dist/lib/oauth-success.js +202 -202
  47. package/dist/lib/opencode-v2-provider.d.ts +2 -0
  48. package/dist/lib/opencode-v2-provider.d.ts.map +1 -0
  49. package/dist/lib/opencode-v2-provider.js +4 -0
  50. package/dist/lib/opencode-v2-provider.js.map +1 -0
  51. package/dist/lib/opencode-v2-rpc.d.ts +31 -0
  52. package/dist/lib/opencode-v2-rpc.d.ts.map +1 -0
  53. package/dist/lib/opencode-v2-rpc.js +17 -0
  54. package/dist/lib/opencode-v2-rpc.js.map +1 -0
  55. package/dist/lib/opencode-v2-status.d.ts +18 -0
  56. package/dist/lib/opencode-v2-status.d.ts.map +1 -0
  57. package/dist/lib/opencode-v2-status.js +98 -0
  58. package/dist/lib/opencode-v2-status.js.map +1 -0
  59. package/dist/lib/opencode-v2-tui.d.ts +4 -0
  60. package/dist/lib/opencode-v2-tui.d.ts.map +1 -0
  61. package/dist/lib/opencode-v2-tui.js +112 -0
  62. package/dist/lib/opencode-v2-tui.js.map +1 -0
  63. package/dist/lib/opencode-v2.d.ts +11 -0
  64. package/dist/lib/opencode-v2.d.ts.map +1 -0
  65. package/dist/lib/opencode-v2.js +235 -0
  66. package/dist/lib/opencode-v2.js.map +1 -0
  67. package/dist/lib/prompts/codex-opencode-bridge.js +67 -67
  68. package/dist/lib/prompts/codex.d.ts.map +1 -1
  69. package/dist/lib/prompts/codex.js +79 -75
  70. package/dist/lib/prompts/codex.js.map +1 -1
  71. package/dist/lib/quota-capacity.d.ts +4 -0
  72. package/dist/lib/quota-capacity.d.ts.map +1 -0
  73. package/dist/lib/quota-capacity.js +45 -0
  74. package/dist/lib/quota-capacity.js.map +1 -0
  75. package/dist/lib/quota-overview.d.ts +13 -27
  76. package/dist/lib/quota-overview.d.ts.map +1 -1
  77. package/dist/lib/quota-overview.js +59 -99
  78. package/dist/lib/quota-overview.js.map +1 -1
  79. package/dist/lib/quota-recovery.d.ts +5 -0
  80. package/dist/lib/quota-recovery.d.ts.map +1 -0
  81. package/dist/lib/quota-recovery.js +66 -0
  82. package/dist/lib/quota-recovery.js.map +1 -0
  83. package/dist/lib/request/fetch-helpers.d.ts +1 -1
  84. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  85. package/dist/lib/request/fetch-helpers.js +1 -1
  86. package/dist/lib/request/fetch-helpers.js.map +1 -1
  87. package/dist/lib/schemas.d.ts +14 -1
  88. package/dist/lib/schemas.d.ts.map +1 -1
  89. package/dist/lib/schemas.js +7 -2
  90. package/dist/lib/schemas.js.map +1 -1
  91. package/dist/lib/storage/load-save.d.ts.map +1 -1
  92. package/dist/lib/storage/load-save.js +22 -20
  93. package/dist/lib/storage/load-save.js.map +1 -1
  94. package/dist/lib/storage/state.d.ts +8 -0
  95. package/dist/lib/storage/state.d.ts.map +1 -1
  96. package/dist/lib/storage/state.js +47 -20
  97. package/dist/lib/storage/state.js.map +1 -1
  98. package/dist/lib/tools/codex-limits.d.ts.map +1 -1
  99. package/dist/lib/tools/codex-limits.js +49 -3
  100. package/dist/lib/tools/codex-limits.js.map +1 -1
  101. package/dist/lib/tui-quota-cache.d.ts +8 -0
  102. package/dist/lib/tui-quota-cache.d.ts.map +1 -1
  103. package/dist/lib/tui-quota-cache.js +4 -1
  104. package/dist/lib/tui-quota-cache.js.map +1 -1
  105. package/dist/lib/tui-quota-overview.d.ts.map +1 -1
  106. package/dist/lib/tui-quota-overview.js +15 -2
  107. package/dist/lib/tui-quota-overview.js.map +1 -1
  108. package/dist/lib/tui-status-slot.d.ts +3 -0
  109. package/dist/lib/tui-status-slot.d.ts.map +1 -0
  110. package/dist/lib/tui-status-slot.js +27 -0
  111. package/dist/lib/tui-status-slot.js.map +1 -0
  112. package/dist/lib/tui-status.d.ts +1 -0
  113. package/dist/lib/tui-status.d.ts.map +1 -1
  114. package/dist/lib/tui-status.js +1 -0
  115. package/dist/lib/tui-status.js.map +1 -1
  116. package/dist/tui.d.ts +10 -3
  117. package/dist/tui.d.ts.map +1 -1
  118. package/dist/tui.js +22 -1
  119. package/dist/tui.js.map +1 -1
  120. package/package.json +157 -155
  121. package/scripts/audit-dev-allowlist.js +114 -114
  122. package/scripts/clean-dist.js +27 -27
  123. package/scripts/copy-oauth-success.js +47 -47
  124. package/scripts/install-oc-codex-multi-auth-core.js +2661 -2016
  125. package/scripts/install-oc-codex-multi-auth.js +37 -37
  126. package/scripts/test-all-models.sh +260 -260
  127. package/scripts/validate-model-map.sh +97 -97
package/README.md CHANGED
@@ -1,729 +1,795 @@
1
- # oc-codex-multi-auth: ChatGPT OAuth and multi-account Codex routing for OpenCode
2
-
3
- [![npm version](https://img.shields.io/npm/v/oc-codex-multi-auth.svg)](https://www.npmjs.com/package/oc-codex-multi-auth)
4
- [![npm downloads](https://img.shields.io/npm/dw/oc-codex-multi-auth.svg)](https://www.npmjs.com/package/oc-codex-multi-auth)
5
- [![CI](https://github.com/ndycode/oc-codex-multi-auth/actions/workflows/ci.yml/badge.svg)](https://github.com/ndycode/oc-codex-multi-auth/actions/workflows/ci.yml)
6
- [![MIT license](https://img.shields.io/npm/l/oc-codex-multi-auth.svg)](LICENSE)
7
-
8
- `oc-codex-multi-auth` is an OpenCode plugin for ChatGPT Plus/Pro OAuth, Codex and GPT-5/GPT-6 model routing (including GPT-6 Astra/Sol/Luna, the Daybreak cyber tiers, and GPT-5.6 Sol/Terra/Luna), multi-account rotation, account switching, health checks, quota visibility, diagnostics, and recovery tools. It installs the OpenCode provider/TUI configuration, registers a 24-tool `codex-*` command toolkit, and routes OpenCode OpenAI SDK requests through the ChatGPT-backed Codex flow with local account state.
9
-
10
- Use it when you want OpenCode to run Codex-style coding workflows from your own ChatGPT subscription while keeping accounts visible, switchable, health-checked, and recoverable from the terminal.
11
-
12
- <img width="1227" height="702" alt="oc-codex-multi-auth OpenCode plugin dashboard for ChatGPT OAuth, Codex routing, and multi-account health" src="https://github.com/user-attachments/assets/b796eb2f-282e-468a-ba6a-acadf09d731b" />
13
-
14
-
15
-
16
- > [!NOTE]
17
- > This package is the supported OpenCode plugin line.
18
- > Older package names and config entries should be replaced with `oc-codex-multi-auth`.
19
-
20
- ## What You Get
21
-
22
- - OpenCode plugin support for ChatGPT Plus/Pro OAuth and Codex/GPT-5 coding workflows
23
- - GPT-6 Astra, GPT-6 Sol/Luna, GPT-5.6 Sol/Terra/Luna, and the Daybreak Blue/Red cyber tiers on the responses-lite request path, plus GPT-5.5, GPT-5.5 Fast, GPT-5.4 Nano, and GPT-5.1 templates
24
- - Routing for the Daybreak-gated cyber tiers (`gpt-daybreak-blue-latest`, `gpt-daybreak-red-latest`, `gpt-5.6-cyber`), deliberately kept out of the shipped templates since they need program approval
25
- - Compact modern OpenCode config with 10 base families and 53 variant presets; explicit legacy selector IDs when needed
26
- - Stateless Codex-compatible request handling with `store: false` and `reasoning.encrypted_content`
27
- - Multi-account rotation with hybrid health scoring, cooldowns, automatic token refresh, and failover
28
- - Explicit saved-account listing, account switching, labeling, tagging, notes, health checks, and diagnostics
29
- - Per-project account storage under `~/.opencode/projects/<project-key>/...`
30
- - Guided setup, doctor, next-action, dashboard, export/import, keychain, and troubleshooting tools
31
- - Optional OS-native keychain backend for stored account pools
32
- - TUI prompt quota status and quota detail views for OpenCode sessions
33
- - Request logging, runtime metrics, routing visibility, and redacted diagnostic snapshots for debugging
34
- - Stable docs for install, configuration, troubleshooting, privacy, architecture, testing, and release history
35
-
36
- ---
37
-
38
- ## Why Developers Use It
39
-
40
- `oc-codex-multi-auth` makes OpenCode's ChatGPT OAuth state understandable and operable. Instead of treating auth as one opaque provider file, you get a local account pool, deterministic account switching, health-aware request selection, visible quota status, JSON-friendly diagnostics, and safe repair commands for stale or damaged state. The plugin is designed for personal development workflows: credentials stay local, OpenCode keeps owning the host runtime, and the plugin only handles the OAuth-backed Codex routing layer it is installed for.
41
-
42
- ---
43
-
44
- ## Current Architecture At A Glance
45
-
46
- `oc-codex-multi-auth` ships four user-visible surfaces:
47
-
48
- | Surface | Purpose |
49
- | --- | --- |
50
- | `oc-codex-multi-auth` | npm CLI; explicit install modes manage OpenCode provider/TUI config, while `update` only clears the managed package cache. Also runs standalone commands: `doctor`, `status`, `list`, `limits`, `dashboard`, `health`, `diag`, `warm` |
51
- | OpenCode plugin entry (`index.ts`) | auth loader, OAuth login modes, provider fetch pipeline, account rotation, retry/failover, and `codex-*` tool registry |
52
- | OpenCode TUI plugin (`tui.ts`) | prompt quota status, quota details, shared quota cache, and active-account-aware display |
53
- | 24 `codex-*` tools | setup, help, status, list, switch, warm, limits, health, metrics, doctor, dashboard, pool, backup, keychain, diagnostics, and repair actions |
54
-
55
- The plugin does not replace OpenCode. OpenCode remains the host; this package installs provider/TUI config and supplies the OAuth-backed Codex request pipeline that OpenCode calls.
56
-
57
- ---
58
-
59
- <details open>
60
- <summary><b>Terms and Usage Notice</b></summary>
61
-
62
- > [!CAUTION]
63
- > This project is for personal development use with your own ChatGPT Plus/Pro subscription.
64
- >
65
- > By using this plugin, you acknowledge:
66
- > - This is an independent open-source project, not an official OpenAI product
67
- > - It is not intended for commercial resale, shared multi-user access, or production services
68
- > - You are responsible for your own usage and policy compliance
69
- > - For production/commercial workloads, use the OpenAI Platform API
70
-
71
- </details>
72
-
73
- ---
74
-
75
- ## Installation
76
-
77
- <details open>
78
- <summary><b>For Humans</b></summary>
79
-
80
- ### Option A: Standard install (preserve provider config)
81
-
82
- Default mode registers the OpenCode and TUI plugin entries without changing `provider.openai`.
83
-
84
- ```bash
85
- npx -y oc-codex-multi-auth@latest
86
- ```
87
-
88
- Installer flags:
89
-
90
- | Flag | Effect |
91
- | --- | --- |
92
- | (default) / `--plugin-only` | Register the plugin and TUI integration without changing `provider.openai` |
93
- | `--modern` | Install compact modern catalog: 10 bases, 53 variants |
94
- | `--full` | Compact bases plus 53 explicit selector IDs |
95
- | `--legacy` | Explicit-only catalog for older OpenCode |
96
- | `--dry-run` | Show changed config paths without values or writes |
97
- | `--no-cache-clear` | Skip clearing the OpenCode plugin cache |
98
-
99
- ### Option B: Compact modern model catalog
100
-
101
- ```bash
102
- npx -y oc-codex-multi-auth@latest --modern
103
- ```
104
-
105
- Use this when OpenCode does not already provide the OAuth model definitions or you want the shipped variant presets.
106
-
107
- ### Option C: Full explicit model catalog
108
-
109
- Use this when you want direct selector IDs such as `openai/gpt-5.5-medium` in addition to OpenCode variants.
110
-
111
- ```bash
112
- npx -y oc-codex-multi-auth@latest --full
113
- ```
114
-
115
- ### Updating without config changes
116
-
117
- ```bash
118
- npx -y oc-codex-multi-auth@latest update
119
- ```
120
-
121
- `update` clears only the OpenCode-managed package cache. It does not read or write `opencode.json` or `tui.json`; restart OpenCode afterward to install the current package.
122
-
123
- ### Option D: Verify wiring
124
-
125
- ```bash
126
- opencode --version
127
- opencode debug config
128
- opencode auth login
129
- ```
130
-
131
- The default installer only registers the plugin entry in `~/.config/opencode/opencode.json`, enables the TUI status plugin in `~/.config/opencode/tui.json`, and clears the cached plugin copy. Catalog modes also merge their selected `provider.openai` definitions. Changed config files are backed up before writing.
132
-
133
- ### Running from a local checkout
134
-
135
- You can point OpenCode at a clone of this repository instead of the published
136
- package, which is how the project is developed:
137
-
138
- ```json
139
- { "plugin": ["file:///path/to/oc-codex-multi-auth"] }
140
- ```
141
-
142
- The installer leaves that entry exactly as written. It identifies an entry by
143
- the package it resolves to rather than by how the path is spelled, so a clone
144
- is recognized under any directory name, whether it is referenced as a path, a
145
- `file://` URL, or its build output. `oc-codex-multi-auth` is appended only when
146
- no entry in the config resolves to this plugin, so the installer never replaces
147
- a checkout with the published package or registers both at once.
148
-
149
- Stale references the installer itself produced are still retired: the bare
150
- package name repeated, version-pinned entries, the former
151
- `oc-chatgpt-multi-auth` name, and paths into `node_modules` or the OpenCode
152
- package cache.
153
-
154
- ### Standalone CLI (no agent / no token cost)
155
-
156
- ```bash
157
- oc-codex-multi-auth status
158
- oc-codex-multi-auth list
159
- oc-codex-multi-auth warm
160
- oc-codex-multi-auth doctor
161
- oc-codex-multi-auth health
162
- oc-codex-multi-auth limits
163
- oc-codex-multi-auth dashboard
164
- oc-codex-multi-auth diag
165
- # or: npx -y oc-codex-multi-auth@latest warm --json
166
- ```
167
-
168
- </details>
169
-
170
- <details>
171
- <summary><b>For LLM Agents</b></summary>
172
-
173
- ### Step-by-step
174
-
175
- 1. Register the plugin without changing `provider.openai`:
176
- - `npx -y oc-codex-multi-auth@latest`
177
- - Use `--modern` only when the shipped compact model catalog is required.
178
- 2. Run first login flow:
179
- - `opencode auth login`
180
- 3. Validate config:
181
- - `opencode debug config`
182
- 4. Run a smoke request (after OpenCode or `--modern` supplies the selector):
183
- - `opencode run "Explain this repository" --model=openai/gpt-5.5 --variant=medium`
184
- 5. Inspect plugin state with the OpenCode tool surface:
185
- - `codex-status`
186
- - `codex-doctor`
187
- - `codex-list`
188
-
189
- ### Verification
190
-
191
- ```bash
192
- opencode debug config
193
- opencode auth login
194
- opencode run "ping" --model=openai/gpt-5.5 --variant=medium
195
- ```
196
-
197
- </details>
198
-
199
- ---
200
-
201
- ## Quick Start
202
-
203
- Install and sign in:
204
-
205
- ```bash
206
- npx -y oc-codex-multi-auth@latest
207
- opencode auth login
208
- ```
209
-
210
- Run a prompt with compact modern selectors:
211
-
212
- ```bash
213
- opencode run "Summarize the failing test and suggest a fix" --model=openai/gpt-5.5 --variant=medium
214
- opencode run "Summarize the failing test and suggest a fix" --model=openai/gpt-5.5-fast --variant=medium
215
- opencode run "Plan the refactor" --model=openai/gpt-6-astra --variant=high
216
- ```
217
-
218
- Use Codex-focused routing:
219
-
220
- ```bash
221
- opencode run "Refactor the retry logic and update the tests" --model=openai/gpt-6-sol --variant=high
222
- ```
223
-
224
- If browser launch is blocked, use the alternate login paths in [docs/getting-started.md](docs/getting-started.md#remote-or-headless-login).
225
-
226
- ---
227
-
228
- ## Command Toolkit
229
-
230
- ### Start here
231
-
232
- | Tool | What it answers |
233
- | --- | --- |
234
- | `codex-setup` | How do I finish first-run setup safely? |
235
- | `codex-help` | Which plugin commands exist and what do they do? |
236
- | `codex-doctor` | What is wrong with auth, config, storage, or routing? |
237
- | `codex-next` | What should I do next to get unstuck? |
238
-
239
- ### Daily use
240
-
241
- | Tool | What it answers |
242
- | --- | --- |
243
- | `codex-list` | Which accounts are saved and which one is active? |
244
- | `codex-switch` | How do I move to a different saved account? |
245
- | `codex-warm` | How do I start every account's usage window now (stagger quota cooldowns)? |
246
- | `codex-status` | Which account, model family, and routing state are active? |
247
- | `codex-limits` | What quota or rate-limit state is visible now? |
248
- | `codex-reset` | Do I have a banked rate-limit reset credit, and how do I redeem it? |
249
- | `codex-dashboard` | What does a read-only snapshot of account eligibility, retry budgets, and refresh queue health show? |
250
- | `codex-pool` | Which accounts are preferred for each model, and how do I change them? |
251
-
252
- Most of these also run as a **direct CLI** with no agent or model involvement, so there is no token cost. Examples are `oc-codex-multi-auth warm`, `oc-codex-multi-auth status`, or `npx -y oc-codex-multi-auth@latest warm`. Use `oc-codex-multi-auth warm` to open every enabled account's usage window at the start of a session and stagger the rolling quota cooldowns. Add `--json` for scriptable output.
253
-
254
- ### Account management
255
-
256
- | Tool | What it answers |
257
- | --- | --- |
258
- | `codex-label` | How do I name an account? |
259
- | `codex-tag` | How do I group accounts with tags? |
260
- | `codex-note` | How do I attach a private note to an account? |
261
- | `codex-remove` | How do I remove a saved account safely? |
262
- | `codex-refresh` | How do I refresh the OAuth tokens of every saved account to verify they are still valid? |
263
-
264
- ### Diagnostics and backup
265
-
266
- | Tool | What it answers |
267
- | --- | --- |
268
- | `codex-health` | Which accounts look healthy, limited, or disabled? |
269
- | `codex-metrics` | What runtime counters and request metrics are visible? |
270
- | `codex-diag` | Can I export a redacted diagnostic snapshot? |
271
- | `codex-diff` | What changed between account/config snapshots? |
272
- | `codex-export` | How do I back up account storage? |
273
- | `codex-import` | How do I restore accounts with a dry-run first? |
274
- | `codex-keychain` | Which credential backend is active and can I migrate it? |
275
-
276
- ### Reliability behavior
277
-
278
- - stateless request handling forces `store: false`
279
- - `reasoning.encrypted_content` is preserved for multi-turn continuity
280
- - GPT-6 Astra/Sol/Luna, the Daybreak tiers and the GPT-5.6 tiers use the responses-lite request shape and default client identity `opencode`; other models default to `codex_cli_rs`
281
- - account rotation is health-aware (`rotationStrategy` default `hybrid`) and avoids repeatedly selecting cooling accounts
282
- - The quota guard checks each enabled account at a bounded interval (30 minutes by default). When it finds a fully spent 5-hour or weekly subscription quota, rotation skips that account until its reported reset instead of drawing from paid Credits. `codex-limits` applies the same guard immediately when run manually. After running standalone `limits`, restart an already-running OpenCode instance or wait for its next quota poll to reload the updated account state.
283
- - same-host OpenCode processes sharing an account file serialize refresh-token exchange and commit so one current single-use token is exchanged once
284
- - 5xx bursts, network failures, and quota responses penalize account health
285
- - token refresh is queued to avoid refresh races
286
- - unsupported-model handling is strict by default, with opt-in fallback controls
287
- - TUI quota status follows the account/workspace used by the latest request
288
- - Business workspace memberships and Personal accounts keep separate usage and quota windows. Business members sharing one workspace are distinguished by their member/seat identity, so their usage is not collapsed into one row.
289
- - An account identifies itself by its own ChatGPT email and the last 6 characters of its account id, with the email masked when `maskEmail` is on. An account id names a ChatGPT workspace and every member of a Business workspace shares it, so a record that also carries a member/seat id prints a short excerpt of that as `seat:`. The excerpt is a 6-character tail where that is enough to tell the listed accounts apart. Where it is not, it widens, moves to where those ids first differ, or joins two short excerpts with `..` - real member ids are long, share a leading prefix, and differ in more than one place, so a tail alone often cannot separate them. Where no excerpt that short can separate them, `seat:` is instead an **opaque hash prefix** such as `719f78b5`: it identifies the seat and stays stable, but it is not part of the member id and cannot be matched against anything ChatGPT shows you. Whichever form it takes, two distinct seats never render the same `seat:` and a `seat:` is never longer than 32 characters. A record with no member id renders exactly as before. The OAuth id_token also lists the API-platform organizations the login belongs to; those are not ChatGPT workspaces and are never used to name an account, so logging in clears a label left behind by one. A label you set with `codex-label` is always kept.
290
- - The ChatGPT plan (`Free`, `Plus`, `Pro`, `Business`, `Business Premium`, `Enterprise`) is read from the access token, refreshed on every token refresh, and shown by `codex-list` and `codex-status`. `codex-limits` and the TUI read the plan live from the usage endpoint and name it the same way. An unrecognized plan is reported verbatim rather than renamed.
291
-
292
- ---
293
-
294
- ## Storage Paths
295
-
296
- | File | Default path |
297
- | --- | --- |
298
- | OpenCode config | `~/.config/opencode/opencode.json` |
299
- | OpenCode TUI config | `~/.config/opencode/tui.json` |
300
- | OpenCode auth tokens | `~/.opencode/auth/openai.json` |
301
- | Plugin config | `~/.opencode/openai-codex-auth-config.json` |
302
- | Global account storage | `~/.opencode/oc-codex-multi-auth-accounts.json` |
303
- | Per-project accounts | `~/.opencode/projects/<project-key>/oc-codex-multi-auth-accounts.json` |
304
- | Flagged accounts | `oc-codex-multi-auth-flagged-accounts.json`, written beside the active accounts file (per-project path when `perProjectAccounts` is on) |
305
- | Backups | `~/.opencode/backups/` or `~/.opencode/projects/<project-key>/backups/` |
306
- | Logs | `~/.opencode/logs/codex-plugin/` |
307
- | TUI quota cache | OpenCode state dir plus `oc-codex-multi-auth-tui-quota.json`, else `$OPENCODE_STATE_DIR/oc-codex-multi-auth-tui-quota.json` or `~/.local/state/opencode/oc-codex-multi-auth-tui-quota.json` |
308
- | TUI pool quota cache | `oc-codex-multi-auth-tui-quota-overview.json`, in the same directory, written only when `quotaStatus.mode` is `overview` |
309
-
310
- Per-project storage is enabled by default. The plugin walks up from the current directory to find a project root, then stores account pools under the project-specific key. If no project root is found, it falls back to global storage.
311
-
312
- ---
313
-
314
- ## Configuration
315
-
316
- Primary config files:
317
-
318
- - `~/.config/opencode/opencode.json`
319
- - `~/.config/opencode/tui.json`
320
- - `~/.opencode/openai-codex-auth-config.json`
321
-
322
- ### Quota percentage display
323
-
324
- Every quota percentage a person reads is worded as the headroom still left,
325
- which is how Codex itself reports a quota:
326
-
327
- ```text
328
- 5h limit: 88% left # codex-limits, quota details dialog
329
- 5h 88% # TUI prompt status line
330
- ```
331
-
332
- Set `quotaDisplay` to `"used"` to report consumption instead:
333
-
334
- ```json
335
- {
336
- "quotaDisplay": "used"
337
- }
338
- ```
339
-
340
- ```text
341
- 5h limit: 12% used
342
- 5h 12%
343
- ```
344
-
345
- Add it to `~/.opencode/openai-codex-auth-config.json`, or set
346
- `CODEX_AUTH_QUOTA_DISPLAY=used`, then quit and restart OpenCode. The setting
347
- covers the TUI prompt status line and quota details dialog, `codex-limits`,
348
- the standalone `limits` CLI, the interactive account check, and the macOS
349
- quota notifications below.
350
-
351
- It changes wording only. Quota exhaustion, rotation blocks, notification
352
- thresholds, and the status line's warning/danger colouring all stay keyed on
353
- the percentage remaining, so a nearly spent account still colours red while
354
- reading `95%`. The `usedPercent` and `leftPercent` fields in `--json` /
355
- `format="json"` output are unaffected.
356
-
357
- ### Pool-wide quota status
358
-
359
- The prompt status line describes the account that served the last request. On
360
- a pool of several accounts that account changes as rotation moves, so the line
361
- changes identity under you and no single glance shows where the pool stands.
362
-
363
- Set `quotaStatus.mode` to `"overview"` to describe the whole pool on one
364
- constant line instead, which only changes when a quota does:
365
-
366
- ```json
367
- {
368
- "quotaStatus": {
369
- "mode": "overview",
370
- "layout": "accounts",
371
- "accountNames": "number",
372
- "order": "number",
373
- "multipliers": false,
374
- "allotment": false,
375
- "resetTimes": "low",
376
- "resetCredits": false,
377
- "recovery": false,
378
- "rows": 1,
379
- "showFor": "always"
380
- }
381
- }
382
- ```
383
-
384
- ```text
385
- 24%: #1 13%, #2 0% 3d, #3 12% # defaults
386
- 24%: 3 accounts # "layout": "count"
387
- 24%: #1 5x 13%, #2 20x 0% 3d, #3 1x 12% # "multipliers": true
388
- 24%: #1 5x 13%, #2 20x 0% 3d 1r, #3 1x 12% # + "resetCredits": true
389
- 24%: 3 accounts, +12% in 3d # "layout": "count", "recovery": true
390
- 24% of 26x: #1 13%, #2 0% 3d, #3 12% # "allotment": true
391
- 24%: #1 13% 2d, #2 0% 3d, #3 12% 5d # "resetTimes": "always"
392
- 24%: 13%, 0% 3d, 12% # "accountNames": "none"
393
- 24%: damian 13%, work 0% 3d, spare 12% # "accountNames": "label"
394
- 24%: #2 0% 3d, #1 13%, #3 12% # "order": "most-used"
395
- 24%: 13% 2d, 0% 3d 4d 5d # "layout": "aggregate"
396
- ```
397
-
398
- Each switch is independent, so any combination works. `#N` is the account
399
- number `codex-list` and `codex-switch` use. An account is shown by whichever
400
- of its windows has the least headroom, since that is the one that stops a
401
- request; a reset time (`3d`) is added for an account at or below 25% by
402
- default, for every account under `"resetTimes": "always"`, and for none under
403
- `"never"`. `1r` counts banked rate-limit resets that account can redeem now.
404
-
405
- `order` takes `number`, `most-used`, `least-used`, `renewing-earliest`, or
406
- `renewing-latest`. `layout: "aggregate"` prints a shared percentage once and
407
- keeps only what differs after it, which matters most on a pool where several
408
- accounts are spent.
409
-
410
- The leading figure is the pool total, and it is a **weighted** mean: a Pro seat
411
- spent to 50% has given up twenty times the capacity a Business Standard seat
412
- does at 50%, so an unweighted average would describe a pool nobody has. The
413
- per-plan ratios are listed in [docs/plan-allotments.md](docs/plan-allotments.md),
414
- and `"allotment": true` shows what they add up to.
415
-
416
- `mode` also accepts a list, and the line then alternates between those screens
417
- every `rotateMs` (default 5000). The third screen, `resets`, appears only once
418
- every account is spent and lists the banked reset credits worth redeeming,
419
- latest reset first - redeeming one on an account that renews by itself tomorrow
420
- throws it away:
421
-
422
- ```json
423
- {
424
- "quotaStatus": { "mode": ["overview", "resets"] }
425
- }
426
- ```
427
-
428
- ```text
429
- Free resets: 6d 1r damian@nowaker.net, 4d 2r work@example.com
430
- ```
431
-
432
- `"rows"` (1-4, default 1) is a ceiling rather than a height: a rendering that
433
- fits on one row still takes one, so `"rows": 2` costs nothing on a wide terminal
434
- and buys the whole line back on a narrow one, where the agent/model label beside
435
- it has already wrapped to two rows anyway. `"showFor": "codex-models"` hides the
436
- line unless the session is running a model this plugin routes.
437
-
438
- Percentages follow `quotaDisplay`, so the first line above reads
439
- `76%: #1 87%, #2 100% 3d, #3 88%` under `"used"`. The whole setting is
440
- presentation only: rotation, quota blocks and the line's warning/danger
441
- colouring stay keyed on the headroom remaining.
442
-
443
- Add the object to `~/.opencode/openai-codex-auth-config.json`. It is read from
444
- that file only - a display preference belongs to a person, not to a shell - and
445
- the status line re-reads it while sessions are open, so an edit takes effect
446
- within a couple of seconds without a restart.
447
-
448
- ### Desktop quota notifications
449
-
450
- Quota notifications are an optional macOS-only feature. Separately, the quota
451
- guard checks enabled accounts every 30 minutes by default and prevents rotation
452
- from drawing paid Credits after the backend reports a fully spent subscription
453
- window. While notifications are enabled, the same poll also alerts through
454
- Notification Center when the best remaining 5-hour or weekly pool quota crosses
455
- 25%, 10%, or 0%. Alerts are disabled by default; the quota guard is enabled.
456
-
457
- Each line reports the enabled account with the most headroom in that window,
458
- together with that same account's reset time, so the pair always describes a
459
- quota that one account actually has. When a different account recovers sooner,
460
- that reset is appended under its own label rather than folded into the first
461
- one. Windows a plan has switched off are skipped rather than counted as full.
462
- Account identities are omitted for readability and lock-screen privacy:
463
-
464
- ```text
465
- 5h: 10% | resets 02:00 | another account resets 22:30
466
- Weekly: 72% | resets 22:30 on Aug 30
467
- ```
468
-
469
- The percentage follows `quotaDisplay`, so the same two lines read `90%` and
470
- `28%` under `"used"`. `thresholds` are always remaining-percent values
471
- regardless.
472
-
473
- ```json
474
- {
475
- "quotaNotifications": {
476
- "enabled": true,
477
- "autoProtectCredits": true,
478
- "intervalMs": 1800000,
479
- "notifyEveryCheck": false,
480
- "thresholds": [25, 10, 0]
481
- }
482
- }
483
- ```
484
-
485
- Add the object above to `~/.opencode/openai-codex-auth-config.json`, or set
486
- `CODEX_AUTH_QUOTA_NOTIFICATIONS=1`, then quit and restart OpenCode. The minimum
487
- interval is 30 seconds. If macOS blocks the alert, allow notifications for
488
- the process shown in **System Settings > Notifications**. The setting is
489
- ignored on Windows and Linux.
490
-
491
- Set `"notifyEveryCheck": true` to show the aggregate quota notification after
492
- every successful poll interval instead of only when a configured threshold is
493
- crossed. Set `"thresholds": []` to turn threshold alerts off entirely; pair it
494
- with `"notifyEveryCheck": true` to keep receiving alerts. The quota guard keeps
495
- polling unless `"autoProtectCredits": false` is set.
496
-
497
- Delivery state lives beside the accounts file the alerts are computed from, so
498
- OpenCode processes working in the same account scope show only one alert per
499
- interval. With the default `perProjectAccounts`, that scope is one project:
500
- two projects have separate account pools and therefore alert independently.
501
-
502
- ### Route models to preferred accounts
503
-
504
- Use `modelAccountPools` to assign one or more preferred ChatGPT accounts or Business seats to a
505
- model. Account references use stable account or Business-seat identities, so
506
- adding, removing, or reordering accounts does not silently change a model's
507
- routing. A Business membership and a Personal account remain separate pool and
508
- usage identities even when they belong to the same login.
509
-
510
- ```json
511
- {
512
- "modelAccountPools": {
513
- "gpt-5.6-sol": [
514
- "org-example-account-id",
515
- "00000000-0000-0000-0000-000000000000"
516
- ],
517
- "gpt-5.6-terra": [
518
- "org-another-account-id"
519
- ]
520
- },
521
- "modelAccountPoolModes": {
522
- "gpt-5.6-sol": "strict",
523
- "gpt-5.6-terra": "preferred"
524
- }
525
- }
526
- ```
527
-
528
- Save this configuration in `~/.opencode/openai-codex-auth-config.json`, then
529
- restart OpenCode. Model matching is case-insensitive and uses the effective
530
- model after request model normalization.
531
-
532
- Use `codex-pool` to manage these mappings with ordinary 1-based account
533
- numbers. The tool resolves those numbers and writes stable IDs to disk:
534
-
535
- ```text
536
- codex-pool
537
- codex-pool action="set" model="gpt-5.6-sol" accounts=[7,8]
538
- codex-pool action="add" model="gpt-5.6-sol" accounts=[9]
539
- codex-pool action="remove" model="gpt-5.6-sol" accounts=[7]
540
- codex-pool action="set-mode" model="gpt-5.6-sol" poolMode="strict"
541
- codex-pool action="clear" model="gpt-5.6-sol"
542
- ```
543
-
544
- Add `dryRun=true` to preview a mutation. Use `format="json"` for structured
545
- output; stable IDs remain redacted unless `includeSensitive=true` is also set.
546
- Restart OpenCode after an applied mutation. The plugin configuration is global
547
- while account storage is per-project by default, so a reference unresolved in
548
- the current project is reported but never automatically deleted.
549
-
550
- Routing behavior:
551
-
552
- - A mapped model defaults to `preferred` mode and uses healthy, selectable accounts in its pool.
553
- - Existing rotation strategy, quota, cooldown, and token-health rules still apply within the preferred pool.
554
- - In `preferred` mode, an unavailable pool falls back to the healthy general account pool.
555
- - In `strict` mode, routing never leaves the configured pool and immediately returns `strict_pool_unavailable` when no pooled account is selectable.
556
- - An unmapped model or an empty account list uses the general account pool directly.
557
- - `codex-status`, `codex-dashboard`, and routing diagnostics also report `strict` and `strict-unavailable` modes.
558
-
559
- Account IDs are local account metadata but should still be treated as private
560
- configuration. Do not publish a populated configuration file.
561
-
562
- Selected runtime/environment overrides:
563
-
564
- | Variable | Effect |
565
- | --- | --- |
566
- | `OPENAI_BASE_URL=https://gateway.example/v1` | OpenAI-compatible OAuth inference gateway; requires `CODEX_AUTH_ALLOW_OPENAI_BASE_URL=1` |
567
- | `CODEX_AUTH_ALLOW_OPENAI_BASE_URL=1` | Explicitly allow the trusted gateway to receive the ChatGPT OAuth access token; remote gateways require HTTPS, while HTTP is accepted only on literal loopback IPs |
568
- | `CODEX_AUTH_REQUEST_TRANSFORM_MODE=legacy` | Re-enable legacy Codex request rewriting |
569
- | `CODEX_MODE=0/1` | Disable/enable bridge prompt behavior |
570
- | `CODEX_TUI_V2=0/1` | Disable/enable codex-style tool output |
571
- | `CODEX_TUI_COLOR_PROFILE=truecolor\|ansi256\|ansi16` | Force terminal color profile |
572
- | `CODEX_TUI_GLYPHS=ascii\|unicode\|auto` | Force terminal glyph style |
573
- | `CODEX_TUI_MASK_EMAIL=0/1` | Mask account emails across account-display surfaces (list/status/limits/health/dashboard/menus + TUI quota status) |
574
- | `CODEX_TUI_MASK_EMAIL_DETAILS=0/1` | Also hide account email in quota details when prompt masking is enabled |
575
- | `CODEX_AUTH_QUOTA_DISPLAY=free\|used` | Word quota percentages as headroom left (default, matching Codex) or as consumption |
576
-
577
- | `CODEX_AUTH_PER_PROJECT_ACCOUNTS=0/1` | Disable/enable per-project account pools |
578
- | `CODEX_AUTH_CREDENTIAL_SNAPSHOTS=0/1` | Disable/enable pre-write snapshots of the credential store (default on) |
579
- | `CODEX_AUTH_CREDENTIAL_SNAPSHOTS_MAX_COUNT=<n>` | How many credential snapshots to keep (`0` keeps all of them) |
580
- | `CODEX_AUTH_AUTO_UPDATE=0/1` | Disable/enable daily npm update check and cache refresh |
581
- | `CODEX_AUTH_ROTATION_STRATEGY=hybrid\|sticky\|round-robin` | Account selection strategy |
582
- | `CODEX_AUTH_UNSUPPORTED_MODEL_POLICY=strict\|fallback` | Control unsupported-model retry behavior |
583
- | `CODEX_AUTH_ACCOUNT_ID=<id>` | Force a specific workspace/account id |
584
- | `CODEX_AUTH_FETCH_TIMEOUT_MS=<ms>` | Request timeout override |
585
- | `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS=<ms>` | SSE stream stall timeout override |
586
- | `ENABLE_PLUGIN_REQUEST_LOGGING=1` | Enable request metadata logs |
587
- | `CODEX_PLUGIN_LOG_BODIES=1` | Include raw request/response bodies in logs; sensitive |
588
- | `CODEX_KEYCHAIN=1` | Opt in to OS-native keychain account storage |
589
- | `CODEX_AUTH_QUOTA_NOTIFICATIONS=1` | Enable desktop quota notifications (macOS only) |
590
- | `CODEX_AUTH_AUTO_PROTECT_CREDITS=0/1` | Disable/enable the quota guard that keeps rotation off paid Credits after a spent subscription window (default on) |
591
- | `CODEX_AUTH_QUOTA_NOTIFICATIONS_INTERVAL_MS=<ms>` | Override the quota poll interval (default 1800000, minimum 30000) |
592
-
593
- Boolean env overrides are truthy only for the literal string `"1"`.
594
-
595
- Validate config after changes:
596
-
597
- ```bash
598
- opencode debug config
599
- opencode run "test" --model=openai/gpt-5.5 --variant=medium
600
- ```
601
-
602
- Modern OpenCode versions use [config/opencode-modern.json](config/opencode-modern.json). Older versions can use [config/opencode-legacy.json](config/opencode-legacy.json). See [config/README.md](config/README.md) for the full model template matrix.
603
-
604
- ---
605
-
606
- ## Credential Storage
607
-
608
- <details open>
609
- <summary><b>Default JSON backend</b></summary>
610
-
611
- By default, account pools are stored locally as V3 JSON files. File permissions are restricted where the platform supports them.
612
-
613
- Use JSON storage when you want predictable, inspectable local files and easy backup/export behavior.
614
-
615
- Before the store is changed in a way that matters, the plugin copies the previous version of the file into `backups/` as `codex-credential-snapshot-*.json`, mode `0600` in a `0700` directory. The snapshot holds the state being replaced, not the state replacing it, which is what makes it useful if the file is ever overwritten wholesale. Token refreshes count as significant, which bounds how stale a restore can be. Refresh tokens are single-use, so a snapshot taken just before a refresh holds the consumed token for the one account that refresh rotated - that account needs a fresh `opencode auth login` - while every other account in the pool comes back with the token that was live at that moment. A snapshot old enough to predate many refreshes restores a pool where most or all accounts can no longer authenticate, which is the failure this bounding exists to avoid. Rotation bookkeeping - `lastUsed`, rate-limit and cooldown state, quota stamps, and the rotation cursor - never triggers one on its own, so the kept snapshots are not churned away by ordinary traffic. The plugin keeps the 10 most recent and prunes strictly by that filename prefix, so nothing else in `backups/` is touched. Set `credentialSnapshots: false` to turn it off, or `credentialSnapshotsMaxCount` to keep a different number (`0` keeps all of them).
616
-
617
- </details>
618
-
619
- <details>
620
- <summary><b>Optional OS keychain backend</b></summary>
621
-
622
- Set `CODEX_KEYCHAIN=1` to store account pools in the OS keychain instead:
623
-
624
- - macOS: Keychain
625
- - Windows: Credential Manager
626
- - Linux: libsecret, with a running secret service such as GNOME Keyring or KWallet
627
-
628
- Manage the backend from OpenCode:
629
-
630
- ```text
631
- codex-keychain command="status"
632
- codex-keychain command="migrate"
633
- codex-keychain command="rollback"
634
- ```
635
-
636
- If the keychain is unavailable, the plugin logs a warning and falls back to JSON storage for that operation. Credentials are never silently deleted.
637
-
638
- </details>
639
-
640
- ---
641
-
642
- ## Troubleshooting
643
-
644
- <details open>
645
- <summary><b>60-second recovery</b></summary>
646
-
647
- ```text
648
- codex-doctor fix=true
649
- codex-next
650
- codex-status format="json"
651
- ```
652
-
653
- If still broken:
654
-
655
- ```bash
656
- opencode auth login
657
- ```
658
-
659
- </details>
660
-
661
- <details>
662
- <summary><b>Common symptoms</b></summary>
663
-
664
- - Plugin does not load: rerun `npx -y oc-codex-multi-auth@latest`, then restart OpenCode
665
- - Config looks wrong: run `opencode debug config` and confirm `"plugin": ["oc-codex-multi-auth"]`, or the path to your checkout when running one
666
- - OAuth callback fails: free port `1455`, then rerun `opencode auth login`
667
- - Browser launch is blocked: use the remote/headless login path from [docs/getting-started.md](docs/getting-started.md#remote-or-headless-login)
668
- - Wrong account is selected: run `codex-list`, then `codex-switch`
669
- - Account pool looks unhealthy: run `codex-health format="json"` and `codex-doctor deep=true format="json"`
670
- - Import/export feels risky: run `codex-import path="..." dryRun=true` before applying
671
- - Debugging model fallback: enable `ENABLE_PLUGIN_REQUEST_LOGGING=1` and inspect `~/.opencode/logs/codex-plugin/`
672
-
673
- </details>
674
-
675
- <details>
676
- <summary><b>Diagnostics pack</b></summary>
677
-
678
- ```text
679
- codex-status format="json"
680
- codex-limits format="json"
681
- codex-health format="json"
682
- codex-next format="json"
683
- codex-list format="json"
684
- codex-dashboard format="json"
685
- codex-metrics format="json"
686
- codex-doctor deep=true format="json"
687
- ```
688
-
689
- </details>
690
-
691
- ---
692
-
693
- ## Documentation
694
-
695
- - Docs portal: [docs/README.md](docs/README.md)
696
- - Documentation map: [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md)
697
- - Getting started: [docs/getting-started.md](docs/getting-started.md)
698
- - Configuration: [docs/configuration.md](docs/configuration.md)
699
- - Config templates: [config/README.md](config/README.md)
700
- - Troubleshooting: [docs/troubleshooting.md](docs/troubleshooting.md)
701
- - FAQ: [docs/faq.md](docs/faq.md)
702
- - Privacy: [docs/privacy.md](docs/privacy.md)
703
- - Public architecture: [docs/architecture.md](docs/architecture.md)
704
- - Maintainer architecture: [docs/development/ARCHITECTURE.md](docs/development/ARCHITECTURE.md)
705
- - Testing: [docs/development/TESTING.md](docs/development/TESTING.md)
706
- - Discoverability guide: [docs/development/GITHUB_DISCOVERABILITY.md](docs/development/GITHUB_DISCOVERABILITY.md)
707
-
708
- ---
709
-
710
- ## Release Notes
711
-
712
- - Current published version: see the npm badge above, or run `npm view oc-codex-multi-auth version`
713
- - Changelog: [CHANGELOG.md](CHANGELOG.md)
714
- - Releases are automated with [release-please](https://github.com/googleapis/release-please)
715
-
716
- Merging the release-please PR cuts the tagged release and publishes the package through the configured release workflow. Manual `npm publish` is not required for routine releases.
717
-
718
- ## License
719
-
720
- MIT License. See [LICENSE](LICENSE).
721
-
722
- <details>
723
- <summary><b>Legal</b></summary>
724
-
725
- - Not affiliated with OpenAI.
726
- - "ChatGPT", "GPT-5", "Codex", and "OpenAI" are trademarks of OpenAI.
727
- - You assume responsibility for your own usage and compliance.
728
-
729
- </details>
1
+ # oc-codex-multi-auth: ChatGPT OAuth and multi-account Codex routing for OpenCode
2
+
3
+ [![npm version](https://img.shields.io/npm/v/oc-codex-multi-auth.svg)](https://www.npmjs.com/package/oc-codex-multi-auth)
4
+ [![npm downloads](https://img.shields.io/npm/dw/oc-codex-multi-auth.svg)](https://www.npmjs.com/package/oc-codex-multi-auth)
5
+ [![CI](https://github.com/ndycode/oc-codex-multi-auth/actions/workflows/ci.yml/badge.svg)](https://github.com/ndycode/oc-codex-multi-auth/actions/workflows/ci.yml)
6
+ [![MIT license](https://img.shields.io/npm/l/oc-codex-multi-auth.svg)](LICENSE)
7
+
8
+ `oc-codex-multi-auth` is an OpenCode plugin for ChatGPT Plus/Pro OAuth, Codex and GPT-5/GPT-6 model routing (including GPT-6 Astra/Sol/Luna, the Daybreak cyber tiers, and GPT-5.6 Sol/Terra/Luna), multi-account rotation, account switching, health checks, quota visibility, diagnostics, and recovery tools. It installs the OpenCode provider/TUI configuration, registers a 24-tool `codex-*` command toolkit, and routes OpenCode OpenAI SDK requests through the ChatGPT-backed Codex flow with local account state.
9
+
10
+ Use it when you want OpenCode to run Codex-style coding workflows from your own ChatGPT subscription while keeping accounts visible, switchable, health-checked, and recoverable from the terminal.
11
+
12
+ **OpenCode V2 is supported (2.0.16+).** The V2 adapter uses the existing OAuth account pool and Codex routing pipeline; the V1 entrypoint remains available for OpenCode 1.18.29+. See [OpenCode V2 installation](#opencode-v2) for setup and login instructions.
13
+
14
+ <img width="1227" height="702" alt="oc-codex-multi-auth OpenCode plugin dashboard for ChatGPT OAuth, Codex routing, and multi-account health" src="https://github.com/user-attachments/assets/b796eb2f-282e-468a-ba6a-acadf09d731b" />
15
+
16
+
17
+
18
+ > [!NOTE]
19
+ > This package is the supported OpenCode plugin line.
20
+ > Older package names and config entries should be replaced with `oc-codex-multi-auth`.
21
+
22
+ ## What You Get
23
+
24
+ - OpenCode plugin support for ChatGPT Plus/Pro OAuth and Codex/GPT-5 coding workflows
25
+ - GPT-6 Astra, GPT-6 Sol/Luna, GPT-5.6 Sol/Terra/Luna, and the Daybreak Blue/Red cyber tiers on the responses-lite request path, plus GPT-5.5, GPT-5.5 Fast, GPT-5.4 Nano, and GPT-5.1 templates
26
+ - Routing for the Daybreak-gated cyber tiers (`gpt-daybreak-blue-latest`, `gpt-daybreak-red-latest`, `gpt-5.6-cyber`), deliberately kept out of the shipped templates since they need program approval
27
+ - Compact modern OpenCode config with 10 base families and 53 variant presets; explicit legacy selector IDs when needed
28
+ - Stateless Codex-compatible request handling with `store: false` and `reasoning.encrypted_content`
29
+ - Multi-account rotation with hybrid health scoring, cooldowns, automatic token refresh, and failover
30
+ - Explicit saved-account listing, account switching, labeling, tagging, notes, health checks, and diagnostics
31
+ - Per-project account storage under `~/.opencode/projects/<project-key>/...`
32
+ - Guided setup, doctor, next-action, dashboard, export/import, keychain, and troubleshooting tools
33
+ - Optional OS-native keychain backend for stored account pools
34
+ - TUI prompt quota status and quota detail views for OpenCode sessions
35
+ - Request logging, runtime metrics, routing visibility, and redacted diagnostic snapshots for debugging
36
+ - Stable docs for install, configuration, troubleshooting, privacy, architecture, testing, and release history
37
+
38
+ ---
39
+
40
+ ## Why Developers Use It
41
+
42
+ `oc-codex-multi-auth` makes OpenCode's ChatGPT OAuth state understandable and operable. Instead of treating auth as one opaque provider file, you get a local account pool, deterministic account switching, health-aware request selection, visible quota status, JSON-friendly diagnostics, and safe repair commands for stale or damaged state. The plugin is designed for personal development workflows: credentials stay local, OpenCode keeps owning the host runtime, and the plugin only handles the OAuth-backed Codex routing layer it is installed for.
43
+
44
+ ---
45
+
46
+ ## Current Architecture At A Glance
47
+
48
+ `oc-codex-multi-auth` ships four user-visible surfaces:
49
+
50
+ | Surface | Purpose |
51
+ | --- | --- |
52
+ | `oc-codex-multi-auth` | npm CLI; explicit install modes manage OpenCode provider/TUI config, while `update` only clears the managed package cache. Also runs standalone commands: `doctor`, `status`, `list`, `limits`, `dashboard`, `health`, `diag`, `warm` |
53
+ | OpenCode plugin entry (`index.ts`) | auth loader, OAuth login modes, provider fetch pipeline, account rotation, retry/failover, and `codex-*` tool registry |
54
+ | OpenCode TUI plugin (`tui.ts`) | prompt quota status, quota details, shared quota cache, and active-account-aware display |
55
+ | 24 `codex-*` tools | setup, help, status, list, switch, warm, limits, health, metrics, doctor, dashboard, pool, backup, keychain, diagnostics, and repair actions |
56
+
57
+ The plugin does not replace OpenCode. OpenCode remains the host; this package installs provider/TUI config and supplies the OAuth-backed Codex request pipeline that OpenCode calls.
58
+
59
+ ---
60
+
61
+ <details open>
62
+ <summary><b>Terms and Usage Notice</b></summary>
63
+
64
+ > [!CAUTION]
65
+ > This project is for personal development use with your own ChatGPT Plus/Pro subscription.
66
+ >
67
+ > By using this plugin, you acknowledge:
68
+ > - This is an independent open-source project, not an official OpenAI product
69
+ > - It is not intended for commercial resale, shared multi-user access, or production services
70
+ > - You are responsible for your own usage and policy compliance
71
+ > - For production/commercial workloads, use the OpenAI Platform API
72
+
73
+ </details>
74
+
75
+ ---
76
+
77
+ ## Installation
78
+
79
+ ### OpenCode V2
80
+
81
+ The V2 compatibility adapter targets OpenCode **2.0.16 or newer** and reuses the
82
+ existing OAuth account pool, refresh, rotation, retry, and Codex request pipeline.
83
+ Register the package in `opencode.json(c)`:
84
+
85
+ ```json
86
+ {
87
+ "$schema": "https://opencode.ai/config.json",
88
+ "plugins": ["oc-codex-multi-auth"]
89
+ }
90
+ ```
91
+
92
+ For a working checkout, use its absolute directory path in `plugins`, then run
93
+ `npm install` and `npm run build` in that checkout. The V2 terminal automatically
94
+ loads the package's quota UI through its `./tui` export.
95
+
96
+ The installer also accepts `--v2` to register the plugin without rewriting the
97
+ model catalog. It refuses to modify an existing `opencode.jsonc` or convert a
98
+ config with V1 `plugin` entries: edit the JSONC `plugins` list directly, or keep
99
+ separate V1 and V2 configurations so the V1 registration is not lost. Restart
100
+ the background service after installing or rebuilding:
101
+
102
+ ```bash
103
+ opencode service restart
104
+ ```
105
+
106
+ Run `opencode auth login` from your project directory and select **OpenAI** →
107
+ **Codex OAuth (Add account — ChatGPT Plus/Pro)**. Repeat for each account, using
108
+ a private browser window or switching browser accounts to select a different login.
109
+ The built-in **ChatGPT Pro/Plus (browser)** method does not run the plugin's add-account flow.
110
+ The plugin's **Device Code**, **Open URL Manually**, and **Manual URL Paste**
111
+ methods are also available through login or `/connect`. Each adds to the pool;
112
+ logging into the same account updates its existing entry. Pools are per-project
113
+ by default, so log in from the directory where you use OpenCode. Existing
114
+ plugin accounts remain usable; V2's own credentials are managed through its
115
+ integration API. Use the plugin's `codex-list` and `codex-switch` tools to manage
116
+ its pool. V2 normalizes tool names, so these appear as `codex_list`, `codex_switch`,
117
+ and so on. The **Codex accounts** sidebar section lists the pool and marks its
118
+ active account. Use `/codex-accounts` or **Codex accounts** in the command palette
119
+ to view the list even when the sidebar is hidden. The quota details command is
120
+ also available in the command palette.
121
+
122
+ Existing supported V1 provider/model config can remain in place. The adapter
123
+ uses HTTP Responses through the existing plugin transport. V1's interactive
124
+ multi-account login menu and session-repair client calls are replaced by the
125
+ V2 connection UI and host session handling. The V1 entrypoint remains available
126
+ for OpenCode **1.18.29+**.
127
+
128
+ ### OpenCode V1
129
+
130
+ <details open>
131
+ <summary><b>For Humans</b></summary>
132
+
133
+ ### Option A: Standard install (preserve provider config)
134
+
135
+ Default mode registers the OpenCode and TUI plugin entries without changing `provider.openai`.
136
+
137
+ ```bash
138
+ npx -y oc-codex-multi-auth@latest
139
+ ```
140
+
141
+ Installer flags:
142
+
143
+ | Flag | Effect |
144
+ | --- | --- |
145
+ | (default) / `--plugin-only` | Register the plugin and TUI integration without changing `provider.openai` |
146
+ | `--modern` | Install compact modern catalog: 10 bases, 53 variants |
147
+ | `--full` | Compact bases plus 53 explicit selector IDs |
148
+ | `--legacy` | Explicit-only catalog for older OpenCode |
149
+ | `--dry-run` | Show changed config paths without values or writes |
150
+ | `--no-cache-clear` | Skip clearing the OpenCode plugin cache |
151
+
152
+ ### Option B: Compact modern model catalog
153
+
154
+ ```bash
155
+ npx -y oc-codex-multi-auth@latest --modern
156
+ ```
157
+
158
+ Use this when OpenCode does not already provide the OAuth model definitions or you want the shipped variant presets.
159
+
160
+ ### Option C: Full explicit model catalog
161
+
162
+ Use this when you want direct selector IDs such as `openai/gpt-5.5-medium` in addition to OpenCode variants.
163
+
164
+ ```bash
165
+ npx -y oc-codex-multi-auth@latest --full
166
+ ```
167
+
168
+ ### Updating without config changes
169
+
170
+ ```bash
171
+ npx -y oc-codex-multi-auth@latest update
172
+ ```
173
+
174
+ `update` clears only the OpenCode-managed package cache. It does not read or write `opencode.json` or `tui.json`; restart OpenCode afterward to install the current package.
175
+
176
+ ### Option D: Verify wiring
177
+
178
+ ```bash
179
+ opencode --version
180
+ opencode debug config
181
+ opencode auth login
182
+ ```
183
+
184
+ The default installer only registers the plugin entry in `~/.config/opencode/opencode.json`, enables the TUI status plugin in `~/.config/opencode/tui.json`, and clears the cached plugin copy. Catalog modes also merge their selected `provider.openai` definitions. Changed config files are backed up before writing.
185
+
186
+ ### Running from a local checkout
187
+
188
+ You can point OpenCode at a clone of this repository instead of the published
189
+ package, which is how the project is developed:
190
+
191
+ ```json
192
+ { "plugin": ["file:///path/to/oc-codex-multi-auth"] }
193
+ ```
194
+
195
+ The installer leaves that entry exactly as written. It identifies an entry by
196
+ the package it resolves to rather than by how the path is spelled, so a clone
197
+ is recognized under any directory name, whether it is referenced as a path, a
198
+ `file://` URL, or its build output. `oc-codex-multi-auth` is appended only when
199
+ no entry in the config resolves to this plugin, so the installer never replaces
200
+ a checkout with the published package or registers both at once.
201
+
202
+ Stale references the installer itself produced are still retired: the bare
203
+ package name repeated, version-pinned entries, the former
204
+ `oc-chatgpt-multi-auth` name, and paths into `node_modules` or the OpenCode
205
+ package cache.
206
+
207
+ ### Standalone CLI (no agent / no token cost)
208
+
209
+ ```bash
210
+ oc-codex-multi-auth status
211
+ oc-codex-multi-auth list
212
+ oc-codex-multi-auth warm
213
+ oc-codex-multi-auth doctor
214
+ oc-codex-multi-auth health
215
+ oc-codex-multi-auth limits
216
+ oc-codex-multi-auth dashboard
217
+ oc-codex-multi-auth diag
218
+ # or: npx -y oc-codex-multi-auth@latest warm --json
219
+ ```
220
+
221
+ </details>
222
+
223
+ <details>
224
+ <summary><b>For LLM Agents</b></summary>
225
+
226
+ ### Step-by-step
227
+
228
+ 1. Register the plugin without changing `provider.openai`:
229
+ - `npx -y oc-codex-multi-auth@latest`
230
+ - Use `--modern` only when the shipped compact model catalog is required.
231
+ 2. Run first login flow:
232
+ - `opencode auth login`
233
+ 3. Validate config:
234
+ - `opencode debug config`
235
+ 4. Run a smoke request (after OpenCode or `--modern` supplies the selector):
236
+ - `opencode run "Explain this repository" --model=openai/gpt-5.5 --variant=medium`
237
+ 5. Inspect plugin state with the OpenCode tool surface:
238
+ - `codex-status`
239
+ - `codex-doctor`
240
+ - `codex-list`
241
+
242
+ ### Verification
243
+
244
+ ```bash
245
+ opencode debug config
246
+ opencode auth login
247
+ opencode run "ping" --model=openai/gpt-5.5 --variant=medium
248
+ ```
249
+
250
+ </details>
251
+
252
+ ---
253
+
254
+ ## Quick Start
255
+
256
+ Install and sign in:
257
+
258
+ ```bash
259
+ npx -y oc-codex-multi-auth@latest
260
+ opencode auth login
261
+ ```
262
+
263
+ Run a prompt with compact modern selectors:
264
+
265
+ ```bash
266
+ opencode run "Summarize the failing test and suggest a fix" --model=openai/gpt-5.5 --variant=medium
267
+ opencode run "Summarize the failing test and suggest a fix" --model=openai/gpt-5.5-fast --variant=medium
268
+ opencode run "Plan the refactor" --model=openai/gpt-6-astra --variant=high
269
+ ```
270
+
271
+ Use Codex-focused routing:
272
+
273
+ ```bash
274
+ opencode run "Refactor the retry logic and update the tests" --model=openai/gpt-6-sol --variant=high
275
+ ```
276
+
277
+ If browser launch is blocked, use the alternate login paths in [docs/getting-started.md](docs/getting-started.md#remote-or-headless-login).
278
+
279
+ ---
280
+
281
+ ## Command Toolkit
282
+
283
+ ### Start here
284
+
285
+ | Tool | What it answers |
286
+ | --- | --- |
287
+ | `codex-setup` | How do I finish first-run setup safely? |
288
+ | `codex-help` | Which plugin commands exist and what do they do? |
289
+ | `codex-doctor` | What is wrong with auth, config, storage, or routing? |
290
+ | `codex-next` | What should I do next to get unstuck? |
291
+
292
+ ### Daily use
293
+
294
+ | Tool | What it answers |
295
+ | --- | --- |
296
+ | `codex-list` | Which accounts are saved and which one is active? |
297
+ | `codex-switch` | How do I move to a different saved account? |
298
+ | `codex-warm` | How do I start every account's usage window now (stagger quota cooldowns)? |
299
+ | `codex-status` | Which account, model family, and routing state are active? |
300
+ | `codex-limits` | What quota is visible now, per account and across the pool? |
301
+ | `codex-reset` | Do I have a banked rate-limit reset credit, and how do I redeem it? |
302
+ | `codex-dashboard` | What does a read-only snapshot of account eligibility, retry budgets, and refresh queue health show? |
303
+ | `codex-pool` | Which accounts are preferred for each model, and how do I change them? |
304
+
305
+ Most of these also run as a **direct CLI** with no agent or model involvement, so there is no token cost. Examples are `oc-codex-multi-auth warm`, `oc-codex-multi-auth status`, or `npx -y oc-codex-multi-auth@latest warm`. Use `oc-codex-multi-auth warm` to open every enabled account's usage window at the start of a session and stagger the rolling quota cooldowns. Add `--json` for scriptable output.
306
+
307
+ ### Account management
308
+
309
+ | Tool | What it answers |
310
+ | --- | --- |
311
+ | `codex-label` | How do I name an account? |
312
+ | `codex-tag` | How do I group accounts with tags? |
313
+ | `codex-note` | How do I attach a private note to an account? |
314
+ | `codex-remove` | How do I remove a saved account safely? |
315
+ | `codex-refresh` | How do I refresh the OAuth tokens of every saved account to verify they are still valid? |
316
+
317
+ ### Diagnostics and backup
318
+
319
+ | Tool | What it answers |
320
+ | --- | --- |
321
+ | `codex-health` | Which accounts look healthy, limited, or disabled? |
322
+ | `codex-metrics` | What runtime counters and request metrics are visible? |
323
+ | `codex-diag` | Can I export a redacted diagnostic snapshot? |
324
+ | `codex-diff` | What changed between account/config snapshots? |
325
+ | `codex-export` | How do I back up account storage? |
326
+ | `codex-import` | How do I restore accounts with a dry-run first? |
327
+ | `codex-keychain` | Which credential backend is active and can I migrate it? |
328
+
329
+ ### Reliability behavior
330
+
331
+ - stateless request handling forces `store: false`
332
+ - `reasoning.encrypted_content` is preserved for multi-turn continuity
333
+ - GPT-6 Astra/Sol/Luna, the Daybreak tiers and the GPT-5.6 tiers use the responses-lite request shape and default client identity `opencode`; other models default to `codex_cli_rs`
334
+ - account rotation is health-aware (`rotationStrategy` default `hybrid`) and avoids repeatedly selecting cooling accounts
335
+ - The quota guard checks each enabled account at a bounded interval (30 minutes by default). When it finds a fully spent 5-hour or weekly subscription quota, rotation skips that account until its reported reset instead of drawing from paid Credits. `codex-limits` applies the same guard immediately when run manually. After running standalone `limits --refresh` (a plain `limits` reports the plugin's last readings and reads live only accounts it has none for), restart an already-running OpenCode instance or wait for its next quota poll to reload the updated account state.
336
+ - same-host OpenCode processes sharing an account file serialize refresh-token exchange and commit so one current single-use token is exchanged once
337
+ - 5xx bursts, network failures, and quota responses penalize account health
338
+ - token refresh is queued to avoid refresh races
339
+ - unsupported-model handling is strict by default, with opt-in fallback controls
340
+ - TUI quota status follows the account/workspace used by the latest request
341
+ - Business workspace memberships and Personal accounts keep separate usage and quota windows. Business members sharing one workspace are distinguished by their member/seat identity, so their usage is not collapsed into one row.
342
+ - An account identifies itself by its own ChatGPT email and the last 6 characters of its account id, with the email masked when `maskEmail` is on. An account id names a ChatGPT workspace and every member of a Business workspace shares it, so a record that also carries a member/seat id prints a short excerpt of that as `seat:`. The excerpt is a 6-character tail where that is enough to tell the listed accounts apart. Where it is not, it widens, moves to where those ids first differ, or joins two short excerpts with `..` - real member ids are long, share a leading prefix, and differ in more than one place, so a tail alone often cannot separate them. Where no excerpt that short can separate them, `seat:` is instead an **opaque hash prefix** such as `719f78b5`: it identifies the seat and stays stable, but it is not part of the member id and cannot be matched against anything ChatGPT shows you. Whichever form it takes, two distinct seats never render the same `seat:` and a `seat:` is never longer than 32 characters. A record with no member id renders exactly as before. The OAuth id_token also lists the API-platform organizations the login belongs to; those are not ChatGPT workspaces and are never used to name an account, so logging in clears a label left behind by one. A label you set with `codex-label` is always kept.
343
+ - The ChatGPT plan (`Free`, `Plus`, `Pro`, `Business`, `Business Premium`, `Enterprise`) is read from the access token, refreshed on every token refresh, and shown by `codex-list` and `codex-status`. `codex-limits` and the TUI read the plan live from the usage endpoint and name it the same way. An unrecognized plan is reported verbatim rather than renamed.
344
+ - `codex-limits` and the standalone `limits` CLI name what one of that plan's seats is worth beside the others (`Plan: Pro (20x)`) and close with what the pool holds between them (`Pool: 93% used of 81x across 11 accounts`). The percentage is a **weighted** mean over exactly that `81x`, since a spent Pro seat costs the pool twenty times what a spent Plus seat does, and an account whose usage could not be read is left out of both figures. A plan that publishes no ratio carries no badge but still weighs one baseline seat. See [docs/tools-and-cli.md](docs/tools-and-cli.md#what-limits-reports) and [docs/plan-allotments.md](docs/plan-allotments.md).
345
+
346
+ ---
347
+
348
+ ## Storage Paths
349
+
350
+ | File | Default path |
351
+ | --- | --- |
352
+ | OpenCode config | `~/.config/opencode/opencode.json` |
353
+ | OpenCode TUI config | `~/.config/opencode/tui.json` |
354
+ | OpenCode auth tokens | `~/.opencode/auth/openai.json` |
355
+ | Plugin config | `~/.opencode/openai-codex-auth-config.json` |
356
+ | Global account storage | `~/.opencode/oc-codex-multi-auth-accounts.json` |
357
+ | Per-project accounts | `~/.opencode/projects/<project-key>/oc-codex-multi-auth-accounts.json` |
358
+ | Flagged accounts | `oc-codex-multi-auth-flagged-accounts.json`, written beside the active accounts file (per-project path when `perProjectAccounts` is on) |
359
+ | Backups | `~/.opencode/backups/` or `~/.opencode/projects/<project-key>/backups/` |
360
+ | Logs | `~/.opencode/logs/codex-plugin/` |
361
+ | TUI quota cache | OpenCode state dir plus `oc-codex-multi-auth-tui-quota.json`, else `$OPENCODE_STATE_DIR/oc-codex-multi-auth-tui-quota.json` or `~/.local/state/opencode/oc-codex-multi-auth-tui-quota.json` |
362
+ | TUI pool quota cache | `oc-codex-multi-auth-tui-quota-overview.json`, in the same directory, written only when `quotaStatus.mode` is `overview` |
363
+
364
+ Per-project storage is enabled by default. The plugin walks up from the current directory to find a project root, then stores account pools under the project-specific key. If no project root is found, it falls back to global storage.
365
+
366
+ ---
367
+
368
+ ## Configuration
369
+
370
+ Primary config files:
371
+
372
+ - `~/.config/opencode/opencode.json`
373
+ - `~/.config/opencode/tui.json`
374
+ - `~/.opencode/openai-codex-auth-config.json`
375
+
376
+ ### Quota percentage display
377
+
378
+ Every quota percentage a person reads is worded as the headroom still left,
379
+ which is how Codex itself reports a quota:
380
+
381
+ ```text
382
+ 5h limit: 88% left # codex-limits, quota details dialog
383
+ 5h 88% # TUI prompt status line
384
+ ```
385
+
386
+ Set `quotaDisplay` to `"used"` to report consumption instead:
387
+
388
+ ```json
389
+ {
390
+ "quotaDisplay": "used"
391
+ }
392
+ ```
393
+
394
+ ```text
395
+ 5h limit: 12% used
396
+ 5h 12%
397
+ ```
398
+
399
+ Add it to `~/.opencode/openai-codex-auth-config.json`, or set
400
+ `CODEX_AUTH_QUOTA_DISPLAY=used`, then quit and restart OpenCode. The setting
401
+ covers the TUI prompt status line and quota details dialog, `codex-limits`,
402
+ the standalone `limits` CLI, the interactive account check, and the macOS
403
+ quota notifications below.
404
+
405
+ It changes wording only. Quota exhaustion, rotation blocks, notification
406
+ thresholds, and the status line's warning/danger colouring all stay keyed on
407
+ the percentage remaining, so a nearly spent account still colours red while
408
+ reading `95%`. The `usedPercent` and `leftPercent` fields in `--json` /
409
+ `format="json"` output are unaffected.
410
+
411
+ ### Pool-wide quota status
412
+
413
+ The prompt status line describes the account that served the last request. On
414
+ a pool of several accounts that account changes as rotation moves, so the line
415
+ changes identity under you and no single glance shows where the pool stands.
416
+
417
+ Set `quotaStatus.mode` to `"overview"` to describe the whole pool on one
418
+ constant line instead, which only changes when a quota does:
419
+
420
+ ```json
421
+ {
422
+ "quotaStatus": {
423
+ "mode": "overview",
424
+ "layout": "accounts",
425
+ "accountNames": "number",
426
+ "order": "number",
427
+ "multipliers": false,
428
+ "allotment": false,
429
+ "resetTimes": "low",
430
+ "resetCredits": false,
431
+ "recovery": false,
432
+ "rows": 1,
433
+ "showFor": "always"
434
+ }
435
+ }
436
+ ```
437
+
438
+ ```text
439
+ 24%: #1 13%, #2 0% 3d, #3 12% # defaults
440
+ 24%: 3 accounts # "layout": "count"
441
+ 24%: #1 5x 13%, #2 20x 0% 3d, #3 1x 12% # "multipliers": true
442
+ 24%: #1 5x 13%, #2 20x 0% 3d 1r, #3 1x 12% # + "resetCredits": true
443
+ 24%: 3 accounts, +12% in 3d # "layout": "count", "recovery": true
444
+ 24% of 26x: #1 13%, #2 0% 3d, #3 12% # "allotment": true
445
+ 24%: #1 13% 2d, #2 0% 3d, #3 12% 5d # "resetTimes": "always"
446
+ 24%: 13%, 0% 3d, 12% # "accountNames": "none"
447
+ 24%: damian 13%, work 0% 3d, spare 12% # "accountNames": "label"
448
+ 24%: #2 0% 3d, #1 13%, #3 12% # "order": "most-used"
449
+ 24%: 13% 2d, 0% 3d 4d 5d # "layout": "aggregate"
450
+ ```
451
+
452
+ Each switch is independent, so any combination works. `#N` is the account
453
+ number `codex-list` and `codex-switch` use. An account is shown by whichever
454
+ of its windows has the least headroom, since that is the one that stops a
455
+ request; a reset time (`3d`) is added for an account at or below 25% by
456
+ default, for every account under `"resetTimes": "always"`, and for none under
457
+ `"never"`. `1r` counts banked rate-limit resets that account can redeem now.
458
+
459
+ `order` takes `number`, `most-used`, `least-used`, `renewing-earliest`, or
460
+ `renewing-latest`. `layout: "aggregate"` prints a shared percentage once and
461
+ keeps only what differs after it, which matters most on a pool where several
462
+ accounts are spent.
463
+
464
+ The leading figure is the pool total, and it is a **weighted** mean: a Pro seat
465
+ spent to 50% has given up twenty times the capacity a Business Standard seat
466
+ does at 50%, so an unweighted average would describe a pool nobody has. The
467
+ per-plan ratios are listed in [docs/plan-allotments.md](docs/plan-allotments.md),
468
+ and `"allotment": true` shows what they add up to.
469
+
470
+ For just that total and its recovery forecast, use `"layout": "total"`,
471
+ `"recovery": "all"`, and `"allotment": false`. This reads, for example,
472
+ `25% +1% in 3h, +12% in 3d, +5% in 4d`: each positive figure is incremental
473
+ capacity returned in percentage points, even with `quotaDisplay: "used"`.
474
+ See the [forecast semantics](docs/configuration.md#what-the-line-says).
475
+
476
+ `mode` also accepts a list, and the line then alternates between those screens
477
+ every `rotateMs` (default 5000). The third screen, `resets`, appears by default
478
+ once every readable account is spent. Set `resetsMinUsedPercent` (0-100) to
479
+ show it earlier, for example at 90% total weighted usage. It lists known
480
+ applicable banked reset credits,
481
+ latest reset first - redeeming one on an account that renews by itself tomorrow
482
+ throws it away:
483
+
484
+ ```json
485
+ {
486
+ "quotaStatus": { "mode": ["overview", "resets"] }
487
+ }
488
+ ```
489
+
490
+ ```text
491
+ Free resets: 6d 1r damian@nowaker.net, 4d 2r work@example.com
492
+ ```
493
+
494
+ `"rows"` (1-4, default 1) is a ceiling rather than a height: a rendering that
495
+ fits on one row still takes one, so `"rows": 2` costs nothing on a wide terminal
496
+ and buys the whole line back on a narrow one, where the agent/model label beside
497
+ it has already wrapped to two rows anyway. `"showFor": "codex-models"` hides the
498
+ line unless the session is running a model this plugin routes.
499
+
500
+ Percentages follow `quotaDisplay`, so the first line above reads
501
+ `76%: #1 87%, #2 100% 3d, #3 88%` under `"used"`. The whole setting is
502
+ presentation only: rotation, quota blocks and the line's warning/danger
503
+ colouring stay keyed on the headroom remaining.
504
+
505
+ Add the object to `~/.opencode/openai-codex-auth-config.json`. It is read from
506
+ that file only - a display preference belongs to a person, not to a shell - and
507
+ the status line re-reads it while sessions are open, so an edit takes effect
508
+ within a couple of seconds without a restart.
509
+ New plugin code still needs a one-time process restart after an upgrade;
510
+ subsequent changes to these settings reload live.
511
+
512
+ ### Desktop quota notifications
513
+
514
+ Quota notifications are an optional macOS-only feature. Separately, the quota
515
+ guard checks enabled accounts every 30 minutes by default and prevents rotation
516
+ from drawing paid Credits after the backend reports a fully spent subscription
517
+ window. While notifications are enabled, the same poll also alerts through
518
+ Notification Center when the best remaining 5-hour or weekly pool quota crosses
519
+ 25%, 10%, or 0%. Alerts are disabled by default; the quota guard is enabled.
520
+
521
+ Each line reports the enabled account with the most headroom in that window,
522
+ together with that same account's reset time, so the pair always describes a
523
+ quota that one account actually has. When a different account recovers sooner,
524
+ that reset is appended under its own label rather than folded into the first
525
+ one. Windows a plan has switched off are skipped rather than counted as full.
526
+ Account identities are omitted for readability and lock-screen privacy:
527
+
528
+ ```text
529
+ 5h: 10% | resets 02:00 | another account resets 22:30
530
+ Weekly: 72% | resets 22:30 on Aug 30
531
+ ```
532
+
533
+ The percentage follows `quotaDisplay`, so the same two lines read `90%` and
534
+ `28%` under `"used"`. `thresholds` are always remaining-percent values
535
+ regardless.
536
+
537
+ ```json
538
+ {
539
+ "quotaNotifications": {
540
+ "enabled": true,
541
+ "autoProtectCredits": true,
542
+ "intervalMs": 1800000,
543
+ "notifyEveryCheck": false,
544
+ "thresholds": [25, 10, 0]
545
+ }
546
+ }
547
+ ```
548
+
549
+ Add the object above to `~/.opencode/openai-codex-auth-config.json`, or set
550
+ `CODEX_AUTH_QUOTA_NOTIFICATIONS=1`, then quit and restart OpenCode. The minimum
551
+ interval is 30 seconds. If macOS blocks the alert, allow notifications for
552
+ the process shown in **System Settings > Notifications**. The setting is
553
+ ignored on Windows and Linux.
554
+
555
+ Set `"notifyEveryCheck": true` to show the aggregate quota notification after
556
+ every successful poll interval instead of only when a configured threshold is
557
+ crossed. Set `"thresholds": []` to turn threshold alerts off entirely; pair it
558
+ with `"notifyEveryCheck": true` to keep receiving alerts. The quota guard keeps
559
+ polling unless `"autoProtectCredits": false` is set.
560
+
561
+ Delivery state lives beside the accounts file the alerts are computed from, so
562
+ OpenCode processes working in the same account scope show only one alert per
563
+ interval. With the default `perProjectAccounts`, that scope is one project:
564
+ two projects have separate account pools and therefore alert independently.
565
+
566
+ ### Route models to preferred accounts
567
+
568
+ Use `modelAccountPools` to assign one or more preferred ChatGPT accounts or Business seats to a
569
+ model. Account references use stable account or Business-seat identities, so
570
+ adding, removing, or reordering accounts does not silently change a model's
571
+ routing. A Business membership and a Personal account remain separate pool and
572
+ usage identities even when they belong to the same login.
573
+
574
+ ```json
575
+ {
576
+ "modelAccountPools": {
577
+ "gpt-5.6-sol": [
578
+ "org-example-account-id",
579
+ "00000000-0000-0000-0000-000000000000"
580
+ ],
581
+ "gpt-5.6-terra": [
582
+ "org-another-account-id"
583
+ ]
584
+ },
585
+ "modelAccountPoolModes": {
586
+ "gpt-5.6-sol": "strict",
587
+ "gpt-5.6-terra": "preferred"
588
+ }
589
+ }
590
+ ```
591
+
592
+ Save this configuration in `~/.opencode/openai-codex-auth-config.json`, then
593
+ restart OpenCode. Model matching is case-insensitive and uses the effective
594
+ model after request model normalization.
595
+
596
+ Use `codex-pool` to manage these mappings with ordinary 1-based account
597
+ numbers. The tool resolves those numbers and writes stable IDs to disk:
598
+
599
+ ```text
600
+ codex-pool
601
+ codex-pool action="set" model="gpt-5.6-sol" accounts=[7,8]
602
+ codex-pool action="add" model="gpt-5.6-sol" accounts=[9]
603
+ codex-pool action="remove" model="gpt-5.6-sol" accounts=[7]
604
+ codex-pool action="set-mode" model="gpt-5.6-sol" poolMode="strict"
605
+ codex-pool action="clear" model="gpt-5.6-sol"
606
+ ```
607
+
608
+ Add `dryRun=true` to preview a mutation. Use `format="json"` for structured
609
+ output; stable IDs remain redacted unless `includeSensitive=true` is also set.
610
+ Restart OpenCode after an applied mutation. The plugin configuration is global
611
+ while account storage is per-project by default, so a reference unresolved in
612
+ the current project is reported but never automatically deleted.
613
+
614
+ Routing behavior:
615
+
616
+ - A mapped model defaults to `preferred` mode and uses healthy, selectable accounts in its pool.
617
+ - Existing rotation strategy, quota, cooldown, and token-health rules still apply within the preferred pool.
618
+ - In `preferred` mode, an unavailable pool falls back to the healthy general account pool.
619
+ - In `strict` mode, routing never leaves the configured pool and immediately returns `strict_pool_unavailable` when no pooled account is selectable.
620
+ - An unmapped model or an empty account list uses the general account pool directly.
621
+ - `codex-status`, `codex-dashboard`, and routing diagnostics also report `strict` and `strict-unavailable` modes.
622
+
623
+ Account IDs are local account metadata but should still be treated as private
624
+ configuration. Do not publish a populated configuration file.
625
+
626
+ Selected runtime/environment overrides:
627
+
628
+ | Variable | Effect |
629
+ | --- | --- |
630
+ | `OPENAI_BASE_URL=https://gateway.example/v1` | OpenAI-compatible OAuth inference gateway; requires `CODEX_AUTH_ALLOW_OPENAI_BASE_URL=1` |
631
+ | `CODEX_AUTH_ALLOW_OPENAI_BASE_URL=1` | Explicitly allow the trusted gateway to receive the ChatGPT OAuth access token; remote gateways require HTTPS, while HTTP is accepted only on literal loopback IPs |
632
+ | `CODEX_AUTH_REQUEST_TRANSFORM_MODE=legacy` | Re-enable legacy Codex request rewriting |
633
+ | `CODEX_MODE=0/1` | Disable/enable bridge prompt behavior |
634
+ | `CODEX_TUI_V2=0/1` | Disable/enable codex-style tool output |
635
+ | `CODEX_TUI_COLOR_PROFILE=truecolor\|ansi256\|ansi16` | Force terminal color profile |
636
+ | `CODEX_TUI_GLYPHS=ascii\|unicode\|auto` | Force terminal glyph style |
637
+ | `CODEX_TUI_MASK_EMAIL=0/1` | Mask account emails across account-display surfaces (list/status/limits/health/dashboard/menus + TUI quota status) |
638
+ | `CODEX_TUI_MASK_EMAIL_DETAILS=0/1` | Also hide account email in quota details when prompt masking is enabled |
639
+ | `CODEX_AUTH_QUOTA_DISPLAY=free\|used` | Word quota percentages as headroom left (default, matching Codex) or as consumption |
640
+
641
+ | `CODEX_AUTH_PER_PROJECT_ACCOUNTS=0/1` | Disable/enable per-project account pools |
642
+ | `CODEX_AUTH_CREDENTIAL_SNAPSHOTS=0/1` | Disable/enable pre-write snapshots of the credential store (default on) |
643
+ | `CODEX_AUTH_CREDENTIAL_SNAPSHOTS_MAX_COUNT=<n>` | How many credential snapshots to keep (`0` keeps all of them) |
644
+ | `CODEX_AUTH_AUTO_UPDATE=0/1` | Disable/enable daily npm update check and cache refresh |
645
+ | `CODEX_AUTH_ROTATION_STRATEGY=hybrid\|sticky\|round-robin` | Account selection strategy |
646
+ | `CODEX_AUTH_UNSUPPORTED_MODEL_POLICY=strict\|fallback` | Control unsupported-model retry behavior |
647
+ | `CODEX_AUTH_ACCOUNT_ID=<id>` | Force a specific workspace/account id |
648
+ | `CODEX_AUTH_FETCH_TIMEOUT_MS=<ms>` | Request timeout override |
649
+ | `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS=<ms>` | SSE stream stall timeout override |
650
+ | `ENABLE_PLUGIN_REQUEST_LOGGING=1` | Enable request metadata logs |
651
+ | `CODEX_PLUGIN_LOG_BODIES=1` | Include raw request/response bodies in logs; sensitive |
652
+ | `CODEX_KEYCHAIN=1` | Opt in to OS-native keychain account storage |
653
+ | `CODEX_AUTH_QUOTA_NOTIFICATIONS=1` | Enable desktop quota notifications (macOS only) |
654
+ | `CODEX_AUTH_AUTO_PROTECT_CREDITS=0/1` | Disable/enable the quota guard that keeps rotation off paid Credits after a spent subscription window (default on) |
655
+ | `CODEX_AUTH_QUOTA_NOTIFICATIONS_INTERVAL_MS=<ms>` | Override the quota poll interval (default 1800000, minimum 30000) |
656
+
657
+ Boolean env overrides are truthy only for the literal string `"1"`.
658
+
659
+ Validate config after changes:
660
+
661
+ ```bash
662
+ opencode debug config
663
+ opencode run "test" --model=openai/gpt-5.5 --variant=medium
664
+ ```
665
+
666
+ Modern OpenCode versions use [config/opencode-modern.json](config/opencode-modern.json). Older versions can use [config/opencode-legacy.json](config/opencode-legacy.json). See [config/README.md](config/README.md) for the full model template matrix.
667
+
668
+ ---
669
+
670
+ ## Credential Storage
671
+
672
+ <details open>
673
+ <summary><b>Default JSON backend</b></summary>
674
+
675
+ By default, account pools are stored locally as V3 JSON files. File permissions are restricted where the platform supports them.
676
+
677
+ Use JSON storage when you want predictable, inspectable local files and easy backup/export behavior.
678
+
679
+ Before the store is changed in a way that matters, the plugin copies the previous version of the file into `backups/` as `codex-credential-snapshot-*.json`, mode `0600` in a `0700` directory. The snapshot holds the state being replaced, not the state replacing it, which is what makes it useful if the file is ever overwritten wholesale. Token refreshes count as significant, which bounds how stale a restore can be. Refresh tokens are single-use, so a snapshot taken just before a refresh holds the consumed token for the one account that refresh rotated - that account needs a fresh `opencode auth login` - while every other account in the pool comes back with the token that was live at that moment. A snapshot old enough to predate many refreshes restores a pool where most or all accounts can no longer authenticate, which is the failure this bounding exists to avoid. Rotation bookkeeping - `lastUsed`, rate-limit and cooldown state, quota stamps, and the rotation cursor - never triggers one on its own, so the kept snapshots are not churned away by ordinary traffic. The plugin keeps the 10 most recent and prunes strictly by that filename prefix, so nothing else in `backups/` is touched. Set `credentialSnapshots: false` to turn it off, or `credentialSnapshotsMaxCount` to keep a different number (`0` keeps all of them).
680
+
681
+ Concurrent OpenCode sessions sharing a store preserve the on-disk account list during background saves. Three consecutive authentication failures disable the affected account instead of deleting its credentials; re-login can repair it. If an existing store is unreadable or has an invalid format, the plugin refuses to treat it as an empty pool and will not overwrite it through an account-storage transaction. See [troubleshooting](docs/troubleshooting.md) for recovery steps.
682
+
683
+ </details>
684
+
685
+ <details>
686
+ <summary><b>Optional OS keychain backend</b></summary>
687
+
688
+ Set `CODEX_KEYCHAIN=1` to store account pools in the OS keychain instead:
689
+
690
+ - macOS: Keychain
691
+ - Windows: Credential Manager
692
+ - Linux: libsecret, with a running secret service such as GNOME Keyring or KWallet
693
+
694
+ Manage the backend from OpenCode:
695
+
696
+ ```text
697
+ codex-keychain command="status"
698
+ codex-keychain command="migrate"
699
+ codex-keychain command="rollback"
700
+ ```
701
+
702
+ If the keychain is unavailable, the plugin logs a warning and falls back to JSON storage for that operation. Credentials are never silently deleted.
703
+
704
+ </details>
705
+
706
+ ---
707
+
708
+ ## Troubleshooting
709
+
710
+ <details open>
711
+ <summary><b>60-second recovery</b></summary>
712
+
713
+ ```text
714
+ codex-doctor fix=true
715
+ codex-next
716
+ codex-status format="json"
717
+ ```
718
+
719
+ If still broken:
720
+
721
+ ```bash
722
+ opencode auth login
723
+ ```
724
+
725
+ </details>
726
+
727
+ <details>
728
+ <summary><b>Common symptoms</b></summary>
729
+
730
+ - Plugin does not load: rerun `npx -y oc-codex-multi-auth@latest`, then restart OpenCode
731
+ - Config looks wrong: run `opencode debug config` and confirm `"plugin": ["oc-codex-multi-auth"]`, or the path to your checkout when running one
732
+ - OAuth callback fails: free port `1455`, then rerun `opencode auth login`
733
+ - Browser launch is blocked: use the remote/headless login path from [docs/getting-started.md](docs/getting-started.md#remote-or-headless-login)
734
+ - Wrong account is selected: run `codex-list`, then `codex-switch`
735
+ - Account pool looks unhealthy: run `codex-health format="json"` and `codex-doctor deep=true format="json"`
736
+ - Import/export feels risky: run `codex-import path="..." dryRun=true` before applying
737
+ - Debugging model fallback: enable `ENABLE_PLUGIN_REQUEST_LOGGING=1` and inspect `~/.opencode/logs/codex-plugin/`
738
+
739
+ </details>
740
+
741
+ <details>
742
+ <summary><b>Diagnostics pack</b></summary>
743
+
744
+ ```text
745
+ codex-status format="json"
746
+ codex-limits format="json"
747
+ codex-health format="json"
748
+ codex-next format="json"
749
+ codex-list format="json"
750
+ codex-dashboard format="json"
751
+ codex-metrics format="json"
752
+ codex-doctor deep=true format="json"
753
+ ```
754
+
755
+ </details>
756
+
757
+ ---
758
+
759
+ ## Documentation
760
+
761
+ - Docs portal: [docs/README.md](docs/README.md)
762
+ - Documentation map: [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md)
763
+ - Getting started: [docs/getting-started.md](docs/getting-started.md)
764
+ - Configuration: [docs/configuration.md](docs/configuration.md)
765
+ - Config templates: [config/README.md](config/README.md)
766
+ - Troubleshooting: [docs/troubleshooting.md](docs/troubleshooting.md)
767
+ - FAQ: [docs/faq.md](docs/faq.md)
768
+ - Privacy: [docs/privacy.md](docs/privacy.md)
769
+ - Public architecture: [docs/architecture.md](docs/architecture.md)
770
+ - Maintainer architecture: [docs/development/ARCHITECTURE.md](docs/development/ARCHITECTURE.md)
771
+ - Testing: [docs/development/TESTING.md](docs/development/TESTING.md)
772
+ - Discoverability guide: [docs/development/GITHUB_DISCOVERABILITY.md](docs/development/GITHUB_DISCOVERABILITY.md)
773
+
774
+ ---
775
+
776
+ ## Release Notes
777
+
778
+ - Current published version: see the npm badge above, or run `npm view oc-codex-multi-auth version`
779
+ - Changelog: [CHANGELOG.md](CHANGELOG.md)
780
+ - Releases are automated with [release-please](https://github.com/googleapis/release-please)
781
+
782
+ Merging the release-please PR cuts the tagged release and publishes the package through the configured release workflow. Manual `npm publish` is not required for routine releases.
783
+
784
+ ## License
785
+
786
+ MIT License. See [LICENSE](LICENSE).
787
+
788
+ <details>
789
+ <summary><b>Legal</b></summary>
790
+
791
+ - Not affiliated with OpenAI.
792
+ - "ChatGPT", "GPT-5", "Codex", and "OpenAI" are trademarks of OpenAI.
793
+ - You assume responsibility for your own usage and compliance.
794
+
795
+ </details>