tokenmaxxing 1.15.0 → 1.16.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/agent-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/src/cli/status.ts +129 -73
package/agent-plugin/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "tokenmaxxing",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.16.0",
|
|
5
5
|
"description": "Pool Claude Code and Codex logins, switch on pace pressure, and expose safe ops tools to agent clients.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "anaclumos",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.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/status.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sampleSize, sortBy } from "es-toolkit";
|
|
1
|
+
import { chunk, sampleSize, sortBy } from "es-toolkit";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { loadAccounts, loadConfig, loadUsage, loadUsageSnapshot, loadModelUsage, saveAccounts } from "../lib/state.ts";
|
|
4
4
|
import { readOAuthAccount } from "../lib/claudejson.ts";
|
|
@@ -10,7 +10,7 @@ import { loadCodexAccounts, saveCodexAccounts } from "../lib/codexstate.ts";
|
|
|
10
10
|
import { liveCodexAccountId, sampleCodexAccount, type CodexSampleOutcome } from "../lib/codexsample.ts";
|
|
11
11
|
import { isCodexExhausted } from "../lib/codexpick.ts";
|
|
12
12
|
import { codexLimitLabel, isSessionWindow } from "../lib/codexusage.ts";
|
|
13
|
-
import { bar, c, claudeTierLabel, count, emitJson, fmtAgo
|
|
13
|
+
import { bar, c, claudeTierLabel, count, emitJson, fmtAgo } from "./render.ts";
|
|
14
14
|
import { gatedFamilies, type FullUsage } from "../lib/usage.ts";
|
|
15
15
|
import { ThresholdsSchema, UsageWindowSchema, type Account, type CodexWindow, type Config, type UsageWindow } from "../lib/types.ts";
|
|
16
16
|
|
|
@@ -268,48 +268,138 @@ async function collectCodex(input: { cfg: Config; now: number }): Promise<Status
|
|
|
268
268
|
return { bars, accounts };
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
console.log(`${marker} ${c.bold(account.label)}${account.planType ? ` ${c.dim(account.planType)}` : ""}${badges.length ? ` ${badges.join(" ")}` : ""}`);
|
|
285
|
-
if (account.usage) {
|
|
286
|
-
for (const window of account.usage.aggregate) row(windowLabel(window), window);
|
|
287
|
-
for (const [name, windows] of Object.entries(account.usage.perLimit)) {
|
|
288
|
-
for (const window of windows) row(codexLimitLabel({ limitName: name }), window);
|
|
289
|
-
}
|
|
271
|
+
const CARD_GAP = 3;
|
|
272
|
+
const NOTE_INDENT = " ";
|
|
273
|
+
|
|
274
|
+
type Note = { paint: (s: string) => string; text: string };
|
|
275
|
+
type Card = { lines: string[]; notes: Note[] };
|
|
276
|
+
|
|
277
|
+
function splitToWidth(token: string, width: number): string[] {
|
|
278
|
+
const parts: string[] = [];
|
|
279
|
+
let part = "";
|
|
280
|
+
for (const ch of token) {
|
|
281
|
+
if (part !== "" && Bun.stringWidth(part + ch) > width) {
|
|
282
|
+
parts.push(part);
|
|
283
|
+
part = "";
|
|
290
284
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
285
|
+
part += ch;
|
|
286
|
+
}
|
|
287
|
+
if (part !== "") parts.push(part);
|
|
288
|
+
return parts;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function wrapWords(text: string, width: number): string[] {
|
|
292
|
+
const out: string[] = [];
|
|
293
|
+
let line = "";
|
|
294
|
+
for (const word of text.split(" ").flatMap((token) => splitToWidth(token, width))) {
|
|
295
|
+
if (line !== "" && Bun.stringWidth(`${line} ${word}`) > width) {
|
|
296
|
+
out.push(line);
|
|
297
|
+
line = word;
|
|
298
|
+
} else {
|
|
299
|
+
line = line === "" ? word : `${line} ${word}`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (line !== "") out.push(line);
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function renderGrid(cards: Card[]): void {
|
|
307
|
+
const bodyWidth = Math.max(...cards.flatMap((card) => card.lines.map((line) => Bun.stringWidth(line))));
|
|
308
|
+
const termWidth = process.stdout.isTTY ? process.stdout.columns : Number.POSITIVE_INFINITY;
|
|
309
|
+
const columns = process.stdout.isTTY ? Math.min(cards.length, Math.max(1, Math.floor((termWidth + CARD_GAP) / (bodyWidth + CARD_GAP)))) : 1;
|
|
310
|
+
const cellWidth = columns === 1 ? termWidth : Math.floor((termWidth + CARD_GAP) / columns) - CARD_GAP;
|
|
311
|
+
const blocks = cards.map((card) => [
|
|
312
|
+
...card.lines,
|
|
313
|
+
...card.notes.flatMap((note) => wrapWords(note.text, cellWidth - NOTE_INDENT.length).map((line) => `${NOTE_INDENT}${note.paint(line)}`)),
|
|
314
|
+
]);
|
|
315
|
+
const width = Math.max(...blocks.flat().map((line) => Bun.stringWidth(line)));
|
|
316
|
+
for (const rowBlocks of chunk(blocks, columns)) {
|
|
317
|
+
const height = Math.max(...rowBlocks.map((block) => block.length));
|
|
318
|
+
for (let i = 0; i < height; i++) {
|
|
319
|
+
const cells = rowBlocks.map((block) => block[i] ?? "");
|
|
320
|
+
const padded = cells.map((cell, col) => (col === cells.length - 1 ? cell : cell + " ".repeat(Math.max(0, width + CARD_GAP - Bun.stringWidth(cell)))));
|
|
321
|
+
console.log(padded.join("").trimEnd());
|
|
294
322
|
}
|
|
295
323
|
console.log();
|
|
296
324
|
}
|
|
297
325
|
}
|
|
298
326
|
|
|
299
|
-
function
|
|
300
|
-
return (
|
|
301
|
-
|
|
302
|
-
|
|
327
|
+
function usageRow(name: string, w: UsageWindow): string {
|
|
328
|
+
return `${NOTE_INDENT}${name.padEnd(5)} ${bar(w.usedPercentage)}`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function sampleFailedNotes(input: { cached: boolean; usageAt: number | null; reason: string; now: number }): Note[] {
|
|
332
|
+
const cached = input.cached ? `cached${input.usageAt != null ? ` ${fmtAgo(input.usageAt, input.now)}` : ""}, ` : "";
|
|
333
|
+
return [
|
|
334
|
+
{ paint: c.yellow, text: `${cached}live sample failed:` },
|
|
335
|
+
{ paint: c.dim, text: input.reason },
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function headerLine(input: { active: boolean; needsReauth: boolean; exhausted: boolean; name: string; tier: string | null }): string {
|
|
340
|
+
const marker = input.active ? c.green("●") : c.dim("○");
|
|
341
|
+
const badges: string[] = [];
|
|
342
|
+
if (input.active) badges.push(c.green("active"));
|
|
343
|
+
if (input.needsReauth) badges.push(c.red("needs-reauth"));
|
|
344
|
+
if (input.exhausted) badges.push(c.yellow("exhausted"));
|
|
345
|
+
return `${marker} ${c.bold(input.name)}${input.tier ? ` ${c.dim(input.tier)}` : ""}${badges.length ? ` ${badges.join(" ")}` : ""}`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function codexCard(account: CodexStatusAccount, now: number): Card {
|
|
349
|
+
const windowLabel = (window: CodexWindow) =>
|
|
350
|
+
isSessionWindow({ window }) ? `${Math.round((window.windowSeconds ?? 0) / 3600)}h` : "week";
|
|
351
|
+
const lines = [headerLine({ ...account, name: account.label, tier: account.planType })];
|
|
352
|
+
if (account.usage) {
|
|
353
|
+
for (const window of account.usage.aggregate) lines.push(usageRow(windowLabel(window), window));
|
|
354
|
+
for (const [name, windows] of Object.entries(account.usage.perLimit)) {
|
|
355
|
+
for (const window of windows) lines.push(usageRow(codexLimitLabel({ limitName: name }), window));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const notes = account.sample.ok ? [] : sampleFailedNotes({ cached: account.usage != null, usageAt: account.usageAt, reason: account.sample.reason, now });
|
|
359
|
+
return { lines, notes };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function renderCodex(input: { codex: StatusReport["codex"]; now: number }): void {
|
|
363
|
+
const { codex, now } = input;
|
|
364
|
+
if (codex.accounts.length === 0) return;
|
|
365
|
+
console.log(c.dim(`codex (${count({ n: codex.accounts.length, noun: "account" })})`));
|
|
366
|
+
console.log();
|
|
367
|
+
renderGrid(codex.accounts.map((account) => codexCard(account, now)));
|
|
303
368
|
}
|
|
304
369
|
|
|
305
|
-
function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
370
|
+
function claudeCard(a: ClaudeStatusAccount, now: number, staleAfterMs: number): Card {
|
|
371
|
+
const lines = [headerLine({ ...a, name: a.label || a.email })];
|
|
372
|
+
if (a.usage) {
|
|
373
|
+
lines.push(usageRow("5h", a.usage.fiveHour));
|
|
374
|
+
lines.push(usageRow("week", a.usage.week));
|
|
375
|
+
}
|
|
376
|
+
for (const [name, w] of Object.entries(a.perModel)) lines.push(usageRow(name.toLowerCase(), w));
|
|
377
|
+
const notes: Note[] = [];
|
|
378
|
+
if (a.sample.ok && a.sample.source === "statusline") {
|
|
379
|
+
const stale = a.usageAt == null || now - a.usageAt > staleAfterMs;
|
|
380
|
+
const age = a.usageAt != null ? fmtAgo(a.usageAt, now) : "age unknown";
|
|
381
|
+
notes.push({ paint: stale ? c.yellow : c.dim, text: `statusline tee ${age}${stale ? " (stale)" : ""}` });
|
|
382
|
+
}
|
|
383
|
+
if (!a.sample.ok) {
|
|
384
|
+
notes.push(...sampleFailedNotes({ cached: a.usage != null || Object.keys(a.perModel).length > 0, usageAt: a.usageAt, reason: a.sample.reason, now }));
|
|
385
|
+
}
|
|
386
|
+
if (a.pingError != null && a.pingRejected) {
|
|
387
|
+
const at = a.pingError.indexOf(": ");
|
|
388
|
+
const head = at >= 0 ? a.pingError.slice(0, at) : a.pingError;
|
|
389
|
+
const tail = at >= 0 ? a.pingError.slice(at + 2) : "";
|
|
390
|
+
notes.push({ paint: c.yellow, text: `ping ${head}${tail ? ":" : ""}` });
|
|
391
|
+
if (tail) notes.push({ paint: c.dim, text: tail });
|
|
392
|
+
} else if (a.pingError != null) {
|
|
393
|
+
notes.push({ paint: c.yellow, text: "ping failed (5h timer may not have started):" }, { paint: c.dim, text: a.pingError });
|
|
394
|
+
}
|
|
395
|
+
if (a.pinged) {
|
|
396
|
+
notes.push({ paint: c.dim, text: "pinged - 5h timer started this run; the usage feed lags, re-run status shortly for the fresh window" });
|
|
397
|
+
}
|
|
398
|
+
return { lines, notes };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function renderClaude(input: { claude: StatusReport["claude"]; codexPooled: boolean; now: number; staleAfterMs: number }): void {
|
|
402
|
+
const { claude, codexPooled, now, staleAfterMs } = input;
|
|
313
403
|
if (claude.accounts.length === 0) {
|
|
314
404
|
if (!codexPooled) {
|
|
315
405
|
console.log(c.dim("no accounts yet, run `tokenmaxxing init` (or `tokenmaxxing init --codex`)"));
|
|
@@ -322,40 +412,7 @@ function renderClaude(input: {
|
|
|
322
412
|
|
|
323
413
|
console.log(c.dim(`thresholds 5h ${claude.thresholds.session.join("/")}% (at ${claude.bars.session + claude.projectionMargin}%) weekly ${claude.thresholds.weekly}% (${count({ n: claude.accounts.length, noun: "claude account" })})`));
|
|
324
414
|
console.log();
|
|
325
|
-
|
|
326
|
-
const marker = a.active ? c.green("●") : c.dim("○");
|
|
327
|
-
const badges: string[] = [];
|
|
328
|
-
if (a.active) badges.push(c.green("active"));
|
|
329
|
-
if (a.needsReauth) badges.push(c.red("needs-reauth"));
|
|
330
|
-
if (a.exhausted) badges.push(c.yellow("exhausted"));
|
|
331
|
-
console.log(`${marker} ${c.bold(a.label || a.email)}${a.tier ? ` ${c.dim(a.tier)}` : ""}${badges.length ? ` ${badges.join(" ")}` : ""}`);
|
|
332
|
-
if (a.usage) {
|
|
333
|
-
row("5h", a.usage.fiveHour);
|
|
334
|
-
row("week", a.usage.week);
|
|
335
|
-
}
|
|
336
|
-
for (const [name, w] of Object.entries(a.perModel)) row(name.toLowerCase(), w);
|
|
337
|
-
if (a.sample.ok && a.sample.source === "statusline") {
|
|
338
|
-
const stale = a.usageAt == null || now - a.usageAt > staleAfterMs;
|
|
339
|
-
const age = a.usageAt != null ? fmtAgo(a.usageAt, now) : "age unknown";
|
|
340
|
-
console.log(` ${(stale ? c.yellow : c.dim)(`statusline tee ${age}${stale ? " (stale)" : ""}`)}`);
|
|
341
|
-
}
|
|
342
|
-
if (!a.sample.ok) {
|
|
343
|
-
const cached = a.usage || Object.keys(a.perModel).length > 0 ? `cached${a.usageAt != null ? ` ${fmtAgo(a.usageAt, now)}` : ""}, ` : "";
|
|
344
|
-
console.log(` ${c.yellow(`${cached}live sample failed`)}: ${c.dim(a.sample.reason)}`);
|
|
345
|
-
}
|
|
346
|
-
if (a.pingError != null && a.pingRejected) {
|
|
347
|
-
const at = a.pingError.indexOf(": ");
|
|
348
|
-
const head = at >= 0 ? a.pingError.slice(0, at) : a.pingError;
|
|
349
|
-
const tail = at >= 0 ? a.pingError.slice(at + 2) : "";
|
|
350
|
-
console.log(` ${c.yellow(`ping ${head}`)}${tail ? `: ${c.dim(tail)}` : ""}`);
|
|
351
|
-
} else if (a.pingError != null) {
|
|
352
|
-
console.log(` ${c.yellow("ping failed (5h timer may not have started)")}: ${c.dim(a.pingError)}`);
|
|
353
|
-
}
|
|
354
|
-
if (a.pinged) {
|
|
355
|
-
console.log(` ${c.dim("pinged - 5h timer started this run; the usage feed lags, re-run status shortly for the fresh window")}`);
|
|
356
|
-
}
|
|
357
|
-
console.log();
|
|
358
|
-
}
|
|
415
|
+
renderGrid(claude.accounts.map((a) => claudeCard(a, now, staleAfterMs)));
|
|
359
416
|
}
|
|
360
417
|
|
|
361
418
|
export async function cmdStatus(opts: { ping?: boolean; pingCount?: number; json?: boolean; preRender?: () => void } = {}): Promise<number> {
|
|
@@ -366,7 +423,7 @@ export async function cmdStatus(opts: { ping?: boolean; pingCount?: number; json
|
|
|
366
423
|
if (!json) {
|
|
367
424
|
opts.preRender?.();
|
|
368
425
|
const at = Date.now();
|
|
369
|
-
renderClaude({ claude, codexPooled: loadCodexAccounts().accounts.length > 0, now: at, staleAfterMs: cfg.policy.usagePollTtlMs
|
|
426
|
+
renderClaude({ claude, codexPooled: loadCodexAccounts().accounts.length > 0, now: at, staleAfterMs: cfg.policy.usagePollTtlMs });
|
|
370
427
|
}
|
|
371
428
|
const codex = await collectCodex({ cfg, now });
|
|
372
429
|
if (json) {
|
|
@@ -374,7 +431,6 @@ export async function cmdStatus(opts: { ping?: boolean; pingCount?: number; json
|
|
|
374
431
|
emitJson({ ok: true, ...report });
|
|
375
432
|
return 0;
|
|
376
433
|
}
|
|
377
|
-
|
|
378
|
-
renderCodex({ codex, now: at, row: rowPrinter(at) });
|
|
434
|
+
renderCodex({ codex, now: Date.now() });
|
|
379
435
|
return 0;
|
|
380
436
|
}
|