tokenmaxxing 1.102.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 +1 -1
- package/src/entries/codexstophook.ts +2 -2
- package/src/entries/codexsupervisor.ts +3 -3
- package/src/entries/supervisor.ts +4 -13
- package/src/lib/claude.ts +3 -3
- package/src/lib/grok.ts +8 -10
- package/src/lib/opencodego.ts +8 -9
- package/src/lib/sample.ts +3 -4
- package/src/lib/state.ts +14 -10
- package/src/lib/usage.ts +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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 =
|
|
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/grok.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import { writeFileAtomic } from "./atomic.ts";
|
|
@@ -6,9 +5,9 @@ import { fetchGrokUsage, GrokUsageReadError, type GrokUsage } from "./grokusage.
|
|
|
6
5
|
import { errorMessage } from "./log.ts";
|
|
7
6
|
import { grokAuthJsonFor, grokPaths, grokPool, grokSeatFromEnv, grokStoreDirFor } from "./paths.ts";
|
|
8
7
|
import type { Provider, SampleReport } from "./provider.ts";
|
|
9
|
-
import { loadAccounts, type Harvest } from "./state.ts";
|
|
8
|
+
import { loadAccounts, readJsonFile, type Harvest } from "./state.ts";
|
|
10
9
|
import { statusOnlyProvider, type AuthEntry } from "./statusonly.ts";
|
|
11
|
-
import { InstantSchema, type Account } from "./types.ts";
|
|
10
|
+
import { ErrnoSchema, InstantSchema, type Account } from "./types.ts";
|
|
12
11
|
import { c } from "../cli/render.ts";
|
|
13
12
|
|
|
14
13
|
const GrokAuthEntrySchema = z.looseObject({
|
|
@@ -37,16 +36,15 @@ function harvestOf(id: string, key: string, entry: GrokAuthEntry): Harvest {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
function readMap(path: string): [string, GrokAuthEntry][] {
|
|
40
|
-
let
|
|
39
|
+
let map: Record<string, unknown>;
|
|
41
40
|
try {
|
|
42
|
-
|
|
43
|
-
} catch {
|
|
44
|
-
return [];
|
|
41
|
+
map = readJsonFile(path, z.record(z.string(), z.unknown()));
|
|
42
|
+
} catch (e) {
|
|
43
|
+
if (ErrnoSchema.safeParse(e).data?.code === "ENOENT") return [];
|
|
44
|
+
throw e;
|
|
45
45
|
}
|
|
46
|
-
const map = z.record(z.string(), z.unknown()).safeParse(raw);
|
|
47
|
-
if (!map.success) return [];
|
|
48
46
|
const out: [string, GrokAuthEntry][] = [];
|
|
49
|
-
for (const [key, value] of Object.entries(map
|
|
47
|
+
for (const [key, value] of Object.entries(map)) {
|
|
50
48
|
const parsed = GrokAuthEntrySchema.safeParse(value);
|
|
51
49
|
if (!parsed.success) continue;
|
|
52
50
|
out.push([key, parsed.data]);
|
package/src/lib/opencodego.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import { writeFileAtomic } from "./atomic.ts";
|
|
5
4
|
import { env, HOME, opencodeGoAuthJsonFor, opencodeGoPaths, opencodeGoPool, opencodeGoStoreDirFor } from "./paths.ts";
|
|
6
5
|
import type { Provider } from "./provider.ts";
|
|
7
|
-
import { type Harvest } from "./state.ts";
|
|
6
|
+
import { readJsonFile, type Harvest } from "./state.ts";
|
|
8
7
|
import { statusOnlyProvider, type AuthEntry } from "./statusonly.ts";
|
|
8
|
+
import { ErrnoSchema } from "./types.ts";
|
|
9
9
|
import { c } from "../cli/render.ts";
|
|
10
10
|
|
|
11
11
|
const OpencodeAuthEntrySchema = z.looseObject({ type: z.string(), key: z.string().optional() });
|
|
@@ -35,15 +35,14 @@ function harvestOf(key: string): Harvest {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function readAuth(path: string): AuthEntry[] {
|
|
38
|
-
let
|
|
38
|
+
let map: z.infer<typeof OpencodeAuthFileSchema>;
|
|
39
39
|
try {
|
|
40
|
-
|
|
41
|
-
} catch {
|
|
42
|
-
return [];
|
|
40
|
+
map = readJsonFile(path, OpencodeAuthFileSchema);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (ErrnoSchema.safeParse(e).data?.code === "ENOENT") return [];
|
|
43
|
+
throw e;
|
|
43
44
|
}
|
|
44
|
-
const
|
|
45
|
-
if (!map.success) return [];
|
|
46
|
-
const entry = OpencodeAuthEntrySchema.safeParse(map.data["opencode-go"]);
|
|
45
|
+
const entry = OpencodeAuthEntrySchema.safeParse(map["opencode-go"]);
|
|
47
46
|
if (!entry.success || entry.data.type !== "api" || !entry.data.key) return [];
|
|
48
47
|
return [{ id: idOfKey(entry.data.key), usable: true, harvest: harvestOf(entry.data.key) }];
|
|
49
48
|
}
|
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,
|
|
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 =
|
|
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(
|
|
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
|
|
131
|
-
return
|
|
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
|
-
|
|
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;
|