tokenmaxxing 0.6.2 → 0.7.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/cli/switch.ts +3 -2
- package/src/lib/picker.ts +30 -9
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 (not rate-limited,
|
|
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, furthest behind its own weekly pace first: highest remaining% / time-to-weekly-reset, since unused allowance is forfeited at the fixed per-account reset; tiebreak soonest expiry then lowest 7-day usage), 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.7.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/cli/switch.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// `tokenmaxxing switch [selector]`.
|
|
2
|
-
// No selector → greedy: rank EVERY account (current included) by
|
|
3
|
-
//
|
|
2
|
+
// No selector → greedy: rank EVERY account (current included) by pace pressure
|
|
3
|
+
// (furthest behind its own weekly pace first, see picker.ts) among those with
|
|
4
|
+
// session/week under threshold, off the cached windows.
|
|
4
5
|
// When the current account already wins (or ties - swapping between equals buys
|
|
5
6
|
// nothing), do nothing: the command is idempotent, so running it periodically
|
|
6
7
|
// converges on the right account. With a selector → switch to that one. Runs
|
package/src/lib/picker.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// Choose the account to switch TO. Greedy policy: among usable accounts (no
|
|
2
|
-
// reauth, no window >= threshold that hasn't reset yet), take the one
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// is use-it-or-lose-it
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
2
|
+
// reauth, no window >= threshold that hasn't reset yet), take the one furthest
|
|
3
|
+
// behind its own weekly pace - highest pacePressure, the burn rate its
|
|
4
|
+
// remaining weekly quota demands to be consumed at before the fixed
|
|
5
|
+
// per-account reset forfeits it (weekly allowance is use-it-or-lose-it).
|
|
6
|
+
// This refines the older soonest-expiry policy in both directions: equal
|
|
7
|
+
// remaining reduces to soonest expiry first, equal expiry to most remaining
|
|
8
|
+
// first. Runs entirely off each account's cached windows (absolute UTC
|
|
9
|
+
// epochs, so a stale snapshot still resolves to the correct upcoming reset),
|
|
10
|
+
// which makes the pick deterministic and idempotent: re-running lands on the
|
|
11
|
+
// same account.
|
|
9
12
|
|
|
10
13
|
import { minBy, sortBy } from "es-toolkit";
|
|
11
14
|
import { z } from "zod";
|
|
@@ -75,10 +78,28 @@ export function weeklyExpiry(a: Account, now: number): number {
|
|
|
75
78
|
return nextWeeklyReset(a.lastUsage?.sevenDay.resetsAt ?? null, now) ?? Number.POSITIVE_INFINITY;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
/**
|
|
79
|
-
*
|
|
81
|
+
/** How far behind its own weekly pace the account is, measured forward: the
|
|
82
|
+
* burn rate (percent per ms) its remaining weekly quota must be consumed at
|
|
83
|
+
* to beat the reset that forfeits it. A backward-looking used/expected ratio
|
|
84
|
+
* blows up right after a reset (expected ~0) and ignores how much quota is
|
|
85
|
+
* at risk; the required forward rate has neither problem. A window past its
|
|
86
|
+
* cached reset counts as empty (the account is fresh again); an account with
|
|
87
|
+
* no sampled reset anchor has nothing to forfeit on any known clock and
|
|
88
|
+
* ranks last (0). */
|
|
89
|
+
export function pacePressure(a: Account, now: number): number {
|
|
90
|
+
const cached = a.lastUsage?.sevenDay;
|
|
91
|
+
const reset = nextWeeklyReset(cached?.resetsAt ?? null, now);
|
|
92
|
+
if (cached == null || reset == null) return 0;
|
|
93
|
+
const used = cached.resetsAt != null && cached.resetsAt <= now ? 0 : cached.usedPercentage;
|
|
94
|
+
return Math.max(0, 100 - used) / Math.max(1, reset - now);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The switch preference: furthest behind its own weekly pace first (highest
|
|
98
|
+
* pacePressure), tiebreak soonest weekly expiry then lowest 7-day usage.
|
|
99
|
+
* Shared with the statusLine pool ordering so the display order IS the
|
|
80
100
|
* swap order. */
|
|
81
101
|
export const swapPreference = (now: number) => [
|
|
102
|
+
(a: Account) => -pacePressure(a, now),
|
|
82
103
|
(a: Account) => weeklyExpiry(a, now),
|
|
83
104
|
(a: Account) => a.lastUsage?.sevenDay.usedPercentage ?? 0,
|
|
84
105
|
];
|