claude-multiacc 2.0.35 → 2.0.36
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 +31 -0
- package/bin/claude +68 -1
- package/bin/claude-accounts +153 -10
- package/bin/codex-accounts +23 -1
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.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/__pycache__/shim_path.cpython-312.pyc +0 -0
- package/lib/claude_reset.py +673 -0
- package/lib/common.sh +3 -1
- package/lib/report.py +18 -0
- package/package.json +1 -1
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.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/run-tests.sh +108 -0
- package/tests/test_claude_reset.py +622 -0
- package/tests/test_codex_reset.py +35 -0
package/lib/common.sh
CHANGED
|
@@ -36,7 +36,9 @@ else
|
|
|
36
36
|
ACC_ROOT="${CLAUDE_ACCOUNTS_ROOT:-${CLAUDE_ACCOUNTS_DIR:-$HOME/.claude-accounts}}"
|
|
37
37
|
DEFAULT_ACC_ROOT="$HOME/.claude-accounts"
|
|
38
38
|
DEFAULT_SERVER_ROOT="/root/.claude-accounts"
|
|
39
|
-
|
|
39
|
+
# `?cedar_ember=1&skip_spend=1` is Claude Code's own /limit-reset read: the same
|
|
40
|
+
# usage payload plus the limit-reset status block (lib/claude_reset.py), in ONE call.
|
|
41
|
+
USAGE_URL="${CLAUDE_MULTIACC_USAGE_URL:-https://api.anthropic.com/api/oauth/usage?cedar_ember=1&skip_spend=1}"
|
|
40
42
|
AUDIT_PY="$LIB_DIR/audit.py"
|
|
41
43
|
PROVIDER_CLI="claude-accounts"
|
|
42
44
|
SYNC_TARGET_ENV="${CLAUDE_MULTIACC_SYNC_TARGET:-${MULTIACC_SYNC_TARGET:-}}"
|
package/lib/report.py
CHANGED
|
@@ -47,6 +47,7 @@ import time
|
|
|
47
47
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
48
48
|
import keychain # noqa: E402
|
|
49
49
|
from audit import token_verified # noqa: E402
|
|
50
|
+
from claude_reset import marker_superseded, record_from # noqa: E402
|
|
50
51
|
|
|
51
52
|
SCHEMA = 'claude-multiacc/pool.v1'
|
|
52
53
|
|
|
@@ -246,6 +247,12 @@ def _usage(d, stale_after):
|
|
|
246
247
|
reset_stamp = _iso(lim.get('reset_credits_fetched_at'))
|
|
247
248
|
if type(reset_count) is int and 0 <= reset_count <= 1_000_000 and reset_stamp:
|
|
248
249
|
out.update(reset_credits_available=reset_count, reset_credits_fetched_at=reset_stamp)
|
|
250
|
+
# A confirmed limit reset (claude): when it happened and which limit types it refilled.
|
|
251
|
+
# Every park written before it for one of those windows is lifted — here, in the shim,
|
|
252
|
+
# and (from this field) in the app-robot panel. Emitted only while the writer carries it.
|
|
253
|
+
record = record_from(lim, time.time())
|
|
254
|
+
if record:
|
|
255
|
+
out.update(reset_redeemed_at=_iso(record[0]), reset_cleared=list(record[1]))
|
|
249
256
|
try:
|
|
250
257
|
out['age_seconds'] = max(0, int(time.time() - float(fetched)))
|
|
251
258
|
except (TypeError, ValueError):
|
|
@@ -368,6 +375,17 @@ def _account_row(root, a, aid, d, provider, jwt_claims, audit_account, now, kind
|
|
|
368
375
|
cclass, cdetail = _claude_credentials(d)
|
|
369
376
|
reset_epoch, marker_detail = _marker(os.path.join(d, '.limited'))
|
|
370
377
|
limited = reset_epoch > now or (reset_epoch == 0 and os.path.isfile(os.path.join(d, '.limited')))
|
|
378
|
+
if limited and provider == 'claude':
|
|
379
|
+
# The shim's reset_supersedes_marker: a park from before a confirmed limit reset,
|
|
380
|
+
# for a window it refilled, no longer binds — even as a stale copy on a peer.
|
|
381
|
+
try:
|
|
382
|
+
with open(os.path.join(d, '.limited'), errors='replace') as handle:
|
|
383
|
+
text = handle.read(4096)
|
|
384
|
+
if marker_superseded(text, record_from(_load_json(os.path.join(d, 'limits.json')),
|
|
385
|
+
time.time())):
|
|
386
|
+
limited = False
|
|
387
|
+
except OSError:
|
|
388
|
+
pass
|
|
371
389
|
status = st['state']
|
|
372
390
|
if status == 'ok':
|
|
373
391
|
status = 'limited' if limited else 'active'
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/tests/run-tests.sh
CHANGED
|
@@ -2271,6 +2271,107 @@ grep -q "acct-01 client limit cleared by newer telemetry (5%)" "$ND/selection.lo
|
|
|
2271
2271
|
&& t_ok "the five_hour recovery still logs the reading it acted on" \
|
|
2272
2272
|
|| t_fail "five_hour client marker" "selection.log: $(tail -2 "$ND/selection.log" 2>/dev/null | tr '\n' ' ')"
|
|
2273
2273
|
|
|
2274
|
+
# ---- 16-limit-reset-fleet. a confirmed limit reset lifts every OLDER park, fleet-wide --
|
|
2275
|
+
# The writer that redeemed a reset deletes its own marker, but peers are pushed `.limited`
|
|
2276
|
+
# files and never their deletion, and the panel re-writes its verdicts: so the reset
|
|
2277
|
+
# travels in limits.json (reset_redeemed_at + reset_cleared) and the shim lifts any park
|
|
2278
|
+
# written at or before it for a window it refilled — even a client:seven_day one, which
|
|
2279
|
+
# no telemetry may lift. A park written AFTER the reset, or for a window the reset did
|
|
2280
|
+
# not refill, stays exactly as it was.
|
|
2281
|
+
lr_doc() { # lr_doc <redeemed_at epoch> <cleared csv> -> a post-reset limits.json (stale)
|
|
2282
|
+
printf '{"fetched_at":0,"source":"oauth","reset_redeemed_at":%s,"reset_cleared":"%s","buckets":[]}' "$1" "$2"
|
|
2283
|
+
}
|
|
2284
|
+
# The ND pool is shared with the blocks below: save what this block overwrites.
|
|
2285
|
+
for lr_f in acct-01/limits.json acct-02/limits.json acct-01/.limited acct-01/.client-limit-cleared; do
|
|
2286
|
+
rm -f "$WORK/lr-save.${lr_f//\//_}"
|
|
2287
|
+
[ -f "$ND/$lr_f" ] && cp -p "$ND/$lr_f" "$WORK/lr-save.${lr_f//\//_}"
|
|
2288
|
+
done
|
|
2289
|
+
lr_now="$(date +%s)"
|
|
2290
|
+
lr_at=$((lr_now - 600))
|
|
2291
|
+
lr_before="$(date -u -r $((lr_at - 3600)) +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "@$((lr_at - 3600))" +%Y-%m-%dT%H:%M:%SZ)"
|
|
2292
|
+
lr_after="$(date -u -r $((lr_at + 60)) +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "@$((lr_at + 60))" +%Y-%m-%dT%H:%M:%SZ)"
|
|
2293
|
+
lr_doc "$lr_at" "five_hour,seven_day,seven_day_overage_included" > "$ND/acct-01/limits.json"
|
|
2294
|
+
ndl 45 10 45 > "$ND/acct-02/limits.json"
|
|
2295
|
+
printf '%s\nbucket=client:seven_day percent=100 marked_at=%s reason=client-rate-limit\n' \
|
|
2296
|
+
"$((lr_now + 345600))" "$lr_before" > "$ND/acct-01/.limited"
|
|
2297
|
+
rm -f "$ND"/acct-0*/.client-limit-cleared "$ND/.pick-seq" "$ND"/acct-0*/.last-pick
|
|
2298
|
+
: > "$ND/selection.log"
|
|
2299
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2300
|
+
[ ! -f "$ND/acct-01/.limited" ] \
|
|
2301
|
+
&& t_ok "a client:seven_day park older than a confirmed limit reset is lifted" \
|
|
2302
|
+
|| t_fail "limit reset lift" "marker kept: $(sed -n 2p "$ND/acct-01/.limited")"
|
|
2303
|
+
grep -q "acct-01 marker lifted — written before a confirmed limit reset" "$ND/selection.log" \
|
|
2304
|
+
&& t_ok "the lift is logged" || t_fail "limit reset lift" "selection.log: $(tail -2 "$ND/selection.log" | tr '\n' ' ')"
|
|
2305
|
+
# the panel's own shapes: a stream weekly_all park, and a bucket-less panel:observed one
|
|
2306
|
+
for lr_bucket in weekly_all panel:observed weekly_scoped:Fable session; do
|
|
2307
|
+
printf '%s\nbucket=%s percent=100 marked_at=%s reason=client-rate-limit source=stream\n' \
|
|
2308
|
+
"$((lr_now + 345600))" "$lr_bucket" "$lr_before" > "$ND/acct-01/.limited"
|
|
2309
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2310
|
+
[ ! -f "$ND/acct-01/.limited" ] \
|
|
2311
|
+
&& t_ok "an older $lr_bucket park is lifted by the reset" \
|
|
2312
|
+
|| t_fail "limit reset lift" "$lr_bucket kept"
|
|
2313
|
+
done
|
|
2314
|
+
printf '%s\nbucket=client:seven_day percent=100 marked_at=%s reason=client-rate-limit\n' \
|
|
2315
|
+
"$((lr_now + 345600))" "$lr_after" > "$ND/acct-01/.limited"
|
|
2316
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2317
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2318
|
+
&& t_ok "a park written AFTER the reset stands" || t_fail "limit reset lift" "a post-reset park was lifted"
|
|
2319
|
+
printf '%s\nbucket=client:seven_day_opus percent=100 marked_at=%s reason=client-rate-limit\n' \
|
|
2320
|
+
"$((lr_now + 345600))" "$lr_before" > "$ND/acct-01/.limited"
|
|
2321
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2322
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2323
|
+
&& t_ok "a park for a window the reset did not refill stands" \
|
|
2324
|
+
|| t_fail "limit reset lift" "seven_day_opus was lifted by a reset that did not clear it"
|
|
2325
|
+
# an expired record (older than eight days) lifts nothing
|
|
2326
|
+
lr_doc "$((lr_now - 9 * 86400))" "five_hour,seven_day" > "$ND/acct-01/limits.json"
|
|
2327
|
+
printf '%s\nbucket=client:seven_day percent=100 marked_at=2020-01-01T00:00:00Z reason=client-rate-limit\n' \
|
|
2328
|
+
"$((lr_now + 345600))" > "$ND/acct-01/.limited"
|
|
2329
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2330
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2331
|
+
&& t_ok "an expired reset record lifts nothing" || t_fail "limit reset lift" "an expired record lifted a park"
|
|
2332
|
+
# the report agrees with the shim: a superseded park is not `limited`
|
|
2333
|
+
lr_doc "$lr_at" "five_hour,seven_day,seven_day_overage_included" > "$ND/acct-01/limits.json"
|
|
2334
|
+
printf '%s\nbucket=weekly_all percent=95 marked_at=%s reason=client-rate-limit source=stream\n' \
|
|
2335
|
+
"$((lr_now + 345600))" "$lr_before" > "$ND/acct-01/.limited"
|
|
2336
|
+
lr_json="$(CLAUDE_ACCOUNTS_ROOT="$ND" claude-accounts list --json 2>/dev/null)"
|
|
2337
|
+
printf '%s' "$lr_json" | python3 -c '
|
|
2338
|
+
import json, sys
|
|
2339
|
+
row = next(a for a in json.load(sys.stdin)["accounts"] if a["id"] == "acct-01")
|
|
2340
|
+
assert row["limited"] is False and row["status"] != "limited", row
|
|
2341
|
+
assert row["usage"]["reset_cleared"] == ["five_hour", "seven_day", "seven_day_overage_included"], row["usage"]
|
|
2342
|
+
assert row["usage"]["reset_redeemed_at"].endswith("Z"), row["usage"]
|
|
2343
|
+
' && t_ok "list --json reports a superseded park as not limited, with the reset record" \
|
|
2344
|
+
|| t_fail "limit reset report" "$(printf '%s' "$lr_json" | head -c 400)"
|
|
2345
|
+
lr_text="$(CLAUDE_ACCOUNTS_ROOT="$ND" claude-accounts list 2>/dev/null | grep '^acct-01')"
|
|
2346
|
+
case "$lr_text" in
|
|
2347
|
+
*LIMITED*) t_fail "limit reset report" "text list still says: $lr_text" ;;
|
|
2348
|
+
*) t_ok "the text list agrees: a superseded park is not LIMITED" ;;
|
|
2349
|
+
esac
|
|
2350
|
+
lr_status="$(CLAUDE_ACCOUNTS_ROOT="$ND" CLAUDE_MULTIACC_PATH_PROBE=0 claude-accounts status 2>/dev/null)"
|
|
2351
|
+
check "status says the park was lifted by the reset" "lifted — written before a confirmed limit reset" "$lr_status"
|
|
2352
|
+
# ...and the transcript scan cannot re-create a park from a rejection the reset answered,
|
|
2353
|
+
# on a machine that never saw the source's local watermark (a peer): the record is enough.
|
|
2354
|
+
lr_iso() { date -u -r "$1" +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null || date -u -d "@$1" +%Y-%m-%dT%H:%M:%S.000Z; }
|
|
2355
|
+
rm -f "$ND"/acct-0*/.limited "$ND"/acct-0*/.client-limit-cleared "$ND"/acct-0*/.client-scan
|
|
2356
|
+
lr_doc "$((lr_now - 60))" "five_hour,seven_day,seven_day_overage_included" > "$ND/acct-01/limits.json"
|
|
2357
|
+
mkclientlimit "$ND/acct-01" "5d1c7a42-1f4e-4c55-9c3e-0d2f6a8b9e10" "$((lr_now + 3600))" "$(lr_iso $((lr_now - 300)))"
|
|
2358
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2359
|
+
[ ! -f "$ND/acct-01/.limited" ] \
|
|
2360
|
+
&& t_ok "a rejection the reset answered cannot re-park the account from the transcript" \
|
|
2361
|
+
|| t_fail "limit reset watermark" "the scan re-created: $(sed -n 2p "$ND/acct-01/.limited")"
|
|
2362
|
+
rm -f "$ND"/acct-0*/.client-scan
|
|
2363
|
+
mkclientlimit "$ND/acct-01" "5d1c7a42-1f4e-4c55-9c3e-0d2f6a8b9e10" "$((lr_now + 3600))" "$(lr_iso $((lr_now - 20)))"
|
|
2364
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2365
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2366
|
+
&& t_ok "a rejection AFTER the reset still parks the account" \
|
|
2367
|
+
|| t_fail "limit reset watermark" "a post-reset rejection was ignored"
|
|
2368
|
+
rm -rf "$ND/acct-01/projects" "$ND/acct-01/.sessions-index"
|
|
2369
|
+
rm -f "$ND"/acct-0*/.limited "$ND"/acct-0*/.client-limit-cleared "$ND"/acct-0*/.client-scan
|
|
2370
|
+
for lr_f in acct-01/limits.json acct-02/limits.json acct-01/.limited acct-01/.client-limit-cleared; do
|
|
2371
|
+
rm -f "$ND/$lr_f"
|
|
2372
|
+
[ -f "$WORK/lr-save.${lr_f//\//_}" ] && cp -p "$WORK/lr-save.${lr_f//\//_}" "$ND/$lr_f"
|
|
2373
|
+
done
|
|
2374
|
+
|
|
2274
2375
|
# ---- 16-nodata-shorten. an offender pass must never SHORTEN a weekly client marker ---
|
|
2275
2376
|
# The writer's offenders branch used to overwrite `.limited` unconditionally: a session
|
|
2276
2377
|
# bucket crossing the threshold (+1h reset) replaced a client:seven_day marker four days
|
|
@@ -7200,6 +7301,13 @@ if CODEX_MULTIACC_AUTO_RESET=1 python3 -m unittest discover -s "$REPO_DIR/tests"
|
|
|
7200
7301
|
else
|
|
7201
7302
|
t_fail "codex automatic reset suite" "see unittest output above"
|
|
7202
7303
|
fi
|
|
7304
|
+
# ...and its claude twin: the cedar_ember status rides on the usage response, the claim
|
|
7305
|
+
# is idempotent across the fleet, and a confirmed reset lifts even a weekly client park.
|
|
7306
|
+
if CLAUDE_MULTIACC_AUTO_RESET=1 python3 -m unittest discover -s "$REPO_DIR/tests" -p 'test_claude_reset*.py'; then
|
|
7307
|
+
t_ok "claude: automatic limit-reset integration suite"
|
|
7308
|
+
else
|
|
7309
|
+
t_fail "claude automatic reset suite" "see unittest output above"
|
|
7310
|
+
fi
|
|
7203
7311
|
|
|
7204
7312
|
# ---- summary ---------------------------------------------------------------------
|
|
7205
7313
|
echo
|