tokenmaxxing 1.100.0 → 1.102.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.100.0",
3
+ "version": "1.102.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",
@@ -4,7 +4,7 @@ import { evaluateAndMaybeSwap } from "../lib/decide.ts";
4
4
  import { readStdin } from "../lib/proc.ts";
5
5
  import { adoptLiveSession, refusedAccounts, supervisedSession, writeRespawnMarker } from "../lib/sessions.ts";
6
6
  import { loadConfig } from "../lib/state.ts";
7
- import { classifyEnforcedLimit, findEnforcedRow, parseErrorBody, readTranscriptTail, type TranscriptRow } from "../lib/usage.ts";
7
+ import { classifyEnforcedLimit, ENFORCED_ERRORS, findEnforcedRow, parseErrorBody, readTranscriptTail, type TranscriptRow } from "../lib/usage.ts";
8
8
  import { paths } from "../lib/paths.ts";
9
9
  import { JsonTextSchema, type EnforcedLimit } from "../lib/types.ts";
10
10
  import { errorMessage, log } from "../lib/log.ts";
@@ -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; lastAssistantMessage: string | undefined; now: number }): Promise<TranscriptRow | null> {
23
+ async function awaitEnforcedRow(input: { transcriptPath: string; error: string; lastAssistantMessage: string | undefined; now: number }): 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), lastAssistantMessage: input.lastAssistantMessage, now: input.now });
26
+ const row = findEnforcedRow({ rows: await readTranscriptTail(input.transcriptPath), error: input.error, lastAssistantMessage: input.lastAssistantMessage, now: input.now });
27
27
  if (row || Date.now() >= deadline) return row;
28
28
  await Bun.sleep(ROW_POLL_MS);
29
29
  }
@@ -36,7 +36,8 @@ export async function runStopFailureHook(): Promise<number> {
36
36
  const now = Date.now();
37
37
  const parsed = StopFailureStdin.safeParse(JsonTextSchema.safeParse(await readStdin()).data);
38
38
  const stdin = parsed.success ? parsed.data : {};
39
- if (stdin.error !== undefined && stdin.error !== "rate_limit") return 0;
39
+ const error = stdin.error ?? "rate_limit";
40
+ if (!ENFORCED_ERRORS.includes(error)) return 0;
40
41
 
41
42
  const stdinSid = stdin.session_id;
42
43
  const mainLoop = stdin.agent_id === undefined;
@@ -51,7 +52,7 @@ export async function runStopFailureHook(): Promise<number> {
51
52
  const canRespawn = session != null && mainLoop;
52
53
  const cfg = loadConfig();
53
54
  const row = stdin.transcript_path
54
- ? await awaitEnforcedRow({ transcriptPath: stdin.transcript_path, lastAssistantMessage: stdin.last_assistant_message, now })
55
+ ? await awaitEnforcedRow({ transcriptPath: stdin.transcript_path, error, lastAssistantMessage: stdin.last_assistant_message, now })
55
56
  : null;
56
57
  const limit = row ? classifyEnforcedLimit(row, cfg.policy.switchModels) : null;
57
58
 
package/src/lib/codex.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { z } from "zod";
3
4
  import { writeFileAtomic } from "./atomic.ts";
4
5
  import { CODEX_BIN, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealBin, verifyRealBin } from "./claudebin.ts";
5
6
  import { codexIdentityOf, codexStoreUsable, deleteCodexStoreAuth, ensureCodexStoreHome, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
@@ -189,11 +190,13 @@ async function importLive(): Promise<Harvest | null> {
189
190
  return login();
190
191
  }
191
192
 
193
+ const CodexStoreModeSchema = z.looseObject({ cli_auth_credentials_store: z.string().optional() });
194
+
192
195
  function storePinnedAwayFromFile(): boolean {
193
196
  const configToml = `${codexPaths.home}/config.toml`;
194
197
  if (!existsSync(configToml)) return false;
195
- const config = Bun.TOML.parse(readFileSync(configToml, "utf8"));
196
- return "cli_auth_credentials_store" in config && config.cli_auth_credentials_store !== "file";
198
+ const store = CodexStoreModeSchema.parse(Bun.TOML.parse(readFileSync(configToml, "utf8"))).cli_auth_credentials_store;
199
+ return store !== undefined && store !== "file";
197
200
  }
198
201
 
199
202
  function preflight(): void {
package/src/lib/decide.ts CHANGED
@@ -38,6 +38,7 @@ function modelKindCovered(limit: EnforcedLimit, account: Account, now: number, w
38
38
  }
39
39
 
40
40
  function enforcedWall(limit: EnforcedLimit, account: Account, now: number): number {
41
+ if (limit.kind === "org") return now + FIVE_HOURS_MS;
41
42
  const { family } = limit;
42
43
  const familyReset =
43
44
  family == null
@@ -5,6 +5,7 @@ import { paths } from "./paths.ts";
5
5
  import { writeFileAtomic } from "./atomic.ts";
6
6
  import { readJsonFile } from "./state.ts";
7
7
  import { JsonTextSchema } from "./types.ts";
8
+ import { ENFORCED_ERRORS } from "./usage.ts";
8
9
 
9
10
  export function installedBin(): string {
10
11
  return join(paths.binDir, "tokenmaxxing");
@@ -29,7 +30,7 @@ const SUBCMD = {
29
30
  sessionStart: "__session-start",
30
31
  } as const;
31
32
 
32
- const STOP_FAILURE_MATCHER = "rate_limit";
33
+ const STOP_FAILURE_MATCHER = ENFORCED_ERRORS.join("|");
33
34
 
34
35
  function readSettings(): Settings {
35
36
  if (!existsSync(paths.claudeSettings)) return {};
package/src/lib/types.ts CHANGED
@@ -84,7 +84,7 @@ export type AccountsIndex = z.infer<typeof AccountsIndexSchema>;
84
84
 
85
85
  export type EnforcedLimit = {
86
86
  account: string;
87
- kind: "session" | "weekly" | "model" | "credits";
87
+ kind: "session" | "weekly" | "model" | "credits" | "org";
88
88
  family: string | null;
89
89
  resetsAt: number | null;
90
90
  blind: boolean;
package/src/lib/usage.ts CHANGED
@@ -86,11 +86,13 @@ export function transcriptRowText(row: TranscriptRow): string {
86
86
 
87
87
  const ROW_RECENCY_MS = 60_000;
88
88
 
89
- export function findEnforcedRow(input: { rows: TranscriptRow[]; lastAssistantMessage: string | undefined; now: number }): TranscriptRow | null {
90
- const { rows, lastAssistantMessage, now } = input;
89
+ export const ENFORCED_ERRORS = ["rate_limit", "oauth_org_not_allowed"];
90
+
91
+ export function findEnforcedRow(input: { rows: TranscriptRow[]; error: string; lastAssistantMessage: string | undefined; now: number }): TranscriptRow | null {
92
+ const { rows, error, lastAssistantMessage, now } = input;
91
93
  for (let i = rows.length - 1; i >= 0; i--) {
92
94
  const row = rows[i]!;
93
- if (row.isApiErrorMessage !== true || row.error !== "rate_limit") continue;
95
+ if (row.isApiErrorMessage !== true || row.error !== error) continue;
94
96
  const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
95
97
  const byContent = lastAssistantMessage != null && lastAssistantMessage !== "" && transcriptRowText(row) === lastAssistantMessage;
96
98
  const byRecency = Number.isFinite(ts) && Math.abs(now - ts) <= ROW_RECENCY_MS;
@@ -103,7 +105,8 @@ export type EnforcedClass =
103
105
  | { kind: "session"; resetsAt: number | null }
104
106
  | { kind: "weekly"; resetsAt: number | null }
105
107
  | { kind: "model"; family: string; resetsAt: number | null }
106
- | { kind: "credits"; family: string; resetsAt: null };
108
+ | { kind: "credits"; family: string; resetsAt: null }
109
+ | { kind: "org"; resetsAt: null };
107
110
 
108
111
  const ErrorBodySchema = z.looseObject({
109
112
  error: z.looseObject({ type: z.string().optional(), details: z.looseObject({ error_code: z.string().optional() }).optional() }).optional(),
@@ -120,6 +123,7 @@ export function parseErrorBody(errorDetails: string | undefined): z.infer<typeof
120
123
  }
121
124
 
122
125
  export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]): EnforcedClass | null {
126
+ if (row.error === "oauth_org_not_allowed") return { kind: "org", resetsAt: null };
123
127
  if (row.apiError === "model_requires_usage_credits") return { kind: "credits", family: CREDITS_FAMILY, resetsAt: null };
124
128
  const q = row.quotaLimits;
125
129
  if (!q) return null;