@modelprofile.com/authswitch 3.2.0 → 4.0.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.
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/accounts.d.ts +63 -13
- package/dist_ts/accounts.js +199 -31
- package/dist_ts/classes.accountlist.d.ts +0 -24
- package/dist_ts/classes.accountlist.js +2 -213
- package/dist_ts/classes.claudecodeharness.d.ts +61 -3
- package/dist_ts/classes.claudecodeharness.js +130 -14
- package/dist_ts/classes.claudecodelocks.d.ts +35 -0
- package/dist_ts/classes.claudecodelocks.js +117 -0
- package/dist_ts/classes.claudestatus.d.ts +17 -4
- package/dist_ts/classes.claudestatus.js +89 -73
- package/dist_ts/classes.claudetokenrefresh.d.ts +32 -0
- package/dist_ts/classes.claudetokenrefresh.js +77 -0
- package/dist_ts/classes.cli.d.ts +29 -7
- package/dist_ts/classes.cli.js +172 -78
- package/dist_ts/classes.codexharness.d.ts +6 -2
- package/dist_ts/classes.codexharness.js +7 -3
- package/dist_ts/classes.codexstatus.d.ts +5 -4
- package/dist_ts/classes.codexstatus.js +36 -19
- package/dist_ts/classes.credentialstore.d.ts +28 -2
- package/dist_ts/classes.credentialstore.js +41 -12
- package/dist_ts/classes.fileharness.d.ts +39 -19
- package/dist_ts/classes.fileharness.js +58 -24
- package/dist_ts/classes.limits.d.ts +46 -7
- package/dist_ts/classes.limits.js +106 -32
- package/dist_ts/classes.listrenderer.d.ts +17 -0
- package/dist_ts/classes.listrenderer.js +313 -0
- package/dist_ts/classes.login.d.ts +1 -1
- package/dist_ts/classes.login.js +1 -1
- package/dist_ts/classes.opencodeharness.d.ts +6 -2
- package/dist_ts/classes.opencodeharness.js +9 -5
- package/dist_ts/classes.operations.js +3 -2
- package/dist_ts/classes.tui.js +10 -6
- package/dist_ts/classes.watch.d.ts +108 -0
- package/dist_ts/classes.watch.js +219 -0
- package/dist_ts/classes.watchlock.d.ts +33 -0
- package/dist_ts/classes.watchlock.js +118 -0
- package/dist_ts/claudehttp.d.ts +39 -0
- package/dist_ts/claudehttp.js +83 -0
- package/dist_ts/cliargs.d.ts +36 -0
- package/dist_ts/cliargs.js +60 -0
- package/dist_ts/consoletable.d.ts +21 -0
- package/dist_ts/consoletable.js +63 -0
- package/dist_ts/helpers.d.ts +7 -0
- package/dist_ts/helpers.js +16 -1
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +4 -1
- package/dist_ts/interfaces.harness.d.ts +76 -15
- package/dist_ts/interfaces.list.d.ts +7 -1
- package/dist_ts/plugins.d.ts +7 -0
- package/dist_ts/plugins.js +6 -1
- package/dist_ts/ratelimit.d.ts +8 -0
- package/dist_ts/ratelimit.js +13 -0
- package/dist_ts/watchpolicy.d.ts +44 -0
- package/dist_ts/watchpolicy.js +82 -0
- package/package.json +5 -3
- package/readme.md +360 -106
- package/ts/00_commitinfo_data.ts +3 -3
- package/ts/accounts.ts +202 -34
- package/ts/classes.accountlist.ts +2 -216
- package/ts/classes.claudecodeharness.ts +126 -13
- package/ts/classes.claudecodelocks.ts +132 -0
- package/ts/classes.claudestatus.ts +88 -54
- package/ts/classes.claudetokenrefresh.ts +85 -0
- package/ts/classes.cli.ts +163 -63
- package/ts/classes.codexharness.ts +7 -3
- package/ts/classes.codexstatus.ts +29 -18
- package/ts/classes.credentialstore.ts +53 -9
- package/ts/classes.fileharness.ts +69 -31
- package/ts/classes.limits.ts +126 -33
- package/ts/classes.listrenderer.ts +328 -0
- package/ts/classes.login.ts +1 -1
- package/ts/classes.opencodeharness.ts +9 -5
- package/ts/classes.operations.ts +2 -1
- package/ts/classes.tui.ts +9 -5
- package/ts/classes.watch.ts +263 -0
- package/ts/classes.watchlock.ts +100 -0
- package/ts/claudehttp.ts +92 -0
- package/ts/cliargs.ts +71 -0
- package/ts/consoletable.ts +62 -0
- package/ts/helpers.ts +14 -0
- package/ts/index.ts +3 -0
- package/ts/interfaces.harness.ts +78 -6
- package/ts/interfaces.list.ts +7 -1
- package/ts/plugins.ts +9 -0
- package/ts/ratelimit.ts +14 -0
- package/ts/watchpolicy.ts +121 -0
package/readme.md
CHANGED
|
@@ -58,9 +58,11 @@ authswitch active # which account each provider is using right n
|
|
|
58
58
|
authswitch codex stash # save the active credential under its account email
|
|
59
59
|
authswitch codex list # Codex accounts, including an unsaved active login, with live status
|
|
60
60
|
authswitch codex use [email] # activate one; prompts when no email is given
|
|
61
|
-
authswitch
|
|
62
|
-
authswitch
|
|
61
|
+
authswitch opencode use [email] --stop # stop running OpenCode instances first
|
|
62
|
+
authswitch opencode use [email] --keep-running # switch without stopping anything
|
|
63
63
|
authswitch codex preuse <email> # send the default prompt without activating the account
|
|
64
|
+
authswitch watch # check usage every 2 minutes and switch when an account runs out
|
|
65
|
+
authswitch claude watch --threshold 90 --dry-run # report what it would switch, for one harness
|
|
64
66
|
authswitch codex current # print the account currently in use
|
|
65
67
|
authswitch codex drop <email> # forget a stash
|
|
66
68
|
authswitch codex doctor # report credential storage and remote-control state
|
|
@@ -81,25 +83,42 @@ the management TUI and human/JSON lists. With an AGL installation that supports
|
|
|
81
83
|
authswitch coordination, the CLI delegates credential changes to AGL. AGL owns
|
|
82
84
|
stopping and restarting its OpenCode runtime. Idle changes need no additional
|
|
83
85
|
restart confirmation; active work requires consent to wait for it to finish.
|
|
84
|
-
Every operation, including `use` and `stash`,
|
|
86
|
+
Every operation, including `use` and `stash`, works while OpenCode or Claude
|
|
85
87
|
Code is running: the credential file is written atomically, the outgoing login is
|
|
86
88
|
re-saved from disk first, and a write that races a native rewrite is detected and
|
|
87
|
-
refused rather than half-applied.
|
|
88
|
-
|
|
89
|
+
refused rather than half-applied. A running Claude Code picks a switch up on its next
|
|
90
|
+
request, so it never needs a restart. Status lookups never activate, switch or clear a
|
|
91
|
+
login; reading a saved Claude login whose access token has expired does renew that saved
|
|
92
|
+
copy's own tokens, and nothing else. `preuse` remains a Codex capability.
|
|
89
93
|
|
|
90
94
|
#### Running instances during a switch
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
**Claude Code picks a switch up live.** A running session re-reads its credential
|
|
97
|
+
file whenever the file changes, before its next request, and reloads `.claude.json`
|
|
98
|
+
when that changes (verified against Claude Code 2.1.273). So `use`, `stash` and a
|
|
99
|
+
backend activation never ask for Claude Code to be stopped and never suggest a
|
|
100
|
+
restart; a switch reports `Running Claude Code sessions use it from their next
|
|
101
|
+
request.` What authswitch must not do is write in the middle of that session's own
|
|
102
|
+
writes, so every Claude Code operation that reads or writes the native login runs
|
|
103
|
+
while holding Claude Code's own locks, taken with its own library and options: the
|
|
104
|
+
OAuth refresh lock (`<config dir>/.oauth_refresh.lock` and its legacy twin
|
|
105
|
+
`<config dir>.lock`), the credential write lock (`<config dir>/.storage-write.lock`)
|
|
106
|
+
and the global config lock (`.claude.json.lock`). A lock Claude Code holds is waited
|
|
107
|
+
for up to 15 seconds, without holding the others meanwhile, so a live session is
|
|
108
|
+
never blocked by a waiting switch; after that the operation fails with nothing
|
|
109
|
+
changed. A lock left behind by a Claude Code that died goes stale after its own
|
|
110
|
+
threshold and is taken over, as Claude Code itself would.
|
|
111
|
+
|
|
112
|
+
**OpenCode keeps the previous login until it restarts.** It holds that login in
|
|
113
|
+
memory and can write it back at its next OAuth token refresh. So before `use` and
|
|
114
|
+
before `stash` without `--keep`, authswitch lists OpenCode's own running instances --
|
|
115
|
+
pid, start time and command line -- and offers to stop them:
|
|
97
116
|
|
|
98
117
|
```
|
|
99
|
-
2
|
|
100
|
-
pid 4821 started 2026-09-16 14:02 UTC
|
|
101
|
-
pid 5533 started 2026-09-16 14:02 UTC
|
|
102
|
-
? Stop these 2
|
|
118
|
+
2 OpenCode process(es) are running:
|
|
119
|
+
pid 4821 started 2026-09-16 14:02 UTC opencode
|
|
120
|
+
pid 5533 started 2026-09-16 14:02 UTC opencode serve
|
|
121
|
+
? Stop these 2 OpenCode process(es) first? (y/N)
|
|
103
122
|
```
|
|
104
123
|
|
|
105
124
|
Answering yes sends `SIGTERM` to exactly those pids and waits up to ten seconds;
|
|
@@ -112,6 +131,10 @@ decide the same thing without a prompt:
|
|
|
112
131
|
| `--force-stop` | the same, then `SIGKILL` the ones that ignored `SIGTERM` |
|
|
113
132
|
| `--keep-running` | switch without stopping anything |
|
|
114
133
|
|
|
134
|
+
`--stop` and `--force-stop` also stop Claude Code's instances when you ask for it;
|
|
135
|
+
without them nothing about Claude Code's processes is listed or offered, and
|
|
136
|
+
`--keep-running` is accepted and changes nothing.
|
|
137
|
+
|
|
115
138
|
Without a terminal and without a flag, nothing is ever signalled: the instances are
|
|
116
139
|
listed, the switch proceeds, and the command says which flag would have changed that.
|
|
117
140
|
`SIGKILL` is only ever sent for `--force-stop`. Only pids authswitch enumerated for
|
|
@@ -123,8 +146,8 @@ On Windows the instances are listed but not stopped, because its process list do
|
|
|
123
146
|
not identify their owner. `stash --keep` needs none of this: it writes no native
|
|
124
147
|
file and is guarded by proving the native sources unchanged around the read.
|
|
125
148
|
|
|
126
|
-
After
|
|
127
|
-
one of them refreshing its token can put the previous account back. A backend
|
|
149
|
+
After an OpenCode switch that left instances running, the command says so explicitly,
|
|
150
|
+
because one of them refreshing its token can put the previous account back. A backend
|
|
128
151
|
activation through `AuthSwitchService` gets that same caveat in its outcome, but no
|
|
129
152
|
listing and no offer: a host has no terminal to consent with, and a supervisor that
|
|
130
153
|
completed the mutation itself owns its own runtime's lifecycle.
|
|
@@ -206,7 +229,23 @@ and model windows.
|
|
|
206
229
|
These endpoints require the `user:profile` scope and may reject expired or restricted
|
|
207
230
|
logins. A null reset timestamp is shown as **Not scheduled**. Billing renewal and
|
|
208
231
|
cancellation dates and earned reset credits are unavailable from these endpoints;
|
|
209
|
-
authswitch does not infer them from account creation or token expiry dates.
|
|
232
|
+
authswitch does not infer them from account creation or token expiry dates. The
|
|
233
|
+
Claude Code adapter declares that, so `limits` explains it once for the provider
|
|
234
|
+
instead of per account.
|
|
235
|
+
|
|
236
|
+
Nothing but Claude Code refreshes a Claude login, so a saved one stops being readable
|
|
237
|
+
a few hours after it was saved. Reading a **saved, inactive** account's status therefore
|
|
238
|
+
refreshes it first, exactly as Claude Code would: when its access token expires within
|
|
239
|
+
five minutes, authswitch exchanges the stored refresh token at Claude's token endpoint,
|
|
240
|
+
keeps every other field of the login, and writes the result only while the saved record
|
|
241
|
+
still holds the refresh token it sent. The refresh holds the account store's lock, so a
|
|
242
|
+
switch started at the same time waits for it and then activates the refreshed login. The
|
|
243
|
+
**active** login is never refreshed -- Claude Code owns it, and two parties refreshing one
|
|
244
|
+
login can invalidate the grant. A refusal is reported, never worked around: `Login expired
|
|
245
|
+
or was rejected. Log in again with Claude Code and save it.` for a rejected grant, and a
|
|
246
|
+
service failure, an unreachable service or an unsupported response leave the saved login
|
|
247
|
+
untouched. Requests carry `User-Agent: authswitch/<version>`, and HTTP 429 is reported as
|
|
248
|
+
rate limiting with the service's retry time -- never as an exhausted or expired account.
|
|
210
249
|
|
|
211
250
|
OpenCode and Claude saved records are separate owner-only files under
|
|
212
251
|
`~/.authswitch/opencode` and `~/.authswitch/claude`. Emails are accepted as account
|
|
@@ -251,6 +290,70 @@ inference failure exits with 1, invalid arguments or unsupported harnesses with
|
|
|
251
290
|
2, and an interrupted prompt with 130. Check `authswitch list` before repeating
|
|
252
291
|
an uncertain request. `--json` remains a list-only option.
|
|
253
292
|
|
|
293
|
+
### Watching usage and switching automatically
|
|
294
|
+
|
|
295
|
+
`authswitch watch` reads every account's usage on an interval and switches a harness to a
|
|
296
|
+
better saved account when the one in use runs out:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
authswitch watch # every harness that supports it, every 2m, at 95%
|
|
300
|
+
authswitch claude watch # one harness (also: authswitch watch claude)
|
|
301
|
+
authswitch watch --interval 5m --threshold 90 # check less often, switch earlier
|
|
302
|
+
authswitch watch --dry-run # report decisions, change nothing
|
|
303
|
+
authswitch watch --once # one check, then exit
|
|
304
|
+
authswitch watch --json # one JSON event per line
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
14:02:07 Watching Claude Code, Codex every 2m; switching at 95%.
|
|
309
|
+
14:02:07 Running Codex sessions keep the login they loaded; new sessions use the switched one.
|
|
310
|
+
14:02:09 Claude Code alice@example.com (type:Max) (5h:17%) (w:4%) ok
|
|
311
|
+
14:02:10 Codex switched bob@example.com → carol@example.com (type:Pro) (5h:3%) (w:12%) — Codex primary 96% ≥ 95%
|
|
312
|
+
14:04:09 Claude Code alice@example.com (type:Max) (5h:21%) (w:4%) ok
|
|
313
|
+
14:04:10 Codex carol@example.com (type:Pro) (5h:4%) (w:12%) ok
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Watch covers the harnesses whose adapter declares `autoSwitch`: Claude Code and Codex.
|
|
317
|
+
OpenCode is not watched, because most of its providers report no usage and its lifecycle
|
|
318
|
+
belongs to its supervisor. Naming an unsupported harness exits with 2, as does an interval
|
|
319
|
+
outside 1 minute to 1 day, a threshold outside 50 to 100, or any other argument the command
|
|
320
|
+
cannot act on. `--interval` takes seconds or a unit (`120`, `90s`, `2m`, `1h`, `1d`).
|
|
321
|
+
|
|
322
|
+
**How it decides.** Each check reads the harness's accounts and every account's usage, then
|
|
323
|
+
applies one rule set per harness:
|
|
324
|
+
|
|
325
|
+
- The account in use is *used up* when any of its general account windows has reached the
|
|
326
|
+
threshold. The watch then switches to the saved account whose account windows are all at
|
|
327
|
+
least ten points below the threshold, preferring the one whose longest window resets
|
|
328
|
+
soonest (capacity that expires first is used first), then the least used, then by name.
|
|
329
|
+
- When every other saved account is used up too, it switches to the one that is usable
|
|
330
|
+
again first, and only when that is more than one interval sooner than the account in use.
|
|
331
|
+
- An account limited only on a feature window (one model or surface) is traded for an
|
|
332
|
+
account that is below the margin on *every* window; without one it stays, because it
|
|
333
|
+
still serves everything else.
|
|
334
|
+
- A reading that is unknown, too old, rejected or rate limited never counts as used up and
|
|
335
|
+
never makes an account a target. The line then says so instead of saying `ok`.
|
|
336
|
+
|
|
337
|
+
**What it does and does not do.** A switch goes through the same path as `use`, including
|
|
338
|
+
AGL coordination when AGL manages the login, and never asks for a session of yours to be
|
|
339
|
+
stopped: Claude Code picks the new login up on its next request, and running Codex sessions
|
|
340
|
+
keep the login they loaded while new ones use the switched account (the start line says so
|
|
341
|
+
once). A Codex switch does stop and start Codex' own managed app-server, because `auth.json`
|
|
342
|
+
cannot be rewritten underneath it, so its remote-control connection drops for a moment. A
|
|
343
|
+
switch that is refused or fails is reported and decided again on the next check. Checks
|
|
344
|
+
never overlap; a check that takes longer than the interval is followed by the next one at
|
|
345
|
+
once. An account whose lookup a provider refused (HTTP 429) or whose lookup failed is not
|
|
346
|
+
asked about again until the time the provider named, or after a delay that doubles from one
|
|
347
|
+
interval up to half an hour, and the account's last reading stands in meanwhile while it is
|
|
348
|
+
less than ten minutes old. Claude's profile is read at most once an hour per account while
|
|
349
|
+
watching; usage is always live.
|
|
350
|
+
|
|
351
|
+
One watch runs per `AUTHSWITCH_HOME`: a second one exits with 1 and names the running pid,
|
|
352
|
+
and a lock left behind by a watch that died is taken over. Ctrl-C or `SIGTERM` cancels the
|
|
353
|
+
lookups in flight, releases the lock and exits with 0. `--once` exits with 0, or 1 when a
|
|
354
|
+
switch it attempted did not complete. `--json` writes one event per line
|
|
355
|
+
(`start`, `tick`, `switch`, `error`) with accounts, readings and reasons, and no credential.
|
|
356
|
+
|
|
254
357
|
### Management dashboard
|
|
255
358
|
|
|
256
359
|
`authswitch --tui` opens a resizable account table with subscription, usage and
|
|
@@ -258,7 +361,7 @@ reset availability, usage bars for the lead window and the next general window,
|
|
|
258
361
|
scrollable account details and an activity log.
|
|
259
362
|
With multiple registered adapters, a harness selector switches the view; account
|
|
260
363
|
selection and mutations always belong to the displayed harness. Status loads
|
|
261
|
-
incrementally through each account's
|
|
364
|
+
incrementally through each account's status API, without activating it.
|
|
262
365
|
|
|
263
366
|
| Key | Action |
|
|
264
367
|
| --- | --- |
|
|
@@ -307,6 +410,35 @@ authswitch codex use
|
|
|
307
410
|
|
|
308
411
|
The account argument also accepts the directory slug, the local part, or a unique prefix, so `authswitch codex use alice` resolves to `alice@example.com` when that is unambiguous. It is deliberately not a substring match — this argument decides which credential becomes active, and an incidental substring hit should never pick an account you did not name.
|
|
309
412
|
|
|
413
|
+
So you know what you switch to, every harness's switch names the account with a
|
|
414
|
+
badge of its type and its current usage:
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
? Which Claude Code account should be active?
|
|
418
|
+
❯ phil@example.com (active) (type:Max) (5h:12%) (w:75%)
|
|
419
|
+
alice@example.com (type:Pro, unverified) (w:40%)
|
|
420
|
+
...
|
|
421
|
+
Now active: alice@example.com (type:Pro, unverified) (w:40%)
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The badge appears in the account picker of `use`, in the dashboard's switch
|
|
425
|
+
confirmation and on the `Now active:` line that reports a completed switch (in the
|
|
426
|
+
dashboard, its activity log). `type` is the account type as `limits` shows it; `5h`
|
|
427
|
+
and `w` are the account's five-hour and weekly usage windows, rounded to whole
|
|
428
|
+
percent -- except that a window the provider reports below 100% never reads as the
|
|
429
|
+
exhausted `100%`. Feature limits never appear in it, and when several account windows share a
|
|
430
|
+
period without the provider picking one of them, that part is left out rather than
|
|
431
|
+
guessed.
|
|
432
|
+
|
|
433
|
+
Usage is read live and once per command. The picker looks up the offered
|
|
434
|
+
accounts, at most four at a time, before it asks; a switch named on the command line
|
|
435
|
+
switches first and then looks up only its target for the `Now active:` line. Whatever
|
|
436
|
+
has not answered after 2.5 seconds is left out and its requests are cancelled, so the
|
|
437
|
+
command still exits at once. Every part the provider did not report is omitted, and
|
|
438
|
+
with none known the account is named alone. Reading usage never delays the switch
|
|
439
|
+
beyond that deadline and never fails it. The dashboard reuses the status it already
|
|
440
|
+
shows and makes no extra lookup.
|
|
441
|
+
|
|
310
442
|
## What a switch actually does
|
|
311
443
|
|
|
312
444
|
`use` runs as one sequence, and steps 1 to 4 stop at the first failure:
|
|
@@ -333,86 +465,159 @@ by an interrupted write.
|
|
|
333
465
|
## Account status
|
|
334
466
|
|
|
335
467
|
`authswitch list` lists every registered harness; `authswitch codex list` limits the
|
|
336
|
-
output to Codex.
|
|
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
|
-
|
|
468
|
+
output to Codex. Harnesses appear in the order `limits` uses, by provider name. Each
|
|
469
|
+
section includes saved accounts and an identifiable active login even if it has
|
|
470
|
+
never been saved, and draws up to four tables under the harness's name:
|
|
471
|
+
|
|
472
|
+
```
|
|
473
|
+
Codex
|
|
474
|
+
┌───────────────────┬──────────────────┬────────────────────┬─────────────┬───────────────┐
|
|
475
|
+
│ Account │ Status │ Usage │ Renewal │ Earned resets │
|
|
476
|
+
├───────────────────┼──────────────────┼────────────────────┼─────────────┼───────────────┤
|
|
477
|
+
│ alice@example.com │ active, saved │ weekly 12% · 3d 4h │ 2026-10-08 │ 0 │
|
|
478
|
+
│ Pro │ 2026-09-17 13:32 │ 5h 37% · 2h 13m │ auto-renews │ │
|
|
479
|
+
├───────────────────┼──────────────────┼────────────────────┼─────────────┼───────────────┤
|
|
480
|
+
│ bob@example.com │ saved │ usage n/a │ n/a │ n/a │
|
|
481
|
+
│ Plus, unverified │ 2026-09-14 11:32 │ │ │ │
|
|
482
|
+
└───────────────────┴──────────────────┴────────────────────┴─────────────┴───────────────┘
|
|
483
|
+
|
|
484
|
+
┌───────────────────┬───────────────────────┬────────┬───────────┐
|
|
485
|
+
│ Account │ Limit type │ Used % │ Resets in │
|
|
486
|
+
├───────────────────┼───────────────────────┼────────┼───────────┤
|
|
487
|
+
│ alice@example.com │ Codex secondary │ 12% │ 3d 4h │
|
|
488
|
+
│ Pro │ Codex primary │ 37% │ 2h 13m │
|
|
489
|
+
│ │ Code review secondary │ 100% │ 3h 59m │
|
|
490
|
+
└───────────────────┴───────────────────────┴────────┴───────────┘
|
|
491
|
+
|
|
492
|
+
┌────────────────┬───────────────────┐
|
|
493
|
+
│ Metric │ alice@example.com │
|
|
494
|
+
├────────────────┴───────────────────┤
|
|
495
|
+
│ Saved login │
|
|
496
|
+
│ remote-control │ 1 │
|
|
497
|
+
└────────────────┴───────────────────┘
|
|
498
|
+
|
|
499
|
+
┌─────────────────┬─────────────────────────────────────────────────────────┐
|
|
500
|
+
│ Account │ Problem │
|
|
501
|
+
├─────────────────┼─────────────────────────────────────────────────────────┤
|
|
502
|
+
│ bob@example.com │ Subscription and limits: Login expired or was rejected. │
|
|
503
|
+
└─────────────────┴─────────────────────────────────────────────────────────┘
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
1. **Accounts** -- one row per account, divided from the next. **Account** is the
|
|
507
|
+
label (bold when active) with the account type beneath it, as in `limits`, and,
|
|
508
|
+
for a harness with several credential slots, the slot (`openai · Pro`).
|
|
509
|
+
**Status** says whether the login is active and saved, with the local date and
|
|
510
|
+
time of its last save beneath; an active login without a save reads `not saved`.
|
|
511
|
+
**Usage** holds one compact line per headline window, `<window> <used>% · <reset
|
|
512
|
+
countdown>`, lead window first as described below. **Renewal** is the same cell as
|
|
513
|
+
in `limits`, and a harness whose provider never reports billing dates, such as Claude
|
|
514
|
+
Code, gets one note beneath the table instead of an unexplained `n/a` per account.
|
|
515
|
+
**Earned resets** appears only when an account of the harness reports earned reset
|
|
516
|
+
credits: the available count, then one line per reported credit.
|
|
517
|
+
2. **Limits** -- the `limits` table of this harness, without its Provider and
|
|
518
|
+
Renewal columns, for the accounts that report windows.
|
|
519
|
+
3. **Details** -- one comparison of every remaining provider fact: **Metric** and
|
|
520
|
+
one column per account, split into pages when the terminal is too narrow, with a
|
|
521
|
+
full-width row per section. Facts already shown above are left out: those that
|
|
522
|
+
restate a displayed structured field, and one that only repeats the account's own
|
|
523
|
+
label. Dated rows, such as Codex's daily token totals, are merged across accounts
|
|
524
|
+
and keep the newest seven days. A sentence that every account of its section
|
|
525
|
+
states identically, such as Claude's note that it reports no billing dates, is
|
|
526
|
+
printed once beneath the table. Saved-login notes (Codex remote-control pairing,
|
|
527
|
+
account IDs) form the **Saved login** section; a note that only names the
|
|
528
|
+
account's credential slot is left out, because the Accounts table names it.
|
|
529
|
+
4. **Problems** -- a login changed outside authswitch first (`Active login changed
|
|
530
|
+
outside authswitch: expected alice@example.com, now bob@example.com (not saved) —
|
|
531
|
+
authswitch claude stash --keep saves it`), then every account's problems, then the
|
|
532
|
+
harness's own. Problems of one account that share a message are merged with their
|
|
533
|
+
topics (`Profile, usage:` above the message), and accounts with the same problems
|
|
534
|
+
share one block, whose **Account** cell names them all on one line.
|
|
535
|
+
|
|
536
|
+
Human output never prints a raw ISO timestamp: dates and times are local
|
|
537
|
+
(`2026-10-08`, `2026-09-17 13:32`), and a timestamp inside provider text is rewritten
|
|
538
|
+
the same way. `list --json` keeps every original value. A note beneath a table wraps to
|
|
539
|
+
the terminal, with its continuation lines under the note's prefix.
|
|
540
|
+
|
|
541
|
+
The lead window is the provider's headline window, otherwise weekly. Countdowns use
|
|
542
|
+
two units, such as `3d 4h`; `due` means the reported deadline has passed, without
|
|
543
|
+
claiming the service has refreshed the quota, and `not scheduled` means the provider
|
|
544
|
+
reported no reset. All countdowns share the same snapshot time. When the provider
|
|
545
|
+
picks a headline window (Claude does, for example its Fable weekly limit), that
|
|
546
|
+
window leads. Otherwise the general account windows lead, weekly first. A general
|
|
547
|
+
window is named by its period (`weekly`, `5h`) unless another shown general window
|
|
548
|
+
has the same period; a feature limit, or a general window sharing its period, is
|
|
549
|
+
named by its label (`Fable weekly 100% · 2d`). Feature limits such as Spark and code
|
|
550
|
+
review appear in the Limits table and lead the usage cell only when the provider
|
|
551
|
+
picks one as its headline. The TUI uses the same selection and names. Window
|
|
552
|
+
durations come from the provider, not from the plan name or primary/secondary
|
|
553
|
+
position; a Pro account with only a general weekly window gets no invented 5-hour
|
|
554
|
+
limit.
|
|
555
|
+
|
|
556
|
+
When accounts of one harness share a label, every table numbers the later ones
|
|
557
|
+
(`alice@example.com (2)`), so each account keeps its own column in the Details
|
|
558
|
+
comparison and reads the same everywhere. Missing data stays `n/a`; known zeroes
|
|
559
|
+
remain zero. Plans read as in `limits` and the switch badge (`Max`), and stored plans
|
|
560
|
+
are marked unverified (`Plus, unverified`). Renewal dates are shown only when an
|
|
561
|
+
adapter can provide live billing data. Codex uses the desktop app's account-check
|
|
562
|
+
endpoint for automatic renewal, explicit renewal/cancellation dates and subscription
|
|
563
|
+
expiry.
|
|
564
|
+
|
|
565
|
+
The layout follows the terminal width. From 120 columns every Accounts column stands
|
|
566
|
+
alone; from 100 the earned resets join the usage cell; from 60 the status joins the
|
|
567
|
+
account cell; below 60 one Status cell holds status, usage, renewal and earned resets;
|
|
568
|
+
below 40 every table becomes labelled lines, separated by a rule line per block. Each
|
|
569
|
+
`list` section covers one harness, so no colour line is drawn.
|
|
384
570
|
|
|
385
571
|
### Condensed views
|
|
386
572
|
|
|
387
|
-
`authswitch limits` answers "how much is left,
|
|
388
|
-
table. It is one row per account **and** limit type,
|
|
389
|
-
account email with each harness and each account kept
|
|
573
|
+
`authswitch limits` answers "how much is left, when does it come back, and when does
|
|
574
|
+
the subscription renew" in one table. It is one row per account **and** limit type,
|
|
575
|
+
sorted by provider and then by account email with each harness and each account kept
|
|
576
|
+
together, with a two-unit countdown (`2h 13m`, `3d 4h`, `45m`, `<1m`, `due`) and the
|
|
577
|
+
account's next billing event:
|
|
390
578
|
|
|
391
579
|
```
|
|
392
580
|
Account limits
|
|
393
|
-
|
|
394
|
-
│ Provider
|
|
395
|
-
|
|
396
|
-
┃ Claude Code
|
|
397
|
-
┃
|
|
398
|
-
┃
|
|
399
|
-
|
|
400
|
-
┃ Codex
|
|
401
|
-
┃
|
|
402
|
-
┃
|
|
403
|
-
|
|
404
|
-
┃
|
|
405
|
-
|
|
406
|
-
┃
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
┃
|
|
410
|
-
|
|
581
|
+
┌──────────────────┬───────────────────┬───────────────────┬────────┬───────────────┬─────────────┐
|
|
582
|
+
│ Provider │ Account │ Limit type │ Used % │ Resets in │ Renewal │
|
|
583
|
+
├──────────────────┼───────────────────┼───────────────────┼────────┼───────────────┼─────────────┤
|
|
584
|
+
┃ Claude Code │ phil@example.com │ Claude weekly │ 75% │ 4d 3h │ n/a │
|
|
585
|
+
┃ │ Max │ Claude five-hour │ 12% │ 1h 2m │ │
|
|
586
|
+
┃ │ │ Fable weekly │ 88% │ 2d 5h │ │
|
|
587
|
+
├──────────────────┼───────────────────┼───────────────────┼────────┼───────────────┼─────────────┤
|
|
588
|
+
┃ Codex │ alice@example.com │ Codex secondary │ 12% │ 3d 4h │ 2026-10-08 │
|
|
589
|
+
┃ │ Pro │ Codex primary │ 37% │ 2h 13m │ auto-renews │
|
|
590
|
+
┃ │ │ Code review secon │ 100% │ 3h 59m │ │
|
|
591
|
+
┃ │ │ dary │ │ │ │
|
|
592
|
+
┃ ├───────────────────┼───────────────────┼────────┼───────────────┼─────────────┤
|
|
593
|
+
┃ │ bob@example.com │ n/a │ n/a │ n/a │ n/a │
|
|
594
|
+
┃ │ Plus, unverified │ │ │ │ │
|
|
595
|
+
├──────────────────┼───────────────────┼───────────────────┼────────┼───────────────┼─────────────┤
|
|
596
|
+
┃ OpenCode / anthr │ anthropic API key │ n/a │ n/a │ n/a │ n/a │
|
|
597
|
+
┃ opic │ 20e751c3b707 │ │ │ │ │
|
|
598
|
+
┃ │ n/a │ │ │ │ │
|
|
599
|
+
├──────────────────┼───────────────────┼───────────────────┼────────┼───────────────┼─────────────┤
|
|
600
|
+
┃ OpenCode / opena │ alice@example.com │ Codex primary │ 0% │ not scheduled │ 2026-10-02 │
|
|
601
|
+
┃ i │ Pro │ │ │ │ cancels │
|
|
602
|
+
└──────────────────┴───────────────────┴───────────────────┴────────┴───────────────┴─────────────┘
|
|
411
603
|
|
|
412
604
|
n/a — Codex · bob@example.com: Subscription and limits: Login expired or was rejected.
|
|
413
|
-
n/a — OpenCode / anthropic · anthropic API key 20e751c3b707: This provider login does not expose a
|
|
605
|
+
n/a — OpenCode / anthropic · anthropic API key 20e751c3b707: This provider login does not expose a
|
|
606
|
+
supported subscription or quota API.
|
|
607
|
+
n/a — Claude Code: renewal dates are not exposed by Claude's account API.
|
|
414
608
|
```
|
|
415
609
|
|
|
610
|
+
The table fits 100 columns; at that width long provider names, account labels and
|
|
611
|
+
limit types wrap inside their cells.
|
|
612
|
+
|
|
613
|
+
**Renewal** spans the rows of its account with two lines: the local calendar date,
|
|
614
|
+
and beneath it `auto-renews` when the provider says the subscription renews then, or
|
|
615
|
+
`cancels` when it ends then -- a scheduled cancellation, or an expiry with automatic
|
|
616
|
+
renewal off. Anything else reads `n/a`: the date comes only from live billing data,
|
|
617
|
+
never from a plan, a quota reset, account creation or token expiry. A harness whose
|
|
618
|
+
provider never reports billing dates, such as Claude Code, gets one footnote instead
|
|
619
|
+
of an unexplained `n/a` per account.
|
|
620
|
+
|
|
416
621
|
A number is never invented. An account whose provider exposes no quota API, whose
|
|
417
622
|
lookup failed, or whose login cannot be read shows `n/a` in **Used %** and
|
|
418
623
|
**Resets in**, and the footnote under the table says why — one line per distinct
|
|
@@ -427,6 +632,14 @@ session and the weekly total keeps its kind in parentheses
|
|
|
427
632
|
(`Claude weekly (weekly_oauth)`). The provider column names the harness, qualified by
|
|
428
633
|
the credential slot when a harness owns several (`OpenCode / openai`).
|
|
429
634
|
|
|
635
|
+
Each provider and each account is named once. The **Provider** cell spans all rows of
|
|
636
|
+
its provider, and the **Account** cell spans the rows of its account with two lines:
|
|
637
|
+
the account label, and beneath it, dimmed, the **account type** — the provider's own
|
|
638
|
+
plan name with its first letter capitalised (`Max`, `Pro`, `Prolite`). A type read
|
|
639
|
+
from the stored login because the live lookup failed is marked `unverified` (`Plus, unverified`), and
|
|
640
|
+
an account whose provider reports no plan, such as an API-key login, shows `n/a`. A
|
|
641
|
+
provider row without an account has no type line.
|
|
642
|
+
|
|
430
643
|
**Used %** is coloured by the provider's own reading when it reports one: orange for
|
|
431
644
|
`warning`, red for `critical`, uncoloured for `normal`. Without a reading, `n/a` is
|
|
432
645
|
orange and an exhausted window (100% or more) is red. Colour is omitted when
|
|
@@ -435,7 +648,8 @@ columns.
|
|
|
435
648
|
|
|
436
649
|
Rows are grouped in two independent ways. A divider separates one account from the
|
|
437
650
|
next, so an account's limit types read as one block; a provider row without an
|
|
438
|
-
account is a block of its own.
|
|
651
|
+
account is a block of its own. Inside a provider's span the divider starts at the
|
|
652
|
+
**Account** column, and a change of provider draws a full rule. A line along the left edge (`┃`) marks the harness:
|
|
439
653
|
all its rows share one colour, including every credential slot of an OpenCode
|
|
440
654
|
installation, and the next harness takes the next colour (cyan, orange, green, pink,
|
|
441
655
|
blue, then again from the start). Without colour the `┃` edge still shows where the
|
|
@@ -443,8 +657,9 @@ data rows are. Groups follow harness and account ids, never labels: two accounts
|
|
|
443
657
|
that share an email are separate blocks, ordered by account id, and two harnesses
|
|
444
658
|
that share a label are ordered by harness id. A harness is placed by its first
|
|
445
659
|
provider name, so no other provider sorts between its slots. Below 40 columns the
|
|
446
|
-
table becomes labelled lines;
|
|
447
|
-
colour line is
|
|
660
|
+
table becomes labelled lines; every row repeats its provider and its account with
|
|
661
|
+
the type as a second line, a rule line separates the accounts, and no colour line is
|
|
662
|
+
drawn.
|
|
448
663
|
|
|
449
664
|
`authswitch active` answers "what am I logged in as" for every harness at once:
|
|
450
665
|
|
|
@@ -460,7 +675,8 @@ Active accounts
|
|
|
460
675
|
┃ OpenCode / openai │ alice@example.com │ not saved │ credential file │
|
|
461
676
|
└──────────────────────┴────────────────────────────────┴────────────┴─────────────────┘
|
|
462
677
|
|
|
463
|
-
note — OpenCode / openai · alice@example.com: This active login is not saved yet; authswitch cannot
|
|
678
|
+
note — OpenCode / openai · alice@example.com: This active login is not saved yet; authswitch cannot
|
|
679
|
+
restore it after a switch.
|
|
464
680
|
```
|
|
465
681
|
|
|
466
682
|
**Source** is where the shown login comes from: `credential file` is the harness's
|
|
@@ -478,19 +694,26 @@ finds a different account in that slot, the row's source reads
|
|
|
478
694
|
`credential file (changed)` and a note explains it:
|
|
479
695
|
|
|
480
696
|
```
|
|
481
|
-
note — Claude Code · bob@example.test: The
|
|
482
|
-
switch (
|
|
483
|
-
|
|
697
|
+
note — Claude Code · bob@example.test: The active login changed outside authswitch since the last
|
|
698
|
+
switch (for example a new login). It now holds bob@example.test, which is saved; authswitch
|
|
699
|
+
claude use alice@example.test switches back.
|
|
484
700
|
```
|
|
485
701
|
|
|
702
|
+
For a harness that does not pick up swaps live, such as OpenCode, the sentence also
|
|
703
|
+
names the other cause: a running instance that wrote its previous login back. An
|
|
704
|
+
unsaved current login adds how to keep it (`authswitch <harness> stash --keep saves
|
|
705
|
+
it`).
|
|
706
|
+
|
|
486
707
|
`authswitch <harness> current` prints the same sentence. The same account with a
|
|
487
708
|
rotated token is that account refreshing itself and is not reported; re-running the
|
|
488
709
|
switch re-arms the check against the file's current contents.
|
|
489
710
|
|
|
490
711
|
Both commands accept a harness qualifier (`authswitch claude limits`) and `--json`.
|
|
491
|
-
|
|
492
|
-
stopped
|
|
493
|
-
|
|
712
|
+
Neither changes which account is in use: they never activate, switch or clear a login and
|
|
713
|
+
they need no harness stopped. Like `list`, they may renew a saved Claude login's own tokens
|
|
714
|
+
when its access token has expired, because otherwise its usage cannot be read at all. Every
|
|
715
|
+
provider lookup is bounded by the same 10-second timeout and isolated per account as
|
|
716
|
+
`authswitch list`.
|
|
494
717
|
|
|
495
718
|
### JSON output
|
|
496
719
|
|
|
@@ -502,7 +725,12 @@ arguments to these commands exit with 2.
|
|
|
502
725
|
|
|
503
726
|
`limits --json` emits `IAccountLimits` and `active --json` emits `IActiveAccounts`:
|
|
504
727
|
the same rows as the tables, plus the machine-readable fields the tables condense —
|
|
505
|
-
`accountId`, `slotId`, `
|
|
728
|
+
`accountId`, `slotId`, `accountType` (`{ plan, source }` with the adapter's plan
|
|
729
|
+
name, such as `max` or `pro`, sanitised but not display-formatted, and `source` `live`
|
|
730
|
+
or `stored`; null when no type is known or the row names no account; both documents
|
|
731
|
+
carry it), the limits rows' `billing` (the account's reported `hasActiveSubscription`,
|
|
732
|
+
`autoRenew` and ISO `renewsAt`, `cancelsAt` and `expiresAt`, each omitted when not
|
|
733
|
+
reported; null when the provider reported none or the row names no account), `scope`, `windowSeconds`, the ISO `resetAt` beside the human
|
|
506
734
|
`resetsIn`, the provider's `severity` (`normal`, `warning`, `critical`, or null when
|
|
507
735
|
it gave none) and `headline` (true for the window the provider picks for a
|
|
508
736
|
single-value summary), the ISO `savedAt` beside the human `savedAgo`, the per-row
|
|
@@ -563,9 +791,9 @@ The returned usage plan is not a billing status. Billing dates come from the
|
|
|
563
791
|
separate account-check response, never from a quota reset, token expiry or stored
|
|
564
792
|
plan. Automatic renewal is on only when the provider says it will renew and no
|
|
565
793
|
cancellation date is scheduled. A non-renewing account without a cancellation date
|
|
566
|
-
does not acquire an invented date.
|
|
567
|
-
|
|
568
|
-
|
|
794
|
+
or expiry does not acquire an invented date. When renewal is off, a returned renewal
|
|
795
|
+
timestamp is never shown as a renewal; the Renewal cell shows the expiry as the day
|
|
796
|
+
the subscription ends. JSON keeps every returned field.
|
|
569
797
|
|
|
570
798
|
The billing request uses a verified Chromium-compatible User-Agent because the
|
|
571
799
|
service challenged the generic authswitch User-Agent in controlled comparisons.
|
|
@@ -595,7 +823,19 @@ process.exitCode = await cli.run(process.argv.slice(2));
|
|
|
595
823
|
|
|
596
824
|
Each adapter owns discovery, identity matching, save verification, credential
|
|
597
825
|
storage, switching and process lifecycle, live status APIs, and diagnostics.
|
|
598
|
-
`readState()` and `readAccountStatus()` must
|
|
826
|
+
`readState()` and `readAccountStatus()` must never activate, switch or clear a login.
|
|
827
|
+
`readAccountStatus()` may renew the tokens of the saved copy it is reading, when the provider
|
|
828
|
+
gives it no other way to answer (the Claude Code adapter does, under the store's lock, for
|
|
829
|
+
saved and inactive logins only); it must never touch the active login's credential. `readAccountStatus(accountId,
|
|
830
|
+
options)` receives an optional `IHarnessStatusOptions` whose `signal` cancels the
|
|
831
|
+
lookup's requests; authswitch aborts it when a status is no longer wanted, such as a
|
|
832
|
+
switch badge past its deadline or a closed dashboard, so an adapter should pass it to
|
|
833
|
+
every request it makes. Its optional `session` belongs to a caller that reads the same
|
|
834
|
+
accounts repeatedly, such as `authswitch watch`: `session.reuse(key, maxAgeMs, lookup)`
|
|
835
|
+
returns the value that key resolved to less than `maxAgeMs` ago, so an adapter can stop
|
|
836
|
+
repeating a lookup whose answer rarely changes (the Claude adapter reuses its profile for
|
|
837
|
+
an hour). Keys are the adapter's own and carry no secret; a rejected lookup is not
|
|
838
|
+
remembered, and outside a session every lookup is live. `switchAccount()` must
|
|
599
839
|
preserve and verify the outgoing credential without relying on the interactive
|
|
600
840
|
save prompt. `removeAccount()` removes only the saved copy. Status facts carry
|
|
601
841
|
provider-specific labels and units; unsupported capabilities are explicit. Adapter
|
|
@@ -629,7 +869,7 @@ count; if it fails, the limits response can still supply availability.
|
|
|
629
869
|
|
|
630
870
|
Each usage window may set `scope: 'account' | 'feature'` (default `account`).
|
|
631
871
|
Adapters mark model-specific or feature-specific quotas as `feature`; those quotas
|
|
632
|
-
appear in the
|
|
872
|
+
appear in the Limits table, JSON and details and lead the account summary only
|
|
633
873
|
when the provider marks one as its headline (see below). Weekly windows are identified by `durationSeconds: 604800`;
|
|
634
874
|
neither window labels nor plan names are parsed to infer limits.
|
|
635
875
|
|
|
@@ -651,7 +891,21 @@ Facts may supply a `section` label for grouped comparison and a `summaryKey`
|
|
|
651
891
|
(`subscription`, `billing`, `usageWindows`, `resets`, or `resetDetails`) when the
|
|
652
892
|
fact duplicates a structured field. The list hides such a fact only if that field
|
|
653
893
|
is displayed; JSON retains every fact. Adapters without this metadata continue to
|
|
654
|
-
display their facts in Additional information.
|
|
894
|
+
display their facts in Additional information. A fact label ending in an ISO date
|
|
895
|
+
(`Tokens 2026-09-15`) marks a daily series, which the list merges across accounts.
|
|
896
|
+
|
|
897
|
+
Three optional adapter declarations shape the shared views. `liveSwap: true` says that
|
|
898
|
+
running instances pick up a credential swap on their next request: the CLI and TUI
|
|
899
|
+
then offer no stop before a switch (an explicit `--stop` or `--force-stop` is still
|
|
900
|
+
honoured through `processes`), outcomes carry no restart advice, and the credential
|
|
901
|
+
drift sentence names only a new login as the cause. `renewalUnavailableReason` is a
|
|
902
|
+
lower-case clause for a provider that never reports renewal dates; `list` and `limits`
|
|
903
|
+
print it once as `<label>: <reason>`. `autoSwitch: true` says that every saved account's
|
|
904
|
+
usage can be read and that a switch needs no session of the user's stopped, which is what
|
|
905
|
+
`authswitch watch` requires before it switches a harness by itself; a helper process the
|
|
906
|
+
adapter manages itself may still be restarted by its own switch, as Codex' app-server is. The built-in Claude Code adapter declares
|
|
907
|
+
all three; Codex declares `autoSwitch: true` with `liveSwap: false`, and OpenCode declares
|
|
908
|
+
neither.
|
|
655
909
|
|
|
656
910
|
With multiple adapters, the guide first asks which harness to manage and provides
|
|
657
911
|
an action to choose another. Unqualified mutation commands prompt for a harness;
|
|
@@ -678,7 +932,7 @@ Account identity comes from the `id_token` inside the credential — the email c
|
|
|
678
932
|
|
|
679
933
|
**Other Codex clients keep running.** Stopping the managed app-server does not stop an editor extension that spawned its own. Close it, or expect it to keep using the credential it already loaded.
|
|
680
934
|
|
|
681
|
-
**A running OpenCode
|
|
935
|
+
**A running OpenCode can undo a switch.** It holds the previous login in memory and rewrites the credential file at its next token refresh. Authswitch lists those instances, offers to stop them, warns when any keep running, and detects the overwrite afterwards — but it cannot prevent it while they run. Restart them after a switch. A running Claude Code picks the switch up instead; its guarantee rests on the behaviour and lock protocol verified against Claude Code 2.1.273, and `authswitch claude active` still reports a login that changed outside authswitch.
|
|
682
936
|
|
|
683
937
|
**The Codex stash is keyed by email.** Two Codex workspaces with the same email
|
|
684
938
|
cannot both be saved under that key. Their account IDs are distinguished during
|