tokenmaxxing 1.126.0 → 1.127.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenmaxxing",
3
- "version": "1.126.0",
3
+ "version": "1.127.0",
4
4
  "description": "Automatic Claude Code account switching: pool multiple accounts, start each session on its own account, and resume a session on a fresher account when quota fills.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cli/add.ts CHANGED
@@ -1,14 +1,18 @@
1
1
  import { withLock } from "../lib/lock.ts";
2
2
  import type { Provider } from "../lib/provider.ts";
3
- import { loadAccounts, saveAccounts, upsertAccount } from "../lib/state.ts";
4
- import { sessionWindow, weeklyWindow } from "../lib/picker.ts";
3
+ import { loadAccounts, loadConfig, saveAccounts, upsertAccount } from "../lib/state.ts";
4
+ import { bufferedUsed, liveUsed, sessionWindow, thresholdBars, weeklyWindow } from "../lib/picker.ts";
5
5
  import { c, count } from "./render.ts";
6
- import type { Account } from "../lib/types.ts";
6
+ import type { Account, Window } from "../lib/types.ts";
7
7
 
8
- export function usageNote(account: Account): string {
8
+ export function usageNote(p: Provider, account: Account): string {
9
9
  const session = sessionWindow(account);
10
10
  const week = weeklyWindow(account);
11
- return session && week ? ` (session ${session.usedPercentage}% / week ${week.usedPercentage}%)` : "";
11
+ if (!session || !week) return "";
12
+ const bars = thresholdBars(loadConfig());
13
+ const now = Date.now();
14
+ const pct = (w: Window) => Math.round(p.statusOnly ? liveUsed(w, now) : bufferedUsed(account, w, bars, now));
15
+ return ` (session ${pct(session)}% / week ${pct(week)}%)`;
12
16
  }
13
17
 
14
18
  export async function cmdAdd(p: Provider): Promise<number> {
@@ -28,7 +32,7 @@ export async function cmdAdd(p: Provider): Promise<number> {
28
32
  });
29
33
 
30
34
  console.log();
31
- const note = harvested.sample ? usageNote(account) : "";
35
+ const note = harvested.sample ? usageNote(p, account) : "";
32
36
  console.log(`${c.green("✓")} added ${c.bold(account.label)} (${account.tier ?? "?"})${note} → pool now has ${count({ n: poolSize, noun: "account" })}`);
33
37
  return 0;
34
38
  }
package/src/cli/auth.ts CHANGED
@@ -91,7 +91,7 @@ async function reauthOne(p: Provider, target: Account): Promise<boolean> {
91
91
  });
92
92
  if (account === null) return false;
93
93
 
94
- const note = harvested.sample ? usageNote(account) : "";
94
+ const note = harvested.sample ? usageNote(p, account) : "";
95
95
  console.log(`${c.green("✓")} reauthed ${c.bold(account.email ?? account.label)} (${account.tier ?? "?"})${note}`);
96
96
  return true;
97
97
  }
package/src/cli/serve.ts CHANGED
@@ -34,11 +34,11 @@ function iso(epochMs: number | null): string | null {
34
34
  }
35
35
 
36
36
  function claudeBody(usage: UsageReport): unknown {
37
- const win = (w: WindowReport | null) => (w ? { utilization: w.usedPercentage, resets_at: iso(w.resetsAt) } : null);
37
+ const win = (w: WindowReport | null) => (w ? { utilization: w.bufferedPercentage, resets_at: iso(w.resetsAt) } : null);
38
38
  return {
39
39
  five_hour: win(usage.fiveHour),
40
40
  seven_day: win(usage.week),
41
- limits: usage.limits.map((w) => ({ kind: "weekly_scoped", percent: w.usedPercentage, resets_at: iso(w.resetsAt), scope: { model: { display_name: w.name } } })),
41
+ limits: usage.limits.map((w) => ({ kind: "weekly_scoped", percent: w.bufferedPercentage, resets_at: iso(w.resetsAt), scope: { model: { display_name: w.name } } })),
42
42
  };
43
43
  }
44
44
 
@@ -46,7 +46,7 @@ function codexBody(usage: UsageReport, account: Account): unknown {
46
46
  const win = (w: WindowReport | undefined) =>
47
47
  w
48
48
  ? {
49
- used_percent: w.usedPercentage,
49
+ used_percent: w.bufferedPercentage,
50
50
  reset_at: w.resetsAt == null ? null : Math.round(w.resetsAt / 1000),
51
51
  ...(w.windowSeconds == null ? {} : { limit_window_seconds: w.windowSeconds }),
52
52
  }
@@ -142,8 +142,9 @@ async function apiCall(req: Request): Promise<Response> {
142
142
  const now = Date.now();
143
143
  const cfg = loadConfig();
144
144
  const current = await pool.read(account, cfg, now);
145
- pool.fold(current, thresholdBars(cfg));
146
- const usage = usageReport({ ...current, windows: current.windows.filter((w) => now - w.sampledAt <= STALE_AFTER_MS) }, now);
145
+ const bars = thresholdBars(cfg);
146
+ pool.fold(current, bars);
147
+ const usage = usageReport({ ...current, windows: current.windows.filter((w) => now - w.sampledAt <= STALE_AFTER_MS) }, bars, now);
147
148
  if (!usage || (!usage.fiveHour && !usage.week)) return json({ error: `no usage figure newer than ${STALE_AFTER_MS / 3_600_000}h for this account` }, 502);
148
149
  return json({ status_code: 200, header: { "Content-Type": ["application/json"] }, body: JSON.stringify(pool.body(usage, current)) });
149
150
  }
package/src/cli/status.ts CHANGED
@@ -6,12 +6,12 @@ import { opencodeGo } from "../lib/opencodego.ts";
6
6
  import { loadAccounts, loadConfig, saveAccounts } from "../lib/state.ts";
7
7
  import { withLock } from "../lib/lock.ts";
8
8
  import { codexPool, grokPool, opencodeGoPool } from "../lib/paths.ts";
9
- import { barFor, earliestReset, gatedWindows, landGrant, landWindows, isExhausted, isSessionWindow, limitWindows, liveUsed, nextWeeklyReset, sessionWindow, thresholdBars, weeklyWindow } from "../lib/picker.ts";
9
+ import { barFor, bufferedUsed, earliestReset, gatedWindows, landGrant, landWindows, isExhausted, isSessionWindow, limitWindows, liveUsed, nextWeeklyReset, sessionWindow, thresholdBars, weeklyWindow } from "../lib/picker.ts";
10
10
  import type { Provider, SampleReport } from "../lib/provider.ts";
11
11
  import { bar, c, count, emitJson, fmtAgo } from "./render.ts";
12
12
  import type { Account, AccountThresholds, Bars, Config, Thresholds, Window } from "../lib/types.ts";
13
13
 
14
- export type WindowReport = { usedPercentage: number; resetsAt: number | null; windowSeconds: number | null };
14
+ export type WindowReport = { usedPercentage: number; bufferedPercentage: number; resetsAt: number | null; windowSeconds: number | null };
15
15
 
16
16
  export type UsageReport = { fiveHour: WindowReport | null; week: WindowReport | null; limits: (WindowReport & { name: string })[] };
17
17
 
@@ -41,23 +41,24 @@ type PoolReport = {
41
41
 
42
42
  export type StatusReport = { now: number; claude: PoolReport; codex: PoolReport; grok: PoolReport; opencodeGo: PoolReport };
43
43
 
44
- function currentWindow(w: Window, now: number): WindowReport {
44
+ function currentWindow(a: Account, w: Window, bars: Bars | null, now: number): WindowReport {
45
45
  const passed = w.resetsAt != null && w.resetsAt <= now;
46
46
  return {
47
- usedPercentage: passed ? 0 : w.usedPercentage,
47
+ usedPercentage: liveUsed(w, now),
48
+ bufferedPercentage: bars == null ? liveUsed(w, now) : bufferedUsed(a, w, bars, now),
48
49
  resetsAt: isSessionWindow(w) ? (passed ? null : w.resetsAt) : nextWeeklyReset(w.resetsAt, now),
49
50
  windowSeconds: w.windowSeconds,
50
51
  };
51
52
  }
52
53
 
53
- export function usageReport(a: Account, now: number): UsageReport | null {
54
+ export function usageReport(a: Account, bars: Bars | null, now: number): UsageReport | null {
54
55
  if (a.lastUsageAt == null) return null;
55
56
  const session = sessionWindow(a);
56
57
  const week = weeklyWindow(a);
57
58
  return {
58
- fiveHour: session ? currentWindow(session, now) : null,
59
- week: week ? currentWindow(week, now) : null,
60
- limits: limitWindows(a).map((w) => ({ name: w.name ?? "", ...currentWindow(w, now) })),
59
+ fiveHour: session ? currentWindow(a, session, bars, now) : null,
60
+ week: week ? currentWindow(a, week, bars, now) : null,
61
+ limits: limitWindows(a).map((w) => ({ name: w.name ?? "", ...currentWindow(a, w, bars, now) })),
61
62
  };
62
63
  }
63
64
 
@@ -151,7 +152,7 @@ async function collect(p: Provider, cfg: Config, now: number, cached: boolean):
151
152
  needsReauth: a.needsReauth === true,
152
153
  exhausted: isExhausted(a, ctx),
153
154
  thresholds: own.get(a.label) ?? null,
154
- usage: usageReport(a, now),
155
+ usage: usageReport(a, p.statusOnly ? null : bars, now),
155
156
  usageAt: a.lastUsageAt ?? null,
156
157
  limitsAt: limits.length > 0 ? Math.max(...limits.map((w) => w.sampledAt)) : null,
157
158
  sample: reports.get(a.id) ?? (cached ? { ok: true, source: "cached" } : { ok: false, reason: "not sampled" }),
@@ -194,7 +195,7 @@ function renderGrid(cards: Card[]): void {
194
195
  }
195
196
 
196
197
  function usageRow(name: string, w: WindowReport): string {
197
- return `${NOTE_INDENT}${name.padEnd(5)} ${bar(w.usedPercentage)}`;
198
+ return `${NOTE_INDENT}${name.padEnd(5)} ${bar(w.bufferedPercentage)}`;
198
199
  }
199
200
 
200
201
  function sampleFailedNotes(input: { cached: boolean; usageAt: number | null; reason: string; now: number }): Note[] {
@@ -245,6 +246,7 @@ function card(p: Provider, a: StatusAccount, now: number, staleAfterMs: number):
245
246
 
246
247
  function renderPool(p: Provider, pool: PoolReport, header: string, now: number, staleAfterMs: number): void {
247
248
  console.log(c.dim(header));
249
+ if (!p.statusOnly) console.log(c.dim("buffered usage quota: percent of each account's switch threshold, so it reads higher than the usage Claude or Codex reports"));
248
250
  console.log();
249
251
  renderGrid(pool.accounts.map((a) => card(p, a, now, staleAfterMs)));
250
252
  if (pool.gatedNote) {
@@ -3,7 +3,7 @@ import { claudePool, seatFromEnv } from "../lib/paths.ts";
3
3
  import { errorMessage } from "../lib/log.ts";
4
4
  import { loadAccounts, loadConfig, writeUsage } from "../lib/state.ts";
5
5
  import { familyTokens, matchedFamily, parseStatusLineStdin, parseStatusLineModel } from "../lib/usage.ts";
6
- import { earliestReset, limitWindows, weeklyExpiry, weeklyWindow } from "../lib/picker.ts";
6
+ import { bufferedUsed, earliestReset, limitWindows, liveUsed, thresholdBars, weeklyExpiry, weeklyWindow } from "../lib/picker.ts";
7
7
  import { readStdin } from "../lib/proc.ts";
8
8
  import { worktreeName } from "../lib/worktree.ts";
9
9
  import { fmtResetShort, makeColors, makeUsagePaint, statuslineColor } from "../cli/render.ts";
@@ -12,13 +12,18 @@ import {
12
12
  StatusLineStdinSchema,
13
13
  type Account,
14
14
  type AccountsIndex,
15
+ type Bars,
15
16
  type UsageState,
16
17
  type UsageWindow,
17
18
  type Window,
18
19
  } from "../lib/types.ts";
19
20
 
21
+ const FIVE_HOUR_S = 5 * 3600;
22
+ const SEVEN_DAY_S = 7 * 24 * 3600;
23
+
20
24
  export type RenderCtx = {
21
25
  accounts: AccountsIndex;
26
+ bars: Bars;
22
27
  perModel: Record<string, Window>;
23
28
  switchModels: string[];
24
29
  worktree: string | null;
@@ -31,7 +36,7 @@ export type RenderCtx = {
31
36
  export function renderStatusline(stdinObj: unknown, ctx: RenderCtx): string {
32
37
  const col = makeColors(ctx.color);
33
38
  const paint = makeUsagePaint({ enabled: ctx.color, truecolor: ctx.truecolor });
34
- const used = (w: UsageWindow) => (w.resetsAt != null && w.resetsAt <= ctx.now ? 0 : w.usedPercentage);
39
+ const used = (a: Account, w: Window) => bufferedUsed(a, w, ctx.bars, ctx.now);
35
40
  const reset = (epochMs: number | null) => fmtResetShort(epochMs, ctx.now);
36
41
 
37
42
  const parsed = StatusLineStdinSchema.safeParse(stdinObj);
@@ -51,8 +56,7 @@ export function renderStatusline(stdinObj: unknown, ctx: RenderCtx): string {
51
56
  const removed = d?.cost?.total_lines_removed ?? 0;
52
57
  if (added > 0 || removed > 0) info.push(`${col.green(`+${added}`)}/-${removed}`);
53
58
 
54
- const seg = (label: string, w: UsageWindow, resetAt: number | null) => {
55
- const u = used(w);
59
+ const seg = (label: string, u: number, resetAt: number | null) => {
56
60
  if (!ctx.color) return `${label}${reset(resetAt)}${Math.round(u)}`;
57
61
  const body = label !== "" ? label : reset(resetAt);
58
62
  return col.bold(paint(u)(body !== "" ? body : u > 0 ? "?" : "0"));
@@ -64,15 +68,17 @@ export function renderStatusline(stdinObj: unknown, ctx: RenderCtx): string {
64
68
  if (family && !Object.keys(ctx.perModel).some((k) => familyTokens(k).includes(family))) {
65
69
  windows.push(`${initial(family)}?`);
66
70
  }
67
- for (const [name, w] of Object.entries(ctx.perModel)) windows.push(seg(initial(name), w, null));
71
+ const seatUuid = ctx.liveAccount;
72
+ const seat = ctx.accounts.accounts.find((a) => a.id === seatUuid);
73
+ const seatUsed = (w: Window) => (seat ? used(seat, w) : liveUsed(w, ctx.now));
74
+ const stdinWindow = (w: UsageWindow, windowSeconds: number): Window => ({ name: null, ...w, windowSeconds, sampledAt: ctx.now });
75
+ for (const [name, w] of Object.entries(ctx.perModel)) windows.push(seg(initial(name), seatUsed(w), null));
68
76
  if (wins) {
69
- windows.push(seg("", wins.fiveHour, wins.fiveHour.resetsAt));
70
- windows.push(seg("", wins.sevenDay, wins.sevenDay.resetsAt));
77
+ windows.push(seg("", seatUsed(stdinWindow(wins.fiveHour, FIVE_HOUR_S)), wins.fiveHour.resetsAt));
78
+ windows.push(seg("", seatUsed(stdinWindow(wins.sevenDay, SEVEN_DAY_S)), wins.sevenDay.resetsAt));
71
79
  }
72
- const seatUuid = ctx.liveAccount;
73
80
  const walled = (a: Account) => a.enforcedUntil != null && a.enforcedUntil > ctx.now;
74
- const wallSeg = (wall: number) => seg("", { usedPercentage: 100, resetsAt: wall }, wall);
75
- const seat = ctx.accounts.accounts.find((a) => a.id === seatUuid);
81
+ const wallSeg = (wall: number) => seg("", 100, wall);
76
82
  if (seat?.enforcedUntil != null && walled(seat)) windows.push(wallSeg(seat.enforcedUntil));
77
83
  const seatMarker = seat && walled(seat) ? paint(100)("◆") : col.green("◆");
78
84
  const active =
@@ -91,15 +97,16 @@ export function renderStatusline(stdinObj: unknown, ctx: RenderCtx): string {
91
97
  if (a.enforcedUntil != null && walled(a)) return `${marker} ${wallSeg(a.enforcedUntil)}`;
92
98
  const week = weeklyWindow(a);
93
99
  if (week == null) return `${marker} ?`;
94
- const weekUsed = used(week);
100
+ const weekUsed = used(a, week);
95
101
  if (Math.round(weekUsed) <= 0) return `${marker} ${paint(0)("full")}`;
96
102
 
97
103
  const parts: string[] = [];
98
104
  for (const w of limitWindows(a)) {
99
- if (used(w) > weekUsed) parts.push(seg(initial(w.name ?? ""), w, null));
105
+ const u = used(a, w);
106
+ if (u > weekUsed) parts.push(seg(initial(w.name ?? ""), u, null));
100
107
  }
101
108
  const expiry = weeklyExpiry(a, ctx.now);
102
- parts.push(seg("", week, Number.isFinite(expiry) ? expiry : null));
109
+ parts.push(seg("", weekUsed, Number.isFinite(expiry) ? expiry : null));
103
110
  return `${marker} ${parts.join(" ")}`;
104
111
  };
105
112
 
@@ -130,6 +137,7 @@ export async function runStatusline(): Promise<number> {
130
137
  const live = accounts.accounts.find((a) => a.id === account);
131
138
  const ctx: RenderCtx = {
132
139
  accounts,
140
+ bars: thresholdBars(cfg),
133
141
  perModel: Object.fromEntries((live ? limitWindows(live) : []).map((w) => [w.name ?? "", w])),
134
142
  switchModels: cfg.policy.switchModels,
135
143
  worktree: dir == null ? null : worktreeName(dir),
package/src/lib/picker.ts CHANGED
@@ -64,6 +64,13 @@ export function barFor(a: Account, w: Window, bars: Bars, now: number): number {
64
64
  return released ? Math.max(pick(own), pick(bars)) : pick(own);
65
65
  }
66
66
 
67
+ export function bufferedUsed(a: Account, w: Window, bars: Bars, now: number): number {
68
+ const used = liveUsed(w, now);
69
+ const bar = barFor(a, w, bars, now);
70
+ if (bar <= 0) return used > 0 ? 100 : 0;
71
+ return Math.min(100, (used / bar) * 100);
72
+ }
73
+
67
74
  function windowEnd(w: Window): number {
68
75
  return w.resetsAt ?? w.sampledAt + (w.windowSeconds ?? 0) * 1000;
69
76
  }