claude-multiacc 1.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.
@@ -0,0 +1,236 @@
1
+ # claude-multiacc — Multi-Account Claude Code Addon (random account + server sync)
2
+
3
+ **Goal:** every `claude` / `claude -p` invocation runs under a randomly picked account
4
+ (~10 Claude **subscription** accounts — no API keys, ever). Account set mirrors
5
+ automatically to the deploy server `root@138.197.36.107` on every add/remove.
6
+ `claude -p` must work in 100% of cases on both machines.
7
+
8
+ **Packaging requirement (added 2026-07-13):** ship as an **installable addon from this
9
+ git repo** for macOS + Linux that **never touches the original Claude Code app**, so the
10
+ app keeps working and updating exactly as-is.
11
+
12
+ **Limit-aware selection requirement (added 2026-07-13):** the shim must be smart about
13
+ usage limits — pick only accounts that still have limit headroom; when ANY limit bucket
14
+ of an account reaches **≥90% utilization**, the account gets marked and excluded from the
15
+ random pool until that bucket resets. **Fable model limits must be tracked as their own
16
+ bucket** (not just the 5-hour session limit): a Fable weekly bucket at ≥90% marks the
17
+ account even if session limit is fine. Subscription accounts only — limits are read via
18
+ each account's own OAuth usage endpoint, never via API keys.
19
+
20
+ **Status:** IMPLEMENTED & VERIFIED 2026-07-13 — all phases complete on both machines.
21
+ See README.md (tested reality). Deltas from plan, decided during implementation:
22
+ - **Account discovery result:** exactly 2 unique accounts exist across the fleet —
23
+ support@gowalkae.com (Mac default login) and hasan@gowalkqa.com (server default
24
+ login, found in /root/.claude). No dead credentials found anywhere.
25
+ - **`adopt` mechanism (new):** an existing machine login joins the pool as a dir
26
+ symlink to ~/.claude — single credential file, zero grant-fork/refresh-race risk.
27
+ Used for acct-01 (Mac) and acct-02 (server).
28
+ - **Asymmetric pools:** each machine's pool = accounts valid on THAT machine. Adding a
29
+ fresh account (`claude-accounts add`) now shows a sign-in link and captures a
30
+ subscription setup-token that works on BOTH machines, so new accounts are symmetric;
31
+ the two adopted accounts stay home-bound by design.
32
+ - **Auto-retry decision:** ON by default for -p, gated to finite stdin (tty/regular
33
+ file//dev/null) so service-spawned pipes can never hang; CLAUDE_SHIM_RETRY=0 disables.
34
+ - **Server call sites fixed:** CLAUDE_BIN=/usr/local/bin/claude in
35
+ vpn-server-management, inapp-onboarding, vpn-seo (.env backups kept), services
36
+ restarted healthy. /root/poll.sh pgrep kept working (shim execs the symlink path).
37
+ - Adversarially reviewed (11-agent workflow): fail-open on unset HOME, sync
38
+ mass-delete guards, atomic manifest writes, cooldown persistence all fixed + tested.
39
+
40
+ Original plan below. Verified facts from planning:
41
+ - This Mac stores Claude Code credentials per-config-dir in files (`.credentials.json`),
42
+ NOT in macOS Keychain (tested 2026-07-13, Claude Code 2.1.207) → per-dir logins coexist.
43
+ - `CLAUDE_CONFIG_DIR` isolation confirmed working on this Mac (isolated dir = "Not logged in").
44
+ - Real binary: `~/.local/bin/claude`. Interactive `claude` is a zsh alias adding
45
+ `--dangerously-skip-permissions` (kept; it composes with the shim via PATH).
46
+ - Current login: support@gowalkae.com (Claude Max) → becomes `acct-01`.
47
+
48
+ ---
49
+
50
+ ## Addon requirement — analysis & design decisions
51
+
52
+ **Feasibility:** clean. The whole mechanism uses only documented, version-stable
53
+ interfaces — PATH resolution, `CLAUDE_CONFIG_DIR`, `CLAUDE_CODE_OAUTH_TOKEN` — so the
54
+ addon needs zero modifications to the app and is agnostic to Claude Code versions.
55
+
56
+ **Non-invasive guarantees (hard rules):**
57
+ 1. Never writes to the Claude Code binary, its install dir, npm/native update machinery,
58
+ or the contents of `~/.claude` app files. (Exception: the shared-history design
59
+ symlinks *into* `~/.claude/projects` — user data, not app files.)
60
+ 2. Shim resolves the real binary **dynamically at exec time** (next `claude` on PATH
61
+ after itself; fallback to known locations `~/.local/bin/claude`,
62
+ `/usr/local/bin/claude.real-candidates`) → survives `claude update`, reinstalls, and
63
+ install-location changes. No hardcoded paths baked at install time.
64
+ 3. Everything the addon creates lives in exactly three places, all reversible:
65
+ - the cloned repo itself (code)
66
+ - `~/.claude-accounts/` (account data: manifest, per-acct config dirs, tokens, logs)
67
+ - one marked PATH block in shell rc (`~/.zshenv` on macOS, `~/.bashrc` +
68
+ `/etc/profile.d/claude-multiacc.sh` on Linux) and, on Linux servers,
69
+ a shim at `/usr/local/bin/claude` (shadows via PATH order; original untouched)
70
+ 4. `install.sh --uninstall` removes all of the above (accounts dir only with
71
+ `--purge-data`), restoring stock behavior byte-for-byte.
72
+
73
+ **Install / update UX:**
74
+ ```
75
+ git clone <repo> && cd claude-multiacc && ./install.sh # macOS or Linux, idempotent
76
+ git pull && ./install.sh # addon update (data untouched)
77
+ ./install.sh --uninstall [--purge-data] # full removal
78
+ ```
79
+
80
+ **Portability:** shim + CLI written for bash 3.2 (macOS stock) and bash 4+ (Linux);
81
+ no GNU-only flags; server mode auto-detected (root/Linux → /usr/local/bin shim,
82
+ systemd-compatible PATH placement).
83
+
84
+ ## Repo layout
85
+
86
+ ```
87
+ claude-multiacc/
88
+ install.sh idempotent installer/uninstaller, macOS + Linux
89
+ bin/claude the shim (PATH-shadows the real binary, never replaces it)
90
+ bin/claude-accounts helper CLI: list|status|add|import|remove|mint|sync|verify|limits
91
+ lib/common.sh shared functions (account discovery, manifest, rsync filters)
92
+ CLAUDE_ACCS_TASK.md this plan
93
+ README.md REQUIRED DELIVERABLE — written when implementation completes
94
+ ```
95
+
96
+ ## Architecture
97
+
98
+ ```
99
+ MAC (source of truth) SERVER 138.197.36.107 (mirror)
100
+ ~/.claude-accounts/ /root/.claude-accounts/
101
+ accounts.json <- manifest accounts.json (synced)
102
+ acct-01/ .. acct-NN/ acct-NN/server.token (synced, 600)
103
+ .credentials.json (per-dir /login) acct-NN/ (= CLAUDE_CONFIG_DIR, own history)
104
+ .claude.json, settings.json (seeded) /usr/local/bin/claude -> repo shim
105
+ server.token (setup-token) /root/claude-multiacc/ (addon repo, git pull to update)
106
+ projects/ -> ~/.claude/projects
107
+ selection.log, sync.log
108
+ ```
109
+
110
+ - **Mac auth:** normal `/login` per config dir (subscription OAuth; files coexist per-dir,
111
+ auto-refresh). NO API keys anywhere.
112
+ - **Server auth:** per-account long-lived token from `claude setup-token` (subscription
113
+ feature, headless-safe, no refresh rotation → no token races between machines;
114
+ inference-only — fine, server only runs `claude -p`). We deliberately do NOT copy
115
+ `.credentials.json` to the server: two machines refreshing one OAuth refresh token can
116
+ invalidate each other → violates the 100% rule.
117
+ - **Sync:** one-way Mac → server (Mac = source of truth): rsync `accounts.json` +
118
+ `acct-*/server.token` with delete-propagation, fired automatically at the end of
119
+ `claude-accounts add|remove` (and runnable manually via `claude-accounts sync`).
120
+ Addon code on the server updates via `git pull`, not via sync.
121
+
122
+ ## Shim selection logic (identical file on both machines)
123
+
124
+ 1. `CLAUDE_CONFIG_DIR` already set → pass straight through (scripts can pin).
125
+ 2. `CLAUDE_ACCOUNT=acct-03` set → use that account (even if limit-marked — explicit wins).
126
+ 3. Else pick uniformly at random among accounts with valid auth
127
+ (`.credentials.json` on Mac; `server.token` on server → also exports
128
+ `CLAUDE_CODE_OAUTH_TOKEN`) **that are not limit-marked** (no `.limited` marker).
129
+ If EVERY account is marked, fall back to the least-utilized account and log a warning —
130
+ degraded service beats a hard failure (the 100% rule).
131
+ 4. Append `timestamp account cwd` to `selection.log` (never prompt text); print NOTHING;
132
+ `exec` the dynamically-resolved real binary — stdin/stdout/exit codes pass through
133
+ byte-identically → `-p` pipe-safe.
134
+
135
+ ## Limit-aware selection (smart marking)
136
+
137
+ - `claude-accounts limits` refreshes per-account usage by calling the account's OAuth
138
+ usage endpoint with that account's own subscription token (the same data `/usage`
139
+ shows: 5-hour session bucket, weekly bucket, and per-model-tier weekly buckets —
140
+ **Fable tracked as its own bucket**). Results cached in `acct-NN/limits.json`
141
+ (utilization % + reset time per bucket).
142
+ - Marking rule: any bucket ≥ **90%** → create `acct-NN/.limited` (contains reason bucket
143
+ + reset timestamp). Marker auto-clears when the bucket's reset time passes or a refresh
144
+ shows utilization back under threshold.
145
+ - Refresh cadence: cron/launchd every **1 min** on both machines (each machine queries
146
+ independently — usage is per-account global, so both see the same numbers), plus
147
+ opportunistic refresh when the shim notices a stale (>3 min) limits.json.
148
+ - The usage endpoint is the one Claude Code itself uses for `/usage` (undocumented,
149
+ subscription-OAuth only — no API keys involved). It must be captured and validated
150
+ during implementation; if it ever breaks, the shim degrades gracefully: no fresh limit
151
+ data → treat account as available, rely on error-retry instead (never block work on
152
+ missing telemetry).
153
+
154
+ ## Phases
155
+
156
+ ### Phase 0 — Audit call sites (Mac + server)
157
+ - Mac: grep all projects for `claude` invocations (shell scripts, package.json, Makefiles,
158
+ cron, launchd plists). Fix absolute-path calls and bare-`sh` minimal-PATH contexts to
159
+ resolve the shim. IDE/desktop integrations spawn the binary directly → list them
160
+ (they'll use the default account).
161
+ - Server: same audit over /root, systemd units, crontabs, deploy scripts. Check whether
162
+ Claude Code is installed; if not, install it (official installer — the addon never
163
+ bundles or patches the app).
164
+
165
+ ### Phase 1 — Build the addon (this repo)
166
+ Shim, `claude-accounts` CLI, `install.sh` (+ uninstall), lib. Bash 3.2-compatible.
167
+ Commit to the repo; this repo IS the distribution artifact.
168
+
169
+ ### Phase 2 — Install on Mac + discover existing accounts
170
+ - `./install.sh` → PATH block, `~/.claude-accounts/` skeleton, acct-01 = existing login.
171
+ - **Local account discovery:** search for every credential this Mac has ever held —
172
+ `~/.claude/backups/`, `~/.claude.json*` (oauthAccount history), any `.credentials.json`
173
+ on disk, stray `CLAUDE_CONFIG_DIR` dirs. Dedupe by account email keeping the **latest**
174
+ auth per unique account; `claude-accounts import` each; validate each with
175
+ `claude auth status` + minimal `-p "reply OK"`. Report accounts found-but-dead
176
+ (need re-login) separately.
177
+ - Seed each acct dir: copy current `.claude.json` (strip `oauthAccount`) + `settings.json`
178
+ → preserves project trust/onboarding so headless mode never prompts; symlink
179
+ `projects/` → `~/.claude/projects` (shared history/memory; `--continue`/`--resume`
180
+ work regardless of picked account).
181
+
182
+ ### Phase 3 — Auth ceremony (user, ~3-4 min per account, one-time)
183
+ Per account, in ONE incognito browser window signed into that claude.ai account:
184
+ 1. `CLAUDE_ACCOUNT=acct-NN claude` → `/login` (press `c` to copy URL into incognito) →
185
+ `/status` confirms email.
186
+ 2. `claude-accounts mint acct-NN` (wraps `claude setup-token`) → second OAuth click →
187
+ token saved to `acct-NN/server.token` (600). This is the server credential.
188
+ 3. CLI verifies and records the email in `accounts.json`.
189
+
190
+ ### Phase 4 — Server bootstrap + sync
191
+ - On server: `git clone` this repo → `./install.sh` (detects Linux/root → shim at
192
+ `/usr/local/bin/claude`, which precedes the real binary in login-shell AND systemd
193
+ default PATH; original binary untouched).
194
+ - Mac: `claude-accounts sync` pushes manifest + tokens; add/remove auto-sync
195
+ (removals propagate). All syncs log to `sync.log`; non-zero exit = loud failure.
196
+ - Post-sync hook re-runs the server verification matrix automatically.
197
+
198
+ ### Phase 5 — Verification (the 100% requirement)
199
+ - Mac matrix: per acct dir → `claude auth status` + `claude -p "reply OK"` all pass.
200
+ - Server matrix: per token → `claude -p "reply OK"` all pass.
201
+ - Shim spread: ~20 invocations per machine → all accounts hit, zero failures.
202
+ - Real-world: run one actual project `-p` command through the shim on each machine.
203
+ - Non-invasiveness check: `claude update` / version check still works; uninstall on a
204
+ scratch account restores stock behavior.
205
+
206
+ ### Phase 6 — Ops + documentation
207
+ - `claude-accounts status`: per-account email, Mac auth health, server token presence/age,
208
+ last-picked time.
209
+ - Weekly health check (launchd on Mac, cron on server): full auth matrix; alert on any
210
+ failure so a dead account can't silently eat ~10% of `-p` calls.
211
+ - Limits refresh every 1 min (launchd/cron) → `.limited` markers per the 90% rule;
212
+ `claude-accounts status` shows per-bucket utilization incl. the Fable bucket.
213
+ - OAuth logins auto-refresh on use; `setup-token` tokens live ~1 year → health check
214
+ warns ahead; renewal = re-run `claude-accounts mint` for that account.
215
+ - **README.md (required, written only once everything above is implemented AND tested):**
216
+ what it is, install (macOS/Linux), auth ceremony walkthrough, adding/removing accounts,
217
+ pinning (`CLAUDE_ACCOUNT`), server sync setup, verification commands, uninstall,
218
+ troubleshooting. README must reflect tested reality, not the plan.
219
+
220
+ ## Open decisions
221
+ - [ ] **Auto-retry** (recommended): for `-p` invocations only, on auth/rate-limit error
222
+ patterns, retry once with a different random account; legit non-zero exits pass
223
+ through. Default on/off TBD (`CLAUDE_SHIM_RETRY`).
224
+ - [ ] Final account count/names (user adds ~10 subscription accounts).
225
+ - [ ] Server: does anything call `claude` on it today? (Phase 0 answers.)
226
+
227
+ ## Risks
228
+ - Rotating ~10 subscriptions to spread usage is a pattern Anthropic anti-abuse may flag;
229
+ accounts can be banned for limit circumvention. Raised with user 2026-07-13.
230
+ - Tokens on server are plain files under /root (600/700) — server compromise = account access.
231
+ - `setup-token` tokens are inference-only (no Remote Control) — acceptable for `-p` usage.
232
+ - macOS installs that store creds in Keychain (not this Mac) would break per-dir `/login`
233
+ isolation; the addon must detect this at install time and warn (token-based fallback).
234
+ - The usage/limits endpoint is internal (what `/usage` consumes) and may change without
235
+ notice; limit tracking must fail OPEN (account treated available, retry logic as backstop)
236
+ so limits telemetry can never break the 100% `-p` guarantee.
package/README.md ADDED
@@ -0,0 +1,263 @@
1
+ # claude-multiacc
2
+
3
+ Multi-account addon for Claude Code: every `claude` / `claude -p` invocation runs under a
4
+ randomly picked **subscription** account with limit headroom (no API keys, ever). The
5
+ account set mirrors automatically from the Mac (source of truth) to the deploy server.
6
+
7
+ Installs as a PATH shim + helper CLI from this repo. **Never touches the Claude Code app**:
8
+ no binary edits, no install-dir writes, survives `claude update` — the real binary is
9
+ resolved dynamically at exec time.
10
+
11
+ Tested on: macOS (bash 3.2, zsh, Claude Code 2.1.207) and Ubuntu 24.04
12
+ (bash 5.2, Claude Code 2.1.201). Test suite: `tests/run-tests.sh` (35 sandboxed tests,
13
+ no network / no quota — passes on both platforms).
14
+
15
+ ---
16
+
17
+ ## How it works
18
+
19
+ ```
20
+ MAC (source of truth) SERVER (mirror)
21
+ ~/.claude-accounts/ /root/.claude-accounts/
22
+ accounts.json <- manifest accounts.json (synced)
23
+ acct-01 -> ~/.claude ("adopted") acct-01/server.token (synced, 600, once minted)
24
+ acct-02/ (registered) acct-02 -> /root/.claude ("adopted")
25
+ server.token / .credentials.json
26
+ selection.log sync.log health.log /usr/local/bin/claude -> repo shim
27
+ repo bin/ first on PATH (rc-file block) /root/claude-multiacc/ (addon repo)
28
+ ```
29
+
30
+ **Shim selection order** (identical file on both machines, `bin/claude`):
31
+
32
+ 1. `CLAUDE_CONFIG_DIR` or `CLAUDE_CODE_OAUTH_TOKEN` already set → pass straight through.
33
+ 2. `CLAUDE_ACCOUNT=acct-NN` → use that account (even if limit-marked — explicit wins).
34
+ 3. Among accounts valid **on this machine** (`.credentials.json` or `server.token`
35
+ present) and not excluded, pick the one with the **most remaining headroom** (see
36
+ below). Ties break randomly to spread load. `CLAUDE_SHIM_SELECT=random` restores plain
37
+ uniform-random selection.
38
+ 4. If every account is excluded → the highest-headroom valid account anyway + a warning
39
+ in `selection.log` (degraded beats down: the 100% rule).
40
+
41
+ **Which account has "the most headroom"?** A Max subscription has three limits that a
42
+ Fable request consumes at once: the 5-hour **session** bucket, the **weekly all-models**
43
+ bucket, and the **weekly Fable** bucket. Anthropic's docs confirm these reset on very
44
+ different horizons — the session bucket refills every ~5 hours, but weekly buckets only
45
+ refill on the account's fixed weekly reset (days away). So the picker ranks primarily on
46
+ **weekly headroom** (the peak of the durable buckets) and uses the self-healing session
47
+ bucket only as a tiebreaker. An account sitting at 85% session / 10% weekly outranks one
48
+ at 20% session / 70% weekly: the first only needs a few hours to recover, the second is
49
+ burning headroom that's gone until next week. (`score = weekly% × 1000 + session%`,
50
+ lowest wins.) **Exclusion** still fires on *any* bucket ≥ 90% — a full session bucket
51
+ really does block right now — but that marker expires when the session resets, not days
52
+ later.
53
+
54
+ The shim prints nothing, logs `timestamp account cwd` (never prompt text) to
55
+ `selection.log`, and `exec`s the real binary — stdin/stdout/exit codes pass through
56
+ byte-identically. If anything is missing (no manifest, no accounts, unreadable state,
57
+ even an unset `HOME`) it fails **open** into plain passthrough.
58
+
59
+ **Limit-aware marking.** `claude-accounts limits` (every 60s via launchd on the Mac,
60
+ cron on the server, plus an opportunistic non-blocking kick from the shim when data is
61
+ >3 min stale) reads each account's own OAuth usage endpoint — the same data `/usage`
62
+ shows. Every bucket the endpoint returns is tracked separately: `session`, `weekly_all`,
63
+ and per-model scoped buckets — **`weekly_scoped:Fable` is its own bucket**. Any bucket
64
+ ≥ 90% ⇒ the account gets a `.limited` marker (with the bucket's reset time) and leaves the
65
+ random pool until that bucket resets or a refresh shows it back under threshold.
66
+
67
+ Bucket parsing is **shape-agnostic**: buckets are derived from whatever the endpoint
68
+ returns (`kind`, plus `scope.model` when present), so if Anthropic renames a bucket,
69
+ reshapes the payload, or drops the per-model Fable separation entirely, the remaining
70
+ buckets keep being tracked and the ≥90% rule keeps working. Unparseable entries are
71
+ skipped, an all-legacy payload falls back to `five_hour`/`seven_day`, and a payload the
72
+ code cannot read at all degrades that one account (fail open), never the run.
73
+
74
+ Telemetry failures never block work: no fresh data ⇒ account treated as available. The
75
+ endpoint rate-limits per account, so the refresher skips accounts fetched in the last 45s
76
+ and backs off exponentially (honoring `Retry-After`) on a 429 — `limits --force` overrides
77
+ both. If an account's OAuth access token has expired (quiet machine, nothing ran claude for
78
+ hours), the refresher first runs a zero-cost `claude auth status` under that account to let
79
+ the app refresh its own credentials, then fetches; if that fails it logs and fails open.
80
+
81
+ **Auto-retry** (`-p`/`--print` only, default on, `CLAUDE_SHIM_RETRY=0` disables): on an
82
+ auth/rate-limit-looking failure the shim marks the account with a 10-minute error
83
+ cooldown and retries once on a different account. Output is buffered so a retried call
84
+ never double-emits. Only engages when stdin is finite (tty / regular file / `/dev/null`)
85
+ and ≥2 accounts are eligible; service-spawned pipes take the plain exec path untouched.
86
+
87
+ ## Install / update / uninstall
88
+
89
+ ### npm (recommended)
90
+
91
+ ```bash
92
+ npm install -g claude-multiacc # postinstall wires up the shim, CLI, and scheduled jobs
93
+ # or run it once without installing globally:
94
+ npx claude-multiacc
95
+ ```
96
+
97
+ `claude-multiacc` then wraps everything:
98
+
99
+ ```bash
100
+ claude-multiacc # install or update the addon (idempotent)
101
+ claude-multiacc self-update # npm i -g @latest + re-install
102
+ claude-multiacc uninstall [--purge-data]
103
+ claude-multiacc list | status | add <email> | ... # passthrough to claude-accounts
104
+ ```
105
+
106
+ **Automatic updates:** a globally-installed package re-runs `install.sh` from its own
107
+ postinstall, so `npm i -g claude-multiacc@latest` fully updates the shim, CLI, and jobs.
108
+ A weekly job (launchd on macOS, cron on Linux) runs `claude-accounts self-update` to pull
109
+ the latest npm release on its own; set `CLAUDE_MULTIACC_AUTOUPDATE=0` before install to
110
+ opt out. `update-notifier` also prints a one-line nudge when a newer version is on npm.
111
+ (Note: newer npm versions gate install scripts; if postinstall is blocked, just run
112
+ `claude-multiacc install` once.)
113
+
114
+ New versions publish to npm automatically from `main` via GitHub Actions
115
+ (`.github/workflows/publish.yml`, OIDC Trusted Publishing — no long-lived token after the
116
+ first bootstrap publish; `scripts/auto-version.mjs` bumps the patch above the last release).
117
+
118
+ ### From a git checkout
119
+
120
+ ```bash
121
+ git clone <repo> && cd claude-multiacc && ./install.sh # macOS or Linux, idempotent
122
+ ./install.sh --server root@1.2.3.4 # override sync target
123
+ git pull && ./install.sh # update (data untouched)
124
+ ./install.sh --uninstall # restores stock behavior, keeps account data
125
+ ./install.sh --uninstall --purge-data # also deletes ~/.claude-accounts
126
+ ```
127
+
128
+ `self-update` auto-detects which of these you used (npm global vs git checkout).
129
+
130
+ What install does (all reversible, nothing else):
131
+
132
+ - **macOS:** marked PATH block at the END of `~/.zshenv`, `~/.zprofile`, `~/.zshrc`
133
+ (+ bash rc files if present) — end-of-file placement matters because those files
134
+ re-prepend `~/.local/bin`; launchd agents `com.claude-multiacc.limits` (60s) and
135
+ `.health` (weekly Mon 09:17). Warns if this Mac stores credentials in the Keychain
136
+ (file-based credentials are required for per-dir logins).
137
+ - **Linux (root):** PATH block in `~/.bashrc` + `/etc/profile.d/claude-multiacc.sh`,
138
+ shim symlink at `/usr/local/bin/claude` (shadows via PATH order — on the systemd
139
+ default PATH too; the original binary is untouched), cron entries for limits/health.
140
+ - Both: `~/.claude-accounts/` skeleton + `accounts.json` manifest.
141
+
142
+ ## Accounts
143
+
144
+ ```bash
145
+ claude-accounts list # pool at a glance
146
+ claude-accounts status # per-account auth health, per-bucket limits incl. Fable, markers, last pick
147
+ ```
148
+
149
+ Every account in the pool is authenticated — `add` cannot register one otherwise.
150
+
151
+ ### Adopt this machine's existing login (no browser needed)
152
+
153
+ ```bash
154
+ claude-accounts import you@example.com --id acct-01 --home mac
155
+ claude-accounts adopt acct-01
156
+ ```
157
+
158
+ `adopt` makes the account dir a **symlink to `~/.claude`** — the machine's existing
159
+ login joins the pool with a single credential file (no copy of the OAuth grant exists
160
+ anywhere else, so refreshes can never race). This is how the two current accounts run:
161
+ acct-01 = the Mac's login, acct-02 = the server's login (adopted server-side).
162
+
163
+ ### Add a fresh account (~1 min, one browser sign-in)
164
+
165
+ ```bash
166
+ claude-accounts add new@example.com
167
+ ```
168
+
169
+ It prints a **clickable sign-in link**. Open it in a browser signed into that account,
170
+ approve, and paste the code back into the same prompt. The account is registered **only
171
+ after the sign-in is verified** — an aborted or failed sign-in leaves nothing behind, and
172
+ a duplicate email is refused outright (`--force` overrides). The captured token has a
173
+ ~1-year lifetime, is inference-only, and does not rotate → it works on the Mac **and**
174
+ the server with no cross-machine refresh races, so the account is usable everywhere the
175
+ moment `add` returns.
176
+
177
+ ```bash
178
+ claude-accounts login acct-NN # re-run the same flow for an existing account
179
+ # (complete missing auth, or refresh an aging token)
180
+ claude-accounts add x@y.com --tui # alternative: interactive /login instead, storing
181
+ # auto-refreshing OAuth creds (that machine only)
182
+ ```
183
+
184
+ `status` warns when a token nears end of life; re-run `login` for that account.
185
+ Both accounts currently in the pool are live and verified: acct-01 (support@gowalkae.com,
186
+ Mac) and acct-02 (hasan@gowalkqa.com, server) — each adopted from its machine's existing
187
+ login, so each runs on the machine that holds its credential.
188
+
189
+ ### Remove
190
+
191
+ ```bash
192
+ claude-accounts remove acct-NN # deletes locally, propagates to the server
193
+ ```
194
+
195
+ ## Server sync
196
+
197
+ Mac → server, one-way (Mac = source of truth), fired automatically by
198
+ `add`/`import`/`remove`/`mint` and manually via:
199
+
200
+ ```bash
201
+ claude-accounts sync
202
+ ```
203
+
204
+ Pushes the manifest + per-account `server.token` (0600) + one-time seeds; **never**
205
+ syncs `.credentials.json` in either direction (two machines refreshing one OAuth grant
206
+ invalidate each other). Removals propagate; an empty/corrupt manifest refuses to sync
207
+ (guard against wiping the server pool). After every push the server re-seeds dirs and
208
+ re-runs its quick verification matrix (`post-sync` hook). Everything logs to `sync.log`;
209
+ failures are loud and non-zero.
210
+
211
+ ## Pinning & env switches
212
+
213
+ | Env | Effect |
214
+ | --- | --- |
215
+ | `CLAUDE_ACCOUNT=acct-03` | pin this invocation to one account (wins over markers) |
216
+ | `CLAUDE_CONFIG_DIR=...` | shim passes straight through (scripts can pin the old way) |
217
+ | `CLAUDE_MULTIACC_DISABLE=1` | bypass selection entirely |
218
+ | `CLAUDE_SHIM_RETRY=0` | disable the `-p` auto-retry |
219
+ | `CLAUDE_ACCOUNTS_DIR=...` | relocate the pool (used by the test suite) |
220
+
221
+ ## Verification
222
+
223
+ ```bash
224
+ tests/run-tests.sh # 35 sandboxed tests, no quota
225
+ claude-accounts verify # real matrix: `claude -p "reply OK"` per authed account
226
+ claude-accounts verify --quick# auth presence/expiry only, no inference
227
+ claude-accounts limits # live per-bucket usage incl. the Fable bucket
228
+ claude-accounts health # limits + full verify, logs to health.log, notifies on failure (Mac)
229
+ ```
230
+
231
+ Verified end-to-end on both machines (2026-07-13): full matrix PASS, 20-invocation shim
232
+ spread clean, real project `-p` through the shim OK on Mac (`~/pipeline`) and server
233
+ (`/opt/inapp-onboarding`), uninstall→stock→reinstall roundtrip byte-identical, three
234
+ server systemd services (`vpn-server-management`, `inapp-onboarding`, `vpn-seo-panel`)
235
+ repointed via `CLAUDE_BIN=/usr/local/bin/claude` and restarted healthy.
236
+
237
+ ## Troubleshooting
238
+
239
+ - **`claude` resolves to the real binary, not the shim** — open a new shell, or check
240
+ that the marked block is the LAST PATH manipulation in your rc file
241
+ (`grep -A2 'claude-multiacc >>>' ~/.zshrc`).
242
+ - **An account never gets picked** — `claude-accounts status`: no auth on this machine,
243
+ or an active `.limited` marker (shows bucket + minutes to reset).
244
+ - **Everything marked limited** — the shim still runs (least-utilized fallback);
245
+ check `selection.log` for `all-limited` lines.
246
+ - **Sync fails** — `tail ~/.claude-accounts/sync.log`; it's ssh/rsync to the manifest's
247
+ `server` (BatchMode — needs key auth).
248
+ - **A service bypasses the shim** — it spawns an absolute path. Point its env
249
+ (`CLAUDE_BIN` etc.) at `/usr/local/bin/claude`. Docker-internal `claude` installs
250
+ (e.g. openclaw live-test containers) are out of scope for a host shim.
251
+ - **Limits look stale** — launchd: `launchctl list | grep claude-multiacc`;
252
+ cron: `crontab -l | grep claude-multiacc`. The shim also kicks a refresh when data
253
+ is >3 min old.
254
+
255
+ ## Security notes & known risks
256
+
257
+ - Tokens and credentials live as 0600 files under `~/.claude-accounts` (and
258
+ `/root/.claude-accounts` on the server). Server compromise = account access.
259
+ - `selection.log` records timestamps/account/cwd only — never prompt text.
260
+ - Rotating multiple subscriptions to spread usage may be flagged by anti-abuse systems;
261
+ accounts can be banned for limit circumvention. Known and accepted by the operator.
262
+ - The usage endpoint is the internal one `/usage` consumes; if it changes shape, limit
263
+ tracking fails open (accounts stay available, error-retry is the backstop).