claude-multiacc 2.0.27 → 2.0.28

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/README.md CHANGED
@@ -16,8 +16,8 @@ completely independent — separate manifests, credentials, telemetry, and logs
16
16
  either provider can be used, re-authenticated, or emptied without touching the other.
17
17
 
18
18
  Tested on: macOS (bash 3.2, zsh, Claude Code 2.1.207+, Codex CLI 0.147) and Ubuntu 24.04
19
- (bash 5.2). The compatibility suite currently covers 918 sandboxed cases with no
20
- network/quota use; the unified selector has its own adversarial contract suite.
19
+ (bash 5.2). The compatibility suite exercises both providers with no network/quota
20
+ use; the unified selector and packed npm commands have their own contract suites.
21
21
 
22
22
  ## Unified selector for app-robot
23
23
 
@@ -366,149 +366,22 @@ and ≥2 accounts are eligible; service-spawned pipes take the plain exec path u
366
366
 
367
367
  ## Codex support (OpenAI Codex CLI)
368
368
 
369
- The exact same machinery, instantiated a second time for Codex. Everything documented
370
- above about the claude pool holds for the codex pool with these translations:
371
-
372
- | Claude side | Codex side |
373
- | --- | --- |
374
- | `bin/claude` shim, `claude-accounts` CLI | `bin/codex` shim, `codex-accounts` CLI |
375
- | pool `~/.claude-accounts` | pool `~/.codex-accounts` |
376
- | `CLAUDE_CONFIG_DIR` per-account dirs | `CODEX_HOME` per-account dirs |
377
- | `.credentials.json` / macOS Keychain item (OAuth, machine-local) | `auth.json` (ChatGPT OAuth, machine-local) |
378
- | `claude -p` auto-retry | `codex exec` auto-retry |
379
- | Anthropic OAuth usage and Fable buckets | Codex usage endpoint and per-model buckets |
380
- | `CLAUDE_*` pool controls | equivalent `CODEX_*` controls |
381
-
382
- Same selection rule (session gate first — `CODEX_MULTIACC_SESSION_GATE`, default 50 —
383
- then the 30-point weekly-headroom band with random spread, ≥90% any-bucket
384
- exclusion, peers rotate), same marker semantics (`.limited` cooldowns, `.expired` parks with
385
- credential/policy scoping and soft expiry), same fail-open guarantees, same sync
386
- safety guards. The `codex` shim engages the buffered auto-retry only for
387
- `codex exec` runs with finite stdin, exactly like `-p` on the claude side.
388
-
389
- Codex-specific notes:
390
-
391
- - **Auth is the Codex device-code sign-in by default**: `codex-accounts add` prints
392
- a URL + one-time code you can open in ANY browser (this machine, your laptop, a
393
- phone), so it works identically on a local Mac, over SSH, and on servers — there
394
- is no portable setup-token equivalent for Codex, so a server account is signed in
395
- ON the server the same way. `--browser` opts into the localhost browser-callback
396
- flow instead (only works when the browser runs on the same machine — the callback
397
- goes to `localhost:1455`). Either way the login runs with `CODEX_HOME` pointed at
398
- the account dir, and identity is verified offline from the id-token before
399
- anything is registered.
400
- - **`auth.json` is never synced** in either direction, for the same reason
401
- `.credentials.json` never is: the refresh token rotates, and two machines
402
- refreshing one grant strand each other. Sync pushes manifest + `config.toml`
403
- seeds + advisory limit state only.
404
- - **Windows are classified by length**, not by name: any window ≤6h counts as the
405
- self-healing session signal, anything longer as durable/weekly. A hard
406
- `limit_reached`/`allowed:false` verdict marks the account even if no window shows
407
- ≥90%, and a reshaped payload is still scanned recursively for window-shaped
408
- objects (fail open if nothing parses).
409
- - **Idle-account telemetry** stays fresh the same way: a long-expired access token is
410
- renewed via the OAuth refresh-token grant (`auth.openai.com/oauth/token`, the CLI's
411
- own public client id) and the rotated credential is persisted 0600. Overrides for
412
- tests: `CODEX_MULTIACC_TOKEN_URL`, `CODEX_MULTIACC_CLIENT_ID`,
413
- `CODEX_MULTIACC_USAGE_URL`.
414
- - **Earned usage resets are redeemed automatically at 95% used** (5% remaining) or
415
- when the backend reports the limit finished. The limits refresher checks the reset
416
- credits belonging to that same account, uses the soonest-expiring available credit,
417
- and writes a per-account/window idempotency key before redemption so a lost response
418
- or simultaneous fleet poll cannot spend a second reset. Set
419
- `CODEX_MULTIACC_AUTO_RESET=0` only for emergency rollback/testing.
420
- A finished-limit verdict takes precedence over a conflicting percentage. The
421
- redemption key excludes unused model windows whose reset clocks move between reads.
422
- Eligible accounts, including spent ones, check the credit endpoint every five
423
- minutes even if the usage summary still says zero credits. A newly granted reset
424
- is redeemed during that pass and clears the old limit; no-credit results are
425
- checked again next pass, without waiting for the weekly reset date.
426
- All authenticated accounts also report `usage.reset_credits_available` and
427
- `usage.reset_credits_fetched_at` (ISO 8601) in `list/status/limits --json`.
428
- The count comes from the credit endpoint, including below 95% used and with
429
- automatic redemption disabled. The scheduled usage cadence bounds these reads.
430
- Redemption shares that read, then reads back the remaining count after a POST;
431
- no subtraction or embedded usage summary substitutes for provider evidence.
432
- Zero means no available resets; missing fields mean the read was unavailable.
433
- Credit identifiers remain local and selection does not use these fields.
434
- - **API-key logins are rejected** — ChatGPT subscription accounts only, matching the
435
- addon's no-API-keys rule.
436
-
437
- ```bash
438
- codex-accounts add # sign in a new ChatGPT account (device code —
439
- # open the URL in any browser, enter the code)
440
- codex-accounts add --browser # localhost browser-callback flow instead
441
- codex-accounts import <email> --id acct-01 && codex-accounts adopt acct-01
442
- # adopt this machine's existing ~/.codex login
443
- codex-accounts list | status | expired | relogin | verify | limits | health | sync
444
- claude-multiacc codex <cmd> # same commands via the npm wrapper
445
- ```
369
+ Both pools use the same selection policy. See [Codex account commands and behavior](docs/CODEX.md).
446
370
 
447
371
  ## Install / update / uninstall
448
372
 
449
- ### npm (recommended)
450
-
451
373
  ```bash
452
- npm install -g claude-multiacc # postinstall wires up the shim, CLI, and scheduled jobs
453
- # or run it once without installing globally:
454
- npx claude-multiacc
374
+ npm install -g claude-multiacc@latest
375
+ codex-accounts verify
455
376
  ```
456
377
 
457
- `claude-multiacc` then wraps everything:
458
-
459
- ```bash
460
- claude-multiacc # install or update the addon (idempotent)
461
- claude-multiacc self-update # npm i -g @latest + re-install
462
- claude-multiacc uninstall [--purge-data]
463
- claude-multiacc list | status | add <email> | ... # passthrough to claude-accounts
464
- ```
465
-
466
- **Automatic updates:** a globally-installed package re-runs `install.sh` from its own
467
- postinstall, so `npm i -g claude-multiacc@latest` fully updates the shim, CLI, and jobs.
468
- A daily job (launchd on macOS, cron on Linux, 04:07) runs `claude-accounts self-update`
469
- to pull the latest release on its own — `npm i -g @latest` for npm installs, `git pull +
470
- reinstall` for git checkouts; set `CLAUDE_MULTIACC_AUTOUPDATE=0` before install to
471
- opt out. `update-notifier` also prints a one-line nudge when a newer version is on npm.
472
- (Note: newer npm versions gate install scripts; if postinstall is blocked, just run
473
- `claude-multiacc install` once.)
474
-
475
- New versions publish to npm automatically from `main` via GitHub Actions
476
- (`.github/workflows/publish.yml`, OIDC Trusted Publishing — no long-lived token after the
477
- first bootstrap publish; `scripts/auto-version.mjs` bumps the patch above the last release).
478
-
479
- ### From a git checkout
480
-
481
- ```bash
482
- git clone <repo> && cd claude-multiacc && ./install.sh # macOS or Linux, idempotent
483
- ./install.sh --server root@1.2.3.4 # override sync target
484
- git pull && ./install.sh # update (data untouched)
485
- ./install.sh --uninstall # restores stock behavior, keeps account data
486
- ./install.sh --uninstall --purge-data # also deletes ~/.claude-accounts
487
- ```
378
+ npm installs `claude-multiacc`, `claude-accounts`, `codex-accounts`, and `multiacc-select`
379
+ into its global bin directory, so account commands work in the current shell. Open a
380
+ new shell for the `claude` and `codex` shims. If auto-setup was skipped, run
381
+ `claude-multiacc install` to initialize the pools and shell setup.
488
382
 
489
- `self-update` auto-detects which of these you used (npm global vs git checkout).
490
-
491
- **Servers / machines without repo access:** install via **npm** — the registry is
492
- public, so the daily self-update needs no credentials at all (no rsync, no keys).
493
- A git checkout self-updates too, but only if `git pull` can authenticate; for a
494
- private repo on a server that means a read-only deploy key (the 138 server runs
495
- this way: `core.sshCommand` pinned to its deploy key). A plain copied tree is the
496
- one layout that can NOT self-update — don't ship the addon that way.
497
-
498
- What install does (all reversible, nothing else):
499
-
500
- - **macOS:** marked PATH block at the END of `~/.zshenv`, `~/.zprofile`, `~/.zshrc`
501
- (+ bash rc files if present) — end-of-file placement matters because those files
502
- re-prepend `~/.local/bin`; launchd agents `com.claude-multiacc.limits` +
503
- `.codex-limits` (5m), `.health` + `.codex-health` (weekly Mon morning), and
504
- `.update` (daily 04:07). Notes when this Mac keeps Claude Code logins in the
505
- Keychain (the pool reads them; ssh sessions cannot — mint portable tokens for
506
- accounts that must work from everywhere).
507
- - **Linux (root):** PATH block in `~/.bashrc` + `/etc/profile.d/claude-multiacc.sh`,
508
- shim symlinks at `/usr/local/bin/claude` and `/usr/local/bin/codex` (shadow via
509
- PATH order — on the systemd default PATH too; the original binaries are untouched),
510
- cron entries for limits/health (both providers) + the daily auto-update.
511
- - Both: `~/.claude-accounts/` skeleton + `accounts.json` manifest.
383
+ See [installation, updates, removal, and command-not-found recovery](docs/INSTALLATION.md)
384
+ for npm, npx, git checkout, and server instructions.
512
385
 
513
386
  Account lifecycle, synchronization, fleet integration, environment switches,
514
387
  verification, troubleshooting, and security guidance continue in
@@ -86,8 +86,7 @@ USAGE
86
86
  Skips accounts fetched in the last 45s and honors 429/refresh backoff;
87
87
  --force ignores all three.
88
88
  claude-accounts health limits + full verify; logs to health.log
89
- claude-accounts self-update update the addon (npm i -g @latest, or git
90
- pull + reinstall); logs to update.log
89
+ claude-accounts self-update update this npm/git install; logs to update.log
91
90
  claude-accounts post-sync (server side) seed dirs, fix perms, quick verify
92
91
 
93
92
  ENV
@@ -103,7 +102,7 @@ ENV
103
102
  EOF
104
103
  }
105
104
 
106
- require_manifest() { [ -f "$MANIFEST" ] || die "no manifest at $MANIFEST — run install.sh first"; }
105
+ require_manifest() { [ -f "$MANIFEST" ] || die "no manifest at $MANIFEST — run claude-multiacc install first"; }
107
106
 
108
107
  next_id() {
109
108
  local n=1 id
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // The installer adds bin/ to PATH for git and npx installs too.
3
+ import './cli.mjs';
package/bin/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  // claude-multiacc npm entrypoint. Thin dispatcher over the bash addon:
3
3
  // claude-multiacc [install] run install.sh (install or update, idempotent)
4
4
  // claude-multiacc uninstall [--purge-data]
5
- // claude-multiacc self-update npm i -g claude-multiacc@latest, then re-install
5
+ // claude-multiacc self-update update the running npm/git installation
6
6
  // claude-multiacc doctor environment + auth summary
7
7
  // claude-multiacc <accounts...> passthrough to claude-accounts (list/status/add/...)
8
8
  // A globally-installed package auto-runs install.sh on (re)install via postinstall, so
@@ -59,10 +59,10 @@ mirror to a deploy server. No API keys — Claude and ChatGPT subscription login
59
59
  USAGE
60
60
  claude-multiacc [install] install or update the addon (idempotent)
61
61
  claude-multiacc uninstall [--purge-data]
62
- claude-multiacc self-update update to the latest npm release + re-install
62
+ claude-multiacc self-update update this npm/git installation + re-install
63
63
  claude-multiacc doctor show environment + account health (both pools)
64
64
  claude-multiacc <cmd> [args...] run a claude-accounts command, e.g.
65
- list | status | add <email> | login <acct-NN> |
65
+ list | status | add [email] | login <acct-NN> |
66
66
  remove <acct-NN> | sync | verify | limits | health
67
67
  claude-multiacc codex <cmd> [args...] same commands for the Codex pool
68
68
  (runs codex-accounts <cmd>)
@@ -71,6 +71,12 @@ USAGE
71
71
  claude-multiacc -v | --version
72
72
  claude-multiacc -h | --help
73
73
 
74
+ ACCOUNT COMMANDS (also installed directly by npm)
75
+ claude-accounts verify verify the Claude pool
76
+ codex-accounts verify verify the Codex pool
77
+ claude-accounts --help all Claude account commands and options
78
+ codex-accounts --help all Codex account commands and options
79
+
74
80
  INSTALL
75
81
  npm install -g claude-multiacc # postinstall wires up the PATH shims + schedules
76
82
  # or, no global install:
@@ -91,23 +97,14 @@ switch (cmd) {
91
97
  case 'uninstall':
92
98
  status = runInstall(['--uninstall', ...argv.slice(1)]);
93
99
  break;
94
- case 'self-update': {
95
- console.log('Updating claude-multiacc from npm…');
96
- const up = sh('npm', ['install', '-g', `${pkg.name}@latest`]);
97
- if (up !== 0) {
98
- console.error(
99
- 'npm update failed — is this a global npm install? '
100
- + '(git checkouts update with: git pull && ./install.sh)',
101
- );
102
- status = up;
103
- break;
104
- }
105
- // The freshly-installed global package re-runs its own postinstall, so the addon
106
- // is already reconciled; nothing else to do here.
107
- console.log('Updated. Open a new shell to pick up any PATH changes.');
100
+ case 'self-update':
101
+ status = runAccounts(argv);
108
102
  break;
109
- }
110
103
  case 'doctor': {
104
+ if (argv.slice(1).some(arg => arg === '--help' || arg === '-h')) {
105
+ console.log(HELP);
106
+ break;
107
+ }
111
108
  console.log('== claude pool ==');
112
109
  const claudeStatus = runAccounts(['status']);
113
110
  console.log('\n== codex pool ==');
@@ -128,6 +125,7 @@ switch (cmd) {
128
125
  break;
129
126
  case '-h':
130
127
  case '--help':
128
+ case 'help':
131
129
  console.log(HELP);
132
130
  break;
133
131
  default:
@@ -90,8 +90,7 @@ USAGE
90
90
  stay selectable. Skips accounts fetched in the last 45s and honors
91
91
  429/refresh backoff; --force ignores all three.
92
92
  codex-accounts health limits + full verify; logs to health.log
93
- codex-accounts self-update update the addon (npm i -g @latest, or git
94
- pull + reinstall); logs to update.log
93
+ codex-accounts self-update update this npm/git install; logs to update.log
95
94
  codex-accounts post-sync (server side) seed dirs, fix perms, quick verify
96
95
 
97
96
  ENV
@@ -108,7 +107,7 @@ ENV
108
107
  EOF
109
108
  }
110
109
 
111
- require_manifest() { [ -f "$MANIFEST" ] || die "no manifest at $MANIFEST — run install.sh first"; }
110
+ require_manifest() { [ -f "$MANIFEST" ] || die "no manifest at $MANIFEST — run claude-multiacc install first"; }
112
111
 
113
112
  next_id() {
114
113
  local n=1 id
@@ -30,6 +30,10 @@ def _strict_object(pairs: list[tuple[str, object]]) -> dict:
30
30
 
31
31
 
32
32
  def main() -> int:
33
+ if sys.argv[1:] in (["--help"], ["-h"]):
34
+ print("usage: multiacc-select --request-json - --response-json -\n"
35
+ " multiacc-select --version")
36
+ return 0
33
37
  if sys.argv[1:] == ["--version"]:
34
38
  print(SELECTOR_VERSION)
35
39
  return 0
@@ -370,65 +370,7 @@ The codex shim honors the same switches spelled `CODEX_*`: `CODEX_ACCOUNT`,
370
370
  300s — weekly markers never clear early), `CODEX_ACCOUNTS_ROOT` (legacy
371
371
  `CODEX_ACCOUNTS_DIR`), `CODEX_MULTIACC_SYNC_TARGET`, `CODEX_MULTIACC_THRESHOLD`.
372
372
 
373
- ## Verification
373
+ ## Verification and troubleshooting
374
374
 
375
- ```bash
376
- tests/run-tests.sh # 645 sandboxed compatibility tests, no quota
377
- claude-accounts verify # real matrix: `claude -p "reply OK"` per authed account
378
- claude-accounts verify --quick# auth presence/expiry only, no inference
379
- claude-accounts limits # live per-bucket usage incl. the Fable bucket
380
- claude-accounts health # limits + full verify, logs to health.log, notifies on failure (Mac)
381
- ```
382
-
383
- Verified end-to-end on both machines (2026-07-13): full matrix PASS, 20-invocation shim
384
- spread clean, real project `-p` through the shim OK on Mac (`~/pipeline`) and server
385
- (`/opt/inapp-onboarding`), uninstall→stock→reinstall roundtrip byte-identical, three
386
- server systemd services (`vpn-server-management`, `inapp-onboarding`, `vpn-seo-panel`)
387
- repointed via `CLAUDE_BIN=/usr/local/bin/claude` and restarted healthy.
388
-
389
- ## Troubleshooting
390
-
391
- - **`claude` resolves to the real binary, not the shim** — open a new shell, or check
392
- that the marked block is the LAST PATH manipulation in your rc file
393
- (`grep -A2 'claude-multiacc >>>' ~/.zshrc`).
394
- - **An account never gets picked** — `claude-accounts status`: no auth on this machine,
395
- a dead login (`selectable: NO`, see `claude-accounts expired`), or an active `.limited`
396
- marker (shows bucket + minutes to reset).
397
- - **"Failed to authenticate: OAuth session expired and could not be refreshed"** — the
398
- login behind that run is dead. Run `claude-accounts expired` to see which, then
399
- `claude-accounts relogin`. Accounts in that state are excluded from selection
400
- automatically, so this only surfaces when *every* account needs a re-login (the shim
401
- then falls back to the machine's own `~/.claude` login) or when it was pinned with
402
- `CLAUDE_ACCOUNT`.
403
- - **"Please run /login · API Error: 401 OAuth access token is invalid"** — the portable
404
- setup-token is rejected even though its presence and `claude auth status` look healthy.
405
- The shim now runs a private first-use inference, parks a rejected token, and reselects
406
- before direct, TUI, or `--resume` work sees the 401. Run `claude-accounts verify` to
407
- check every token immediately; `claude-accounts expired` reports token-only accounts
408
- as `UNVERIFIED` until that proof exists.
409
- - **Everything marked limited** — the shim still runs: the still-serving limited accounts
410
- go through the same two cuts (session gate, then strict best-weekly) and one is handed
411
- out anyway; check `selection.log` for `all-limited fallback=` lines.
412
- - **Sync fails** — `tail ~/.claude-accounts/sync.log`; it's ssh/rsync to the manifest's
413
- `server` (BatchMode — needs key auth).
414
- - **A service bypasses the shim** — it spawns an absolute path. Point its env
415
- (`CLAUDE_BIN` etc.) at `/usr/local/bin/claude`. Docker-internal `claude` installs
416
- (e.g. openclaw live-test containers) are out of scope for a host shim.
417
- - **`add` sign-in says "Sign in again to continue"** — that's Claude's step-up
418
- authentication, not a tool error: granting a long-lived token needs a *recent* sign-in.
419
- Open a fresh incognito window, sign in to the target account at claude.ai **first**,
420
- then paste the link and approve. The approval scope is `user:inference`
421
- (*"Contribute to your Claude subscription usage"*) — that's correct and minimal.
422
- - **Limits look stale** — launchd: `launchctl list | grep claude-multiacc`;
423
- cron: `crontab -l | grep claude-multiacc`. The shim also kicks a refresh when data
424
- is >3 min old.
425
-
426
- ## Security notes & known risks
427
-
428
- - Tokens and credentials live as 0600 files under `~/.claude-accounts` (and
429
- `/root/.claude-accounts` on the server). Server compromise = account access.
430
- - `selection.log` records timestamps/account/cwd only — never prompt text.
431
- - Rotating multiple subscriptions to spread usage may be flagged by anti-abuse systems;
432
- accounts can be banned for limit circumvention. Known and accepted by the operator.
433
- - The usage endpoint is the internal one `/usage` consumes; if it changes shape, limit
434
- tracking fails open (accounts stay available, error-retry is the backstop).
375
+ See [verification commands and troubleshooting](VERIFICATION.md), including the
376
+ packed npm command checks, account health, shell PATH setup, and security notes.
package/docs/CODEX.md ADDED
@@ -0,0 +1,83 @@
1
+ # Codex support (OpenAI Codex CLI)
2
+
3
+ Codex uses the same machinery as the [Claude pool](../README.md), with these translations:
4
+
5
+ | Claude side | Codex side |
6
+ | --- | --- |
7
+ | `bin/claude` shim, `claude-accounts` CLI | `bin/codex` shim, `codex-accounts` CLI |
8
+ | pool `~/.claude-accounts` | pool `~/.codex-accounts` |
9
+ | `CLAUDE_CONFIG_DIR` per-account dirs | `CODEX_HOME` per-account dirs |
10
+ | `.credentials.json` / macOS Keychain item (OAuth, machine-local) | `auth.json` (ChatGPT OAuth, machine-local) |
11
+ | `claude -p` auto-retry | `codex exec` auto-retry |
12
+ | Anthropic OAuth usage and Fable buckets | Codex usage endpoint and per-model buckets |
13
+ | `CLAUDE_*` pool controls | equivalent `CODEX_*` controls |
14
+
15
+ Same selection rule (session gate first — `CODEX_MULTIACC_SESSION_GATE`, default 50 —
16
+ then the 30-point weekly-headroom band with random spread, ≥90% any-bucket
17
+ exclusion, peers rotate), same marker semantics (`.limited` cooldowns, `.expired` parks with
18
+ credential/policy scoping and soft expiry), same fail-open guarantees, same sync
19
+ safety guards. The `codex` shim engages the buffered auto-retry only for
20
+ `codex exec` runs with finite stdin, exactly like `-p` on the claude side.
21
+
22
+ Codex-specific notes:
23
+
24
+ - **Auth is the Codex device-code sign-in by default**: `codex-accounts add` prints
25
+ a URL + one-time code you can open in ANY browser (this machine, your laptop, a
26
+ phone), so it works identically on a local Mac, over SSH, and on servers — there
27
+ is no portable setup-token equivalent for Codex, so a server account is signed in
28
+ ON the server the same way. `--browser` opts into the localhost browser-callback
29
+ flow instead (only works when the browser runs on the same machine — the callback
30
+ goes to `localhost:1455`). Either way the login runs with `CODEX_HOME` pointed at
31
+ the account dir, and identity is verified offline from the id-token before
32
+ anything is registered.
33
+ - **`auth.json` is never synced** in either direction, for the same reason
34
+ `.credentials.json` never is: the refresh token rotates, and two machines
35
+ refreshing one grant strand each other. Sync pushes manifest + `config.toml`
36
+ seeds + advisory limit state only.
37
+ - **Windows are classified by length**, not by name: any window ≤6h counts as the
38
+ self-healing session signal, anything longer as durable/weekly. A hard
39
+ `limit_reached`/`allowed:false` verdict marks the account even if no window shows
40
+ ≥90%, and a reshaped payload is still scanned recursively for window-shaped
41
+ objects (fail open if nothing parses).
42
+ - **Idle-account telemetry** stays fresh the same way: a long-expired access token is
43
+ renewed via the OAuth refresh-token grant (`auth.openai.com/oauth/token`, the CLI's
44
+ own public client id) and the rotated credential is persisted 0600. Overrides for
45
+ tests: `CODEX_MULTIACC_TOKEN_URL`, `CODEX_MULTIACC_CLIENT_ID`,
46
+ `CODEX_MULTIACC_USAGE_URL`.
47
+ - **Earned usage resets are redeemed automatically at 95% used** (5% remaining) or
48
+ when the backend reports the limit finished. The limits refresher checks the reset
49
+ credits belonging to that same account, uses the soonest-expiring available credit,
50
+ and writes a per-account/window idempotency key before redemption so a lost response
51
+ or simultaneous fleet poll cannot spend a second reset. Set
52
+ `CODEX_MULTIACC_AUTO_RESET=0` only for emergency rollback/testing.
53
+ A finished-limit verdict takes precedence over a conflicting percentage. The
54
+ redemption key excludes unused model windows whose reset clocks move between reads.
55
+ Eligible accounts, including spent ones, check the credit endpoint every five
56
+ minutes even if the usage summary still says zero credits. A newly granted reset
57
+ is redeemed during that pass and clears the old limit; no-credit results are
58
+ checked again next pass, without waiting for the weekly reset date.
59
+ All authenticated accounts also report `usage.reset_credits_available` and
60
+ `usage.reset_credits_fetched_at` (ISO 8601) in `list/status/limits --json`.
61
+ The count comes from the credit endpoint, including below 95% used and with
62
+ automatic redemption disabled. The scheduled usage cadence bounds these reads.
63
+ Redemption shares that read, then reads back the remaining count after a POST;
64
+ no subtraction or embedded usage summary substitutes for provider evidence.
65
+ Zero means no available resets; missing fields mean the read was unavailable.
66
+ Credit identifiers remain local and selection does not use these fields.
67
+ - **API-key logins are rejected** — ChatGPT subscription accounts only, matching the
68
+ addon's no-API-keys rule.
69
+
70
+ ```bash
71
+ codex-accounts add # sign in a new ChatGPT account (device code —
72
+ # open the URL in any browser, enter the code)
73
+ codex-accounts add --browser # localhost browser-callback flow instead
74
+ codex-accounts import you@example.com --id acct-01
75
+ codex-accounts adopt acct-01
76
+ # adopt this machine's existing ~/.codex login
77
+ codex-accounts list
78
+ codex-accounts status
79
+ codex-accounts verify --quick # check local auth without inference
80
+ codex-accounts verify # real codex exec call per account
81
+ codex-accounts --help # all account commands and options
82
+ claude-multiacc codex status # same status command via the npm wrapper
83
+ ```
@@ -0,0 +1,91 @@
1
+ # Install / update / uninstall
2
+
3
+ ### npm (recommended)
4
+
5
+ ```bash
6
+ npm install -g claude-multiacc # postinstall wires up the shim, CLI, and scheduled jobs
7
+ # or run it once without installing globally:
8
+ npx claude-multiacc
9
+ ```
10
+
11
+ `claude-multiacc` then wraps everything:
12
+
13
+ ```bash
14
+ claude-multiacc # install or update the addon (idempotent)
15
+ claude-multiacc self-update # update the running installation + re-install
16
+ claude-multiacc list # Claude pool, via claude-accounts
17
+ claude-multiacc codex list # Codex pool, via codex-accounts
18
+ claude-multiacc doctor # status for both pools
19
+ claude-multiacc install --help # installer options
20
+ claude-multiacc uninstall # keep both account pools
21
+ # To delete both pools as well: claude-multiacc uninstall --purge-data
22
+ ```
23
+
24
+ After `claude-multiacc uninstall`, `npm uninstall -g claude-multiacc` also removes
25
+ the npm package and its command links.
26
+
27
+ npm links `claude-multiacc`, `claude-accounts`, `codex-accounts`, and `multiacc-select`
28
+ into its global bin directory. If `claude-multiacc` works, both account commands work
29
+ in that same shell, even when postinstall was skipped. The `claude` and `codex` shims
30
+ still need the installer's PATH setup: open a new shell after installation.
31
+
32
+ If an older installation says `codex-accounts: command not found`, update it:
33
+
34
+ ```bash
35
+ npm install -g claude-multiacc@latest
36
+ codex-accounts verify
37
+ ```
38
+
39
+ Before updating, `claude-multiacc codex verify` runs the same verification through
40
+ the existing npm wrapper. For git/npx installs, open a new shell or run the exact
41
+ `export PATH=...` command printed by the installer. Run `claude-multiacc install`
42
+ if auto-setup was skipped and the pool has no manifest yet.
43
+
44
+ **Automatic updates:** a globally-installed package re-runs `install.sh` from its own
45
+ postinstall, so `npm i -g claude-multiacc@latest` fully updates the shim, CLI, and jobs.
46
+ A daily job (launchd on macOS, cron on Linux, 04:07) runs `claude-accounts self-update`
47
+ to pull the latest release on its own — `npm i -g claude-multiacc@latest` for npm installs,
48
+ `git pull && ./install.sh` for git checkouts; set `CLAUDE_MULTIACC_AUTOUPDATE=0` before install to
49
+ opt out. `update-notifier` also prints a one-line nudge when a newer version is on npm.
50
+ (Note: newer npm versions gate install scripts; if postinstall is blocked, just run
51
+ `claude-multiacc install` once.)
52
+
53
+ New versions publish to npm automatically from `main` via GitHub Actions
54
+ (`.github/workflows/publish.yml`, OIDC Trusted Publishing — no long-lived token after the
55
+ first bootstrap publish; `scripts/auto-version.mjs` bumps the patch above the last release).
56
+
57
+ ### From a git checkout
58
+
59
+ ```bash
60
+ git clone https://github.com/gowalk-public/claude-multiacc.git
61
+ cd claude-multiacc
62
+ ./install.sh # macOS or Linux, idempotent
63
+ ./install.sh --server root@1.2.3.4 # override sync target
64
+ git pull && ./install.sh # update (data untouched)
65
+ ./install.sh --uninstall # restores stock behavior, keeps account data
66
+ ./install.sh --uninstall --purge-data # also deletes both account pools
67
+ ```
68
+
69
+ `self-update` auto-detects which of these you used (npm global vs git checkout).
70
+
71
+ **Servers / machines without repo access:** install via **npm** — the registry is
72
+ public, so the daily self-update needs no credentials at all (no rsync, no keys).
73
+ A git checkout self-updates too, but only if `git pull` can authenticate; for a
74
+ private repo on a server that means a read-only deploy key (the 138 server runs
75
+ this way: `core.sshCommand` pinned to its deploy key). A plain copied tree is the
76
+ one layout that can NOT self-update — don't ship the addon that way.
77
+
78
+ What install does (all reversible, nothing else):
79
+
80
+ - **macOS:** marked PATH block at the END of `~/.zshenv`, `~/.zprofile`, `~/.zshrc`
81
+ (+ bash rc files if present) — end-of-file placement matters because those files
82
+ re-prepend `~/.local/bin`; launchd agents `com.claude-multiacc.limits` +
83
+ `.codex-limits` (15m Claude / 5m Codex), `.health` + `.codex-health` (weekly Mon morning), and
84
+ `.update` (daily 04:07). Notes when this Mac keeps Claude Code logins in the
85
+ Keychain (the pool reads them; ssh sessions cannot — mint portable tokens for
86
+ accounts that must work from everywhere).
87
+ - **Linux (root):** PATH block in `~/.bashrc` + `/etc/profile.d/claude-multiacc.sh`,
88
+ shim symlinks at `/usr/local/bin/claude` and `/usr/local/bin/codex` (shadow via
89
+ PATH order — on the systemd default PATH too; the original binaries are untouched),
90
+ cron entries for limits/health (both providers) + the daily auto-update.
91
+ - Both: `~/.claude-accounts/` and `~/.codex-accounts/`, each with an `accounts.json` manifest.
@@ -13,6 +13,8 @@ multiacc-select --request-json - --response-json -
13
13
 
14
14
  The command reads one UTF-8 JSON object from stdin and writes one JSON object to
15
15
  stdout. `multiacc-select --version` reports the selector contract version.
16
+ `multiacc-select --help` (or `-h`) prints usage and exits successfully without
17
+ reading stdin. All three forms also work through `claude-multiacc select`.
16
18
 
17
19
  The request schema is `claude-multiacc/pool-selection.v2` and contains:
18
20
 
@@ -0,0 +1,68 @@
1
+ # Verification and troubleshooting
2
+
3
+ ## Verification
4
+
5
+ ```bash
6
+ tests/run-tests.sh # sandboxed compatibility tests, no quota
7
+ python3 tests/test_selector.py # unified selector contract
8
+ npm run test:commands # packed npm commands; run npm install --ignore-scripts first
9
+ claude-accounts verify # real matrix: `claude -p "reply OK"` per authed account
10
+ claude-accounts verify --quick # auth presence/expiry only, no inference
11
+ codex-accounts verify # real codex exec call per account
12
+ codex-accounts verify --quick # local Codex auth check, no inference
13
+ claude-accounts limits # live per-bucket usage incl. the Fable bucket
14
+ claude-accounts health # limits + full verify, logs to health.log, notifies on failure (Mac)
15
+ ```
16
+
17
+ Verified end-to-end on both machines (2026-07-13): full matrix PASS, 20-invocation shim
18
+ spread clean, real project `-p` through the shim OK on Mac (`~/pipeline`) and server
19
+ (`/opt/inapp-onboarding`), uninstall→stock→reinstall roundtrip byte-identical, three
20
+ server systemd services (`vpn-server-management`, `inapp-onboarding`, `vpn-seo-panel`)
21
+ repointed via `CLAUDE_BIN=/usr/local/bin/claude` and restarted healthy.
22
+
23
+ ## Troubleshooting
24
+
25
+ - **`claude` resolves to the real binary, not the shim** — open a new shell, or check
26
+ that the marked block is the LAST PATH manipulation in your rc file
27
+ (`grep -A2 'claude-multiacc >>>' ~/.zshrc`).
28
+ - **An account never gets picked** — `claude-accounts status`: no auth on this machine,
29
+ a dead login (`selectable: NO`, see `claude-accounts expired`), or an active `.limited`
30
+ marker (shows bucket + minutes to reset).
31
+ - **"Failed to authenticate: OAuth session expired and could not be refreshed"** — the
32
+ login behind that run is dead. Run `claude-accounts expired` to see which, then
33
+ `claude-accounts relogin`. Accounts in that state are excluded from selection
34
+ automatically, so this only surfaces when *every* account needs a re-login (the shim
35
+ then falls back to the machine's own `~/.claude` login) or when it was pinned with
36
+ `CLAUDE_ACCOUNT`.
37
+ - **"Please run /login · API Error: 401 OAuth access token is invalid"** — the portable
38
+ setup-token is rejected even though its presence and `claude auth status` look healthy.
39
+ The shim now runs a private first-use inference, parks a rejected token, and reselects
40
+ before direct, TUI, or `--resume` work sees the 401. Run `claude-accounts verify` to
41
+ check every token immediately; `claude-accounts expired` reports token-only accounts
42
+ as `UNVERIFIED` until that proof exists.
43
+ - **Everything marked limited** — the shim still runs: the still-serving limited accounts
44
+ go through the same two cuts (session gate, then strict best-weekly) and one is handed
45
+ out anyway; check `selection.log` for `all-limited fallback=` lines.
46
+ - **Sync fails** — `tail ~/.claude-accounts/sync.log`; it's ssh/rsync to the manifest's
47
+ `server` (BatchMode — needs key auth).
48
+ - **A service bypasses the shim** — it spawns an absolute path. Point its env
49
+ (`CLAUDE_BIN` etc.) at `/usr/local/bin/claude`. Docker-internal `claude` installs
50
+ (e.g. openclaw live-test containers) are out of scope for a host shim.
51
+ - **`add` sign-in says "Sign in again to continue"** — that's Claude's step-up
52
+ authentication, not a tool error: granting a long-lived token needs a *recent* sign-in.
53
+ Open a fresh incognito window, sign in to the target account at claude.ai **first**,
54
+ then paste the link and approve. The approval scope is `user:inference`
55
+ (*"Contribute to your Claude subscription usage"*) — that's correct and minimal.
56
+ - **Limits look stale** — launchd: `launchctl list | grep claude-multiacc`;
57
+ cron: `crontab -l | grep claude-multiacc`. The shim also kicks a refresh when data
58
+ is >3 min old.
59
+
60
+ ## Security notes & known risks
61
+
62
+ - Tokens and credentials live as 0600 files under `~/.claude-accounts` (and
63
+ `/root/.claude-accounts` on the server). Server compromise = account access.
64
+ - `selection.log` records timestamps/account/cwd only — never prompt text.
65
+ - Rotating multiple subscriptions to spread usage may be flagged by anti-abuse systems;
66
+ accounts can be banned for limit circumvention. Known and accepted by the operator.
67
+ - The usage endpoint is the internal one `/usage` consumes; if it changes shape, limit
68
+ tracking fails open (accounts stay available, error-retry is the backstop).