tokenmaxxing 1.103.0 → 1.105.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.103.0",
3
+ "version": "1.105.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",
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path";
2
+ import { omit } from "es-toolkit";
2
3
  import type { z } from "zod";
3
4
  import { codexPaths, optionalEnv } from "../lib/paths.ts";
4
5
  import { writeFileAtomic } from "../lib/atomic.ts";
@@ -22,8 +23,7 @@ async function compactBeforeMove(input: { sessionId: string | null; now: number
22
23
  if (!live || live.needsReauth === true || (live.enforcedUntil != null && live.enforcedUntil > now)) return;
23
24
  const observed = await codex.observeLive(live, loadConfig(), now, { probe: true, perModel: false });
24
25
  if (!observed || !isExhausted({ ...live, windows: observed.windows }, codexPickCtx(now, live.id))) return;
25
- const env: Record<string, string | undefined> = { ...process.env, TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH) };
26
- delete env[CODEX_SUPERVISOR_ID_ENV];
26
+ const env = { ...omit(process.env, [CODEX_SUPERVISOR_ID_ENV]), TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH) };
27
27
  log("codexstop.compact_start", { thread: sessionId.slice(0, 8), account: live.id.slice(0, 8) });
28
28
  await compactCodexThread({ real: resolveRealBin(CODEX_BIN), threadId: sessionId, env });
29
29
  }
@@ -1,6 +1,7 @@
1
1
  import type { Subprocess } from "bun";
2
2
  import { existsSync, mkdirSync, rmSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { omit } from "es-toolkit";
4
5
  import { z } from "zod";
5
6
  import { codexPaths, codexPool } from "../lib/paths.ts";
6
7
  import { withLock } from "../lib/lock.ts";
@@ -90,11 +91,10 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
90
91
  if (loopGuardTripped("codex")) return 1;
91
92
 
92
93
  const real = resolveRealBin(CODEX_BIN);
93
- const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
94
+ const childEnv: Record<string, string | undefined> = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
94
95
 
95
96
  if (!shouldManageCodex({ argv }) || process.env[UNMANAGED_ENV]) {
96
- const passthroughEnv: Record<string, string | undefined> = { ...childEnv };
97
- delete passthroughEnv[CODEX_SUPERVISOR_ID_ENV];
97
+ const passthroughEnv = omit(childEnv, [CODEX_SUPERVISOR_ID_ENV]);
98
98
  let child: Subprocess | null = null;
99
99
  process.on("SIGTERM", () => {
100
100
  if (child) child.kill("SIGTERM");
@@ -20,10 +20,10 @@ const StopFailureStdin = z.looseObject({
20
20
  const ROW_WAIT_MS = 3_000;
21
21
  const ROW_POLL_MS = 200;
22
22
 
23
- async function awaitEnforcedRow(input: { transcriptPath: string; error: string; lastAssistantMessage: string | undefined; now: number }): Promise<TranscriptRow | null> {
23
+ async function awaitEnforcedRow(input: { transcriptPath: string; error: string; lastAssistantMessage: string | undefined }): Promise<TranscriptRow | null> {
24
24
  const deadline = Date.now() + ROW_WAIT_MS;
25
25
  while (true) {
26
- const row = findEnforcedRow({ rows: await readTranscriptTail(input.transcriptPath), error: input.error, lastAssistantMessage: input.lastAssistantMessage, now: input.now });
26
+ const row = findEnforcedRow({ rows: await readTranscriptTail(input.transcriptPath), error: input.error, lastAssistantMessage: input.lastAssistantMessage });
27
27
  if (row || Date.now() >= deadline) return row;
28
28
  await Bun.sleep(ROW_POLL_MS);
29
29
  }
@@ -52,7 +52,7 @@ export async function runStopFailureHook(): Promise<number> {
52
52
  const canRespawn = session != null && mainLoop;
53
53
  const cfg = loadConfig();
54
54
  const row = stdin.transcript_path
55
- ? await awaitEnforcedRow({ transcriptPath: stdin.transcript_path, error, lastAssistantMessage: stdin.last_assistant_message, now })
55
+ ? await awaitEnforcedRow({ transcriptPath: stdin.transcript_path, error, lastAssistantMessage: stdin.last_assistant_message })
56
56
  : null;
57
57
  const limit = row ? classifyEnforcedLimit(row, cfg.policy.switchModels) : null;
58
58
 
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, rmSync, readdirSync, statSync } from "node:fs";
2
2
  import { basename, join } from "node:path";
3
3
  import type { FileSink, Subprocess } from "bun";
4
- import { maxBy } from "es-toolkit";
4
+ import { maxBy, omit } from "es-toolkit";
5
5
  import { z } from "zod";
6
6
  import { claudePool, paths, storeDirFor } from "../lib/paths.ts";
7
7
  import { CLAUDE_BIN, UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealBin, wrapDepth } from "../lib/claudebin.ts";
@@ -346,7 +346,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
346
346
  log("supervisor.resolve_failed", { err: errorMessage(e) });
347
347
  return 1;
348
348
  }
349
- const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
349
+ const childEnv: Record<string, string | undefined> = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
350
350
 
351
351
  let child: Subprocess | null = null;
352
352
  let terminating = false;
@@ -374,11 +374,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
374
374
  });
375
375
 
376
376
  if (!info.manage || process.env[UNMANAGED_ENV]) {
377
- const passthroughEnv: Record<string, string | undefined> = { ...childEnv };
378
- delete passthroughEnv.TOKENMAXXING_SUPERVISED;
379
- delete passthroughEnv.TOKENMAXXING_SESSION_ID;
380
- delete passthroughEnv.TOKENMAXXING_MODEL;
381
- delete passthroughEnv.TOKENMAXXING_REFUSED;
377
+ const passthroughEnv = omit(childEnv, ["TOKENMAXXING_SUPERVISED", "TOKENMAXXING_SESSION_ID", "TOKENMAXXING_MODEL", "TOKENMAXXING_REFUSED"]);
382
378
  return runPassthrough({ real, argv, env: passthroughEnv, onSpawn: (p) => { child = p; } });
383
379
  }
384
380
 
@@ -510,12 +506,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
510
506
  log("supervisor.compact_skipped", { sid: m.sessionId.slice(0, 8), seat: seat.id.slice(0, 8), until: walledUntil });
511
507
  } else if (m.compact && seat && resumable) {
512
508
  say(`\n\x1b[36m↻ tokenmaxxing: compacting the conversation on ${seat.label} before the move...\x1b[0m\n`, `tokenmaxxing: compacting the conversation on ${seat.label} before the move.`);
513
- const compactEnv: Record<string, string | undefined> = { ...childEnv, TOKENMAXXING_PROBE: "1", CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(seat.id) };
514
- delete compactEnv.TOKENMAXXING_SUPERVISED;
515
- delete compactEnv.TOKENMAXXING_SESSION_ID;
516
- delete compactEnv.TOKENMAXXING_LAUNCHED_AT;
517
- delete compactEnv.TOKENMAXXING_MODEL;
518
- delete compactEnv.TOKENMAXXING_REFUSED;
509
+ const compactEnv = { ...omit(childEnv, ["TOKENMAXXING_SUPERVISED", "TOKENMAXXING_SESSION_ID", "TOKENMAXXING_LAUNCHED_AT", "TOKENMAXXING_MODEL", "TOKENMAXXING_REFUSED"]), TOKENMAXXING_PROBE: "1", CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(seat.id) };
519
510
  const outcome = await compactClaudeSession({ real, sid: m.sessionId, transcript, env: compactEnv, onSpawn: (p) => { child = p; } });
520
511
  child = null;
521
512
  if (terminating) {
package/src/lib/claude.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { countBy } from "es-toolkit";
3
+ import { countBy, omit } from "es-toolkit";
4
4
  import { z } from "zod";
5
5
  import { readItem, writeItem, deleteItem, isolatedTarget, readStore, storeTarget, claudeAiOauthOnly } from "./credstore.ts";
6
6
  import { CLAUDE_BIN, resolveRealBin, resolveVerifiedClaude } from "./claudebin.ts";
@@ -16,7 +16,7 @@ import { StoreUnusableError, type Observation, type Provider, type SampleReport
16
16
  import { foldTee, readyToSample, runSample, sampleAccountUsage, sampleDue, sampleIntervalMs, teeObservation, usageBlockedUntil } from "./sample.ts";
17
17
  import { clearUsageSnapshot, liveWaitClaims, loadAccounts, loadConfig, loadUsageSnapshot, pinBinOverride, readJsonFile, saveAccounts, type Harvest } from "./state.ts";
18
18
  import { saveTermios, restoreTermios } from "./tty.ts";
19
- import { fetchUsageDirect, gatedFamilies, mergeWindows, modelFromFlag, scrubCredentialEnv, windowsOf } from "./usage.ts";
19
+ import { CRED_ENV_OVERRIDES, fetchUsageDirect, gatedFamilies, mergeWindows, modelFromFlag, windowsOf } from "./usage.ts";
20
20
  import { CredentialBlobSchema, JsonTextSchema, OAuthAccountSchema, type Account, type Config, type ModelInfo } from "./types.ts";
21
21
  import { c } from "../cli/render.ts";
22
22
 
@@ -170,7 +170,7 @@ async function login(): Promise<Harvest | null> {
170
170
  const real = resolveRealBin(CLAUDE_BIN);
171
171
 
172
172
  const savedTermios = saveTermios();
173
- const env = scrubCredentialEnv({ ...process.env, CLAUDE_CONFIG_DIR: onboardDir, TOKENMAXXING_PROBE: "1", TOKENMAXXING_SUPERVISED: "" });
173
+ const env = { ...omit(process.env, CRED_ENV_OVERRIDES), CLAUDE_CONFIG_DIR: onboardDir, TOKENMAXXING_PROBE: "1", TOKENMAXXING_SUPERVISED: "" };
174
174
  const p = Bun.spawn([real], {
175
175
  stdin: "inherit",
176
176
  stdout: "inherit",
package/src/lib/sample.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { mkdtempSync, rmSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import { omit } from "es-toolkit";
4
5
  import { CLAUDE_BIN, MAX_WRAP_DEPTH, resolveRealBin, WRAP_DEPTH_ENV } from "./claudebin.ts";
5
6
  import { deleteItem, readStore, storeTarget } from "./credstore.ts";
6
7
  import { withLock } from "./lock.ts";
@@ -11,7 +12,7 @@ import { barFor, gatedWindows, landWindows, liveUsed, thresholdBars } from "./pi
11
12
  import { seatCounts } from "./presence.ts";
12
13
  import type { Observation } from "./provider.ts";
13
14
  import { loadAccounts, loadUsageSnapshot, saveAccounts } from "./state.ts";
14
- import { fetchUsageDirect, mergeWindows, scrubCredentialEnv, windowsOf } from "./usage.ts";
15
+ import { CRED_ENV_OVERRIDES, fetchUsageDirect, mergeWindows, windowsOf } from "./usage.ts";
15
16
  import type { Account, Config, Thresholds, UsageWindows, Window } from "./types.ts";
16
17
 
17
18
  export type SampleOutcome = { ok: true; usage: UsageWindows; via: "get" } | { ok: false; reason: string; retryAt?: number };
@@ -84,9 +85,7 @@ const REFRESH_KILL_MS = 60_000;
84
85
 
85
86
  async function refreshStore(account: Account): Promise<number | null> {
86
87
  const home = mkdtempSync(join(tmpdir(), "tokenmaxxing-refresh-"));
87
- const env = scrubCredentialEnv({ ...process.env, TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH) });
88
- env.CLAUDE_CONFIG_DIR = home;
89
- env.CLAUDE_SECURESTORAGE_CONFIG_DIR = storeDirFor(account.id);
88
+ const env = { ...omit(process.env, CRED_ENV_OVERRIDES), TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH), CLAUDE_CONFIG_DIR: home, CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(account.id) };
90
89
  try {
91
90
  const p = Bun.spawn([resolveRealBin(CLAUDE_BIN), "-p", "/usage", "--no-session-persistence", "--safe-mode"], {
92
91
  env,
package/src/lib/state.ts CHANGED
@@ -18,8 +18,8 @@ import {
18
18
  type Window,
19
19
  } from "./types.ts";
20
20
 
21
- export function readJsonFile<T extends z.ZodType>(path: string, schema: T): z.output<T> {
22
- const json = JsonTextSchema.safeParse(readFileSync(path, "utf8"));
21
+ export function readJsonFile<T extends z.ZodType>(path: string, schema: T, fd?: number): z.output<T> {
22
+ const json = JsonTextSchema.safeParse(readFileSync(fd ?? path, "utf8"));
23
23
  if (!json.success) throw new Error(`${path} is corrupt (unparsable JSON) - repair or remove the file`);
24
24
  const parsed = schema.safeParse(json.data);
25
25
  if (!parsed.success) {
@@ -119,18 +119,18 @@ export function upsertAccount(
119
119
  export type UsageSnapshot = { state: UsageState; at: number };
120
120
 
121
121
  export function loadUsageSnapshot(accountId: string): UsageSnapshot | null {
122
+ const path = usageJsonFor(accountId);
122
123
  let fd: number;
123
124
  try {
124
- fd = openSync(usageJsonFor(accountId), "r");
125
- } catch {
126
- return null;
125
+ fd = openSync(path, "r");
126
+ } catch (e) {
127
+ if (ErrnoSchema.safeParse(e).data?.code === "ENOENT") return null;
128
+ throw e;
127
129
  }
128
130
  try {
129
131
  const at = fstatSync(fd).mtimeMs;
130
- const parsed = UsageStateSchema.safeParse(JSON.parse(readFileSync(fd, "utf8")));
131
- return parsed.success && parsed.data.account === accountId ? { state: parsed.data, at } : null;
132
- } catch {
133
- return null;
132
+ const state = readJsonFile(path, UsageStateSchema, fd);
133
+ return state.account === accountId ? { state, at } : null;
134
134
  } finally {
135
135
  closeSync(fd);
136
136
  }
@@ -145,7 +145,11 @@ const SAMPLED_AT_REFRESH_MS = 30_000;
145
145
 
146
146
  export function writeUsage(input: UsageState): boolean {
147
147
  const file = usageJsonFor(input.account);
148
- const prev = loadUsageSnapshot(input.account)?.state ?? null;
148
+ let prev: UsageState | null = null;
149
+ try {
150
+ prev = loadUsageSnapshot(input.account)?.state ?? null;
151
+ } catch {
152
+ }
149
153
  const next: UsageState = { ...input, sampledAt: input.ts };
150
154
  if (
151
155
  prev &&
package/src/lib/usage.ts CHANGED
@@ -52,7 +52,6 @@ export function modelFromFlag(value: string | null): ModelInfo | null {
52
52
  const TranscriptBlockSchema = z.looseObject({ type: z.string().optional(), text: z.string().optional() });
53
53
  export const TranscriptRowSchema = z.looseObject({
54
54
  type: z.string().optional(),
55
- timestamp: z.string().optional(),
56
55
  isApiErrorMessage: z.boolean().optional(),
57
56
  apiErrorIsTransient: z.boolean().optional(),
58
57
  apiError: z.string().optional(),
@@ -84,21 +83,11 @@ export function transcriptRowText(row: TranscriptRow): string {
84
83
  return blocks.data.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n").trim();
85
84
  }
86
85
 
87
- const ROW_RECENCY_MS = 60_000;
88
-
89
86
  export const ENFORCED_ERRORS = ["rate_limit", "oauth_org_not_allowed"];
90
87
 
91
- export function findEnforcedRow(input: { rows: TranscriptRow[]; error: string; lastAssistantMessage: string | undefined; now: number }): TranscriptRow | null {
92
- const { rows, error, lastAssistantMessage, now } = input;
93
- for (let i = rows.length - 1; i >= 0; i--) {
94
- const row = rows[i]!;
95
- if (row.isApiErrorMessage !== true || row.error !== error) continue;
96
- const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
97
- const byContent = lastAssistantMessage != null && lastAssistantMessage !== "" && transcriptRowText(row) === lastAssistantMessage;
98
- const byRecency = Number.isFinite(ts) && Math.abs(now - ts) <= ROW_RECENCY_MS;
99
- if (byContent || byRecency) return row;
100
- }
101
- return null;
88
+ export function findEnforcedRow(input: { rows: TranscriptRow[]; error: string; lastAssistantMessage: string | undefined }): TranscriptRow | null {
89
+ const { rows, error, lastAssistantMessage } = input;
90
+ return rows.findLast((row) => row.isApiErrorMessage === true && row.error === error && transcriptRowText(row) === lastAssistantMessage) ?? null;
102
91
  }
103
92
 
104
93
  export type EnforcedClass =
@@ -164,12 +153,6 @@ export const CRED_ENV_OVERRIDES = [
164
153
  "CLAUDE_CODE_USE_VERTEX",
165
154
  ] as const;
166
155
 
167
- export function scrubCredentialEnv(env: Record<string, string>): Record<string, string> {
168
- const scrubbed = { ...env };
169
- for (const k of CRED_ENV_OVERRIDES) delete scrubbed[k];
170
- return scrubbed;
171
- }
172
-
173
156
  export const OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
174
157
  const USAGE_URL = env("TOKENMAXXING_OAUTH_USAGE_URL", OAUTH_USAGE_URL);
175
158
  const USAGE_DEADLINE_MS = 10_000;