tokenmaxxing 1.26.0 → 1.27.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/README.md +1 -2
- package/package.json +1 -1
- package/src/cli/init.ts +0 -1
- package/src/cli/status.ts +13 -63
- package/src/lib/sample.ts +7 -21
- package/src/lib/usage.ts +3 -71
- package/src/main.ts +6 -31
package/README.md
CHANGED
|
@@ -70,8 +70,7 @@ claude # use claude as always
|
|
|
70
70
|
| `tokenmaxxing switch --codex [sel]` | switch the codex pool (takes effect on the next codex start) |
|
|
71
71
|
| `tokenmaxxing ls` | list pooled accounts |
|
|
72
72
|
| `tokenmaxxing status` | accounts with 5h / weekly usage bars, active + exhausted-until-reset |
|
|
73
|
-
| `tokenmaxxing
|
|
74
|
-
| `tokenmaxxing watch [seconds]` | live status: re-render every N seconds (default 120, floor 30; never pings) |
|
|
73
|
+
| `tokenmaxxing watch [seconds]` | live status: re-render every N seconds (default 120, floor 30) |
|
|
75
74
|
| `tokenmaxxing config` | effective config with sources; `get`/`set`/`unset` dotted keys, `tidy` prunes unknown keys |
|
|
76
75
|
| `tokenmaxxing doctor` | verify the supervisor + settings entries survived |
|
|
77
76
|
| `tokenmaxxing rename [--codex] <sel> <label>` / `rm [--codex] <sel>` | manage the pool (`--codex` targets the codex pool: one email can hold both a claude and a codex account) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.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/init.ts
CHANGED
|
@@ -37,7 +37,6 @@ export function printUsage(): void {
|
|
|
37
37
|
console.log(` ${c.bold("how to use")} - ${c.cyan("xx")} is shorthand for ${c.cyan("tokenmaxxing")}:`);
|
|
38
38
|
console.log(` ${c.cyan("claude")} use claude as always; it switches accounts near quota automatically`);
|
|
39
39
|
console.log(` ${c.cyan("xx")} show the pool with usage bars (same as ${c.cyan("xx status")})`);
|
|
40
|
-
console.log(` ${c.cyan("xx status --ping")} ping every account (one tiny haiku request each) so all 5h timers start now, then sample fresh; ${c.cyan("--count N")} pings N random accounts instead`);
|
|
41
40
|
console.log(` ${c.cyan("xx add")} log in and pool another account`);
|
|
42
41
|
console.log(` ${c.cyan("xx switch")} hop to the best account right now (the automatic switching needs no command)`);
|
|
43
42
|
console.log(` ${c.cyan("xx help")} everything else`);
|
package/src/cli/status.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chunk,
|
|
1
|
+
import { chunk, 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";
|
|
@@ -32,9 +32,6 @@ const ClaudeStatusAccountSchema = z.object({
|
|
|
32
32
|
perModel: z.record(z.string(), UsageWindowSchema),
|
|
33
33
|
usageAt: z.number().nullable(),
|
|
34
34
|
sample: SampleReportSchema,
|
|
35
|
-
pingError: z.string().nullable(),
|
|
36
|
-
pingRejected: z.boolean(),
|
|
37
|
-
pinged: z.boolean(),
|
|
38
35
|
});
|
|
39
36
|
type ClaudeStatusAccount = z.infer<typeof ClaudeStatusAccountSchema>;
|
|
40
37
|
|
|
@@ -86,31 +83,14 @@ function currentCodexWindow(w: CodexWindow, now: number): CodexWindow {
|
|
|
86
83
|
return { ...currentWindow(w, !isSessionWindow({ window: w }), now), windowSeconds: w.windowSeconds };
|
|
87
84
|
}
|
|
88
85
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
const usable = accounts.filter((a) => a.needsReauth !== true);
|
|
92
|
-
return sampleSize(usable, Math.min(pingCount, usable.length));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function progressNote(input: { ping: boolean; pingCount: number | undefined; picked: Account[]; total: number }): string {
|
|
96
|
-
const { ping, pingCount, picked, total } = input;
|
|
97
|
-
if (!ping) return "sampling live usage...";
|
|
98
|
-
if (pingCount == null) return "pinging every account (starts each 5h session timer) + sampling live usage...";
|
|
99
|
-
if (picked.length === 0) return "no account to ping (every account needs reauth), sampling live usage...";
|
|
100
|
-
return `pinging ${picked.length} of ${count({ n: total, noun: "account" })} (${picked.map((a) => a.label || a.email).join(", ")}) so their 5h session timers start now + sampling live usage...`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function collectClaude(input: { cfg: Config; ping: boolean; pingCount: number | undefined; now: number }): Promise<StatusReport["claude"]> {
|
|
104
|
-
const { cfg, ping, pingCount, now } = input;
|
|
86
|
+
async function collectClaude(input: { cfg: Config; now: number }): Promise<StatusReport["claude"]> {
|
|
87
|
+
const { cfg, now } = input;
|
|
105
88
|
let idx = loadAccounts();
|
|
106
89
|
const samples = new Map<string, { outcome: SampleOutcome; viaTee: boolean }>();
|
|
107
|
-
let pings = new Set<string>();
|
|
108
90
|
if (idx.accounts.length > 0) {
|
|
109
91
|
await withLock(paths.lockFile, async () => {
|
|
110
92
|
idx = loadAccounts();
|
|
111
|
-
|
|
112
|
-
pings = new Set(picked.map((a) => a.accountUuid));
|
|
113
|
-
console.error(c.dim(progressNote({ ping, pingCount, picked, total: idx.accounts.length })));
|
|
93
|
+
console.error(c.dim("sampling live usage..."));
|
|
114
94
|
const tee = loadUsageSnapshot();
|
|
115
95
|
const live = tee?.state ?? null;
|
|
116
96
|
const teeAt = tee?.at ?? null;
|
|
@@ -129,27 +109,12 @@ async function collectClaude(input: { cfg: Config; ping: boolean; pingCount: num
|
|
|
129
109
|
perModel: modelUsage && modelUsage.account === a.accountUuid ? modelUsage.perModel : {},
|
|
130
110
|
}
|
|
131
111
|
: null;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
outcome = { ok: true, usage: fromStatusLine };
|
|
139
|
-
if (failed.pingError != null) {
|
|
140
|
-
outcome.pingError = failed.pingError;
|
|
141
|
-
outcome.pingRejected = failed.pingRejected;
|
|
142
|
-
}
|
|
143
|
-
viaTee = true;
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
viaTee = fromStatusLine != null;
|
|
147
|
-
outcome = fromStatusLine
|
|
148
|
-
? { ok: true, usage: fromStatusLine }
|
|
149
|
-
: isActive
|
|
150
|
-
? await probeActiveUsage(a)
|
|
151
|
-
: await probeParkedUsage(a);
|
|
152
|
-
}
|
|
112
|
+
const viaTee = fromStatusLine != null;
|
|
113
|
+
const outcome: SampleOutcome = fromStatusLine
|
|
114
|
+
? { ok: true, usage: fromStatusLine }
|
|
115
|
+
: isActive
|
|
116
|
+
? await probeActiveUsage(a)
|
|
117
|
+
: await probeParkedUsage(a);
|
|
153
118
|
samples.set(a.accountUuid, { outcome, viaTee });
|
|
154
119
|
if (!outcome.ok) return;
|
|
155
120
|
a.lastUsage = { fiveHour: outcome.usage.session, sevenDay: outcome.usage.weekAll };
|
|
@@ -192,9 +157,6 @@ async function collectClaude(input: { cfg: Config; ping: boolean; pingCount: num
|
|
|
192
157
|
perModel: Object.fromEntries(Object.entries(perModel).map(([name, w]) => [name, currentWindow(w, true, now)])),
|
|
193
158
|
usageAt: a.lastUsageAt ?? null,
|
|
194
159
|
sample: sampled.outcome.ok ? { ok: true, source: sampled.viaTee ? "statusline" : "probe" } : { ok: false, reason: sampled.outcome.reason },
|
|
195
|
-
pingError: sampled.outcome.pingError ?? null,
|
|
196
|
-
pingRejected: sampled.outcome.pingRejected === true,
|
|
197
|
-
pinged: pings.has(a.accountUuid) && sampled.outcome.ok && sampled.outcome.pingError == null && aggregate != null && aggregate.fiveHour.resetsAt == null,
|
|
198
160
|
};
|
|
199
161
|
});
|
|
200
162
|
return {
|
|
@@ -383,18 +345,6 @@ function claudeCard(a: ClaudeStatusAccount, now: number, staleAfterMs: number):
|
|
|
383
345
|
if (!a.sample.ok) {
|
|
384
346
|
notes.push(...sampleFailedNotes({ cached: a.usage != null || Object.keys(a.perModel).length > 0, usageAt: a.usageAt, reason: a.sample.reason, now }));
|
|
385
347
|
}
|
|
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
348
|
return { lines, notes };
|
|
399
349
|
}
|
|
400
350
|
|
|
@@ -415,11 +365,11 @@ function renderClaude(input: { claude: StatusReport["claude"]; codexPooled: bool
|
|
|
415
365
|
renderGrid(claude.accounts.map((a) => claudeCard(a, now, staleAfterMs)));
|
|
416
366
|
}
|
|
417
367
|
|
|
418
|
-
export async function cmdStatus(opts: {
|
|
419
|
-
const {
|
|
368
|
+
export async function cmdStatus(opts: { json?: boolean; preRender?: () => void } = {}): Promise<number> {
|
|
369
|
+
const { json = false } = opts;
|
|
420
370
|
const cfg = loadConfig();
|
|
421
371
|
const now = Date.now();
|
|
422
|
-
const claude = await collectClaude({ cfg,
|
|
372
|
+
const claude = await collectClaude({ cfg, now });
|
|
423
373
|
if (!json) {
|
|
424
374
|
opts.preRender?.();
|
|
425
375
|
const at = Date.now();
|
package/src/lib/sample.ts
CHANGED
|
@@ -5,15 +5,15 @@ import { readItem, writeItem, deleteItem, liveTarget, parkedTarget, isolatedTarg
|
|
|
5
5
|
import { credItemFor, paths } from "./paths.ts";
|
|
6
6
|
import { withClaudeRefreshLock } from "./claudelock.ts";
|
|
7
7
|
import { refreshCredential, isAccessTokenExpiring, isDeadCredential, fetchTokenIdentity, describeIdentity, IdentityUnavailableError, InvalidGrantError } from "./oauth.ts";
|
|
8
|
-
import { FullUsageSchema,
|
|
8
|
+
import { FullUsageSchema, probeUsage } from "./usage.ts";
|
|
9
9
|
import { loadAccounts } from "./state.ts";
|
|
10
10
|
import { keepRotatedPair } from "./swap.ts";
|
|
11
11
|
import { log } from "./log.ts";
|
|
12
12
|
import { CredentialBlobSchema, TokenIdentitySchema, type Account, type OAuthCreds, type TokenIdentity } from "./types.ts";
|
|
13
13
|
|
|
14
14
|
const SampleOutcomeSchema = z.discriminatedUnion("ok", [
|
|
15
|
-
z.object({ ok: z.literal(true), usage: FullUsageSchema
|
|
16
|
-
z.object({ ok: z.literal(false), reason: z.string()
|
|
15
|
+
z.object({ ok: z.literal(true), usage: FullUsageSchema }),
|
|
16
|
+
z.object({ ok: z.literal(false), reason: z.string() }),
|
|
17
17
|
]);
|
|
18
18
|
export type SampleOutcome = z.infer<typeof SampleOutcomeSchema>;
|
|
19
19
|
|
|
@@ -46,7 +46,7 @@ function refreshPlanFields(account: Account, creds: OAuthCreds): void {
|
|
|
46
46
|
|
|
47
47
|
export async function probeParkedUsage(
|
|
48
48
|
account: Account,
|
|
49
|
-
opts: {
|
|
49
|
+
opts: { retries?: number; refreshParked?: boolean; killMs?: number; signal?: AbortSignal } = {},
|
|
50
50
|
): Promise<SampleOutcome> {
|
|
51
51
|
const backup = parkedTarget(account.keychainItem);
|
|
52
52
|
const parkedRaw = await readItem(backup);
|
|
@@ -165,16 +165,8 @@ export async function probeParkedUsage(
|
|
|
165
165
|
try {
|
|
166
166
|
await writeItem(isoTarget, installed);
|
|
167
167
|
writeFileSync(join(dir, ".claude.json"), JSON.stringify({ oauthAccount: account.oauthAccount, hasCompletedOnboarding: true }));
|
|
168
|
-
const ping = opts.ping ? await pingSession(dir) : null;
|
|
169
168
|
const usage = await probeUsage(dir, Date.now(), opts);
|
|
170
|
-
|
|
171
|
-
? { ok: true, usage }
|
|
172
|
-
: { ok: false, reason: "`/usage` returned no limit data (see log)" };
|
|
173
|
-
if (ping != null) {
|
|
174
|
-
outcome.pingError = ping.reason;
|
|
175
|
-
outcome.pingRejected = ping.rejected;
|
|
176
|
-
}
|
|
177
|
-
return outcome;
|
|
169
|
+
return usage ? { ok: true, usage } : { ok: false, reason: "`/usage` returned no limit data (see log)" };
|
|
178
170
|
} finally {
|
|
179
171
|
try {
|
|
180
172
|
if (opts.refreshParked !== false) {
|
|
@@ -211,7 +203,7 @@ export async function ensureLiveTokenFresh(): Promise<void> {
|
|
|
211
203
|
});
|
|
212
204
|
}
|
|
213
205
|
|
|
214
|
-
export async function probeActiveUsage(account: Account
|
|
206
|
+
export async function probeActiveUsage(account: Account): Promise<SampleOutcome> {
|
|
215
207
|
try {
|
|
216
208
|
await ensureLiveTokenFresh();
|
|
217
209
|
} catch (e) {
|
|
@@ -232,12 +224,6 @@ export async function probeActiveUsage(account: Account, opts: { ping?: boolean
|
|
|
232
224
|
if (identity.status === "unavailable") return { ok: false, reason: identity.reason };
|
|
233
225
|
refreshPlanFields(account, creds);
|
|
234
226
|
|
|
235
|
-
const ping = opts.ping ? await pingSession() : null;
|
|
236
227
|
const usage = await probeUsage();
|
|
237
|
-
|
|
238
|
-
if (ping != null) {
|
|
239
|
-
outcome.pingError = ping.reason;
|
|
240
|
-
outcome.pingRejected = ping.rejected;
|
|
241
|
-
}
|
|
242
|
-
return outcome;
|
|
228
|
+
return usage ? { ok: true, usage } : { ok: false, reason: "`/usage` returned no limit data (see log)" };
|
|
243
229
|
}
|
package/src/lib/usage.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { closeSync,
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { closeSync, fstatSync, openSync, readSync } from "node:fs";
|
|
3
2
|
import { delay } from "es-toolkit";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealClaude } from "./claudebin.ts";
|
|
6
5
|
import { log } from "./log.ts";
|
|
7
|
-
import { paths } from "./paths.ts";
|
|
8
6
|
import { RateLimitsStdinSchema, UsageWindowSchema, type ModelInfo, type UsageWindow, type UsageWindows } from "./types.ts";
|
|
9
7
|
|
|
10
8
|
export function normalizeResetsAt(v: unknown): number | null {
|
|
@@ -285,12 +283,11 @@ type SpawnResult = z.infer<typeof SpawnResultSchema>;
|
|
|
285
283
|
async function spawnClaudeBounded(
|
|
286
284
|
cmd: string[],
|
|
287
285
|
env: Record<string, string>,
|
|
288
|
-
cwd?: string,
|
|
289
286
|
killMs = PROBE_KILL_MS,
|
|
290
287
|
signal?: AbortSignal,
|
|
291
288
|
): Promise<SpawnResult | null> {
|
|
292
289
|
signal?.throwIfAborted();
|
|
293
|
-
const p = Bun.spawn(cmd, { env,
|
|
290
|
+
const p = Bun.spawn(cmd, { env, stdout: "pipe", stderr: "pipe" });
|
|
294
291
|
const abort = () => p.kill("SIGKILL");
|
|
295
292
|
signal?.addEventListener("abort", abort, { once: true });
|
|
296
293
|
const killer = setTimeout(() => p.kill("SIGKILL"), killMs);
|
|
@@ -313,7 +310,7 @@ async function spawnClaudeBounded(
|
|
|
313
310
|
async function probeUsageOnce(env: Record<string, string>, now: number, killMs: number, signal?: AbortSignal): Promise<FullUsage | null> {
|
|
314
311
|
let out: string;
|
|
315
312
|
try {
|
|
316
|
-
const r = await spawnClaudeBounded([resolveRealClaude(), "-p", "/usage", "--output-format", "json"], env,
|
|
313
|
+
const r = await spawnClaudeBounded([resolveRealClaude(), "-p", "/usage", "--output-format", "json"], env, killMs, signal);
|
|
317
314
|
if (r === null) {
|
|
318
315
|
log("usage.probe_failed", { err: "output pipes still open after child exit (leaked descendant)" });
|
|
319
316
|
return null;
|
|
@@ -364,68 +361,3 @@ export async function probeUsage(configDir?: string, now = Date.now(), opts: { r
|
|
|
364
361
|
await delay(PROBE_RETRY_DELAYS_MS[Math.min(attempt, PROBE_RETRY_DELAYS_MS.length - 1)]!);
|
|
365
362
|
}
|
|
366
363
|
}
|
|
367
|
-
|
|
368
|
-
const PING_ARGS = [
|
|
369
|
-
"-p", "Reply with exactly: ok",
|
|
370
|
-
"--model", "haiku",
|
|
371
|
-
"--settings", '{"disableAllHooks":true}',
|
|
372
|
-
"--output-format", "json",
|
|
373
|
-
];
|
|
374
|
-
|
|
375
|
-
const PingResultSchema = z.looseObject({ is_error: z.boolean().optional(), result: z.string().optional(), session_id: z.string().optional() });
|
|
376
|
-
|
|
377
|
-
export const PingFailureSchema = z.object({ reason: z.string(), rejected: z.boolean() });
|
|
378
|
-
export type PingFailure = z.infer<typeof PingFailureSchema>;
|
|
379
|
-
|
|
380
|
-
export function transcriptSlug(cwd: string): string {
|
|
381
|
-
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export function pingTranscriptPath(input: { configDir: string; cwd: string; sessionId: string }): string {
|
|
385
|
-
return join(input.configDir, "projects", transcriptSlug(input.cwd), `${input.sessionId}.jsonl`);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const LIMIT_LABELS: Record<string, string> = { five_hour: "5h", seven_day: "weekly" };
|
|
389
|
-
|
|
390
|
-
export function pingRejection(rows: TranscriptRow[]): string | null {
|
|
391
|
-
const row = rows.findLast((r) => r.isApiErrorMessage === true);
|
|
392
|
-
if (!row || row.error !== "rate_limit" || row.apiErrorIsTransient === true) return null;
|
|
393
|
-
const type = row.quotaLimits?.rateLimitType ?? "";
|
|
394
|
-
if (type === "" && parseErrorBody(row.errorDetails)?.error?.type !== "rate_limit_error") return null;
|
|
395
|
-
const label = LIMIT_LABELS[type] ?? (type !== "" ? type.replace(/_/g, " ") : "usage");
|
|
396
|
-
const text = transcriptRowText(row);
|
|
397
|
-
return `${label} limit${text ? `: ${text}` : ""}`;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
export async function pingSession(configDir?: string): Promise<PingFailure | null> {
|
|
401
|
-
const cwd = join(paths.sampleDir, "ping-cwd");
|
|
402
|
-
const fail = (reason: string, rejected = false): PingFailure => {
|
|
403
|
-
log("usage.ping_failed", { dir: configDir ?? "live", rejected, reason: reason.slice(0, 200) });
|
|
404
|
-
return { reason, rejected };
|
|
405
|
-
};
|
|
406
|
-
let r: SpawnResult | null;
|
|
407
|
-
try {
|
|
408
|
-
mkdirSync(cwd, { recursive: true });
|
|
409
|
-
r = await spawnClaudeBounded([resolveRealClaude(), ...PING_ARGS], probeEnv(configDir), cwd);
|
|
410
|
-
} catch (e) {
|
|
411
|
-
return fail(e instanceof Error ? e.message : String(e));
|
|
412
|
-
}
|
|
413
|
-
if (r === null) return fail("output pipes still open after child exit (leaked descendant)");
|
|
414
|
-
const parsed = PingResultSchema.safeParse((() => { try { return JSON.parse(r.stdout); } catch { return null; } })());
|
|
415
|
-
const result = parsed.success ? parsed.data : null;
|
|
416
|
-
if (r.exitCode === 0 && result?.is_error === false) {
|
|
417
|
-
log("usage.ping_ok", { dir: configDir ?? "live" });
|
|
418
|
-
return null;
|
|
419
|
-
}
|
|
420
|
-
if (result?.session_id) {
|
|
421
|
-
const transcript = pingTranscriptPath({ configDir: configDir ?? paths.claudeDir, cwd, sessionId: result.session_id });
|
|
422
|
-
const rejection = existsSync(transcript) ? pingRejection(readTranscriptTail(transcript)) : null;
|
|
423
|
-
if (rejection) return fail(`rejected at the ${rejection}`.slice(0, 200), true);
|
|
424
|
-
}
|
|
425
|
-
if (r.exitCode !== 0) {
|
|
426
|
-
const detail = result?.result?.trim() || r.stderr.trim() || r.stdout.trim();
|
|
427
|
-
return fail(`claude exited ${r.exitCode ?? "on signal"}: ${detail.slice(0, 160)}`);
|
|
428
|
-
}
|
|
429
|
-
if (result?.is_error === true) return fail((result.result?.trim() || "request errored").slice(0, 160));
|
|
430
|
-
return fail(`unrecognized ping output: ${r.stdout.trim().slice(0, 120)}`);
|
|
431
|
-
}
|
package/src/main.ts
CHANGED
|
@@ -45,8 +45,7 @@ function printHelp(): void {
|
|
|
45
45
|
${c.cyan("tokenmaxxing switch --codex")} [sel] switch the codex pool (takes effect on next codex start)
|
|
46
46
|
${c.cyan("tokenmaxxing ls")} list pooled accounts
|
|
47
47
|
${c.cyan("tokenmaxxing status")} accounts with 5h / weekly / per-model usage bars
|
|
48
|
-
${c.cyan("tokenmaxxing
|
|
49
|
-
${c.cyan("tokenmaxxing watch")} [seconds] live status: re-render every N seconds (default 120, never pings)
|
|
48
|
+
${c.cyan("tokenmaxxing watch")} [seconds] live status: re-render every N seconds (default 120)
|
|
50
49
|
${c.cyan("tokenmaxxing config")} [get|set|unset|tidy] inspect and edit config.json (bare = effective config with sources)
|
|
51
50
|
${c.cyan("tokenmaxxing doctor")} verify the install is intact
|
|
52
51
|
${c.cyan("tokenmaxxing rename")} [--codex] <sel> <label>
|
|
@@ -60,28 +59,6 @@ function printHelp(): void {
|
|
|
60
59
|
|
|
61
60
|
let jsonMode = false;
|
|
62
61
|
|
|
63
|
-
function statusFlags(rest: string[]): { ping: boolean; pingCount: number | undefined } | { error: string } {
|
|
64
|
-
let ping = false;
|
|
65
|
-
let pingCount: number | undefined;
|
|
66
|
-
for (let i = 0; i < rest.length; i++) {
|
|
67
|
-
const arg = rest[i]!;
|
|
68
|
-
if (arg === "--ping") {
|
|
69
|
-
ping = true;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (arg === "--count") {
|
|
73
|
-
if (pingCount != null) return { error: "--count may only be given once" };
|
|
74
|
-
const raw = rest[++i];
|
|
75
|
-
pingCount = Number(raw);
|
|
76
|
-
if (!Number.isInteger(pingCount) || pingCount < 1) return { error: `--count needs a positive whole number of accounts, got: ${raw ?? "nothing"}` };
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
return { error: `unknown status option: ${arg} (status takes --ping and --count N)` };
|
|
80
|
-
}
|
|
81
|
-
if (pingCount != null && !ping) return { error: "--count only applies together with --ping" };
|
|
82
|
-
return { ping, pingCount };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
62
|
async function main(): Promise<number> {
|
|
86
63
|
if (process.platform !== "darwin" && process.platform !== "linux") {
|
|
87
64
|
console.error(`tokenmaxxing supports macOS and Linux only (this is ${process.platform})`);
|
|
@@ -126,15 +103,13 @@ async function main(): Promise<number> {
|
|
|
126
103
|
case "__session-start": return runSessionStart();
|
|
127
104
|
case "__codex-stop-hook": return runCodexStopHook();
|
|
128
105
|
case undefined:
|
|
129
|
-
case "status":
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if ("error" in flags) {
|
|
134
|
-
emitError({ json, message: flags.error });
|
|
106
|
+
case "status": {
|
|
107
|
+
const extra = args[1];
|
|
108
|
+
if (extra != null) {
|
|
109
|
+
emitError({ json, message: `unknown status option: ${extra} (status takes no options)` });
|
|
135
110
|
return 2;
|
|
136
111
|
}
|
|
137
|
-
return cmdStatus({
|
|
112
|
+
return cmdStatus({ json });
|
|
138
113
|
}
|
|
139
114
|
case "switch": {
|
|
140
115
|
const rest = args.slice(1).filter((a) => a !== "--codex");
|