tokenmaxxing 0.2.1 → 0.3.0
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/DESIGN.md +1 -1
- package/package.json +1 -1
- package/src/lib/picker.ts +18 -2
package/DESIGN.md
CHANGED
|
@@ -48,7 +48,7 @@ The Stop hook's stdin has no usage data, but the **statusLine does** (`rate_limi
|
|
|
48
48
|
|
|
49
49
|
### 3.2 Detect + swap + signal (Stop hook, per turn)
|
|
50
50
|
1. Read `usage.json`; `exit 0` fast if both windows `< 95%` (metered per `organizationUuid`).
|
|
51
|
-
2. Else take a `flock` on `~/.config/tokenmaxxing/lock`, re-check under it (parallel sessions race - first winner already swapped), pick the best parked account (
|
|
51
|
+
2. Else take a `flock` on `~/.config/tokenmaxxing/lock`, re-check under it (parallel sessions race - first winner already swapped), pick the best parked account (not rate-limited, soonest-expiring weekly window first since unused allowance is forfeited at the fixed per-account reset, lowest 7-day usage tiebreak), and **swap the credential** (§3.4).
|
|
52
52
|
3. Write `respawn/<session_id>` (atomic temp+rename) and `SIGTERM` the parent `claude` (`kill -TERM $PPID`). The turn is already committed, so this is a clean stop.
|
|
53
53
|
|
|
54
54
|
### 3.3 Respawn (supervisor)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Automatic Claude Code account switching: pool multiple accounts and hot-swap when quota fills, resuming your session on the fresh account.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/lib/picker.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Choose the best account to switch TO when the active one crosses threshold.
|
|
2
2
|
// Policy: exclude the current account and any that need reauth or are still
|
|
3
3
|
// rate-limited (usage >= threshold and not yet past resets_at). Among the rest,
|
|
4
|
-
// prefer
|
|
4
|
+
// prefer the account whose weekly window expires soonest: weekly limits reset
|
|
5
|
+
// at a fixed per-account time and unused allowance is forfeited at reset, so
|
|
6
|
+
// quota nearest its reset is use-it-or-lose-it and should be drained first.
|
|
7
|
+
// Tiebreak on lowest 7-day usage, then soonest 5h reset.
|
|
5
8
|
|
|
6
9
|
import { minBy, sortBy } from "es-toolkit";
|
|
7
10
|
import { z } from "zod";
|
|
@@ -23,6 +26,18 @@ export function isExhausted(a: Account, ctx: PickCtx): boolean {
|
|
|
23
26
|
return blocked(u.fiveHour) || blocked(u.sevenDay);
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
30
|
+
|
|
31
|
+
/** Epoch ms when the account's weekly quota is next forfeited. The weekly reset
|
|
32
|
+
* is a fixed per-account anchor, so a stale (past) resetsAt extrapolates
|
|
33
|
+
* forward in 7-day steps; an account with no sampled reset sorts last. */
|
|
34
|
+
export function weeklyExpiry(a: Account, now: number): number {
|
|
35
|
+
const r = a.lastUsage?.sevenDay.resetsAt;
|
|
36
|
+
if (r == null) return Number.POSITIVE_INFINITY;
|
|
37
|
+
if (r > now) return r;
|
|
38
|
+
return r + (Math.floor((now - r) / WEEK_MS) + 1) * WEEK_MS;
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
export function pickBest(accounts: Account[], ctx: PickCtx): Account | null {
|
|
27
42
|
const candidates = accounts.filter(
|
|
28
43
|
(a) =>
|
|
@@ -32,8 +47,9 @@ export function pickBest(accounts: Account[], ctx: PickCtx): Account | null {
|
|
|
32
47
|
);
|
|
33
48
|
if (candidates.length === 0) return null;
|
|
34
49
|
|
|
35
|
-
// lowest 7-day usage
|
|
50
|
+
// soonest weekly expiry first; tiebreak lowest 7-day usage, then soonest 5h reset.
|
|
36
51
|
return sortBy(candidates, [
|
|
52
|
+
(a) => weeklyExpiry(a, ctx.now),
|
|
37
53
|
(a) => a.lastUsage?.sevenDay.usedPercentage ?? 0,
|
|
38
54
|
(a) => a.lastUsage?.fiveHour.resetsAt ?? Number.POSITIVE_INFINITY,
|
|
39
55
|
])[0]!;
|