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/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
- USAGE_URL="${CLAUDE_MULTIACC_USAGE_URL:-https://api.anthropic.com/api/oauth/usage}"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-multiacc",
3
- "version": "2.0.35",
3
+ "version": "2.0.36",
4
4
  "description": "Unified Claude Code and OpenAI Codex subscription pooling with quota-aware selection.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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