tokenmaxxing 1.104.0 → 1.106.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.104.0",
3
+ "version": "1.106.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",
@@ -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
 
@@ -17,7 +17,7 @@ import { countdownWait, exitStatus, loopGuardTripped, raceMarkerOrExit, recordPr
17
17
  import { saveTermios } from "../lib/tty.ts";
18
18
  import { liveSessionId, loadSessionFlags, pruneStaleSessions, saveSessionFlags, writeRespawnMarker } from "../lib/sessions.ts";
19
19
  import { loadAccounts, loadConfig, readJsonFile, releaseWaitClaim } from "../lib/state.ts";
20
- import { gatedFamilies, modelFromFlag } from "../lib/usage.ts";
20
+ import { gatedFamilies, modelFromFlag, scrubCredEnv } from "../lib/usage.ts";
21
21
  import { RespawnMarkerSchema, type Account, type Config, type ModelInfo } from "../lib/types.ts";
22
22
  import { errorMessage, log } from "../lib/log.ts";
23
23
 
@@ -439,13 +439,12 @@ export async function runSupervisor(argv: string[]): Promise<number> {
439
439
  stdout: "inherit",
440
440
  stderr: "inherit",
441
441
  env: {
442
- ...childEnv,
442
+ ...(picked ? { ...scrubCredEnv(childEnv), CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(picked.id) } : childEnv),
443
443
  TOKENMAXXING_SUPERVISED: "1",
444
444
  TOKENMAXXING_SESSION_ID: sid,
445
445
  TOKENMAXXING_LAUNCHED_AT: String(gate.launchedAt),
446
446
  TOKENMAXXING_MODEL: effective.model ?? "",
447
447
  TOKENMAXXING_REFUSED: refused.join(","),
448
- ...(picked ? { CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(picked.id) } : {}),
449
448
  },
450
449
  });
451
450
  child = spawned;
@@ -506,7 +505,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
506
505
  log("supervisor.compact_skipped", { sid: m.sessionId.slice(0, 8), seat: seat.id.slice(0, 8), until: walledUntil });
507
506
  } else if (m.compact && seat && resumable) {
508
507
  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.`);
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) };
508
+ const compactEnv = { ...omit(scrubCredEnv(childEnv), ["TOKENMAXXING_SUPERVISED", "TOKENMAXXING_SESSION_ID", "TOKENMAXXING_LAUNCHED_AT", "TOKENMAXXING_MODEL", "TOKENMAXXING_REFUSED"]), TOKENMAXXING_PROBE: "1", CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(seat.id) };
510
509
  const outcome = await compactClaudeSession({ real, sid: m.sessionId, transcript, env: compactEnv, onSpawn: (p) => { child = p; } });
511
510
  child = null;
512
511
  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, omit } from "es-toolkit";
3
+ import { countBy } 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 { CRED_ENV_OVERRIDES, fetchUsageDirect, gatedFamilies, mergeWindows, modelFromFlag, windowsOf } from "./usage.ts";
19
+ import { fetchUsageDirect, gatedFamilies, mergeWindows, modelFromFlag, scrubCredEnv, 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 = { ...omit(process.env, CRED_ENV_OVERRIDES), CLAUDE_CONFIG_DIR: onboardDir, TOKENMAXXING_PROBE: "1", TOKENMAXXING_SUPERVISED: "" };
173
+ const env = { ...scrubCredEnv(process.env), 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,7 +1,6 @@
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";
5
4
  import { CLAUDE_BIN, MAX_WRAP_DEPTH, resolveRealBin, WRAP_DEPTH_ENV } from "./claudebin.ts";
6
5
  import { deleteItem, readStore, storeTarget } from "./credstore.ts";
7
6
  import { withLock } from "./lock.ts";
@@ -12,7 +11,7 @@ import { barFor, gatedWindows, landWindows, liveUsed, thresholdBars } from "./pi
12
11
  import { seatCounts } from "./presence.ts";
13
12
  import type { Observation } from "./provider.ts";
14
13
  import { loadAccounts, loadUsageSnapshot, saveAccounts } from "./state.ts";
15
- import { CRED_ENV_OVERRIDES, fetchUsageDirect, mergeWindows, windowsOf } from "./usage.ts";
14
+ import { fetchUsageDirect, mergeWindows, scrubCredEnv, windowsOf } from "./usage.ts";
16
15
  import type { Account, Config, Thresholds, UsageWindows, Window } from "./types.ts";
17
16
 
18
17
  export type SampleOutcome = { ok: true; usage: UsageWindows; via: "get" } | { ok: false; reason: string; retryAt?: number };
@@ -85,7 +84,7 @@ const REFRESH_KILL_MS = 60_000;
85
84
 
86
85
  async function refreshStore(account: Account): Promise<number | null> {
87
86
  const home = mkdtempSync(join(tmpdir(), "tokenmaxxing-refresh-"));
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) };
87
+ const env = { ...scrubCredEnv(process.env), TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH), CLAUDE_CONFIG_DIR: home, CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(account.id) };
89
88
  try {
90
89
  const p = Bun.spawn([resolveRealBin(CLAUDE_BIN), "-p", "/usage", "--no-session-persistence", "--safe-mode"], {
91
90
  env,
package/src/lib/usage.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { omit } from "es-toolkit";
1
2
  import { z } from "zod";
2
3
  import { http } from "./http.ts";
3
4
  import { errorMessage, log } from "./log.ts";
@@ -52,7 +53,6 @@ export function modelFromFlag(value: string | null): ModelInfo | null {
52
53
  const TranscriptBlockSchema = z.looseObject({ type: z.string().optional(), text: z.string().optional() });
53
54
  export const TranscriptRowSchema = z.looseObject({
54
55
  type: z.string().optional(),
55
- timestamp: z.string().optional(),
56
56
  isApiErrorMessage: z.boolean().optional(),
57
57
  apiErrorIsTransient: z.boolean().optional(),
58
58
  apiError: z.string().optional(),
@@ -84,21 +84,11 @@ export function transcriptRowText(row: TranscriptRow): string {
84
84
  return blocks.data.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n").trim();
85
85
  }
86
86
 
87
- const ROW_RECENCY_MS = 60_000;
88
-
89
87
  export const ENFORCED_ERRORS = ["rate_limit", "oauth_org_not_allowed"];
90
88
 
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;
89
+ export function findEnforcedRow(input: { rows: TranscriptRow[]; error: string; lastAssistantMessage: string | undefined }): TranscriptRow | null {
90
+ const { rows, error, lastAssistantMessage } = input;
91
+ return rows.findLast((row) => row.isApiErrorMessage === true && row.error === error && transcriptRowText(row) === lastAssistantMessage) ?? null;
102
92
  }
103
93
 
104
94
  export type EnforcedClass =
@@ -153,17 +143,28 @@ export function mergeWindows(next: Window[], prev: Window[]): Window[] {
153
143
  return [...next.filter((w) => w.name == null), ...rows];
154
144
  }
155
145
 
156
- export const CRED_ENV_OVERRIDES = [
146
+ const CRED_ENV_OVERRIDES = [
157
147
  "ANTHROPIC_API_KEY",
158
148
  "ANTHROPIC_AUTH_TOKEN",
159
149
  "CLAUDE_CODE_OAUTH_TOKEN",
150
+ "CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR",
151
+ "CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR",
160
152
  "CLAUDE_CODE_SUBSCRIPTION_TYPE",
161
153
  "CLAUDE_CODE_RATE_LIMIT_TIER",
162
154
  "CLAUDE_SECURESTORAGE_CONFIG_DIR",
163
155
  "CLAUDE_CODE_USE_BEDROCK",
164
156
  "CLAUDE_CODE_USE_VERTEX",
157
+ "CLAUDE_CODE_USE_FOUNDRY",
158
+ "CLAUDE_CODE_USE_MANTLE",
159
+ "CLAUDE_CODE_USE_ANTHROPIC_AWS",
160
+ "CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD",
165
161
  ] as const;
166
162
 
163
+ export function scrubCredEnv(env: Record<string, string | undefined>): Record<string, string | undefined> {
164
+ const overridden = CRED_ENV_OVERRIDES.some((k) => k !== "CLAUDE_SECURESTORAGE_CONFIG_DIR" && env[k]);
165
+ return omit(env, overridden ? [...CRED_ENV_OVERRIDES, "ANTHROPIC_BASE_URL"] : CRED_ENV_OVERRIDES);
166
+ }
167
+
167
168
  export const OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
168
169
  const USAGE_URL = env("TOKENMAXXING_OAUTH_USAGE_URL", OAUTH_USAGE_URL);
169
170
  const USAGE_DEADLINE_MS = 10_000;