tokenmaxxing 1.91.0 → 1.92.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/cli/seat.ts +2 -41
- package/src/entries/codexsupervisor.ts +54 -11
- package/src/lib/codex.ts +41 -2
- package/src/lib/supervise.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.92.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",
|
package/src/cli/seat.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { codexStoreUsable, ensureCodexStoreHome } from "../lib/codexauth.ts";
|
|
3
|
-
import { withLock } from "../lib/lock.ts";
|
|
4
|
-
import { log } from "../lib/log.ts";
|
|
5
|
-
import { codexPaths, codexPool } from "../lib/paths.ts";
|
|
6
|
-
import { livingPresences, seatCounts, writePresence } from "../lib/presence.ts";
|
|
7
|
-
import { isExhausted, pickBest } from "../lib/picker.ts";
|
|
8
|
-
import { loadAccounts, loadConfig } from "../lib/state.ts";
|
|
1
|
+
import { borrowCodexSeat } from "../lib/codex.ts";
|
|
9
2
|
import { emitError } from "./render.ts";
|
|
10
3
|
|
|
11
4
|
export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Promise<number> {
|
|
@@ -14,38 +7,7 @@ export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Prom
|
|
|
14
7
|
emitError({ message: "usage: tokenmaxxing seat --codex <pid> - print the CODEX_HOME of one pooled account, reserved until <pid> exits" });
|
|
15
8
|
return 2;
|
|
16
9
|
}
|
|
17
|
-
const
|
|
18
|
-
const seatId = `seat-${pid}`;
|
|
19
|
-
const cfg = loadConfig();
|
|
20
|
-
await Promise.all(
|
|
21
|
-
loadAccounts(codexPool)
|
|
22
|
-
.accounts.filter((a) => a.needsReauth !== true && codexStoreUsable(a.id))
|
|
23
|
-
.map((a) => observeCodex(a, cfg, now, { probe: true, refresh: false })),
|
|
24
|
-
);
|
|
25
|
-
const granted = await withLock(codexPool.lockFile, (): { store: string; id: string; reused: boolean } | { denied: string } | null => {
|
|
26
|
-
const ctx = codexPickCtx(now, null);
|
|
27
|
-
const idx = loadAccounts(codexPool);
|
|
28
|
-
const held = livingPresences(codexPaths.presenceDir).find((p) => p.id === seatId);
|
|
29
|
-
const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
|
|
30
|
-
if (heldAccount) {
|
|
31
|
-
if (heldAccount.needsReauth === true) {
|
|
32
|
-
return { denied: "the account this pid holds needs reauthentication - run `tokenmaxxing auth --codex` and borrow again" };
|
|
33
|
-
}
|
|
34
|
-
if (!codexStoreUsable(heldAccount.id)) {
|
|
35
|
-
return { denied: "the account this pid holds has no usable credential in its store - refusing to hand back a credential-less seat" };
|
|
36
|
-
}
|
|
37
|
-
return { store: ensureCodexStoreHome(heldAccount.id), id: heldAccount.id, reused: true };
|
|
38
|
-
}
|
|
39
|
-
const present = seatCounts(codexPaths.presenceDir);
|
|
40
|
-
const usable = idx.accounts.filter(
|
|
41
|
-
(a) => a.needsReauth !== true && !isExhausted(a, ctx) && !present.has(a.id) && codexStoreUsable(a.id)
|
|
42
|
-
);
|
|
43
|
-
const picked = pickBest(usable, ctx);
|
|
44
|
-
if (!picked) return null;
|
|
45
|
-
const store = ensureCodexStoreHome(picked.id);
|
|
46
|
-
writePresence({ dir: codexPaths.presenceDir, id: seatId, accountId: picked.id, pid });
|
|
47
|
-
return { store, id: picked.id, reused: false };
|
|
48
|
-
});
|
|
10
|
+
const granted = await borrowCodexSeat(pid);
|
|
49
11
|
if (!granted) {
|
|
50
12
|
emitError({ message: "no usable codex account (pool empty, every account live in another session, or every account at a limit) - use the ambient codex login" });
|
|
51
13
|
return 1;
|
|
@@ -54,7 +16,6 @@ export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Prom
|
|
|
54
16
|
emitError({ message: granted.denied });
|
|
55
17
|
return 1;
|
|
56
18
|
}
|
|
57
|
-
log("seat.grant", { account: granted.id.slice(0, 8), pid, reused: granted.reused });
|
|
58
19
|
console.log(granted.store);
|
|
59
20
|
return 0;
|
|
60
21
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Subprocess } from "bun";
|
|
1
2
|
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { z } from "zod";
|
|
@@ -5,7 +6,7 @@ import { codexPaths, codexPool } from "../lib/paths.ts";
|
|
|
5
6
|
import { withLock } from "../lib/lock.ts";
|
|
6
7
|
import { CODEX_BIN, UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealBin, wrapDepth } from "../lib/claudebin.ts";
|
|
7
8
|
import { codexStoreUsable, ensureCodexStoreHome } from "../lib/codexauth.ts";
|
|
8
|
-
import { codexPickCtx, pickCodexSeat } from "../lib/codex.ts";
|
|
9
|
+
import { borrowCodexSeat, codexPickCtx, pickCodexSeat } from "../lib/codex.ts";
|
|
9
10
|
import { clearPresence, livingPresences } from "../lib/presence.ts";
|
|
10
11
|
import { isExhausted } from "../lib/picker.ts";
|
|
11
12
|
import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
|
|
@@ -17,11 +18,13 @@ import { errorMessage, log } from "../lib/log.ts";
|
|
|
17
18
|
export const CODEX_SUPERVISOR_ID_ENV = "TOKENMAXXING_CODEX_SUPERVISOR_ID";
|
|
18
19
|
|
|
19
20
|
const NONINTERACTIVE_SUBCMDS = new Set([
|
|
20
|
-
"exec", "review", "login", "logout", "mcp", "plugin", "mcp-server", "app-server",
|
|
21
|
+
"exec", "e", "review", "login", "logout", "mcp", "plugin", "mcp-server", "app-server",
|
|
21
22
|
"remote-control", "app", "completion", "update", "doctor", "sandbox", "debug",
|
|
22
23
|
"apply", "archive", "delete", "unarchive", "cloud", "exec-server", "features", "help",
|
|
23
24
|
]);
|
|
24
25
|
|
|
26
|
+
const BORROWING_SUBCMDS = new Set(["exec", "e", "review", "app-server"]);
|
|
27
|
+
|
|
25
28
|
const PASSTHROUGH_FLAGS = new Set(["--version", "-V", "--help", "-h"]);
|
|
26
29
|
|
|
27
30
|
function readCodexMarker(marker: string): z.infer<typeof CodexRespawnMarkerSchema> {
|
|
@@ -40,19 +43,32 @@ const VALUE_TAKING_ROOT_FLAGS = new Set([
|
|
|
40
43
|
"-s", "--sandbox", "-a", "--ask-for-approval", "-C", "--cd", "--add-dir", "--enable",
|
|
41
44
|
]);
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
let
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (PASSTHROUGH_FLAGS.has(arg)) return
|
|
46
|
+
function scanCodexArgs(argv: string[]): { help: boolean; positionals: string[] } {
|
|
47
|
+
const positionals: string[] = [];
|
|
48
|
+
for (let i = 0; i < argv.length; i++) {
|
|
49
|
+
const arg = argv[i]!;
|
|
50
|
+
if (arg === "--") break;
|
|
51
|
+
if (PASSTHROUGH_FLAGS.has(arg)) return { help: true, positionals };
|
|
49
52
|
if (VALUE_TAKING_ROOT_FLAGS.has(arg)) {
|
|
50
53
|
i++;
|
|
51
54
|
continue;
|
|
52
55
|
}
|
|
53
|
-
if (!arg.startsWith("-")
|
|
56
|
+
if (!arg.startsWith("-")) positionals.push(arg);
|
|
54
57
|
}
|
|
55
|
-
return
|
|
58
|
+
return { help: false, positionals };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function shouldManageCodex(input: { argv: string[] }): boolean {
|
|
62
|
+
if (process.env.TOKENMAXXING_PROBE) return false;
|
|
63
|
+
const { help, positionals: [sub] } = scanCodexArgs(input.argv);
|
|
64
|
+
return !help && (sub === undefined || !NONINTERACTIVE_SUBCMDS.has(sub));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function borrowsCodexSeat(input: { argv: string[] }): boolean {
|
|
68
|
+
if (process.env.TOKENMAXXING_PROBE || process.env[UNMANAGED_ENV] || process.env.CODEX_HOME) return false;
|
|
69
|
+
const { help, positionals: [sub, ...rest] } = scanCodexArgs(input.argv);
|
|
70
|
+
if (help || sub === undefined || !BORROWING_SUBCMDS.has(sub)) return false;
|
|
71
|
+
return sub !== "app-server" || rest.length === 0;
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
function validWanted(input: { wanted: string | null; now: number; supervisorId: string }): Account | null {
|
|
@@ -79,7 +95,34 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
|
|
|
79
95
|
if (!shouldManageCodex({ argv }) || process.env[UNMANAGED_ENV]) {
|
|
80
96
|
const passthroughEnv: Record<string, string | undefined> = { ...childEnv };
|
|
81
97
|
delete passthroughEnv[CODEX_SUPERVISOR_ID_ENV];
|
|
82
|
-
|
|
98
|
+
let child: Subprocess | null = null;
|
|
99
|
+
process.on("SIGTERM", () => {
|
|
100
|
+
if (child) child.kill("SIGTERM");
|
|
101
|
+
else process.exit(143);
|
|
102
|
+
});
|
|
103
|
+
const granted = borrowsCodexSeat({ argv }) ? await borrowCodexSeat(process.pid) : undefined;
|
|
104
|
+
const seat = granted && !("denied" in granted) ? granted : null;
|
|
105
|
+
if (seat) passthroughEnv.CODEX_HOME = seat.store;
|
|
106
|
+
else if (granted !== undefined) log("codexsupervisor.borrow_none", { reason: granted && "denied" in granted ? granted.denied : "no usable account" });
|
|
107
|
+
return runPassthrough({
|
|
108
|
+
real,
|
|
109
|
+
argv,
|
|
110
|
+
env: passthroughEnv,
|
|
111
|
+
onSpawn: (p) => {
|
|
112
|
+
child = p;
|
|
113
|
+
return seat
|
|
114
|
+
? recordPresenceOrStop({
|
|
115
|
+
child: p,
|
|
116
|
+
dir: codexPaths.presenceDir,
|
|
117
|
+
id: `seat-${process.pid}`,
|
|
118
|
+
accountId: seat.id,
|
|
119
|
+
event: "codexsupervisor.borrow_presence_failed",
|
|
120
|
+
message: "could not key the borrowed codex seat to its child - refusing to run on an unreserved store",
|
|
121
|
+
savedTermios: null,
|
|
122
|
+
})
|
|
123
|
+
: undefined;
|
|
124
|
+
},
|
|
125
|
+
});
|
|
83
126
|
}
|
|
84
127
|
|
|
85
128
|
const supervisorId = crypto.randomUUID();
|
package/src/lib/codex.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { writeFileAtomic } from "./atomic.ts";
|
|
4
4
|
import { CODEX_BIN, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealBin, verifyRealBin } from "./claudebin.ts";
|
|
5
|
-
import { codexIdentityOf, codexStoreUsable, deleteCodexStoreAuth, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
|
|
5
|
+
import { codexIdentityOf, codexStoreUsable, deleteCodexStoreAuth, ensureCodexStoreHome, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
|
|
6
6
|
import { CodexInvalidGrantError, CodexRefreshFailedError, refreshCodexAuth } from "./codexoauth.ts";
|
|
7
7
|
import { deletePiStore } from "./piauth.ts";
|
|
8
|
-
import { livingPresences, seatCounts } from "./presence.ts";
|
|
8
|
+
import { livingPresences, seatCounts, writePresence } from "./presence.ts";
|
|
9
9
|
import { CodexUsageReadError, codexLimitLabel, fetchCodexUsage } from "./codexusage.ts";
|
|
10
10
|
import { codexSupervisorLink, ensurePathInRc, installCodexSupervisor, managedShellRcSkipLines, shellRcPath } from "./install.ts";
|
|
11
11
|
import { withLock } from "./lock.ts";
|
|
@@ -236,6 +236,45 @@ export function pickCodexSeat(now: number, eligible: (a: Account) => boolean = (
|
|
|
236
236
|
return pickBest(open.filter((a) => !isExhausted(a, ctx)), ctx) ?? pickEarliestReset(open, ctx)?.account ?? null;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
export type CodexBorrow = { store: string; id: string; reused: boolean } | { denied: string } | null;
|
|
240
|
+
|
|
241
|
+
export async function borrowCodexSeat(pid: number): Promise<CodexBorrow> {
|
|
242
|
+
const now = Date.now();
|
|
243
|
+
const seatId = `seat-${pid}`;
|
|
244
|
+
const cfg = loadConfig();
|
|
245
|
+
await Promise.all(
|
|
246
|
+
loadAccounts(codexPool)
|
|
247
|
+
.accounts.filter((a) => a.needsReauth !== true && codexStoreUsable(a.id))
|
|
248
|
+
.map((a) => observeCodex(a, cfg, now, { probe: true, refresh: false })),
|
|
249
|
+
);
|
|
250
|
+
const granted = await withLock(codexPool.lockFile, (): CodexBorrow => {
|
|
251
|
+
const ctx = codexPickCtx(now, null);
|
|
252
|
+
const idx = loadAccounts(codexPool);
|
|
253
|
+
const held = livingPresences(codexPaths.presenceDir).find((p) => p.id === seatId);
|
|
254
|
+
const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
|
|
255
|
+
if (heldAccount) {
|
|
256
|
+
if (heldAccount.needsReauth === true) {
|
|
257
|
+
return { denied: "the account this pid holds needs reauthentication - run `tokenmaxxing auth --codex` and borrow again" };
|
|
258
|
+
}
|
|
259
|
+
if (!codexStoreUsable(heldAccount.id)) {
|
|
260
|
+
return { denied: "the account this pid holds has no usable credential in its store - refusing to hand back a credential-less seat" };
|
|
261
|
+
}
|
|
262
|
+
return { store: ensureCodexStoreHome(heldAccount.id), id: heldAccount.id, reused: true };
|
|
263
|
+
}
|
|
264
|
+
const present = seatCounts(codexPaths.presenceDir);
|
|
265
|
+
const usable = idx.accounts.filter(
|
|
266
|
+
(a) => a.needsReauth !== true && !isExhausted(a, ctx) && !present.has(a.id) && codexStoreUsable(a.id)
|
|
267
|
+
);
|
|
268
|
+
const picked = pickBest(usable, ctx);
|
|
269
|
+
if (!picked) return null;
|
|
270
|
+
const store = ensureCodexStoreHome(picked.id);
|
|
271
|
+
writePresence({ dir: codexPaths.presenceDir, id: seatId, accountId: picked.id, pid });
|
|
272
|
+
return { store, id: picked.id, reused: false };
|
|
273
|
+
});
|
|
274
|
+
if (granted && !("denied" in granted)) log("seat.grant", { account: granted.id.slice(0, 8), pid, reused: granted.reused });
|
|
275
|
+
return granted;
|
|
276
|
+
}
|
|
277
|
+
|
|
239
278
|
export const codex: Provider = {
|
|
240
279
|
name: "codex",
|
|
241
280
|
flag: " --codex",
|
package/src/lib/supervise.ts
CHANGED
|
@@ -75,9 +75,9 @@ export function loopGuardTripped(product: WrappedProduct): boolean {
|
|
|
75
75
|
return false;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
export async function runPassthrough(input: { real: string; argv: string[]; env: Record<string, string | undefined>; onSpawn?: (child: Subprocess) => void }): Promise<number> {
|
|
78
|
+
export async function runPassthrough(input: { real: string; argv: string[]; env: Record<string, string | undefined>; onSpawn?: (child: Subprocess) => void | Promise<void> }): Promise<number> {
|
|
79
79
|
const p = Bun.spawn([input.real, ...input.argv], { stdin: "inherit", stdout: "inherit", stderr: "inherit", env: input.env });
|
|
80
|
-
input.onSpawn?.(p);
|
|
80
|
+
await input.onSpawn?.(p);
|
|
81
81
|
await p.exited;
|
|
82
82
|
return exitStatus(p);
|
|
83
83
|
}
|