claude-multiacc 2.0.27 → 2.0.29
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 +11 -138
- package/bin/claude-accounts +26 -5
- package/bin/claude-multiacc +3 -0
- package/bin/cli.mjs +16 -18
- package/bin/codex-accounts +35 -5
- package/bin/multiacc-select +4 -0
- package/docs/ACCOUNT_OPERATIONS.md +3 -61
- package/docs/CODEX.md +83 -0
- package/docs/INSTALLATION.md +91 -0
- package/docs/UNIFIED_SELECTOR.md +2 -0
- package/docs/VERIFICATION.md +68 -0
- package/install.sh +9 -284
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/install-help.txt +20 -0
- package/lib/install_actions.sh +238 -0
- package/lib/install_calendar.sh +59 -0
- package/package.json +5 -2
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/packaged_command_support.py +81 -0
- package/tests/run-tests.sh +30 -0
- package/tests/test_packaged_commands.py +170 -0
|
@@ -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).
|
package/install.sh
CHANGED
|
@@ -20,6 +20,7 @@ set -u
|
|
|
20
20
|
REPO_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
21
21
|
# shellcheck source=lib/common.sh
|
|
22
22
|
. "$REPO_DIR/lib/common.sh"
|
|
23
|
+
. "$REPO_DIR/lib/install_actions.sh"
|
|
23
24
|
# common.sh is sourced in claude mode (ACC_ROOT = the claude pool); the codex pool
|
|
24
25
|
# root is needed here too for the codex agents and uninstall/purge messaging.
|
|
25
26
|
CODEX_ACC_ROOT="${CODEX_ACCOUNTS_ROOT:-${CODEX_ACCOUNTS_DIR:-$HOME/.codex-accounts}}"
|
|
@@ -31,6 +32,7 @@ NO_SCHEDULE=0
|
|
|
31
32
|
INSTANCE_OVERRIDE=""
|
|
32
33
|
while [ $# -gt 0 ]; do
|
|
33
34
|
case "$1" in
|
|
35
|
+
--help|-h) cat "$REPO_DIR/lib/install-help.txt"; exit 0 ;;
|
|
34
36
|
--uninstall) UNINSTALL=1; shift ;;
|
|
35
37
|
--purge-data) PURGE=1; shift ;;
|
|
36
38
|
--server) SERVER_OVERRIDE="${2:?--server requires a value}"; shift 2 ;;
|
|
@@ -127,50 +129,9 @@ cron_env_prefix() {
|
|
|
127
129
|
printf "CLAUDE_ACCOUNTS_ROOT='%s' CODEX_ACCOUNTS_ROOT='%s' " "$ACC_ROOT" "$CODEX_ACC_ROOT"
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
is_pool_root() { # true when $1 is safe to delete as an account pool
|
|
131
|
-
case "$1" in
|
|
132
|
-
''|/|"$HOME") return 1 ;;
|
|
133
|
-
*/.claude-accounts|*/.codex-accounts) return 0 ;;
|
|
134
|
-
/*) [ -f "$1/accounts.json" ] ;;
|
|
135
|
-
*) return 1 ;;
|
|
136
|
-
esac
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
strip_block() { # remove our marked block from a file (portable, no sed -i)
|
|
140
|
-
local f="$1"
|
|
141
|
-
[ -f "$f" ] || return 0
|
|
142
|
-
# Unmatched begin marker (end line hand-deleted): stripping would eat the rest
|
|
143
|
-
# of the rc file. Leave it alone and say so.
|
|
144
|
-
if grep -qF "$MARK_BEGIN" "$f" && ! grep -qF "$MARK_END" "$f"; then
|
|
145
|
-
echo " WARNING: $f has an unterminated claude-multiacc block — fix it by hand; not touching this file" >&2
|
|
146
|
-
return 1
|
|
147
|
-
fi
|
|
148
|
-
awk -v b="$MARK_BEGIN" -v e="$MARK_END" '
|
|
149
|
-
$0 == b { skip = 1; next }
|
|
150
|
-
$0 == e { skip = 0; next }
|
|
151
|
-
!skip { print }
|
|
152
|
-
' "$f" > "$f.claude-multiacc.tmp" && mv "$f.claude-multiacc.tmp" "$f"
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
path_block_body() {
|
|
156
|
-
# Move (not just add) the shim dir to the front: later rc lines prepend
|
|
157
|
-
# ~/.local/bin, so a plain add-once guard would leave the real binary first.
|
|
158
|
-
printf 'PATH="$(printf %%s ":$PATH:" | sed '\''s|:%s/bin:|:|g; s|^:||; s|:$||'\'')"\n' "$REPO_DIR"
|
|
159
|
-
printf 'export PATH="%s/bin:$PATH"\n' "$REPO_DIR"
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
append_block() { # strip then append our PATH block to a file
|
|
163
|
-
local f="$1"
|
|
164
|
-
strip_block "$f" || return 1 # never create a second block next to a broken one
|
|
165
|
-
{
|
|
166
|
-
printf '%s\n' "$MARK_BEGIN"
|
|
167
|
-
path_block_body
|
|
168
|
-
printf '%s\n' "$MARK_END"
|
|
169
|
-
} >> "$f"
|
|
170
|
-
}
|
|
171
|
-
|
|
172
132
|
mac_schedule_install() {
|
|
173
133
|
mkdir -p "$HOME/Library/LaunchAgents"
|
|
134
|
+
. "$REPO_DIR/lib/install_calendar.sh"
|
|
174
135
|
cat > "$PLIST_LIMITS" <<EOF
|
|
175
136
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
176
137
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -183,52 +144,12 @@ mac_schedule_install() {
|
|
|
183
144
|
</array>
|
|
184
145
|
$(plist_env_block) <key>StartInterval</key><integer>900</integer>
|
|
185
146
|
<key>RunAtLoad</key><true/>
|
|
186
|
-
<!--
|
|
187
|
-
must not kill that push with the job's process group. -->
|
|
147
|
+
<!-- Preserve the detached telemetry push after this job exits. -->
|
|
188
148
|
<key>AbandonProcessGroup</key><true/>
|
|
189
149
|
<key>StandardOutPath</key><string>/dev/null</string>
|
|
190
150
|
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
191
151
|
</dict></plist>
|
|
192
152
|
EOF
|
|
193
|
-
cat > "$PLIST_HEALTH" <<EOF
|
|
194
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
195
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
196
|
-
<plist version="1.0"><dict>
|
|
197
|
-
<key>Label</key><string>$LABEL.health</string>
|
|
198
|
-
<key>ProgramArguments</key><array>
|
|
199
|
-
<string>$REPO_DIR/bin/claude-accounts</string>
|
|
200
|
-
<string>health</string>
|
|
201
|
-
</array>
|
|
202
|
-
$(plist_env_block) <key>StartCalendarInterval</key><dict>
|
|
203
|
-
<key>Weekday</key><integer>1</integer>
|
|
204
|
-
<key>Hour</key><integer>9</integer>
|
|
205
|
-
<key>Minute</key><integer>17</integer>
|
|
206
|
-
</dict>
|
|
207
|
-
<key>StandardOutPath</key><string>/dev/null</string>
|
|
208
|
-
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
209
|
-
</dict></plist>
|
|
210
|
-
EOF
|
|
211
|
-
# Daily auto-update (04:07). No-op unless this is an npm or git install.
|
|
212
|
-
cat > "$PLIST_UPDATE" <<EOF
|
|
213
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
214
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
215
|
-
<plist version="1.0"><dict>
|
|
216
|
-
<key>Label</key><string>$LABEL.update</string>
|
|
217
|
-
<key>ProgramArguments</key><array>
|
|
218
|
-
<string>$REPO_DIR/bin/claude-accounts</string>
|
|
219
|
-
<string>self-update</string>
|
|
220
|
-
<string>--quiet</string>
|
|
221
|
-
</array>
|
|
222
|
-
$(plist_env_block) <key>StartCalendarInterval</key><dict>
|
|
223
|
-
<key>Hour</key><integer>4</integer>
|
|
224
|
-
<key>Minute</key><integer>7</integer>
|
|
225
|
-
</dict>
|
|
226
|
-
<key>StandardOutPath</key><string>/dev/null</string>
|
|
227
|
-
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
228
|
-
</dict></plist>
|
|
229
|
-
EOF
|
|
230
|
-
# Codex pool: its own limits/health agents, so either provider's telemetry can
|
|
231
|
-
# fail or be removed without touching the other's.
|
|
232
153
|
cat > "$PLIST_CODEX_LIMITS" <<EOF
|
|
233
154
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
234
155
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -246,30 +167,11 @@ $(plist_env_block) <key>StartInterval</key><integer>300</integer>
|
|
|
246
167
|
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
247
168
|
</dict></plist>
|
|
248
169
|
EOF
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<key>ProgramArguments</key><array>
|
|
255
|
-
<string>$REPO_DIR/bin/codex-accounts</string>
|
|
256
|
-
<string>health</string>
|
|
257
|
-
</array>
|
|
258
|
-
$(plist_env_block) <key>StartCalendarInterval</key><dict>
|
|
259
|
-
<key>Weekday</key><integer>1</integer>
|
|
260
|
-
<key>Hour</key><integer>9</integer>
|
|
261
|
-
<key>Minute</key><integer>37</integer>
|
|
262
|
-
</dict>
|
|
263
|
-
<key>StandardOutPath</key><string>/dev/null</string>
|
|
264
|
-
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
265
|
-
</dict></plist>
|
|
266
|
-
EOF
|
|
267
|
-
launchctl unload "$PLIST_LIMITS" 2>/dev/null || true
|
|
268
|
-
launchctl unload "$PLIST_HEALTH" 2>/dev/null || true
|
|
269
|
-
launchctl unload "$PLIST_UPDATE" 2>/dev/null || true
|
|
270
|
-
launchctl unload "$PLIST_CODEX_LIMITS" 2>/dev/null || true
|
|
271
|
-
launchctl unload "$PLIST_CODEX_HEALTH" 2>/dev/null || true
|
|
272
|
-
launchctl load -w "$PLIST_LIMITS" 2>/dev/null || echo " (launchctl load limits agent failed — limits refresh will rely on the shim's opportunistic kick)"
|
|
170
|
+
local plist
|
|
171
|
+
for plist in "$PLIST_LIMITS" "$PLIST_HEALTH" "$PLIST_UPDATE" "$PLIST_CODEX_LIMITS" "$PLIST_CODEX_HEALTH"; do
|
|
172
|
+
launchctl unload "$plist" 2>/dev/null || true
|
|
173
|
+
done
|
|
174
|
+
launchctl load -w "$PLIST_LIMITS" 2>/dev/null || echo " (limits agent failed — the shim will refresh on demand)"
|
|
273
175
|
launchctl load -w "$PLIST_HEALTH" 2>/dev/null || true
|
|
274
176
|
launchctl load -w "$PLIST_CODEX_LIMITS" 2>/dev/null || true
|
|
275
177
|
launchctl load -w "$PLIST_CODEX_HEALTH" 2>/dev/null || true
|
|
@@ -317,183 +219,6 @@ linux_schedule_remove() {
|
|
|
317
219
|
rm -f "$tmp"
|
|
318
220
|
}
|
|
319
221
|
|
|
320
|
-
do_uninstall() {
|
|
321
|
-
echo "claude-multiacc: uninstalling (restoring stock behavior)"
|
|
322
|
-
# An instance install never wrote a shell rc block, a /etc/profile.d file or the
|
|
323
|
-
# /usr/local/bin shims — those belong to the DEFAULT install and are shared with it.
|
|
324
|
-
# Removing them here would disable the operator's shim while only one instance was
|
|
325
|
-
# being uninstalled, so an instance removes exactly its own agents.
|
|
326
|
-
if [ -n "$INSTANCE" ]; then
|
|
327
|
-
echo " instance $INSTANCE: removing its agents only (PATH block and shims belong to the default install)"
|
|
328
|
-
else
|
|
329
|
-
strip_block "$HOME/.zshenv"
|
|
330
|
-
strip_block "$HOME/.zprofile"
|
|
331
|
-
strip_block "$HOME/.zshrc"
|
|
332
|
-
strip_block "$HOME/.bashrc"
|
|
333
|
-
strip_block "$HOME/.bash_profile"
|
|
334
|
-
strip_block "$HOME/.profile"
|
|
335
|
-
fi
|
|
336
|
-
if [ "$(machine_kind)" = "mac" ]; then
|
|
337
|
-
mac_schedule_remove
|
|
338
|
-
else
|
|
339
|
-
linux_schedule_remove
|
|
340
|
-
if [ -z "$INSTANCE" ]; then
|
|
341
|
-
[ -f "$PROFILED" ] && rm -f "$PROFILED"
|
|
342
|
-
if [ -L /usr/local/bin/claude ] && [ "$(canon_path /usr/local/bin/claude)" = "$(canon_path "$REPO_DIR/bin/claude")" ]; then
|
|
343
|
-
rm -f /usr/local/bin/claude
|
|
344
|
-
echo " removed /usr/local/bin/claude shim symlink"
|
|
345
|
-
fi
|
|
346
|
-
if [ -L /usr/local/bin/codex ] && [ "$(canon_path /usr/local/bin/codex)" = "$(canon_path "$REPO_DIR/bin/codex")" ]; then
|
|
347
|
-
rm -f /usr/local/bin/codex
|
|
348
|
-
echo " removed /usr/local/bin/codex shim symlink"
|
|
349
|
-
fi
|
|
350
|
-
fi
|
|
351
|
-
fi
|
|
352
|
-
local root
|
|
353
|
-
for root in "$ACC_ROOT" "$CODEX_ACC_ROOT"; do
|
|
354
|
-
if [ "$PURGE" = "1" ]; then
|
|
355
|
-
# Either the conventional location, or a directory that is provably a pool (it
|
|
356
|
-
# holds a manifest) — an instance root lives anywhere, so the manifest is what
|
|
357
|
-
# makes `rm -rf` safe. Anything else is left alone.
|
|
358
|
-
if is_pool_root "$root"; then
|
|
359
|
-
rm -rf "$root"; echo " purged $root"
|
|
360
|
-
else
|
|
361
|
-
echo " refusing to purge unusual accounts root: $root" >&2
|
|
362
|
-
fi
|
|
363
|
-
fi
|
|
364
|
-
done
|
|
365
|
-
if [ "$PURGE" != "1" ]; then
|
|
366
|
-
echo " account data kept at $ACC_ROOT and $CODEX_ACC_ROOT (use --purge-data to remove)"
|
|
367
|
-
fi
|
|
368
|
-
echo "uninstall complete — stock claude behavior restored"
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
do_install() {
|
|
372
|
-
local kind
|
|
373
|
-
kind="$(machine_kind)"
|
|
374
|
-
echo "claude-multiacc: installing (mode: $kind, repo: $REPO_DIR)"
|
|
375
|
-
if [ -n "$INSTANCE" ]; then
|
|
376
|
-
echo " instance: $INSTANCE (agents labelled $LABEL.*, pools $ACC_ROOT + $CODEX_ACC_ROOT)"
|
|
377
|
-
fi
|
|
378
|
-
|
|
379
|
-
[ -x "$REPO_DIR/bin/claude" ] || chmod +x "$REPO_DIR/bin/claude" 2>/dev/null || true
|
|
380
|
-
[ -x "$REPO_DIR/bin/claude-accounts" ] || chmod +x "$REPO_DIR/bin/claude-accounts" 2>/dev/null || true
|
|
381
|
-
[ -x "$REPO_DIR/bin/codex" ] || chmod +x "$REPO_DIR/bin/codex" 2>/dev/null || true
|
|
382
|
-
[ -x "$REPO_DIR/bin/codex-accounts" ] || chmod +x "$REPO_DIR/bin/codex-accounts" 2>/dev/null || true
|
|
383
|
-
|
|
384
|
-
local real
|
|
385
|
-
if real="$(find_real_claude "$REPO_DIR/bin/claude")"; then
|
|
386
|
-
echo " real claude binary: $real ($("$real" --version 2>/dev/null | head -1 || echo 'version unknown'))"
|
|
387
|
-
else
|
|
388
|
-
echo " WARNING: no real claude binary found yet — install Claude Code first (https://claude.com/claude-code)" >&2
|
|
389
|
-
fi
|
|
390
|
-
local real_codex
|
|
391
|
-
if real_codex="$(find_real_codex "$REPO_DIR/bin/codex")"; then
|
|
392
|
-
echo " real codex binary: $real_codex ($("$real_codex" --version 2>/dev/null | head -1 || echo 'version unknown'))"
|
|
393
|
-
else
|
|
394
|
-
echo " note: no codex binary found — the codex pool stays idle until Codex CLI is installed (npm i -g @openai/codex)"
|
|
395
|
-
fi
|
|
396
|
-
|
|
397
|
-
# Keychain-mode note: Claude Code keeps per-config-dir logins in the login Keychain
|
|
398
|
-
# (one item per dir) whenever the session can open it, and only sessions without
|
|
399
|
-
# keychain access (ssh, launchd background jobs) fall back to .credentials.json. The
|
|
400
|
-
# pool reads both (lib/keychain.py); what an operator has to know is that a login
|
|
401
|
-
# made from a GUI session is invisible to their ssh sessions — mint a portable
|
|
402
|
-
# token for anything that must work from everywhere.
|
|
403
|
-
if [ "$kind" = "mac" ] && [ ! -f "$HOME/.claude/.credentials.json" ] \
|
|
404
|
-
&& security find-generic-password -s "Claude Code-credentials" >/dev/null 2>&1; then
|
|
405
|
-
echo " note: this Mac keeps Claude Code logins in the Keychain. Per-dir logins made here"
|
|
406
|
-
echo " are read from it; ssh/background sessions cannot open it (they see 'locked') —"
|
|
407
|
-
echo " use 'claude-accounts mint <acct-NN>' for accounts that must work from everywhere."
|
|
408
|
-
fi
|
|
409
|
-
|
|
410
|
-
manifest_init "${SERVER_OVERRIDE:-$DEFAULT_SERVER}"
|
|
411
|
-
if [ -n "$SERVER_OVERRIDE" ]; then
|
|
412
|
-
"$PYBIN" - "$MANIFEST" "$SERVER_OVERRIDE" <<'PYEOF'
|
|
413
|
-
import json, os, sys
|
|
414
|
-
doc = json.load(open(sys.argv[1]))
|
|
415
|
-
doc['server'] = sys.argv[2]
|
|
416
|
-
with open(sys.argv[1] + '.tmp', 'w') as f:
|
|
417
|
-
json.dump(doc, f, indent=2)
|
|
418
|
-
f.write('\n')
|
|
419
|
-
os.replace(sys.argv[1] + '.tmp', sys.argv[1])
|
|
420
|
-
PYEOF
|
|
421
|
-
fi
|
|
422
|
-
if sync_target_is_local "$(sync_target)"; then
|
|
423
|
-
echo " account pool: $ACC_ROOT (manifest ready; sync target: none — local-only)"
|
|
424
|
-
else
|
|
425
|
-
echo " account pool: $ACC_ROOT (manifest ready; sync target: $(sync_target))"
|
|
426
|
-
fi
|
|
427
|
-
# The codex pool gets its own skeleton + manifest (same schema, separate root).
|
|
428
|
-
if ! "$REPO_DIR/bin/codex-accounts" init-pool "${SERVER_OVERRIDE:-}" >/dev/null 2>&1; then
|
|
429
|
-
echo " WARNING: codex pool init failed (codex-accounts init-pool)" >&2
|
|
430
|
-
else
|
|
431
|
-
echo " codex account pool: $CODEX_ACC_ROOT (manifest ready)"
|
|
432
|
-
fi
|
|
433
|
-
|
|
434
|
-
# An INSTANCE install never rewrites the shell rc blocks: one interactive PATH
|
|
435
|
-
# cannot serve two pools, and clobbering the default install's block would point
|
|
436
|
-
# the operator's shell at an instance pool. Its agents (below) carry the roots
|
|
437
|
-
# instead, and the runner daemon invokes the shims by absolute path.
|
|
438
|
-
if [ -n "$INSTANCE" ]; then
|
|
439
|
-
echo " PATH block: skipped (instance install) — for a shell against this pool:"
|
|
440
|
-
echo " export CLAUDE_ACCOUNTS_ROOT='$ACC_ROOT' CODEX_ACCOUNTS_ROOT='$CODEX_ACC_ROOT'"
|
|
441
|
-
echo " export PATH=\"$REPO_DIR/bin:\$PATH\""
|
|
442
|
-
fi
|
|
443
|
-
if [ "$kind" = "mac" ]; then
|
|
444
|
-
# .zshenv covers non-interactive zsh; the .zshrc block must be LAST so it wins
|
|
445
|
-
# over ~/.local/bin re-prepends done earlier in .zshrc/.zprofile.
|
|
446
|
-
# zsh reads: .zshenv always; .zprofile for login; .zshrc for interactive.
|
|
447
|
-
# The block must end each file that later re-prepends ~/.local/bin.
|
|
448
|
-
if [ -z "$INSTANCE" ]; then
|
|
449
|
-
touch "$HOME/.zshenv"
|
|
450
|
-
append_block "$HOME/.zshenv"
|
|
451
|
-
touch "$HOME/.zprofile"
|
|
452
|
-
append_block "$HOME/.zprofile"
|
|
453
|
-
touch "$HOME/.zshrc"
|
|
454
|
-
append_block "$HOME/.zshrc"
|
|
455
|
-
[ -f "$HOME/.bash_profile" ] && append_block "$HOME/.bash_profile"
|
|
456
|
-
[ -f "$HOME/.bashrc" ] && append_block "$HOME/.bashrc"
|
|
457
|
-
echo " PATH block: end of ~/.zshenv, ~/.zprofile, ~/.zshrc (+ bash rc files if present)"
|
|
458
|
-
fi
|
|
459
|
-
[ "$NO_SCHEDULE" = "1" ] || mac_schedule_install
|
|
460
|
-
else
|
|
461
|
-
if [ -z "$INSTANCE" ]; then
|
|
462
|
-
touch "$HOME/.bashrc"
|
|
463
|
-
append_block "$HOME/.bashrc"
|
|
464
|
-
if [ -w /etc/profile.d ] 2>/dev/null || [ "$(id -u)" = "0" ]; then
|
|
465
|
-
{
|
|
466
|
-
printf '%s\n' "$MARK_BEGIN"
|
|
467
|
-
path_block_body
|
|
468
|
-
printf '%s\n' "$MARK_END"
|
|
469
|
-
} > "$PROFILED"
|
|
470
|
-
echo " PATH block: ~/.bashrc + $PROFILED"
|
|
471
|
-
fi
|
|
472
|
-
fi
|
|
473
|
-
if [ "$(id -u)" = "0" ]; then
|
|
474
|
-
if [ -e /usr/local/bin/claude ] && [ ! -L /usr/local/bin/claude ]; then
|
|
475
|
-
echo " WARNING: /usr/local/bin/claude exists and is a real file — NOT overwriting." >&2
|
|
476
|
-
else
|
|
477
|
-
ln -sfn "$REPO_DIR/bin/claude" /usr/local/bin/claude
|
|
478
|
-
echo " shim: /usr/local/bin/claude -> $REPO_DIR/bin/claude (systemd-PATH compatible)"
|
|
479
|
-
fi
|
|
480
|
-
if [ -e /usr/local/bin/codex ] && [ ! -L /usr/local/bin/codex ]; then
|
|
481
|
-
echo " WARNING: /usr/local/bin/codex exists and is a real file — NOT overwriting." >&2
|
|
482
|
-
else
|
|
483
|
-
ln -sfn "$REPO_DIR/bin/codex" /usr/local/bin/codex
|
|
484
|
-
echo " shim: /usr/local/bin/codex -> $REPO_DIR/bin/codex (systemd-PATH compatible)"
|
|
485
|
-
fi
|
|
486
|
-
fi
|
|
487
|
-
[ "$NO_SCHEDULE" = "1" ] || linux_schedule_install
|
|
488
|
-
fi
|
|
489
|
-
|
|
490
|
-
echo
|
|
491
|
-
echo "install complete. Open a new shell (or 'export PATH=\"$REPO_DIR/bin:\$PATH\"'), then:"
|
|
492
|
-
echo " claude-accounts list # the Claude Code pool"
|
|
493
|
-
echo " codex-accounts list # the Codex pool"
|
|
494
|
-
echo " claude-accounts status | codex-accounts status # auth + limits detail"
|
|
495
|
-
}
|
|
496
|
-
|
|
497
222
|
if [ "$UNINSTALL" = "1" ]; then
|
|
498
223
|
do_uninstall
|
|
499
224
|
else
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
USAGE
|
|
2
|
+
claude-multiacc install [options]
|
|
3
|
+
./install.sh [options]
|
|
4
|
+
claude-multiacc uninstall [--purge-data]
|
|
5
|
+
./install.sh --uninstall [--purge-data]
|
|
6
|
+
|
|
7
|
+
OPTIONS
|
|
8
|
+
--server user@host set the sync target
|
|
9
|
+
--no-server use local-only pools
|
|
10
|
+
--instance NAME label this instance's scheduled jobs
|
|
11
|
+
--no-schedule install without scheduled jobs
|
|
12
|
+
--uninstall remove shell setup and jobs; keep account data
|
|
13
|
+
--purge-data also delete both account pools when uninstalling
|
|
14
|
+
-h, --help show this help without installing or uninstalling
|
|
15
|
+
|
|
16
|
+
Use CLAUDE_ACCOUNTS_ROOT and CODEX_ACCOUNTS_ROOT to select isolated pools.
|
|
17
|
+
After npm install -g claude-multiacc, both claude-accounts and codex-accounts
|
|
18
|
+
are available alongside claude-multiacc in npm's bin directory immediately.
|
|
19
|
+
Git/npx installs add the package's bin directory through shell startup files;
|
|
20
|
+
open a new shell or use the export PATH command printed by the installer.
|