claude-multiacc 1.0.0 → 1.0.1
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 +14 -7
- package/bin/claude-accounts +42 -39
- package/package.json +1 -1
- package/tests/run-tests.sh +19 -0
package/README.md
CHANGED
|
@@ -163,16 +163,23 @@ 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 — it's read back from the sign-in
|
|
167
|
+
# or name it explicitly (fast-fails on a duplicate before sign-in):
|
|
166
168
|
claude-accounts add new@example.com
|
|
167
169
|
```
|
|
168
170
|
|
|
169
|
-
It prints a **clickable sign-in link**. Open it in a browser signed into
|
|
170
|
-
approve, and paste the code back into the same prompt. The
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
It prints a **clickable sign-in link**. Open it in a browser signed into the account you
|
|
172
|
+
want to add, approve, and paste the code back into the same prompt. The sign-in screen
|
|
173
|
+
asks only for *"Contribute to your Claude subscription usage"* (`user:inference` scope) —
|
|
174
|
+
that's intentional: the token can run `claude` / `claude -p` and nothing else, and it
|
|
175
|
+
draws on the subscription, never API credits.
|
|
176
|
+
|
|
177
|
+
You don't have to pass the email — the command reads the authenticated address back from
|
|
178
|
+
the account and registers under that. The account is registered **only after the sign-in
|
|
179
|
+
is verified** — an aborted or failed sign-in leaves nothing behind, and a duplicate email
|
|
180
|
+
is refused. The captured token has a ~1-year lifetime, is inference-only, and does not
|
|
181
|
+
rotate → it works on the Mac **and** the server with no cross-machine refresh races, so
|
|
182
|
+
the account is usable everywhere the moment `add` returns.
|
|
176
183
|
|
|
177
184
|
```bash
|
|
178
185
|
claude-accounts login acct-NN # re-run the same flow for an existing account
|
package/bin/claude-accounts
CHANGED
|
@@ -20,10 +20,11 @@ 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
|
|
23
|
+
claude-accounts add [email] [--force|--tui]
|
|
24
24
|
login-FIRST: shows a sign-in link, you paste the browser code back, and the
|
|
25
|
-
account is registered only after auth is verified.
|
|
26
|
-
|
|
25
|
+
account is registered only after auth is verified. The email is OPTIONAL — omit
|
|
26
|
+
it and it's read back from whoever you sign in as. Duplicate emails refused.
|
|
27
|
+
--tui uses interactive /login for full OAuth creds instead of a setup-token.
|
|
27
28
|
claude-accounts login <acct-NN> [--force] complete/refresh auth for an existing
|
|
28
29
|
account (same link+code flow)
|
|
29
30
|
claude-accounts import <email> [opts] register an account, optionally with credentials
|
|
@@ -229,27 +230,28 @@ PYEOF
|
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
cmd_add() {
|
|
232
|
-
# Login-FIRST: the account is registered (and synced) only after
|
|
233
|
-
#
|
|
234
|
-
#
|
|
233
|
+
# Login-FIRST: the account is registered (and synced) only after sign-in succeeds
|
|
234
|
+
# and the authenticated email is read back from the account itself. The email
|
|
235
|
+
# argument is OPTIONAL — omit it and it's derived from whoever you sign in as.
|
|
236
|
+
# An aborted or failed sign-in leaves zero traces.
|
|
235
237
|
require_manifest
|
|
236
|
-
local email="
|
|
237
|
-
[ -n "$email" ] || die "usage: claude-accounts add <email> [--force]"
|
|
238
|
-
shift
|
|
239
|
-
local force=0 tui=0
|
|
238
|
+
local email="" force=0 tui=0
|
|
240
239
|
while [ $# -gt 0 ]; do
|
|
241
240
|
case "$1" in
|
|
242
241
|
--force) force=1; shift ;;
|
|
243
242
|
--tui) tui=1; shift ;;
|
|
244
|
-
|
|
243
|
+
--*) die "unknown option: $1" ;;
|
|
244
|
+
*)
|
|
245
|
+
if [ -z "$email" ]; then email="$1"; shift
|
|
246
|
+
else die "unexpected argument: $1 (usage: claude-accounts add [email] [--tui] [--force])"; fi ;;
|
|
245
247
|
esac
|
|
246
248
|
done
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
+
# If an email was named, fast-fail on a duplicate now. If not, we learn (and dedup)
|
|
250
|
+
# the real email from the sign-in below — one account per email either way.
|
|
249
251
|
local owner
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
die "$email is already registered as $owner — nothing created (run 'claude-accounts login $owner' to re-authenticate it)"
|
|
252
|
+
if [ -n "$email" ]; then
|
|
253
|
+
owner="$(email_owner "$email")"
|
|
254
|
+
[ -n "$owner" ] && die "$email is already registered as $owner — nothing created (run 'claude-accounts login $owner' to re-authenticate it)"
|
|
253
255
|
fi
|
|
254
256
|
if [ ! -t 0 ] && [ -z "${CLAUDE_MULTIACC_FORCE_TTY:-}" ]; then
|
|
255
257
|
die "add is interactive (it completes sign-in before registering) — run it from a terminal"
|
|
@@ -269,18 +271,18 @@ cmd_add() {
|
|
|
269
271
|
trap "rm -rf '$lock'" EXIT
|
|
270
272
|
local real
|
|
271
273
|
real="$(find_real_claude "$_self")" || die "real claude binary not found"
|
|
272
|
-
local id d got
|
|
274
|
+
local id d got elabel
|
|
275
|
+
elabel="${email:-the account you sign in as}"
|
|
273
276
|
id="$(next_id)"
|
|
274
277
|
d="$ACC_ROOT/$id"
|
|
275
278
|
seed_account_dir "$d"
|
|
276
279
|
if [ "$tui" = "1" ]; then
|
|
277
280
|
# Full-OAuth variant: interactive /login in the new dir (auto-refreshing creds).
|
|
278
281
|
cat <<EOF
|
|
279
|
-
Preparing $id for $
|
|
280
|
-
An interactive claude session opens now with no account
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
2. /status must show $email
|
|
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
|
|
284
286
|
3. exit the session (/exit or ctrl+d) to finish registration
|
|
285
287
|
EOF
|
|
286
288
|
CLAUDE_CONFIG_DIR="$d" CLAUDE_SHIM_ACTIVE=1 "$real" || true
|
|
@@ -302,7 +304,7 @@ except Exception:
|
|
|
302
304
|
else
|
|
303
305
|
# Default: sign-in link -> user pastes the code back here -> token captured.
|
|
304
306
|
# The token works on BOTH machines (no refresh rotation, ~1y lifetime).
|
|
305
|
-
echo "Preparing $id for $
|
|
307
|
+
echo "Preparing $id for $elabel — NOTHING is registered until sign-in completes."
|
|
306
308
|
if ! run_token_ceremony "$d"; then
|
|
307
309
|
rm -rf "${ACC_ROOT:?}/${id:?}"
|
|
308
310
|
die "sign-in failed or aborted — nothing was created"
|
|
@@ -311,27 +313,28 @@ except Exception:
|
|
|
311
313
|
chmod 600 "$d/server.token"
|
|
312
314
|
got="$(token_email "$CEREMONY_TOKEN")"
|
|
313
315
|
if [ -z "$got" ]; then
|
|
314
|
-
#
|
|
315
|
-
#
|
|
316
|
-
#
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
# Signed in, but we could not read back WHICH account. Without a verified email
|
|
317
|
+
# we cannot label or dedup the account, so refuse — unless an email was named
|
|
318
|
+
# AND --force was given (then we trust the named email).
|
|
319
|
+
if [ -n "$email" ] && [ "$force" = "1" ]; then
|
|
320
|
+
warn "identity unverified — registering as $email because --force was given"
|
|
321
|
+
got="$email"
|
|
322
|
+
else
|
|
319
323
|
rm -rf "${ACC_ROOT:?}/${id:?}"
|
|
320
|
-
die "signed in, but the account identity could not be
|
|
324
|
+
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)"
|
|
321
325
|
fi
|
|
322
|
-
warn "identity unverified — registering as $email because --force was given"
|
|
323
|
-
got="$email"
|
|
324
326
|
fi
|
|
325
327
|
fi
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
328
|
+
# `got` is now the authoritative signed-in email. Dedup against it, warn on mismatch.
|
|
329
|
+
owner="$(email_owner "$got")"
|
|
330
|
+
if [ -n "$owner" ]; then
|
|
331
|
+
rm -rf "${ACC_ROOT:?}/${id:?}"
|
|
332
|
+
die "you signed in as $got, which is already registered as $owner — nothing was created"
|
|
333
|
+
fi
|
|
334
|
+
if [ -n "$email" ] && [ "$got" != "$email" ]; then
|
|
335
|
+
warn "signed in as $got (you named $email) — registering the account that actually authenticated"
|
|
334
336
|
fi
|
|
337
|
+
email="$got"
|
|
335
338
|
manifest_add_account "$id" "$email" "$(machine_kind)"
|
|
336
339
|
log_to ops.log "add $id $email (auth-verified)"
|
|
337
340
|
auto_sync
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-multiacc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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
|
@@ -29,6 +29,7 @@ cat > "$FAKEBIN/claude" <<'EOF'
|
|
|
29
29
|
#!/usr/bin/env bash
|
|
30
30
|
# fake real claude for tests (not a multiacc shim)
|
|
31
31
|
if [ "${1:-}" = "auth" ] && [ "${2:-}" = "status" ]; then
|
|
32
|
+
if [ -n "${FAKE_AUTH_FAIL:-}" ]; then echo '{"loggedIn": false}'; exit 0; fi
|
|
32
33
|
printf '{"loggedIn": true, "email": "%s"}\n' "${FAKE_EMAIL:-fake@test}"
|
|
33
34
|
exit 0
|
|
34
35
|
fi
|
|
@@ -366,6 +367,24 @@ out="$(CLAUDE_ACCOUNT=acct-04 claude 2>&1)"
|
|
|
366
367
|
check "new account usable via pin (token auth)" "TOK=sk-ant-oat01-FAKE" "$out"
|
|
367
368
|
claude-accounts remove acct-04 --yes >/dev/null 2>&1
|
|
368
369
|
|
|
370
|
+
# ---- 15c0. add with NO email: derives it from the verified sign-in -------------------
|
|
371
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=derived@test claude-accounts add 2>&1)"
|
|
372
|
+
check "add without email registers the signed-in address" "Registered acct-04 for derived@test" "$out"
|
|
373
|
+
claude-accounts list 2>&1 | grep -q "derived@test" && t_ok "email-less add lands in the manifest" \
|
|
374
|
+
|| t_fail "email-less add" "derived@test not registered"
|
|
375
|
+
claude-accounts remove acct-04 --yes >/dev/null 2>&1
|
|
376
|
+
# add with no email, but the signed-in email is already registered => refuse + clean up
|
|
377
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=a@test claude-accounts add 2>&1)"
|
|
378
|
+
rc=$?
|
|
379
|
+
check "email-less add refuses a duplicate signed-in email" "already registered as acct-01" "$out"
|
|
380
|
+
[ "$rc" != "0" ] && t_ok "email-less duplicate exits nonzero" || t_fail "email-less dup rc" "rc=0"
|
|
381
|
+
[ ! -d "$ACC/acct-04" ] && t_ok "email-less duplicate cleaned up" || t_fail "email-less dup cleanup" "dir left"
|
|
382
|
+
# add with no email but identity can't be read back => refuse (can't label the account)
|
|
383
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_AUTH_FAIL=1 claude-accounts add 2>&1)"
|
|
384
|
+
rc=$?
|
|
385
|
+
check "email-less add needs a readable identity" "identity could not be read back" "$out"
|
|
386
|
+
[ ! -d "$ACC/acct-04" ] && t_ok "unverifiable email-less add cleaned up" || t_fail "unverifiable cleanup" "dir left"
|
|
387
|
+
|
|
369
388
|
# ---- 15c2. --tui variant registers via /login creds --------------------------------
|
|
370
389
|
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_DO_LOGIN=1 FAKE_EMAIL=tui@test claude-accounts add tui@test --tui 2>&1)"
|
|
371
390
|
check "add --tui registers after verified login" "Registered acct-04 for tui@test" "$out"
|