claude-multiacc 1.0.1 → 1.0.2
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 +28 -18
- package/bin/claude-accounts +128 -66
- package/package.json +1 -1
- package/tests/run-tests.sh +38 -14
package/README.md
CHANGED
|
@@ -163,32 +163,37 @@ acct-01 = the Mac's login, acct-02 = the server's login (adopted server-side).
|
|
|
163
163
|
### Add a fresh account (~1 min, one browser sign-in)
|
|
164
164
|
|
|
165
165
|
```bash
|
|
166
|
-
claude-accounts add # no email needed —
|
|
167
|
-
# or name it explicitly (fast-fails on a duplicate
|
|
166
|
+
claude-accounts add # no email needed — read back from the sign-in
|
|
167
|
+
# or name it explicitly (fast-fails on a duplicate, and pre-fills the login page):
|
|
168
168
|
claude-accounts add new@example.com
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
This runs the **normal full Claude Code login** — the exact same
|
|
172
|
+
`claude auth login` flow the app itself uses (`claude.com/cai/oauth/authorize`, full
|
|
173
|
+
Claude Code scopes). A browser opens (or a sign-in link is printed); sign in to the
|
|
174
|
+
account you're adding, approve, and if a code is shown, paste it back. It draws on the
|
|
175
|
+
subscription — never API keys.
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
is
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
Using the full login on purpose: it does **not** hit the *"Sign in again to continue"*
|
|
178
|
+
step-up wall that the narrow long-lived-token grant triggers, and it stores an
|
|
179
|
+
auto-refreshing `.credentials.json`. That credential is **machine-local** (never synced),
|
|
180
|
+
which is what keeps two machines from invalidating each other's refresh token — so an
|
|
181
|
+
account you `add` on the Mac runs on the Mac, and you `add` it on the server (over SSH) if
|
|
182
|
+
you want it there too. The account is registered **only after the signed-in email is read
|
|
183
|
+
back**; an aborted login leaves nothing behind, and duplicate emails are refused.
|
|
183
184
|
|
|
184
185
|
```bash
|
|
185
|
-
claude-accounts login acct-NN
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
claude-accounts login acct-NN # complete/refresh an existing account (full login)
|
|
187
|
+
claude-accounts add new@example.com --token # portable setup-token instead: works on BOTH
|
|
188
|
+
# machines and syncs to the server, but needs a
|
|
189
|
+
# recent sign-in (may show the step-up screen)
|
|
190
|
+
claude-accounts login acct-NN --token # same, for an existing account
|
|
189
191
|
```
|
|
190
192
|
|
|
191
|
-
`
|
|
193
|
+
Use `--token` when you specifically want one account usable on the server via the Mac's
|
|
194
|
+
sync (the ~1-year inference-only token that mirrors over). For everyday adds, the default
|
|
195
|
+
full login is smoother. `status` warns when a token nears end of life; re-run `login`.
|
|
196
|
+
|
|
192
197
|
Both accounts currently in the pool are live and verified: acct-01 (support@gowalkae.com,
|
|
193
198
|
Mac) and acct-02 (hasan@gowalkqa.com, server) — each adopted from its machine's existing
|
|
194
199
|
login, so each runs on the machine that holds its credential.
|
|
@@ -255,6 +260,11 @@ repointed via `CLAUDE_BIN=/usr/local/bin/claude` and restarted healthy.
|
|
|
255
260
|
- **A service bypasses the shim** — it spawns an absolute path. Point its env
|
|
256
261
|
(`CLAUDE_BIN` etc.) at `/usr/local/bin/claude`. Docker-internal `claude` installs
|
|
257
262
|
(e.g. openclaw live-test containers) are out of scope for a host shim.
|
|
263
|
+
- **`add` sign-in says "Sign in again to continue"** — that's Claude's step-up
|
|
264
|
+
authentication, not a tool error: granting a long-lived token needs a *recent* sign-in.
|
|
265
|
+
Open a fresh incognito window, sign in to the target account at claude.ai **first**,
|
|
266
|
+
then paste the link and approve. The approval scope is `user:inference`
|
|
267
|
+
(*"Contribute to your Claude subscription usage"*) — that's correct and minimal.
|
|
258
268
|
- **Limits look stale** — launchd: `launchctl list | grep claude-multiacc`;
|
|
259
269
|
cron: `crontab -l | grep claude-multiacc`. The shim also kicks a refresh when data
|
|
260
270
|
is >3 min old.
|
package/bin/claude-accounts
CHANGED
|
@@ -20,13 +20,15 @@ claude-accounts — multi-account pool manager for claude-multiacc
|
|
|
20
20
|
USAGE
|
|
21
21
|
claude-accounts list brief account list
|
|
22
22
|
claude-accounts status full health: auth, per-bucket limits, markers
|
|
23
|
-
claude-accounts add [email] [--force
|
|
24
|
-
login-FIRST:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
claude-accounts add [email] [--token] [--force]
|
|
24
|
+
login-FIRST: runs the full Claude Code login (`claude auth login` — the normal
|
|
25
|
+
browser sign-in, no long-lived-token step-up), then registers only after the
|
|
26
|
+
signed-in email is read back. Email is OPTIONAL (derived from the sign-in).
|
|
27
|
+
Stores auto-refreshing .credentials.json valid on THIS machine. Duplicates refused.
|
|
28
|
+
--token instead mints a portable setup-token (works on Mac AND server; needs a
|
|
29
|
+
recent sign-in — use it when you want the account usable on the server too).
|
|
30
|
+
claude-accounts login <acct-NN> [--token] [--force]
|
|
31
|
+
complete/refresh auth for an existing account (full login, or --token)
|
|
30
32
|
claude-accounts import <email> [opts] register an account, optionally with credentials
|
|
31
33
|
--id acct-NN explicit id (default: next free)
|
|
32
34
|
--home mac|server which machine owns the OAuth grant (default: this one)
|
|
@@ -235,15 +237,16 @@ cmd_add() {
|
|
|
235
237
|
# argument is OPTIONAL — omit it and it's derived from whoever you sign in as.
|
|
236
238
|
# An aborted or failed sign-in leaves zero traces.
|
|
237
239
|
require_manifest
|
|
238
|
-
local email="" force=0
|
|
240
|
+
local email="" force=0 token=0
|
|
239
241
|
while [ $# -gt 0 ]; do
|
|
240
242
|
case "$1" in
|
|
241
243
|
--force) force=1; shift ;;
|
|
242
|
-
--
|
|
244
|
+
--token) token=1; shift ;; # portable setup-token instead of full login (server use)
|
|
245
|
+
--tui) shift ;; # accepted for compatibility; full login is now the default
|
|
243
246
|
--*) die "unknown option: $1" ;;
|
|
244
247
|
*)
|
|
245
248
|
if [ -z "$email" ]; then email="$1"; shift
|
|
246
|
-
else die "unexpected argument: $1 (usage: claude-accounts add [email] [--
|
|
249
|
+
else die "unexpected argument: $1 (usage: claude-accounts add [email] [--token] [--force])"; fi ;;
|
|
247
250
|
esac
|
|
248
251
|
done
|
|
249
252
|
# If an email was named, fast-fail on a duplicate now. If not, we learn (and dedup)
|
|
@@ -276,35 +279,9 @@ cmd_add() {
|
|
|
276
279
|
id="$(next_id)"
|
|
277
280
|
d="$ACC_ROOT/$id"
|
|
278
281
|
seed_account_dir "$d"
|
|
279
|
-
if [ "$
|
|
280
|
-
#
|
|
281
|
-
|
|
282
|
-
Preparing $id for $elabel — NOTHING is registered until login succeeds.
|
|
283
|
-
An interactive claude session opens now with no account:
|
|
284
|
-
1. type /login (press c to copy the URL into an incognito window)
|
|
285
|
-
2. /status must show the right email
|
|
286
|
-
3. exit the session (/exit or ctrl+d) to finish registration
|
|
287
|
-
EOF
|
|
288
|
-
CLAUDE_CONFIG_DIR="$d" CLAUDE_SHIM_ACTIVE=1 "$real" || true
|
|
289
|
-
if [ ! -f "$d/.credentials.json" ]; then
|
|
290
|
-
rm -rf "${ACC_ROOT:?}/${id:?}"
|
|
291
|
-
die "no login detected — nothing was created"
|
|
292
|
-
fi
|
|
293
|
-
got="$(CLAUDE_CONFIG_DIR="$d" CLAUDE_SHIM_ACTIVE=1 "$real" auth status 2>/dev/null | "$PYBIN" -c '
|
|
294
|
-
import json, sys
|
|
295
|
-
try:
|
|
296
|
-
doc = json.loads(sys.stdin.read())
|
|
297
|
-
print(doc.get("email", "") if doc.get("loggedIn") else "")
|
|
298
|
-
except Exception:
|
|
299
|
-
print("")')"
|
|
300
|
-
if [ -z "$got" ]; then
|
|
301
|
-
rm -rf "${ACC_ROOT:?}/${id:?}"
|
|
302
|
-
die "login could not be verified — nothing was created"
|
|
303
|
-
fi
|
|
304
|
-
else
|
|
305
|
-
# Default: sign-in link -> user pastes the code back here -> token captured.
|
|
306
|
-
# The token works on BOTH machines (no refresh rotation, ~1y lifetime).
|
|
307
|
-
echo "Preparing $id for $elabel — NOTHING is registered until sign-in completes."
|
|
282
|
+
if [ "$token" = "1" ]; then
|
|
283
|
+
# Portable setup-token variant (works on Mac AND server; needs a recent sign-in).
|
|
284
|
+
echo "Preparing $id for $elabel (portable token) — NOTHING is registered until sign-in completes."
|
|
308
285
|
if ! run_token_ceremony "$d"; then
|
|
309
286
|
rm -rf "${ACC_ROOT:?}/${id:?}"
|
|
310
287
|
die "sign-in failed or aborted — nothing was created"
|
|
@@ -312,17 +289,27 @@ except Exception:
|
|
|
312
289
|
( umask 077; printf '%s' "$CEREMONY_TOKEN" > "$d/server.token" )
|
|
313
290
|
chmod 600 "$d/server.token"
|
|
314
291
|
got="$(token_email "$CEREMONY_TOKEN")"
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
292
|
+
else
|
|
293
|
+
# Default: full Claude Code login (the flow that just works — full scopes, no
|
|
294
|
+
# long-lived-token step-up). Writes auto-refreshing .credentials.json, valid on
|
|
295
|
+
# THIS machine (creds are never synced, so no cross-machine refresh races).
|
|
296
|
+
echo "Preparing $id for $elabel — NOTHING is registered until login completes."
|
|
297
|
+
if ! run_login_ceremony "$d" "$email"; then
|
|
298
|
+
rm -rf "${ACC_ROOT:?}/${id:?}"
|
|
299
|
+
die "login failed or aborted — nothing was created"
|
|
300
|
+
fi
|
|
301
|
+
got="$(config_dir_email "$d")"
|
|
302
|
+
fi
|
|
303
|
+
if [ -z "$got" ]; then
|
|
304
|
+
# Signed in, but we could not read back WHICH account. Without a verified email we
|
|
305
|
+
# cannot label or dedup the account, so refuse — unless an email was named AND
|
|
306
|
+
# --force was given (then we trust the named email).
|
|
307
|
+
if [ -n "$email" ] && [ "$force" = "1" ]; then
|
|
308
|
+
warn "identity unverified — registering as $email because --force was given"
|
|
309
|
+
got="$email"
|
|
310
|
+
else
|
|
311
|
+
rm -rf "${ACC_ROOT:?}/${id:?}"
|
|
312
|
+
die "signed in, but the account identity could not be read back — nothing was created (retry; or 'add <email> --force' to trust a named email)"
|
|
326
313
|
fi
|
|
327
314
|
fi
|
|
328
315
|
# `got` is now the authoritative signed-in email. Dedup against it, warn on mismatch.
|
|
@@ -491,8 +478,17 @@ run_token_ceremony() { # $1 = config dir
|
|
|
491
478
|
umask 077 # the capture file briefly holds the raw token (removed inline below).
|
|
492
479
|
# NB: no EXIT trap here — cmd_add owns the EXIT trap for its mutation lock, and a
|
|
493
480
|
# second EXIT trap would clobber it and leak the lock.
|
|
494
|
-
|
|
495
|
-
|
|
481
|
+
cat <<'TIP'
|
|
482
|
+
A sign-in link will appear below. For a smooth grant (avoids the "Sign in again
|
|
483
|
+
to continue" step-up screen):
|
|
484
|
+
1. Open a fresh incognito/private window.
|
|
485
|
+
2. Sign in at claude.ai to the EXACT account you're adding — do this FIRST.
|
|
486
|
+
3. THEN paste the link below into that same window and approve
|
|
487
|
+
("Contribute to your Claude subscription usage" is the correct permission).
|
|
488
|
+
4. Copy the code it shows and paste it back here.
|
|
489
|
+
If you do see "Sign in again to continue", that is Claude's security step, not an
|
|
490
|
+
error — just sign in to that account and approve; the code still appears.
|
|
491
|
+
TIP
|
|
496
492
|
if [ -t 0 ]; then
|
|
497
493
|
if [ "$(machine_kind)" = "mac" ]; then
|
|
498
494
|
script -q "$cap" env CLAUDE_CONFIG_DIR="$d" CLAUDE_SHIM_ACTIVE=1 "$real" setup-token
|
|
@@ -528,6 +524,56 @@ except Exception:
|
|
|
528
524
|
print("")'
|
|
529
525
|
}
|
|
530
526
|
|
|
527
|
+
config_dir_email() { # $1 = config dir; prints the logged-in email or empty
|
|
528
|
+
local real
|
|
529
|
+
real="$(find_real_claude "$_self")" || { echo ""; return 0; }
|
|
530
|
+
CLAUDE_CONFIG_DIR="$1" CLAUDE_SHIM_ACTIVE=1 "$real" auth status 2>/dev/null | "$PYBIN" -c '
|
|
531
|
+
import json, sys
|
|
532
|
+
try:
|
|
533
|
+
doc = json.loads(sys.stdin.read())
|
|
534
|
+
print(doc.get("email", "") if doc.get("loggedIn") else "")
|
|
535
|
+
except Exception:
|
|
536
|
+
print("")'
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
# Full subscription login: `claude auth login --claudeai`, the SAME flow the
|
|
540
|
+
# interactive app uses (claude.com/cai/oauth/authorize, full Claude Code scopes,
|
|
541
|
+
# normal sign-in — no long-lived-token step-up wall). Writes <dir>/.credentials.json
|
|
542
|
+
# (auto-refreshing). Returns success iff the credential landed. $2 = optional email hint.
|
|
543
|
+
run_login_ceremony() { # $1 = config dir, $2 = email hint
|
|
544
|
+
local d="$1" hint="${2:-}" real
|
|
545
|
+
real="$(find_real_claude "$_self")" || { warn "real claude binary not found"; return 1; }
|
|
546
|
+
cat <<'TIP'
|
|
547
|
+
A browser will open (or a sign-in link will be printed) for the FULL Claude Code
|
|
548
|
+
login. Sign in as the account you want to add and approve; if a code is shown,
|
|
549
|
+
paste it back here. This is the normal login — it draws on the subscription, no
|
|
550
|
+
API keys, and refreshes itself over time.
|
|
551
|
+
TIP
|
|
552
|
+
if [ -t 0 ]; then
|
|
553
|
+
if [ "$(machine_kind)" = "mac" ]; then
|
|
554
|
+
if [ -n "$hint" ]; then
|
|
555
|
+
script -q /dev/null env CLAUDE_CONFIG_DIR="$d" CLAUDE_SHIM_ACTIVE=1 "$real" auth login --claudeai --email "$hint"
|
|
556
|
+
else
|
|
557
|
+
script -q /dev/null env CLAUDE_CONFIG_DIR="$d" CLAUDE_SHIM_ACTIVE=1 "$real" auth login --claudeai
|
|
558
|
+
fi
|
|
559
|
+
else
|
|
560
|
+
if [ -n "$hint" ]; then
|
|
561
|
+
script -q -c "CLAUDE_CONFIG_DIR='$d' CLAUDE_SHIM_ACTIVE=1 '$real' auth login --claudeai --email '$hint'" /dev/null
|
|
562
|
+
else
|
|
563
|
+
script -q -c "CLAUDE_CONFIG_DIR='$d' CLAUDE_SHIM_ACTIVE=1 '$real' auth login --claudeai" /dev/null
|
|
564
|
+
fi
|
|
565
|
+
fi
|
|
566
|
+
else
|
|
567
|
+
# Headless (tests / automation): no PTY.
|
|
568
|
+
if [ -n "$hint" ]; then
|
|
569
|
+
CLAUDE_CONFIG_DIR="$d" CLAUDE_SHIM_ACTIVE=1 "$real" auth login --claudeai --email "$hint" || true
|
|
570
|
+
else
|
|
571
|
+
CLAUDE_CONFIG_DIR="$d" CLAUDE_SHIM_ACTIVE=1 "$real" auth login --claudeai || true
|
|
572
|
+
fi
|
|
573
|
+
fi
|
|
574
|
+
[ -f "$d/.credentials.json" ]
|
|
575
|
+
}
|
|
576
|
+
|
|
531
577
|
cmd_mint() {
|
|
532
578
|
require_manifest
|
|
533
579
|
local id="${1:-}" paste="${2:-}"
|
|
@@ -556,11 +602,20 @@ cmd_mint() {
|
|
|
556
602
|
}
|
|
557
603
|
|
|
558
604
|
cmd_login() {
|
|
559
|
-
# Complete (or refresh) auth for an EXISTING account:
|
|
560
|
-
#
|
|
605
|
+
# Complete (or refresh) auth for an EXISTING account. Default: full Claude Code login
|
|
606
|
+
# (auto-refreshing .credentials.json, this machine). --token: portable setup-token
|
|
607
|
+
# (Mac + server). Verifies the signed-in email matches the manifest.
|
|
561
608
|
require_manifest
|
|
562
|
-
local id="
|
|
563
|
-
[ -
|
|
609
|
+
local id="" force=0 token=0
|
|
610
|
+
while [ $# -gt 0 ]; do
|
|
611
|
+
case "$1" in
|
|
612
|
+
--force) force=1; shift ;;
|
|
613
|
+
--token) token=1; shift ;;
|
|
614
|
+
--*) die "unknown option: $1" ;;
|
|
615
|
+
*) if [ -z "$id" ]; then id="$1"; shift; else die "unexpected argument: $1"; fi ;;
|
|
616
|
+
esac
|
|
617
|
+
done
|
|
618
|
+
[ -n "$id" ] || die "usage: claude-accounts login <acct-NN> [--token] [--force]"
|
|
564
619
|
valid_acct_id "$id" || die "not a valid account id: $id"
|
|
565
620
|
account_ids | grep -qx "$id" || die "unknown account: $id"
|
|
566
621
|
local d="$ACC_ROOT/$id" email got
|
|
@@ -574,17 +629,24 @@ for a in json.load(open(sys.argv[1])).get('accounts', []):
|
|
|
574
629
|
PYEOF
|
|
575
630
|
)"
|
|
576
631
|
echo "Sign in as $email for $id."
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
632
|
+
if [ "$token" = "1" ]; then
|
|
633
|
+
run_token_ceremony "$d" || die "sign-in failed or aborted — nothing changed"
|
|
634
|
+
got="$(token_email "$CEREMONY_TOKEN")"
|
|
635
|
+
if [ -n "$got" ] && [ "$got" != "$email" ] && [ "$force" != "1" ]; then
|
|
636
|
+
die "you signed in as $got but $id is $email — nothing saved (use --force to override)"
|
|
637
|
+
fi
|
|
638
|
+
( umask 077; printf '%s' "$CEREMONY_TOKEN" > "$d/server.token" )
|
|
639
|
+
chmod 600 "$d/server.token"
|
|
640
|
+
echo "$id token saved (portable — works on Mac and server)."
|
|
641
|
+
else
|
|
642
|
+
run_login_ceremony "$d" "$email" || die "login failed or aborted — nothing changed"
|
|
643
|
+
got="$(config_dir_email "$d")"
|
|
644
|
+
if [ -n "$got" ] && [ "$got" != "$email" ] && [ "$force" != "1" ]; then
|
|
645
|
+
die "you signed in as $got but $id is $email — nothing saved (use --force to override)"
|
|
646
|
+
fi
|
|
647
|
+
echo "$id login saved (.credentials.json, this machine, auto-refreshing)."
|
|
581
648
|
fi
|
|
582
|
-
|
|
583
|
-
chmod 600 "$d/server.token"
|
|
584
|
-
log_to ops.log "login $id verified=${got:-unverified}"
|
|
585
|
-
echo "$id auth saved (token, works on Mac and server)."
|
|
586
|
-
echo "Optional, for auto-refreshing OAuth creds on this machine instead:"
|
|
587
|
-
echo " CLAUDE_ACCOUNT=$id claude # then /login"
|
|
649
|
+
log_to ops.log "login $id verified=${got:-unverified} mode=$([ "$token" = 1 ] && echo token || echo login)"
|
|
588
650
|
auto_sync
|
|
589
651
|
}
|
|
590
652
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-multiacc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Multi-account addon for Claude Code: every claude / claude -p runs under a randomly-picked subscription account with the most usage headroom. Mirrors to a deploy server. No API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/tests/run-tests.sh
CHANGED
|
@@ -33,6 +33,13 @@ if [ "${1:-}" = "auth" ] && [ "${2:-}" = "status" ]; then
|
|
|
33
33
|
printf '{"loggedIn": true, "email": "%s"}\n' "${FAKE_EMAIL:-fake@test}"
|
|
34
34
|
exit 0
|
|
35
35
|
fi
|
|
36
|
+
if [ "${1:-}" = "auth" ] && [ "${2:-}" = "login" ]; then
|
|
37
|
+
[ -n "${FAKE_LOGIN_FAIL:-}" ] && { echo "login aborted" >&2; exit 1; }
|
|
38
|
+
# simulate a completed full-scope login: write auto-refreshing creds to the config dir
|
|
39
|
+
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-login","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999,"scopes":["user:inference"],"subscriptionType":"max"}}' > "${CLAUDE_CONFIG_DIR:-/dev/null}/.credentials.json"
|
|
40
|
+
echo "Logged in."
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
36
43
|
if [ "${1:-}" = "setup-token" ]; then
|
|
37
44
|
echo "Open this sign-in link: https://claude.ai/oauth/authorize?fake=1"
|
|
38
45
|
[ -n "${FAKE_TOKEN_FAIL:-}" ] && { echo "sign-in aborted" >&2; exit 1; }
|
|
@@ -358,13 +365,21 @@ check "import refuses duplicate email" "already registered as acct-01" "$out"
|
|
|
358
365
|
out="$(claude-accounts import a@test --id acct-01 --no-sync 2>&1)"
|
|
359
366
|
check "import same-id update allowed" "Imported acct-01" "$out"
|
|
360
367
|
|
|
361
|
-
# ---- 15c. login-first add (
|
|
368
|
+
# ---- 15c. login-first add (DEFAULT full-login flow): registers after verified auth --
|
|
362
369
|
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=new@test claude-accounts add new@test 2>&1)"
|
|
363
|
-
check "add
|
|
364
|
-
|
|
365
|
-
[ -
|
|
370
|
+
check "add registers after verified login" "Registered acct-04 for new@test" "$out"
|
|
371
|
+
[ -f "$ACC/acct-04/.credentials.json" ] && t_ok "default add writes .credentials.json (full login)" || t_fail "add creds" "missing"
|
|
372
|
+
[ ! -f "$ACC/acct-04/server.token" ] && t_ok "default add does NOT mint a setup-token" || t_fail "add token" "unexpected server.token"
|
|
373
|
+
out="$(CLAUDE_ACCOUNT=acct-04 claude 2>&1)"
|
|
374
|
+
check "new account usable via pin (oauth creds)" "CFG=acct-04" "$out"
|
|
375
|
+
claude-accounts remove acct-04 --yes >/dev/null 2>&1
|
|
376
|
+
|
|
377
|
+
# ---- 15c1. add --token: portable setup-token instead of creds ------------------------
|
|
378
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=tok@test claude-accounts add tok@test --token 2>&1)"
|
|
379
|
+
check "add --token registers via portable token" "Registered acct-04 for tok@test" "$out"
|
|
380
|
+
[ -s "$ACC/acct-04/server.token" ] && t_ok "add --token writes server.token" || t_fail "add --token" "missing"
|
|
366
381
|
out="$(CLAUDE_ACCOUNT=acct-04 claude 2>&1)"
|
|
367
|
-
check "
|
|
382
|
+
check "token account exports CLAUDE_CODE_OAUTH_TOKEN" "TOK=sk-ant-oat01-FAKE" "$out"
|
|
368
383
|
claude-accounts remove acct-04 --yes >/dev/null 2>&1
|
|
369
384
|
|
|
370
385
|
# ---- 15c0. add with NO email: derives it from the verified sign-in -------------------
|
|
@@ -385,19 +400,24 @@ rc=$?
|
|
|
385
400
|
check "email-less add needs a readable identity" "identity could not be read back" "$out"
|
|
386
401
|
[ ! -d "$ACC/acct-04" ] && t_ok "unverifiable email-less add cleaned up" || t_fail "unverifiable cleanup" "dir left"
|
|
387
402
|
|
|
388
|
-
# ---- 15c2. --tui
|
|
389
|
-
out="$(CLAUDE_MULTIACC_FORCE_TTY=1
|
|
390
|
-
check "add --tui
|
|
391
|
-
[ -f "$ACC/acct-04/.credentials.json" ] && t_ok "tui
|
|
403
|
+
# ---- 15c2. --tui is accepted as a no-op alias (full login is the default now) --------
|
|
404
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=tui@test claude-accounts add tui@test --tui 2>&1)"
|
|
405
|
+
check "add --tui still works (alias for default login)" "Registered acct-04 for tui@test" "$out"
|
|
406
|
+
[ -f "$ACC/acct-04/.credentials.json" ] && t_ok "--tui writes creds like the default" || t_fail "tui creds" "missing"
|
|
392
407
|
claude-accounts remove acct-04 --yes >/dev/null 2>&1
|
|
393
408
|
|
|
394
|
-
# ---- 15d. aborted
|
|
395
|
-
out="$(CLAUDE_MULTIACC_FORCE_TTY=1
|
|
409
|
+
# ---- 15d. aborted login leaves zero traces ------------------------------------------
|
|
410
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_LOGIN_FAIL=1 claude-accounts add ghost@test 2>&1)"
|
|
396
411
|
rc=$?
|
|
397
|
-
check "aborted
|
|
412
|
+
check "aborted login detected" "login failed or aborted" "$out"
|
|
398
413
|
[ "$rc" != "0" ] && t_ok "aborted add exits nonzero" || t_fail "aborted add rc" "rc=0"
|
|
399
414
|
[ ! -d "$ACC/acct-04" ] && t_ok "aborted add cleaned up its dir" || t_fail "aborted add cleanup" "dir left behind"
|
|
400
415
|
claude-accounts list 2>&1 | grep -q ghost@test && t_fail "aborted add not in manifest" "ghost@test registered" || t_ok "aborted add not in manifest"
|
|
416
|
+
# aborted --token path too
|
|
417
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_TOKEN_FAIL=1 claude-accounts add ghost2@test --token 2>&1)"
|
|
418
|
+
rc=$?
|
|
419
|
+
check "aborted --token add detected" "sign-in failed or aborted" "$out"
|
|
420
|
+
[ ! -d "$ACC/acct-04" ] && t_ok "aborted --token add cleaned up" || t_fail "aborted token cleanup" "dir left"
|
|
401
421
|
|
|
402
422
|
# ---- 15e. sign-in as an already-registered email is rejected + cleaned ---------------
|
|
403
423
|
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=a@test claude-accounts add brand@test 2>&1)"
|
|
@@ -408,12 +428,16 @@ check "wrong-account sign-in rejected" "already registered as acct-01" "$out"
|
|
|
408
428
|
# ---- 15f. login command completes auth for an existing auth-less account -------------
|
|
409
429
|
claude-accounts import pending@test --id acct-08 --no-sync >/dev/null 2>&1
|
|
410
430
|
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=pending@test claude-accounts login acct-08 2>&1)"
|
|
411
|
-
check "login completes existing account" "acct-08
|
|
412
|
-
[ -
|
|
431
|
+
check "login completes existing account (full login)" "acct-08 login saved" "$out"
|
|
432
|
+
[ -f "$ACC/acct-08/.credentials.json" ] && t_ok "login writes .credentials.json" || t_fail "login creds" "missing"
|
|
413
433
|
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=other@test claude-accounts login acct-08 2>&1)"
|
|
414
434
|
rc=$?
|
|
415
435
|
check "login email mismatch refused" "nothing saved" "$out"
|
|
416
436
|
[ "$rc" != "0" ] && t_ok "mismatched login exits nonzero" || t_fail "mismatched login rc" "rc=0"
|
|
437
|
+
# login --token writes a portable token for the same account
|
|
438
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=pending@test claude-accounts login acct-08 --token 2>&1)"
|
|
439
|
+
check "login --token saves a portable token" "acct-08 token saved" "$out"
|
|
440
|
+
[ -s "$ACC/acct-08/server.token" ] && t_ok "login --token writes server.token" || t_fail "login token" "missing"
|
|
417
441
|
claude-accounts remove acct-08 --yes >/dev/null 2>&1
|
|
418
442
|
|
|
419
443
|
# ---- 16. CLI: limits marking via fixture endpoint ------------------------------
|