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 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 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.
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
@@ -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 <email> [--force|--tui]
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. Duplicate emails refused
26
- (--force overrides). --tui uses interactive /login for full OAuth creds.
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 /login
233
- # succeeded in the new dir and the authenticated email has been verified.
234
- # An aborted or failed login leaves zero traces.
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="${1:-}"
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
- *) die "unknown option: $1" ;;
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
- # One account per email, always: a duplicate is never useful and silently splits the
248
- # pool. There is no --force escape hatch here (use `login <acct-NN>` to re-auth).
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
- owner="$(email_owner "$email")"
251
- if [ -n "$owner" ]; then
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 $email — NOTHING is registered until login succeeds.
280
- An interactive claude session opens now with no account. In ONE incognito
281
- window signed into $email:
282
- 1. type /login (press c to copy the URL into the incognito window)
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 $email — NOTHING is registered until sign-in completes."
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
- # Authentication produced a token, but we could not confirm WHICH account it
315
- # belongs to. Registering on the claimed email could silently create a duplicate
316
- # or mislabel the account, so refuse the requirement is verified auth, not
317
- # merely successful auth. --force accepts the claimed email deliberately.
318
- if [ "$force" != "1" ]; then
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 verified — nothing was created (retry, or use --force to trust '$email')"
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
- if [ "$got" != "$email" ]; then
327
- owner="$(email_owner "$got")"
328
- if [ -n "$owner" ]; then
329
- rm -rf "${ACC_ROOT:?}/${id:?}"
330
- die "you signed in as $got, which is already registered as $owner — nothing was created"
331
- fi
332
- warn "signed in as $got (expected $email) registering the account that actually authenticated"
333
- email="$got"
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.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": {
@@ -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"