claude-multiacc 2.0.13 → 2.0.14
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/bin/claude-accounts +50 -4
- 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/package.json +1 -1
- package/tests/run-tests.sh +19 -1
package/bin/claude-accounts
CHANGED
|
@@ -507,7 +507,7 @@ cmd_add() {
|
|
|
507
507
|
if [ "$token" = "1" ]; then
|
|
508
508
|
# Portable setup-token variant (works on Mac AND server; needs a recent sign-in).
|
|
509
509
|
echo "Preparing $id for $elabel (portable token) — NOTHING is registered until sign-in completes."
|
|
510
|
-
run_token_ceremony "$d" || die "sign-in failed or aborted — nothing was created"
|
|
510
|
+
run_token_ceremony "$d" || die "sign-in failed or aborted — nothing was created${CEREMONY_LAST_WORDS:+ — the client said: $CEREMONY_LAST_WORDS}${CEREMONY_TRANSCRIPT:+ (transcript: $CEREMONY_TRANSCRIPT)}"
|
|
511
511
|
commit_ceremony_token "$d" "$CEREMONY_TOKEN" "$id"
|
|
512
512
|
got="$(token_email "$CEREMONY_TOKEN")"
|
|
513
513
|
else
|
|
@@ -934,9 +934,47 @@ commit_ceremony_token() {
|
|
|
934
934
|
fi
|
|
935
935
|
}
|
|
936
936
|
|
|
937
|
+
# When a ceremony ends without a token, the CLIENT said why — "Your account is on
|
|
938
|
+
# hold", a policy refusal, a subscription it could not find — and that sentence is
|
|
939
|
+
# the only thing that lets anyone fix it. It used to vanish with the capture file,
|
|
940
|
+
# leaving "no token captured" (operator, 2026-08-29). The last visible lines are
|
|
941
|
+
# kept (ANSI-stripped, spinners/logo/URL/prompt dropped, any sk-ant-… redacted) and
|
|
942
|
+
# the whole redacted transcript is written beside the pool for a closer look.
|
|
943
|
+
CEREMONY_LAST_WORDS=""
|
|
944
|
+
CEREMONY_TRANSCRIPT=""
|
|
945
|
+
ceremony_debrief() { # $1 = capture file, $2 = redacted transcript to write; prints the last words
|
|
946
|
+
"$PYBIN" - "$1" "$2" <<'PYEOF'
|
|
947
|
+
import os, re, sys
|
|
948
|
+
raw = open(sys.argv[1], 'rb').read().decode('utf-8', 'ignore')
|
|
949
|
+
txt = re.sub(r'\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)', '', raw) # OSC (hyperlink payloads)
|
|
950
|
+
txt = re.sub(r'\x1b\[[0-9;?]*[A-Za-z]', '', txt) # CSI (colour, cursor)
|
|
951
|
+
txt = re.sub(r'\x1b[()][A-Z0-9]', '', txt).replace('\r', '\n')
|
|
952
|
+
txt = re.sub(r'sk-ant-[A-Za-z0-9]+-[A-Za-z0-9_-]+', 'sk-ant-***', txt)
|
|
953
|
+
lines = []
|
|
954
|
+
for ln in txt.splitlines():
|
|
955
|
+
ln = ln.strip()
|
|
956
|
+
if not ln or re.fullmatch(r'[\W_]+', ln): # spinner frames, logo art
|
|
957
|
+
continue
|
|
958
|
+
if 'https://' in ln or re.search(r'Paste\s*code\s*here', ln): # the sign-in link, the paste prompt
|
|
959
|
+
continue
|
|
960
|
+
if lines and lines[-1] == ln:
|
|
961
|
+
continue
|
|
962
|
+
lines.append(ln)
|
|
963
|
+
try:
|
|
964
|
+
fd = os.open(sys.argv[2], os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
965
|
+
with os.fdopen(fd, 'w') as f:
|
|
966
|
+
f.write('\n'.join(lines) + '\n')
|
|
967
|
+
except OSError:
|
|
968
|
+
pass
|
|
969
|
+
print(' | '.join(lines[-3:])[:400])
|
|
970
|
+
PYEOF
|
|
971
|
+
}
|
|
972
|
+
|
|
937
973
|
CEREMONY_TOKEN=""
|
|
938
974
|
run_token_ceremony() { # $1 = config dir
|
|
939
975
|
CEREMONY_TOKEN=""
|
|
976
|
+
CEREMONY_LAST_WORDS=""
|
|
977
|
+
CEREMONY_TRANSCRIPT=""
|
|
940
978
|
local d="$1" real cap old_umask
|
|
941
979
|
real="$(find_real_claude "$_self")" || { warn "real claude binary not found"; return 1; }
|
|
942
980
|
mkdir -p "$ACC_ROOT/tmp"
|
|
@@ -978,8 +1016,16 @@ TIP
|
|
|
978
1016
|
# frame rendered while the terminal was still narrow holds a wrapped fragment.
|
|
979
1017
|
CEREMONY_TOKEN="$(grep -aoE 'sk-ant-oat[0-9]{2}-[A-Za-z0-9_-]{40,}' "$cap" \
|
|
980
1018
|
| awk '{ if (length($0) > length(best)) best = $0 } END { if (best != "") print best }')"
|
|
1019
|
+
if [ -z "$CEREMONY_TOKEN" ]; then
|
|
1020
|
+
CEREMONY_TRANSCRIPT="$ACC_ROOT/tmp/mint-failed.$(date -u +%Y%m%dT%H%M%SZ).log"
|
|
1021
|
+
CEREMONY_LAST_WORDS="$(ceremony_debrief "$cap" "$CEREMONY_TRANSCRIPT" 2>/dev/null)"
|
|
1022
|
+
[ -s "$CEREMONY_TRANSCRIPT" ] || CEREMONY_TRANSCRIPT=""
|
|
1023
|
+
rm -f "$cap"
|
|
1024
|
+
[ -n "$CEREMONY_LAST_WORDS" ] && warn "the sign-in ended without a token — the client said: $CEREMONY_LAST_WORDS"
|
|
1025
|
+
[ -n "$CEREMONY_TRANSCRIPT" ] && warn "redacted transcript kept at $CEREMONY_TRANSCRIPT"
|
|
1026
|
+
return 1
|
|
1027
|
+
fi
|
|
981
1028
|
rm -f "$cap"
|
|
982
|
-
[ -n "$CEREMONY_TOKEN" ] || return 1
|
|
983
1029
|
valid_subscription_token "$CEREMONY_TOKEN" || {
|
|
984
1030
|
CEREMONY_TOKEN=""
|
|
985
1031
|
warn "captured credential is not a subscription setup-token"
|
|
@@ -1097,7 +1143,7 @@ cmd_mint() {
|
|
|
1097
1143
|
tok="$(printf '%s' "$tok" | tr -d '[:space:]')"
|
|
1098
1144
|
else
|
|
1099
1145
|
echo "Running 'claude setup-token' for $id — approve in a browser signed in as ${email:-THIS account}."
|
|
1100
|
-
run_token_ceremony "$d" || die "no token captured — mint failed"
|
|
1146
|
+
run_token_ceremony "$d" || die "no token captured — mint failed${CEREMONY_LAST_WORDS:+ — the client said: $CEREMONY_LAST_WORDS}${CEREMONY_TRANSCRIPT:+ (transcript: $CEREMONY_TRANSCRIPT)}"
|
|
1101
1147
|
tok="$CEREMONY_TOKEN"
|
|
1102
1148
|
fi
|
|
1103
1149
|
[ -n "$tok" ] || die "no token captured — mint failed"
|
|
@@ -1139,7 +1185,7 @@ cmd_login() {
|
|
|
1139
1185
|
if [ "$token" = "1" ]; then
|
|
1140
1186
|
trap 'restore_ceremony_tty; exit 130' INT TERM
|
|
1141
1187
|
trap 'restore_ceremony_tty' EXIT
|
|
1142
|
-
run_token_ceremony "$d" || die "sign-in failed or aborted — nothing changed"
|
|
1188
|
+
run_token_ceremony "$d" || die "sign-in failed or aborted — nothing changed${CEREMONY_LAST_WORDS:+ — the client said: $CEREMONY_LAST_WORDS}${CEREMONY_TRANSCRIPT:+ (transcript: $CEREMONY_TRANSCRIPT)}"
|
|
1143
1189
|
got="$(token_email "$CEREMONY_TOKEN")"
|
|
1144
1190
|
if [ -n "$got" ] && [ "$got" != "$email" ] && [ "$force" != "1" ]; then
|
|
1145
1191
|
die "you signed in as $got but $id is $email — nothing saved (use --force to override)"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/tests/run-tests.sh
CHANGED
|
@@ -73,7 +73,7 @@ if [ "${1:-}" = "auth" ] && [ "${2:-}" = "login" ]; then
|
|
|
73
73
|
fi
|
|
74
74
|
if [ "${1:-}" = "setup-token" ]; then
|
|
75
75
|
echo "Open this sign-in link: https://claude.ai/oauth/authorize?fake=1"
|
|
76
|
-
[ -n "${FAKE_TOKEN_FAIL:-}" ] && { echo "sign-in aborted" >&2; exit 1; }
|
|
76
|
+
[ -n "${FAKE_TOKEN_FAIL:-}" ] && { echo "${FAKE_TOKEN_FAIL_MSG:-sign-in aborted}" >&2; exit 1; }
|
|
77
77
|
if [ -n "${FAKE_TOKEN_FULL:-}" ]; then
|
|
78
78
|
# The real client's shape: a 108-character token that the TUI HARD-WRAPS at the
|
|
79
79
|
# terminal's width — an unsized pty (0 columns) renders as 80, and a 79-character
|
|
@@ -1415,6 +1415,24 @@ check "the inconclusive save says so" "saved as UNVERIFIED" "$out"
|
|
|
1415
1415
|
[ ! -f "$ACC/acct-04/.server-token-verified" ] && t_ok "an unproven save records no proof" \
|
|
1416
1416
|
|| t_fail "unproven proof marker" "marker present"
|
|
1417
1417
|
printf '%s' "$orig" > "$ACC/acct-04/server.token"
|
|
1418
|
+
# The client ended the ceremony without a token and said why: that sentence must
|
|
1419
|
+
# reach the operator (the panel shows the mint's own error line), and the redacted
|
|
1420
|
+
# transcript must be kept for a closer look — "no token captured" alone left an
|
|
1421
|
+
# operator with nothing to act on (2026-08-29).
|
|
1422
|
+
out="$(FAKE_TOKEN_FAIL=1 FAKE_TOKEN_FAIL_MSG="Your account is on hold. You can close this window." claude-accounts mint acct-04 2>&1 </dev/null)"
|
|
1423
|
+
rc=$?
|
|
1424
|
+
[ "$rc" != "0" ] && t_ok "a ceremony that ends without a token fails the mint" || t_fail "no-token mint rc" "rc=0"
|
|
1425
|
+
check "the mint error carries the client's last words" "the client said: Your account is on hold" "$out"
|
|
1426
|
+
check "the mint error names the kept transcript" "transcript: $ACC/tmp/mint-failed." "$out"
|
|
1427
|
+
tr="$(printf '%s' "$out" | sed -n 's/.*transcript: \([^)]*\)).*/\1/p' | head -1)"
|
|
1428
|
+
[ -n "$tr" ] && [ -s "$tr" ] && t_ok "the redacted transcript exists" || t_fail "transcript file" "missing: '$tr'"
|
|
1429
|
+
grep -q 'Your account is on hold' "$tr" 2>/dev/null && t_ok "the transcript holds the client's words" \
|
|
1430
|
+
|| t_fail "transcript content" "message missing"
|
|
1431
|
+
# (python, not stat: GNU stat reads -f as "file system" and answers something else)
|
|
1432
|
+
[ "$(python3 -c 'import os, sys; print(oct(os.stat(sys.argv[1]).st_mode & 0o777))' "$tr")" = "0o600" ] \
|
|
1433
|
+
&& t_ok "the transcript is private" || t_fail "transcript mode" "not 0600"
|
|
1434
|
+
[ "$(cat "$ACC/acct-04/server.token")" = "$orig" ] && t_ok "a ceremony without a token saves nothing" \
|
|
1435
|
+
|| t_fail "no-token save" "server.token was overwritten"
|
|
1418
1436
|
# A pasted token gets the same proof: a revoked one is refused, not saved.
|
|
1419
1437
|
# (A complete-length token, so the verdict is the rejection itself and not the
|
|
1420
1438
|
# length rule that catches wrapped fragments first.)
|