tokenmaxxing 1.103.0 → 1.104.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.104.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");
@@ -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
@@ -164,12 +164,6 @@ export const CRED_ENV_OVERRIDES = [
164
164
  "CLAUDE_CODE_USE_VERTEX",
165
165
  ] as const;
166
166
 
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
167
  export const OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
174
168
  const USAGE_URL = env("TOKENMAXXING_OAUTH_USAGE_URL", OAUTH_USAGE_URL);
175
169
  const USAGE_DEADLINE_MS = 10_000;