tokenmaxxing 1.61.0 → 1.62.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.61.0",
3
+ "version": "1.62.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",
@@ -0,0 +1,62 @@
1
+ import { observeCodex } from "../lib/codex.ts";
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, writePresence } from "../lib/presence.ts";
7
+ import { isExhausted, pickBest, thresholdBars, type PickCtx } from "../lib/picker.ts";
8
+ import { loadAccounts, loadConfig } from "../lib/state.ts";
9
+ import { emitError } from "./render.ts";
10
+
11
+ export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Promise<number> {
12
+ const pid = pidRaw != null && /^(0|[1-9][0-9]*)$/.test(pidRaw) ? Number(pidRaw) : NaN;
13
+ if (!Number.isSafeInteger(pid) || pid < 2 || extra.length > 0) {
14
+ emitError({ message: "usage: tokenmaxxing seat --codex <pid> - print the CODEX_HOME of one pooled account, reserved until <pid> exits" });
15
+ return 2;
16
+ }
17
+ const now = Date.now();
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: PickCtx = { now, thresholds: thresholdBars(loadConfig()), currentId: null, families: null, seats: null };
27
+ const idx = loadAccounts(codexPool);
28
+ const living = livingPresences(codexPaths.presenceDir);
29
+ const held = living.find((p) => p.id === seatId);
30
+ const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
31
+ if (heldAccount) {
32
+ if (heldAccount.needsReauth === true) {
33
+ return { denied: "the account this pid holds needs reauthentication - run `tokenmaxxing auth --codex` and borrow again" };
34
+ }
35
+ if (!codexStoreUsable(heldAccount.id)) {
36
+ return { denied: "the account this pid holds has no usable credential in its store - refusing to hand back a credential-less seat" };
37
+ }
38
+ return { store: ensureCodexStoreHome(heldAccount.id), id: heldAccount.id, reused: true };
39
+ }
40
+ const present = new Map<string, number>();
41
+ for (const p of living) present.set(p.accountId, (present.get(p.accountId) ?? 0) + 1);
42
+ const usable = idx.accounts.filter(
43
+ (a) => a.needsReauth !== true && !isExhausted(a, ctx) && !present.has(a.id) && codexStoreUsable(a.id)
44
+ );
45
+ const picked = pickBest(usable, ctx);
46
+ if (!picked) return null;
47
+ const store = ensureCodexStoreHome(picked.id);
48
+ writePresence({ dir: codexPaths.presenceDir, id: seatId, accountId: picked.id, pid });
49
+ return { store, id: picked.id, reused: false };
50
+ });
51
+ if (!granted) {
52
+ 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" });
53
+ return 1;
54
+ }
55
+ if ("denied" in granted) {
56
+ emitError({ message: granted.denied });
57
+ return 1;
58
+ }
59
+ log("seat.grant", { account: granted.id.slice(0, 8), pid, reused: granted.reused });
60
+ console.log(granted.store);
61
+ return 0;
62
+ }
@@ -9,6 +9,7 @@ import { resolveRealCodex } from "../lib/codexbin.ts";
9
9
  import { compactCodexThread } from "../lib/compact.ts";
10
10
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
11
11
  import { isExhausted } from "../lib/picker.ts";
12
+ import { livingPresences } from "../lib/presence.ts";
12
13
  import { readStdin } from "../lib/proc.ts";
13
14
  import { loadAccounts, loadConfig } from "../lib/state.ts";
14
15
  import { CODEX_SUPERVISOR_ID_ENV } from "./codexsupervisor.ts";
@@ -39,6 +40,12 @@ async function handleCodexStop(rawStdin: string): Promise<void> {
39
40
  log("codexstop.unsupervised_skip", {});
40
41
  return;
41
42
  }
43
+ const own = livingPresences(codexPaths.presenceDir).find((p) => p.id === supervisorId);
44
+ const seat = codex.liveId();
45
+ if (!own || own.accountId !== seat) {
46
+ log("codexstop.foreign_seat", { supervisorId: supervisorId.slice(0, 8), seat: seat?.slice(0, 8) ?? null });
47
+ return;
48
+ }
42
49
  const now = Date.now();
43
50
  await compactBeforeMove({ sessionId, now });
44
51
  const decision = await evaluateAndMaybeSwap(codex, now);
@@ -5,13 +5,14 @@ import { codexPaths, codexPool } from "../lib/paths.ts";
5
5
  import { withLock } from "../lib/lock.ts";
6
6
  import { UNMANAGED_ENV, WRAP_DEPTH_ENV, wrapDepth } from "../lib/claudebin.ts";
7
7
  import { resolveRealCodex } from "../lib/codexbin.ts";
8
- import { ensureCodexStoreHome } from "../lib/codexauth.ts";
9
- import { pickCodexSeat } from "../lib/codex.ts";
10
- import { clearPresence } from "../lib/presence.ts";
8
+ import { codexStoreUsable, ensureCodexStoreHome } from "../lib/codexauth.ts";
9
+ import { codexPickCtx, pickCodexSeat } from "../lib/codex.ts";
10
+ import { clearPresence, livingPresences } from "../lib/presence.ts";
11
+ import { isExhausted } from "../lib/picker.ts";
11
12
  import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
12
13
  import { saveTermios } from "../lib/tty.ts";
13
14
  import { loadAccounts } from "../lib/state.ts";
14
- import { CodexRespawnMarkerSchema } from "../lib/types.ts";
15
+ import { CodexRespawnMarkerSchema, type Account } from "../lib/types.ts";
15
16
  import { errorMessage, log } from "../lib/log.ts";
16
17
 
17
18
  export const CODEX_SUPERVISOR_ID_ENV = "TOKENMAXXING_CODEX_SUPERVISOR_ID";
@@ -55,6 +56,20 @@ export function shouldManageCodex(input: { argv: string[] }): boolean {
55
56
  return firstPositional === null || !NONINTERACTIVE_SUBCMDS.has(firstPositional);
56
57
  }
57
58
 
59
+ function validWanted(input: { wanted: string | null; now: number; supervisorId: string }): Account | null {
60
+ if (input.wanted == null) return null;
61
+ const a = loadAccounts(codexPool).accounts.find((x) => x.id === input.wanted);
62
+ if (!a || a.needsReauth === true) return null;
63
+ if (isExhausted(a, codexPickCtx(input.now, input.wanted))) return null;
64
+ if (!codexStoreUsable(a.id)) return null;
65
+ const foreign = livingPresences(codexPaths.presenceDir).some((p) => p.accountId === a.id && p.id !== input.supervisorId);
66
+ if (foreign) {
67
+ log("codexsupervisor.wanted_present", { account: a.id.slice(0, 8) });
68
+ return null;
69
+ }
70
+ return a;
71
+ }
72
+
58
73
  export async function runCodexSupervisor(input: { argv: string[] }): Promise<number> {
59
74
  const { argv } = input;
60
75
  if (loopGuardTripped("codex")) return 1;
@@ -83,8 +98,9 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
83
98
  if (existsSync(marker)) rmSync(marker, { force: true });
84
99
 
85
100
  const launchedAt = Date.now();
86
- const { child } = await withLock(codexPool.lockFile, async () => {
87
- const picked = (wanted == null ? null : (loadAccounts(codexPool).accounts.find((a) => a.id === wanted) ?? null)) ?? pickCodexSeat(launchedAt, wanted);
101
+ const { child, seat } = await withLock(codexPool.lockFile, async () => {
102
+ clearPresence({ dir: codexPaths.presenceDir, id: supervisorId });
103
+ const picked = validWanted({ wanted, now: launchedAt, supervisorId }) ?? pickCodexSeat(launchedAt);
88
104
  log("codexsupervisor.launch", { supervisorId: supervisorId.slice(0, 8), respawns, seat: picked?.id.slice(0, 8) ?? null, args: launchArgs.join(" ") });
89
105
  const store = picked ? ensureCodexStoreHome(picked.id) : undefined;
90
106
  const spawned = Bun.spawn([real, ...launchArgs], {
@@ -110,6 +126,9 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
110
126
  }
111
127
  return { child: spawned, seat: picked };
112
128
  });
129
+ if (respawns > 0) {
130
+ process.stdout.write(`\n\x1b[36m↻ tokenmaxxing: switched codex to ${seat?.label ?? "the ambient codex login"} - resuming...\x1b[0m\n`);
131
+ }
113
132
 
114
133
  await raceMarkerOrExit({
115
134
  child,
@@ -125,8 +144,6 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
125
144
  if (payload) {
126
145
  rmSync(marker, { force: true });
127
146
  respawns++;
128
- const label = loadAccounts(codexPool).accounts.find((a) => a.id === payload.accountId)?.label ?? payload.accountId.slice(0, 8);
129
- process.stdout.write(`\n\x1b[36m↻ tokenmaxxing: switched codex to ${label} - resuming...\x1b[0m\n`);
130
147
  wanted = payload.accountId;
131
148
  launchArgs = payload.sessionId ? ["resume", payload.sessionId] : ["resume", "--last"];
132
149
  continue;
package/src/lib/codex.ts CHANGED
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { writeFileAtomic } from "./atomic.ts";
4
4
  import { MAX_WRAP_DEPTH, WRAP_DEPTH_ENV } from "./claudebin.ts";
5
- import { codexIdentityOf, deleteCodexStoreAuth, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
5
+ import { codexIdentityOf, codexStoreUsable, deleteCodexStoreAuth, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
6
6
  import { resolveRealCodex, verifyRealCodex } from "./codexbin.ts";
7
7
  import { CodexInvalidGrantError, CodexRefreshFailedError, refreshCodexAuth } from "./codexoauth.ts";
8
8
  import { seatCounts } from "./presence.ts";
@@ -42,7 +42,7 @@ function applyUsage(account: Account, usage: CodexUsage, at: number): void {
42
42
 
43
43
  type CodexReadOutcome = { ok: true; usage: CodexUsage; at: number } | { ok: false; reason: string; deadGrant: boolean };
44
44
 
45
- async function readCodexUsage(account: Account, now: number): Promise<CodexReadOutcome> {
45
+ async function readCodexUsage(account: Account, now: number, refresh: boolean): Promise<CodexReadOutcome> {
46
46
  let auth: CodexAuthJson | null;
47
47
  try {
48
48
  auth = readCodexStoreAuth(account.id);
@@ -52,6 +52,9 @@ async function readCodexUsage(account: Account, now: number): Promise<CodexReadO
52
52
  if (!auth) return { ok: false, reason: "no credential in this account's store - run `tokenmaxxing auth --codex`", deadGrant: false };
53
53
  try {
54
54
  if (isCodexAccessExpiring({ auth, now })) {
55
+ if (!refresh) {
56
+ return { ok: false, reason: "stored access token is expiring and this read never refreshes a store", deadGrant: false };
57
+ }
55
58
  if (seatCounts(codexPaths.presenceDir).has(account.id)) {
56
59
  return { ok: false, reason: "running in a live codex session (store refresh unsafe)", deadGrant: false };
57
60
  }
@@ -67,9 +70,9 @@ async function readCodexUsage(account: Account, now: number): Promise<CodexReadO
67
70
  }
68
71
  }
69
72
 
70
- async function observeLive(account: Account, cfg: Config, now: number, opts: { probe: boolean }): Promise<Observation | null> {
73
+ export async function observeCodex(account: Account, cfg: Config, now: number, opts: { probe: boolean; refresh: boolean }): Promise<Observation | null> {
71
74
  if (opts.probe && (account.lastUsageAt == null || now - account.lastUsageAt > cfg.policy.usagePollTtlMs)) {
72
- const outcome = await readCodexUsage(account, now);
75
+ const outcome = await readCodexUsage(account, now, opts.refresh);
73
76
  await withLock(codexPool.lockFile, () => {
74
77
  const idx = loadAccounts(codexPool);
75
78
  const a = idx.accounts.find((x) => x.id === account.id);
@@ -92,7 +95,7 @@ async function samplePool(accounts: Account[], _liveId: string | null, now: numb
92
95
  const reports = new Map<string, SampleReport>();
93
96
  await Promise.all(
94
97
  accounts.map(async (account) => {
95
- const outcome = await readCodexUsage(account, now);
98
+ const outcome = await readCodexUsage(account, now, true);
96
99
  if (outcome.ok) {
97
100
  applyUsage(account, outcome.usage, outcome.at);
98
101
  reports.set(account.id, { ok: true, source: "probe" });
@@ -216,17 +219,11 @@ export function codexPickCtx(now: number, currentId: string | null): PickCtx {
216
219
  return { now, thresholds: thresholdBars(loadConfig()), currentId, families: null, seats: null };
217
220
  }
218
221
 
219
- export function pickCodexSeat(now: number, wantedId: string | null = null): Account | null {
220
- const cfg = loadConfig();
221
- const idx = loadAccounts(codexPool);
222
- const ctx: PickCtx = { now, thresholds: thresholdBars(cfg), currentId: null, families: null, seats: null };
223
- if (wantedId != null) {
224
- const wanted = idx.accounts.find((a) => a.id === wantedId && a.needsReauth !== true && !isExhausted(a, { ...ctx, currentId: wantedId }));
225
- if (wanted) return wanted;
226
- }
222
+ export function pickCodexSeat(now: number): Account | null {
223
+ const ctx = codexPickCtx(now, null);
227
224
  const present = seatCounts(codexPaths.presenceDir);
228
- const usable = idx.accounts.filter((a) => a.needsReauth !== true && !isExhausted(a, ctx) && !present.has(a.id));
229
- return pickBest(usable, ctx) ?? pickEarliestReset(idx.accounts.filter((a) => a.needsReauth !== true && !present.has(a.id)), ctx)?.account ?? null;
225
+ const open = loadAccounts(codexPool).accounts.filter((a) => a.needsReauth !== true && !present.has(a.id) && codexStoreUsable(a.id));
226
+ return pickBest(open.filter((a) => !isExhausted(a, ctx)), ctx) ?? pickEarliestReset(open, ctx)?.account ?? null;
230
227
  }
231
228
 
232
229
  export const codex: Provider = {
@@ -239,19 +236,13 @@ export const codex: Provider = {
239
236
  liveId,
240
237
  presence,
241
238
  gatedFamilies: () => null,
242
- observeLive,
239
+ observeLive: (account, cfg, now, opts) => observeCodex(account, cfg, now, { probe: opts.probe, refresh: true }),
243
240
  samplePool,
244
241
  mergeWindows: (next) => next,
245
242
  swap: prepareMove,
246
243
  classifySwapError: (e) => (e instanceof CodexInvalidGrantError ? "dead-grant" : e instanceof StoreUnusableError ? "skip" : "fatal"),
247
244
  removeCredentials: async (a) => deleteCodexStoreAuth(a.id),
248
- storeUsable: async (a) => {
249
- try {
250
- return readCodexStoreAuth(a.id) != null;
251
- } catch {
252
- return false;
253
- }
254
- },
245
+ storeUsable: async (a) => codexStoreUsable(a.id),
255
246
  login,
256
247
  importLive,
257
248
  preflight,
@@ -23,6 +23,14 @@ export function readCodexStoreAuth(accountId: string): CodexAuthJson | null {
23
23
  return readCodexAuthAt({ path: codexAuthJsonFor(accountId) });
24
24
  }
25
25
 
26
+ export function codexStoreUsable(accountId: string): boolean {
27
+ try {
28
+ return readCodexStoreAuth(accountId) != null;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
26
34
  export function writeCodexStoreAuth(accountId: string, auth: CodexAuthJson): void {
27
35
  mkdirSync(codexStoreDirFor(accountId), { recursive: true });
28
36
  writeFileAtomic(codexAuthJsonFor(accountId), JSON.stringify(CodexAuthJsonSchema.parse(auth), null, 2), 0o600);
package/src/main.ts CHANGED
@@ -21,6 +21,7 @@ import { cmdRm } from "./cli/rm.ts";
21
21
  import { cmdRename } from "./cli/rename.ts";
22
22
  import { cmdCheck } from "./cli/check.ts";
23
23
  import { cmdConfig } from "./cli/config.ts";
24
+ import { cmdSeat } from "./cli/seat.ts";
24
25
  import { cmdSetupToken } from "./cli/setuptoken.ts";
25
26
  import { cmdCloudRun } from "./cli/cloudrun.ts";
26
27
  import { cmdCursorInit } from "./cli/cursorinit.ts";
@@ -36,7 +37,7 @@ const CODEX_FLAG = "--codex";
36
37
  const GROK_FLAG = "--grok";
37
38
  const OPENCODE_GO_FLAG = "--opencode-go";
38
39
  const JSON_COMMANDS = new Set(["status", "config", "check"]);
39
- const CODEX_COMMANDS = new Set(["init", "add", "auth", "rm", "rename"]);
40
+ const CODEX_COMMANDS = new Set(["init", "add", "auth", "rm", "rename", "seat"]);
40
41
  const STATUS_ONLY_COMMANDS = new Set(["init", "add", "auth", "rm", "rename"]);
41
42
 
42
43
  function printHelp(): void {
@@ -56,6 +57,7 @@ function printHelp(): void {
56
57
  ${c.cyan("tokenmaxxing doctor")} verify the install is intact
57
58
  ${c.cyan("tokenmaxxing rename")} [--codex | --grok | --opencode-go] <sel> <label>
58
59
  ${c.cyan("tokenmaxxing rm")} [--codex | --grok | --opencode-go] <sel>
60
+ ${c.cyan("tokenmaxxing seat --codex")} <pid> borrow one pooled codex account for an unattended consumer (plugin, script): prints the CODEX_HOME to set, reserved until <pid> exits; exit 1 = none usable, fall back to the ambient login
59
61
  ${c.cyan("tokenmaxxing uninstall")} [--yes] print the targets, then remove supervisor + settings entries (refused without ${c.cyan("--yes")} when HOME is the login home)
60
62
  ${c.cyan("tokenmaxxing setup-token")} [--print | rm <label|uuid>] Cursor Cloud only: mint one \`claude setup-token\` per pooled account (browser sign-in each) and print the TOKENMAXXING_TOKENS secret value; ${c.cyan("--print")} prints the stored set, ${c.cyan("rm")} drops one
61
63
  ${c.cyan("tokenmaxxing cursor init")} [dir] write the Claude relay subagent (.cursor/agents/claude.md) and .cursor/environment.json into a repo
@@ -151,6 +153,13 @@ async function main(): Promise<number> {
151
153
  case "doctor": return cmdDoctor();
152
154
  case "rm": return cmdRm(provider, args[1]);
153
155
  case "rename": return cmdRename(provider, args.slice(1));
156
+ case "seat": {
157
+ if (provider !== codex) {
158
+ emitError({ message: `seat borrows a pooled codex account - pass ${CODEX_FLAG} (usage: tokenmaxxing seat ${CODEX_FLAG} <pid>)` });
159
+ return 2;
160
+ }
161
+ return cmdSeat(args[1], args.slice(2));
162
+ }
154
163
  case "setup-token": return cmdSetupToken(args.slice(1));
155
164
  case "cursor": {
156
165
  if (args[1] === "init") return cmdCursorInit(args.slice(2));