@modelprofile.com/authswitch 3.3.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 +42 -15
- package/dist_ts/accounts.js +98 -27
- package/dist_ts/classes.accountlist.d.ts +0 -25
- package/dist_ts/classes.accountlist.js +2 -216
- package/dist_ts/classes.claudecodeharness.d.ts +59 -1
- 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 +15 -2
- package/dist_ts/classes.claudestatus.js +88 -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 +14 -7
- package/dist_ts/classes.cli.js +125 -66
- package/dist_ts/classes.codexharness.d.ts +4 -0
- package/dist_ts/classes.codexharness.js +5 -1
- package/dist_ts/classes.codexstatus.d.ts +2 -1
- package/dist_ts/classes.codexstatus.js +25 -9
- package/dist_ts/classes.credentialstore.d.ts +28 -2
- package/dist_ts/classes.credentialstore.js +41 -12
- package/dist_ts/classes.fileharness.d.ts +37 -17
- package/dist_ts/classes.fileharness.js +58 -24
- package/dist_ts/classes.limits.d.ts +46 -7
- package/dist_ts/classes.limits.js +94 -36
- 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 +4 -0
- package/dist_ts/classes.opencodeharness.js +7 -3
- package/dist_ts/classes.operations.js +3 -2
- package/dist_ts/classes.tui.js +4 -3
- 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 +60 -11
- package/dist_ts/interfaces.list.d.ts +3 -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 +315 -110
- package/ts/00_commitinfo_data.ts +3 -3
- package/ts/accounts.ts +109 -34
- package/ts/classes.accountlist.ts +2 -219
- package/ts/classes.claudecodeharness.ts +125 -12
- package/ts/classes.claudecodelocks.ts +132 -0
- package/ts/classes.claudestatus.ts +87 -54
- package/ts/classes.claudetokenrefresh.ts +85 -0
- package/ts/classes.cli.ts +112 -53
- package/ts/classes.codexharness.ts +4 -0
- package/ts/classes.codexstatus.ts +17 -7
- package/ts/classes.credentialstore.ts +53 -9
- package/ts/classes.fileharness.ts +67 -29
- package/ts/classes.limits.ts +110 -36
- package/ts/classes.listrenderer.ts +328 -0
- package/ts/classes.login.ts +1 -1
- package/ts/classes.opencodeharness.ts +6 -2
- package/ts/classes.operations.ts +2 -1
- package/ts/classes.tui.ts +3 -2
- 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 +60 -5
- package/ts/interfaces.list.ts +3 -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
|
| --- | --- |
|
|
@@ -322,11 +425,12 @@ The badge appears in the account picker of `use`, in the dashboard's switch
|
|
|
322
425
|
confirmation and on the `Now active:` line that reports a completed switch (in the
|
|
323
426
|
dashboard, its activity log). `type` is the account type as `limits` shows it; `5h`
|
|
324
427
|
and `w` are the account's five-hour and weekly usage windows, rounded to whole
|
|
325
|
-
percent
|
|
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
|
|
326
430
|
period without the provider picking one of them, that part is left out rather than
|
|
327
431
|
guessed.
|
|
328
432
|
|
|
329
|
-
Usage is read live
|
|
433
|
+
Usage is read live and once per command. The picker looks up the offered
|
|
330
434
|
accounts, at most four at a time, before it asks; a switch named on the command line
|
|
331
435
|
switches first and then looks up only its target for the `Now active:` line. Whatever
|
|
332
436
|
has not answered after 2.5 seconds is left out and its requests are cancelled, so the
|
|
@@ -361,90 +465,159 @@ by an interrupted write.
|
|
|
361
465
|
## Account status
|
|
362
466
|
|
|
363
467
|
`authswitch list` lists every registered harness; `authswitch codex list` limits the
|
|
364
|
-
output to Codex.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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.
|
|
413
570
|
|
|
414
571
|
### Condensed views
|
|
415
572
|
|
|
416
|
-
`authswitch limits` answers "how much is left,
|
|
417
|
-
table. It is one row per account **and** limit type,
|
|
418
|
-
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:
|
|
419
578
|
|
|
420
579
|
```
|
|
421
580
|
Account limits
|
|
422
|
-
|
|
423
|
-
│ Provider
|
|
424
|
-
|
|
425
|
-
┃ Claude Code
|
|
426
|
-
┃
|
|
427
|
-
┃
|
|
428
|
-
|
|
429
|
-
┃ Codex
|
|
430
|
-
┃
|
|
431
|
-
┃
|
|
432
|
-
┃
|
|
433
|
-
┃
|
|
434
|
-
┃
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
┃
|
|
438
|
-
┃
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
┃
|
|
442
|
-
|
|
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
|
+
└──────────────────┴───────────────────┴───────────────────┴────────┴───────────────┴─────────────┘
|
|
443
603
|
|
|
444
604
|
n/a — Codex · bob@example.com: Subscription and limits: Login expired or was rejected.
|
|
445
|
-
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.
|
|
446
608
|
```
|
|
447
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
|
+
|
|
448
621
|
A number is never invented. An account whose provider exposes no quota API, whose
|
|
449
622
|
lookup failed, or whose login cannot be read shows `n/a` in **Used %** and
|
|
450
623
|
**Resets in**, and the footnote under the table says why — one line per distinct
|
|
@@ -502,7 +675,8 @@ Active accounts
|
|
|
502
675
|
┃ OpenCode / openai │ alice@example.com │ not saved │ credential file │
|
|
503
676
|
└──────────────────────┴────────────────────────────────┴────────────┴─────────────────┘
|
|
504
677
|
|
|
505
|
-
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.
|
|
506
680
|
```
|
|
507
681
|
|
|
508
682
|
**Source** is where the shown login comes from: `credential file` is the harness's
|
|
@@ -520,19 +694,26 @@ finds a different account in that slot, the row's source reads
|
|
|
520
694
|
`credential file (changed)` and a note explains it:
|
|
521
695
|
|
|
522
696
|
```
|
|
523
|
-
note — Claude Code · bob@example.test: The
|
|
524
|
-
switch (
|
|
525
|
-
|
|
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.
|
|
526
700
|
```
|
|
527
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
|
+
|
|
528
707
|
`authswitch <harness> current` prints the same sentence. The same account with a
|
|
529
708
|
rotated token is that account refreshing itself and is not reported; re-running the
|
|
530
709
|
switch re-arms the check against the file's current contents.
|
|
531
710
|
|
|
532
711
|
Both commands accept a harness qualifier (`authswitch claude limits`) and `--json`.
|
|
533
|
-
|
|
534
|
-
stopped
|
|
535
|
-
|
|
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`.
|
|
536
717
|
|
|
537
718
|
### JSON output
|
|
538
719
|
|
|
@@ -547,7 +728,9 @@ the same rows as the tables, plus the machine-readable fields the tables condens
|
|
|
547
728
|
`accountId`, `slotId`, `accountType` (`{ plan, source }` with the adapter's plan
|
|
548
729
|
name, such as `max` or `pro`, sanitised but not display-formatted, and `source` `live`
|
|
549
730
|
or `stored`; null when no type is known or the row names no account; both documents
|
|
550
|
-
carry it),
|
|
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
|
|
551
734
|
`resetsIn`, the provider's `severity` (`normal`, `warning`, `critical`, or null when
|
|
552
735
|
it gave none) and `headline` (true for the window the provider picks for a
|
|
553
736
|
single-value summary), the ISO `savedAt` beside the human `savedAgo`, the per-row
|
|
@@ -608,9 +791,9 @@ The returned usage plan is not a billing status. Billing dates come from the
|
|
|
608
791
|
separate account-check response, never from a quota reset, token expiry or stored
|
|
609
792
|
plan. Automatic renewal is on only when the provider says it will renew and no
|
|
610
793
|
cancellation date is scheduled. A non-renewing account without a cancellation date
|
|
611
|
-
does not acquire an invented date.
|
|
612
|
-
|
|
613
|
-
|
|
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.
|
|
614
797
|
|
|
615
798
|
The billing request uses a verified Chromium-compatible User-Agent because the
|
|
616
799
|
service challenged the generic authswitch User-Agent in controlled comparisons.
|
|
@@ -640,11 +823,19 @@ process.exitCode = await cli.run(process.argv.slice(2));
|
|
|
640
823
|
|
|
641
824
|
Each adapter owns discovery, identity matching, save verification, credential
|
|
642
825
|
storage, switching and process lifecycle, live status APIs, and diagnostics.
|
|
643
|
-
`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,
|
|
644
830
|
options)` receives an optional `IHarnessStatusOptions` whose `signal` cancels the
|
|
645
831
|
lookup's requests; authswitch aborts it when a status is no longer wanted, such as a
|
|
646
832
|
switch badge past its deadline or a closed dashboard, so an adapter should pass it to
|
|
647
|
-
every request it makes. `
|
|
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
|
|
648
839
|
preserve and verify the outgoing credential without relying on the interactive
|
|
649
840
|
save prompt. `removeAccount()` removes only the saved copy. Status facts carry
|
|
650
841
|
provider-specific labels and units; unsupported capabilities are explicit. Adapter
|
|
@@ -678,7 +869,7 @@ count; if it fails, the limits response can still supply availability.
|
|
|
678
869
|
|
|
679
870
|
Each usage window may set `scope: 'account' | 'feature'` (default `account`).
|
|
680
871
|
Adapters mark model-specific or feature-specific quotas as `feature`; those quotas
|
|
681
|
-
appear in the
|
|
872
|
+
appear in the Limits table, JSON and details and lead the account summary only
|
|
682
873
|
when the provider marks one as its headline (see below). Weekly windows are identified by `durationSeconds: 604800`;
|
|
683
874
|
neither window labels nor plan names are parsed to infer limits.
|
|
684
875
|
|
|
@@ -700,7 +891,21 @@ Facts may supply a `section` label for grouped comparison and a `summaryKey`
|
|
|
700
891
|
(`subscription`, `billing`, `usageWindows`, `resets`, or `resetDetails`) when the
|
|
701
892
|
fact duplicates a structured field. The list hides such a fact only if that field
|
|
702
893
|
is displayed; JSON retains every fact. Adapters without this metadata continue to
|
|
703
|
-
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.
|
|
704
909
|
|
|
705
910
|
With multiple adapters, the guide first asks which harness to manage and provides
|
|
706
911
|
an action to choose another. Unqualified mutation commands prompt for a harness;
|
|
@@ -727,7 +932,7 @@ Account identity comes from the `id_token` inside the credential — the email c
|
|
|
727
932
|
|
|
728
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.
|
|
729
934
|
|
|
730
|
-
**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.
|
|
731
936
|
|
|
732
937
|
**The Codex stash is keyed by email.** Two Codex workspaces with the same email
|
|
733
938
|
cannot both be saved under that key. Their account IDs are distinguished during
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* autocreated commitinfo by @push.rocks/commitinfo
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
|
-
name:
|
|
6
|
-
version:
|
|
7
|
-
description:
|
|
5
|
+
name: "@modelprofile.com/authswitch",
|
|
6
|
+
version: "4.0.0",
|
|
7
|
+
description: "Manage Codex, OpenCode and Claude Code accounts with guided switching and live usage status"
|
|
8
8
|
}
|