claude-multiacc 2.0.17 → 2.0.18
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 +19 -2
- 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 +64 -0
package/bin/claude-accounts
CHANGED
|
@@ -2114,6 +2114,10 @@ for acct in manifest.get('accounts', []):
|
|
|
2114
2114
|
'percent': pct,
|
|
2115
2115
|
'resets_at': resets,
|
|
2116
2116
|
'resets_epoch': int(parse_iso(resets) or now + 3600),
|
|
2117
|
+
# Present only for a bucket that belongs to ONE model: such a
|
|
2118
|
+
# bucket parks that model, never the account (see the marker
|
|
2119
|
+
# choice below and the shim's scoped_blocks_run).
|
|
2120
|
+
'model': model,
|
|
2117
2121
|
})
|
|
2118
2122
|
if not buckets:
|
|
2119
2123
|
# Fallback for a payload with no limits[] array: scan EVERY top-level object
|
|
@@ -2175,8 +2179,21 @@ for acct in manifest.get('accounts', []):
|
|
|
2175
2179
|
offenders = [b for b in buckets if b['percent'] >= threshold]
|
|
2176
2180
|
mpath = os.path.join(d, '.limited')
|
|
2177
2181
|
if offenders:
|
|
2178
|
-
|
|
2179
|
-
|
|
2182
|
+
# ONE bucket, described consistently: the marker's epoch is the reset of
|
|
2183
|
+
# the bucket its detail line names. It used to pair the highest PERCENT
|
|
2184
|
+
# with the LATEST reset over every offender, and the two came from
|
|
2185
|
+
# different buckets: a 100% session bucket resetting in a minute was
|
|
2186
|
+
# written with a Fable weekly bucket's epoch six days out, and everything
|
|
2187
|
+
# that trusts the marker (this shim, app-robot's fleet verdict on every
|
|
2188
|
+
# Mac) parked the whole account for six days over a five-hour window
|
|
2189
|
+
# (2026-08-29, acct-05). An account-level bucket wins over a model-scoped
|
|
2190
|
+
# one — the latter parks a model, not the account (scoped_blocks_run) —
|
|
2191
|
+
# and among those, the longest-lived exclusion, since any bucket over the
|
|
2192
|
+
# threshold stays there until its own reset.
|
|
2193
|
+
account_level = [b for b in offenders if not b.get('model')]
|
|
2194
|
+
worst = max(account_level or offenders,
|
|
2195
|
+
key=lambda b: (int(b['resets_epoch']), b['percent']))
|
|
2196
|
+
reset_epoch = int(worst['resets_epoch'])
|
|
2180
2197
|
# Atomic: a concurrent shim must never read a half-written marker.
|
|
2181
2198
|
with open(mpath + '.tmp', 'w') as f:
|
|
2182
2199
|
f.write(f'{reset_epoch}\n')
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/tests/run-tests.sh
CHANGED
|
@@ -1883,6 +1883,70 @@ sc="$(python3 -c "import json;d=json.load(open('$ACC/acct-01/limits.json'));prin
|
|
|
1883
1883
|
|| t_fail "ranking score" "got $sc"
|
|
1884
1884
|
rm -f "$ACC/acct-01/limits.json" "$ACC/acct-01/.limited"
|
|
1885
1885
|
|
|
1886
|
+
# ---- 16-marker. the marker names ONE bucket and carries THAT bucket's reset ----------
|
|
1887
|
+
# A 100% session bucket (resets in an hour) beside a 100% Fable-only weekly bucket
|
|
1888
|
+
# (resets in five days) used to produce "bucket=session … resets_at=<+1h>" on line 2
|
|
1889
|
+
# under a line-1 epoch five days out — the highest percent paired with the latest
|
|
1890
|
+
# reset of a DIFFERENT bucket. Every reader of the marker (this shim, app-robot's
|
|
1891
|
+
# fleet-wide verdict) then parked the whole account for five days over a five-hour
|
|
1892
|
+
# window (2026-08-29, acct-05). The account-level bucket is named with its own reset.
|
|
1893
|
+
cat > "$WORK/usage-session-vs-fable.json" <<'EOF2'
|
|
1894
|
+
{"limits":[
|
|
1895
|
+
{"kind":"session","percent":100,"resets_at":"2099-01-01T01:00:00+00:00","scope":null},
|
|
1896
|
+
{"kind":"weekly_all","percent":40,"resets_at":"2099-01-05T00:00:00+00:00","scope":null},
|
|
1897
|
+
{"kind":"weekly_scoped","percent":100,"resets_at":"2099-01-06T00:00:00+00:00","scope":{"model":{"display_name":"Fable"}}}
|
|
1898
|
+
]}
|
|
1899
|
+
EOF2
|
|
1900
|
+
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-session-vs-fable.json" claude-accounts limits --force 2>&1)"
|
|
1901
|
+
check "session over threshold names the session bucket, not the Fable one" "LIMITED session at 100%" "$out"
|
|
1902
|
+
python3 - "$ACC/acct-01/.limited" <<'EOF2'
|
|
1903
|
+
import sys, time, calendar
|
|
1904
|
+
lines = open(sys.argv[1]).read().splitlines()
|
|
1905
|
+
epoch, detail = int(lines[0]), lines[1]
|
|
1906
|
+
want = calendar.timegm(time.strptime("2099-01-01T01:00:00", "%Y-%m-%dT%H:%M:%S"))
|
|
1907
|
+
assert epoch == want, (epoch, want, detail) # the SESSION reset
|
|
1908
|
+
assert "bucket=session percent=100" in detail, detail
|
|
1909
|
+
assert "resets_at=2099-01-01T01:00:00+00:00" in detail, detail
|
|
1910
|
+
EOF2
|
|
1911
|
+
[ $? -eq 0 ] && t_ok "marker epoch is the named bucket's own reset (session +1h, not Fable +5d)" \
|
|
1912
|
+
|| t_fail "marker epoch/bucket consistency" "$(cat "$ACC/acct-01/.limited" 2>&1 | tr '\n' ' ')"
|
|
1913
|
+
# Two ACCOUNT-level offenders: the longest-lived exclusion is the one named, with its
|
|
1914
|
+
# own reset — a session bucket that resets in an hour must not shorten a weekly park.
|
|
1915
|
+
cat > "$WORK/usage-session-vs-weekly.json" <<'EOF2'
|
|
1916
|
+
{"limits":[
|
|
1917
|
+
{"kind":"session","percent":100,"resets_at":"2099-01-01T01:00:00+00:00","scope":null},
|
|
1918
|
+
{"kind":"weekly_all","percent":95,"resets_at":"2099-01-05T00:00:00+00:00","scope":null}
|
|
1919
|
+
]}
|
|
1920
|
+
EOF2
|
|
1921
|
+
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-session-vs-weekly.json" claude-accounts limits --force 2>&1)"
|
|
1922
|
+
check "two account-level offenders: the longest-lived one is named" "LIMITED weekly_all at 95%" "$out"
|
|
1923
|
+
python3 - "$ACC/acct-01/.limited" <<'EOF2'
|
|
1924
|
+
import sys, time, calendar
|
|
1925
|
+
lines = open(sys.argv[1]).read().splitlines()
|
|
1926
|
+
want = calendar.timegm(time.strptime("2099-01-05T00:00:00", "%Y-%m-%dT%H:%M:%S"))
|
|
1927
|
+
assert int(lines[0]) == want, lines
|
|
1928
|
+
assert "bucket=weekly_all percent=95" in lines[1] and "resets_at=2099-01-05T00:00:00+00:00" in lines[1], lines
|
|
1929
|
+
EOF2
|
|
1930
|
+
[ $? -eq 0 ] && t_ok "weekly_all marker carries the weekly reset" \
|
|
1931
|
+
|| t_fail "weekly_all marker" "$(cat "$ACC/acct-01/.limited" 2>&1 | tr '\n' ' ')"
|
|
1932
|
+
# Only a model-scoped offender left: the scoped bucket is named (the shim treats it
|
|
1933
|
+
# as "switch model", never as an account park) with ITS reset.
|
|
1934
|
+
cat > "$WORK/usage-fable-only.json" <<'EOF2'
|
|
1935
|
+
{"limits":[
|
|
1936
|
+
{"kind":"session","percent":10,"resets_at":"2099-01-01T01:00:00+00:00","scope":null},
|
|
1937
|
+
{"kind":"weekly_scoped","percent":100,"resets_at":"2099-01-06T00:00:00+00:00","scope":{"model":{"display_name":"Fable"}}}
|
|
1938
|
+
]}
|
|
1939
|
+
EOF2
|
|
1940
|
+
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-fable-only.json" claude-accounts limits --force 2>&1)"
|
|
1941
|
+
check "a scoped-only offender still marks, scoped" "LIMITED weekly_scoped:Fable at 100%" "$out"
|
|
1942
|
+
grep -q "^bucket=weekly_scoped:Fable percent=100 " "$ACC/acct-01/.limited" 2>/dev/null \
|
|
1943
|
+
&& [ "$(head -1 "$ACC/acct-01/.limited")" = "$(python3 -c 'import time,calendar;print(calendar.timegm(time.strptime("2099-01-06T00:00:00","%Y-%m-%dT%H:%M:%S")))')" ] \
|
|
1944
|
+
&& t_ok "scoped marker carries the scoped bucket's reset" \
|
|
1945
|
+
|| t_fail "scoped marker" "$(cat "$ACC/acct-01/.limited" 2>&1 | tr '\n' ' ')"
|
|
1946
|
+
grep -q '"model": "Fable"' "$ACC/acct-01/limits.json" && t_ok "limits.json buckets record the model scope" \
|
|
1947
|
+
|| t_fail "bucket model field" "missing from limits.json"
|
|
1948
|
+
rm -f "$ACC/acct-01/limits.json" "$ACC/acct-01/.limited"
|
|
1949
|
+
|
|
1886
1950
|
# ---- 16a. future-proofing: works if the Fable bucket separation disappears ---------
|
|
1887
1951
|
cat > "$WORK/usage-no-fable.json" <<'EOF'
|
|
1888
1952
|
{"limits":[
|