claude-multiacc 1.0.20 → 1.0.21

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
@@ -122,7 +122,7 @@ and backs off on **every** non-2xx, not just a 429 — honoring `Retry-After`, a
122
122
  for 6 h on a refusal the server marks `x-should-retry: false`. `limits --force` overrides
123
123
  all of it. The cadence is deliberately unhurried: several machines polling the same
124
124
  accounts every minute earns a 429 with `Retry-After: 3600`, and telemetry then goes stale
125
- for an hour at a time — which is exactly when every account starts scoring *neutral* and
125
+ for an hour at a time — which is exactly when every account starts scoring *unknown* and
126
126
  the picker loses its ability to tell them apart.
127
127
 
128
128
  > **The usage endpoint needs an OAuth login, not a setup token.** A portable
@@ -131,7 +131,7 @@ the picker loses its ability to tell them apart.
131
131
  > user:profile`: setup tokens are minted without that scope. So an account whose
132
132
  > `.credentials.json` grant has lapsed keeps working perfectly while going **permanently
133
133
  > dark for telemetry**, and a pool where that happens to every account ranks everything
134
- > neutral and picks at random. `claude-accounts status` says `RANKING IS BLIND` when the
134
+ > unknown and picks at random. `claude-accounts status` says `RANKING IS BLIND` when the
135
135
  > pool is in that state, and the shim prints an hourly warning on a terminal. The fix is
136
136
  > a real sign-in on the machine that polls (`claude-accounts login <acct-NN>`); since
137
137
  > `limits.json` is one of the things `sync` pushes, only the **source** machine needs it —
@@ -145,7 +145,7 @@ working accounts, while the cutoff declares one unusable, and an account reading
145
145
  hour ago may be well past 90% now. When nothing is in-window at all, a stale weekly
146
146
  reading is still used **if its bucket has not reset yet** (a weekly bucket only rises
147
147
  until then, so the number remains a true lower bound) — logged as `ranking=DEGRADED`.
148
- Only when even that is unavailable does selection fall back to neutral, logged as
148
+ Only when even that is unavailable does selection rank the account last, logged as
149
149
  `ranking=BLIND`.
150
150
 
151
151
  If an account's OAuth access token has been expired for a while (idle account,
package/bin/claude CHANGED
@@ -214,17 +214,17 @@ age_human() { # $1 = seconds
214
214
  # confirm this reset asymmetry — an account whose only near-full bucket is the cheap
215
215
  # session one must NOT rank behind one burning durable weekly headroom.)
216
216
  # score = weekly%*1000 + session% weekly,session in [0,100]
217
- # Stale/unreadable telemetry ranks NEUTRAL (weekly 50, session 50), never "free" —
217
+ # Stale/unreadable telemetry ranks LAST (weekly 100, session 100), never "free" —
218
218
  # EXCEPT in a blind pool (SEL_DEGRADED=1), where a still-valid stale weekly reading is
219
- # used instead. Neutral is only the right answer while some other account HAS fresh
220
- # data to be neutral against; when no account does, neutral is just a coin flip.
219
+ # used instead. Unknown data must never beat a truthful usage reading; when every
220
+ # candidate is unknown, the equal worst-case scores still preserve fail-open selection.
221
221
  SEL_DEGRADED=0
222
222
  sel_score_of() { # $1 = acct dir
223
223
  local w s
224
224
  if ! w="$(fresh_field "$1" weekly_percent)" && ! w="$(fresh_field "$1" max_percent)"; then
225
- if [ "$SEL_DEGRADED" = 1 ]; then w="$(stale_weekly "$1")" || w=50; else w=50; fi
225
+ if [ "$SEL_DEGRADED" = 1 ]; then w="$(stale_weekly "$1")" || w=100; else w=100; fi
226
226
  fi
227
- s="$(fresh_field "$1" session_percent)" || s=50
227
+ s="$(fresh_field "$1" session_percent)" || s=100
228
228
  printf '%s\n' $((w * 1000 + s))
229
229
  }
230
230
 
package/bin/codex CHANGED
@@ -144,11 +144,13 @@ fresh_field() { # fresh_field <acct dir> <json key> -> integer if telemetry fres
144
144
  # window only refills on its multi-day reset, while the ~5h window self-heals, so
145
145
  # session is a mild tiebreaker only (same reset asymmetry as the claude pool).
146
146
  # score = weekly%*1000 + session% weekly,session in [0,100]
147
- # Stale/unreadable telemetry ranks NEUTRAL (weekly 50, session 50), never "free".
147
+ # Stale/unreadable telemetry ranks LAST (weekly 100, session 100), never "free".
148
+ # Equal worst-case scores keep an entirely unknown pool selectable, but an unknown
149
+ # account can never beat a candidate with truthful usage telemetry.
148
150
  sel_score_of() { # $1 = acct dir
149
151
  local w s
150
- w="$(fresh_field "$1" weekly_percent)" || w="$(fresh_field "$1" max_percent)" || w=50
151
- s="$(fresh_field "$1" session_percent)" || s=50
152
+ w="$(fresh_field "$1" weekly_percent)" || w="$(fresh_field "$1" max_percent)" || w=100
153
+ s="$(fresh_field "$1" session_percent)" || s=100
152
154
  printf '%s\n' $((w * 1000 + s))
153
155
  }
154
156
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-multiacc",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "Multi-account addon for Claude Code and OpenAI Codex CLI: every claude / claude -p and every codex / codex exec 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": {
@@ -253,11 +253,30 @@ done
253
253
  && t_ok "CLAUDE_SHIM_SELECT=random restores uniform spread" \
254
254
  || t_fail "random opt-out" "acct-01=$hits1 acct-02=$hits2"
255
255
 
256
- # stale telemetry is neutral, never assumed free
256
+ # Unknown telemetry ranks behind every truthful reading, never as neutral or free.
257
257
  printf '{"fetched_at":1,"weekly_percent":1,"session_percent":1,"max_percent":1,"buckets":[]}' > "$ACC/acct-01/limits.json"
258
258
  lj 30 30 30 > "$ACC/acct-02/limits.json"
259
259
  out="$(claude 2>&1)"
260
260
  check "stale 1% loses to fresh 30% (stale is not trusted)" "CFG=acct-02" "$out"
261
+ lj 88 88 88 > "$ACC/acct-01/limits.json"
262
+ rm -f "$ACC/acct-02/limits.json" "$ACC/.last-pick"
263
+ all1=1
264
+ for _ in $(seq 1 15); do
265
+ case "$(claude 2>&1)" in *CFG=acct-01*) ;; *) all1=0 ;; esac
266
+ done
267
+ [ "$all1" = "1" ] && t_ok "unknown telemetry never beats a known account at 88%" \
268
+ || t_fail "unknown telemetry ranking" "the unknown account beat truthful 88% usage"
269
+ rm -f "$ACC/acct-01/limits.json" "$ACC/.last-pick"
270
+ hits1=0; hits2=0
271
+ for _ in $(seq 1 40); do
272
+ case "$(claude 2>&1)" in
273
+ *CFG=acct-01*) hits1=$((hits1+1)) ;;
274
+ *CFG=acct-02*) hits2=$((hits2+1)) ;;
275
+ esac
276
+ done
277
+ { [ "$hits1" -gt 0 ] && [ "$hits2" -gt 0 ]; } \
278
+ && t_ok "an entirely unknown Claude pool still fails open" \
279
+ || t_fail "unknown Claude pool fail-open" "acct-01=$hits1 acct-02=$hits2"
261
280
  rm -f "$ACC"/acct-*/limits.json
262
281
 
263
282
  # ---- 6. explicit pin -------------------------------------------------------
@@ -2525,11 +2544,30 @@ done
2525
2544
  { [ "$hits1" -gt 0 ] && [ "$hits2" -gt 0 ] && [ $((hits1+hits2)) -eq 40 ]; } \
2526
2545
  && t_ok "codex: equal scores spread randomly (acct-01=$hits1 acct-02=$hits2)" \
2527
2546
  || t_fail "codex tie spreading" "acct-01=$hits1 acct-02=$hits2 (want both >0, total 40)"
2528
- # stale telemetry ranks neutral, never free
2547
+ # Unknown telemetry ranks behind every truthful reading, never as neutral or free.
2529
2548
  printf '{"fetched_at":1,"weekly_percent":1,"session_percent":1,"max_percent":1,"buckets":[]}' > "$CX/acct-01/limits.json"
2530
2549
  cxlj 30 30 30 > "$CX/acct-02/limits.json"
2531
2550
  out="$(codex 2>&1)"
2532
2551
  check "codex: stale 1% loses to fresh 30%" "CFG=acct-02" "$out"
2552
+ cxlj 88 88 88 > "$CX/acct-01/limits.json"
2553
+ rm -f "$CX/acct-02/limits.json" "$CX/.last-pick"
2554
+ all1=1
2555
+ for _ in $(seq 1 15); do
2556
+ case "$(codex 2>&1)" in *CFG=acct-01*) ;; *) all1=0 ;; esac
2557
+ done
2558
+ [ "$all1" = "1" ] && t_ok "codex: unknown telemetry never beats known 88%" \
2559
+ || t_fail "codex unknown telemetry ranking" "the unknown account beat truthful 88% usage"
2560
+ rm -f "$CX/acct-01/limits.json" "$CX/.last-pick"
2561
+ hits1=0; hits2=0
2562
+ for _ in $(seq 1 40); do
2563
+ case "$(codex 2>&1)" in
2564
+ *CFG=acct-01*) hits1=$((hits1+1)) ;;
2565
+ *CFG=acct-02*) hits2=$((hits2+1)) ;;
2566
+ esac
2567
+ done
2568
+ { [ "$hits1" -gt 0 ] && [ "$hits2" -gt 0 ]; } \
2569
+ && t_ok "codex: an entirely unknown pool still fails open" \
2570
+ || t_fail "codex unknown pool fail-open" "acct-01=$hits1 acct-02=$hits2"
2533
2571
  rm -f "$CX"/acct-*/limits.json
2534
2572
 
2535
2573
  # ---- C4. pin -----------------------------------------------------------------------