tokenmaxxing 1.108.0 → 1.110.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 +6 -6
- package/src/entries/codexstophook.ts +1 -2
- package/src/entries/codexsupervisor.ts +1 -3
- package/src/entries/pisupervisor.ts +2 -2
- package/src/lib/claude.ts +1 -2
- package/src/lib/codex.ts +13 -6
- package/src/lib/opencodego.ts +4 -4
- package/src/lib/picker.ts +1 -0
- package/src/lib/presence.ts +2 -0
- package/src/main.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.110.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
|
@@ -6,18 +6,18 @@ import { emitError } from "./render.ts";
|
|
|
6
6
|
const POOLS = {
|
|
7
7
|
claude: {
|
|
8
8
|
borrow: borrowClaudeSeat,
|
|
9
|
-
usage: "usage: tokenmaxxing seat <pid> - print the CLAUDE_SECURESTORAGE_CONFIG_DIR of one pooled claude account, lent to <pid>
|
|
10
|
-
none: "no usable claude account (pool empty,
|
|
9
|
+
usage: "usage: tokenmaxxing seat <pid> - print the CLAUDE_SECURESTORAGE_CONFIG_DIR of one pooled claude account, lent to <pid> until it exits and shared with host sessions and other borrowers",
|
|
10
|
+
none: "no usable claude account (pool empty, or every account at a limit or flagged for reauthentication)",
|
|
11
11
|
},
|
|
12
12
|
codex: {
|
|
13
13
|
borrow: borrowCodexSeat,
|
|
14
|
-
usage: "usage: tokenmaxxing seat --codex <pid> - print the CODEX_HOME of one pooled account,
|
|
15
|
-
none: "no usable codex account (pool empty, every account
|
|
14
|
+
usage: "usage: tokenmaxxing seat --codex <pid> - print the CODEX_HOME of one pooled account, lent to <pid> until it exits and shared with codex sessions and other borrowers; codex processes that share one auth.json can lose a refresh race that signs the account out until `tokenmaxxing auth --codex` runs again",
|
|
15
|
+
none: "no usable codex account (pool empty, or every account at a limit, flagged for reauthentication, held by a pi session, or held by the supervised codex session this runs in) - use the ambient codex login",
|
|
16
16
|
},
|
|
17
17
|
"opencode-go": {
|
|
18
18
|
borrow: borrowOpencodeGoSeat,
|
|
19
|
-
usage: "usage: tokenmaxxing seat --opencode-go <pid> - print the store directory of one pooled opencode-go key, whose auth.json is an opencode auth file, lent to <pid>
|
|
20
|
-
none: "no usable opencode-go key (pool empty,
|
|
19
|
+
usage: "usage: tokenmaxxing seat --opencode-go <pid> - print the store directory of one pooled opencode-go key, whose auth.json is an opencode auth file, lent to <pid> until it exits and shared with other borrowers",
|
|
20
|
+
none: "no usable opencode-go key (pool empty, or no store holds a usable key)",
|
|
21
21
|
},
|
|
22
22
|
};
|
|
23
23
|
|
|
@@ -4,14 +4,13 @@ import type { z } from "zod";
|
|
|
4
4
|
import { codexPaths, optionalEnv } from "../lib/paths.ts";
|
|
5
5
|
import { writeFileAtomic } from "../lib/atomic.ts";
|
|
6
6
|
import { CODEX_BIN, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealBin } from "../lib/claudebin.ts";
|
|
7
|
-
import { codex, codexPickCtx } from "../lib/codex.ts";
|
|
7
|
+
import { CODEX_SUPERVISOR_ID_ENV, codex, codexPickCtx } from "../lib/codex.ts";
|
|
8
8
|
import { compactCodexThread } from "../lib/compact.ts";
|
|
9
9
|
import { evaluateAndMaybeSwap } from "../lib/decide.ts";
|
|
10
10
|
import { isExhausted } from "../lib/picker.ts";
|
|
11
11
|
import { livingPresences } from "../lib/presence.ts";
|
|
12
12
|
import { readStdin } from "../lib/proc.ts";
|
|
13
13
|
import { loadAccounts, loadConfig } from "../lib/state.ts";
|
|
14
|
-
import { CODEX_SUPERVISOR_ID_ENV } from "./codexsupervisor.ts";
|
|
15
14
|
import { CodexRespawnMarkerSchema, CodexStopStdinSchema, JsonTextSchema } from "../lib/types.ts";
|
|
16
15
|
import { errorMessage, log } from "../lib/log.ts";
|
|
17
16
|
|
|
@@ -7,7 +7,7 @@ import { codexPaths, codexPool } from "../lib/paths.ts";
|
|
|
7
7
|
import { withLock } from "../lib/lock.ts";
|
|
8
8
|
import { CODEX_BIN, UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealBin, wrapDepth } from "../lib/claudebin.ts";
|
|
9
9
|
import { codexStoreUsable, ensureCodexStoreHome } from "../lib/codexauth.ts";
|
|
10
|
-
import { borrowCodexSeat, codexPickCtx, pickCodexSeat } from "../lib/codex.ts";
|
|
10
|
+
import { CODEX_SUPERVISOR_ID_ENV, borrowCodexSeat, codexPickCtx, pickCodexSeat } from "../lib/codex.ts";
|
|
11
11
|
import { clearPresence, livingPresences } from "../lib/presence.ts";
|
|
12
12
|
import { isExhausted } from "../lib/picker.ts";
|
|
13
13
|
import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
|
|
@@ -16,8 +16,6 @@ import { loadAccounts, readJsonFile } from "../lib/state.ts";
|
|
|
16
16
|
import { CodexRespawnMarkerSchema, type Account } from "../lib/types.ts";
|
|
17
17
|
import { errorMessage, log } from "../lib/log.ts";
|
|
18
18
|
|
|
19
|
-
export const CODEX_SUPERVISOR_ID_ENV = "TOKENMAXXING_CODEX_SUPERVISOR_ID";
|
|
20
|
-
|
|
21
19
|
const NONINTERACTIVE_SUBCMDS = new Set([
|
|
22
20
|
"exec", "e", "review", "login", "logout", "mcp", "plugin", "mcp-server", "app-server",
|
|
23
21
|
"remote-control", "app", "completion", "update", "doctor", "sandbox", "debug",
|
|
@@ -12,7 +12,7 @@ import { claudePool, codexPaths, expandTilde, optionalEnv, paths, piPaths, type
|
|
|
12
12
|
import { pickPiSeat, piMovers } from "../lib/pi.ts";
|
|
13
13
|
import { ensurePiStoreHome, piStoreUsable } from "../lib/piauth.ts";
|
|
14
14
|
import { thresholdBars, usableAt } from "../lib/picker.ts";
|
|
15
|
-
import { clearPresence, livingPresences } from "../lib/presence.ts";
|
|
15
|
+
import { PI_PRESENCE_PREFIX, clearPresence, livingPresences } from "../lib/presence.ts";
|
|
16
16
|
import { teeObservation } from "../lib/sample.ts";
|
|
17
17
|
import { countdownWait, exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough, SEAT_POLL_MS, SEAT_RETRY_MS } from "../lib/supervise.ts";
|
|
18
18
|
import { saveTermios } from "../lib/tty.ts";
|
|
@@ -327,7 +327,7 @@ export async function runPiSupervisor(argv: string[]): Promise<number> {
|
|
|
327
327
|
if (launch == null) return runPassthrough({ real, argv, env: childEnv, onSpawn: (p) => { child = p; } });
|
|
328
328
|
const { args, pool, dirs, sid } = launch;
|
|
329
329
|
|
|
330
|
-
const id =
|
|
330
|
+
const id = `${PI_PRESENCE_PREFIX}${crypto.randomUUID()}`;
|
|
331
331
|
const mover = piMovers[pool];
|
|
332
332
|
const presenceDir = pool === "claude" ? paths.presenceDir : codexPaths.presenceDir;
|
|
333
333
|
const releaseClaim = async (): Promise<void> => {
|
package/src/lib/claude.ts
CHANGED
|
@@ -163,8 +163,7 @@ export async function borrowClaudeSeat(pid: number): Promise<SeatBorrow> {
|
|
|
163
163
|
}
|
|
164
164
|
return { store: storeDirFor(heldAccount.id), id: heldAccount.id, reused: true };
|
|
165
165
|
}
|
|
166
|
-
const
|
|
167
|
-
const open = idx.accounts.filter((a) => a.needsReauth !== true && !lent.has(a.id));
|
|
166
|
+
const open = idx.accounts.filter((a) => a.needsReauth !== true);
|
|
168
167
|
const usable = await Promise.all(open.map(storeUsable));
|
|
169
168
|
const ctx: PickCtx = { now: Date.now(), thresholds: bars, currentId: null, families: gatedFamilies(null, cfg.policy.switchModels), seats: presence() };
|
|
170
169
|
const picked = pickBest(open.filter((_, i) => usable[i]), ctx);
|
package/src/lib/codex.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { countBy } from "es-toolkit";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { writeFileAtomic } from "./atomic.ts";
|
|
5
6
|
import { CODEX_BIN, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealBin, verifyRealBin } from "./claudebin.ts";
|
|
6
7
|
import { codexIdentityOf, codexStoreUsable, deleteCodexStoreAuth, ensureCodexStoreHome, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
|
|
7
8
|
import { CodexInvalidGrantError, CodexRefreshFailedError, refreshCodexAuth } from "./codexoauth.ts";
|
|
8
9
|
import { deletePiStore } from "./piauth.ts";
|
|
9
|
-
import { livingPresences, seatCounts, writePresence } from "./presence.ts";
|
|
10
|
+
import { PI_PRESENCE_PREFIX, livingPresences, seatCounts, writePresence } from "./presence.ts";
|
|
10
11
|
import { CodexUsageReadError, codexLimitLabel, fetchCodexUsage } from "./codexusage.ts";
|
|
11
12
|
import { codexSupervisorLink, ensurePathInRc, installCodexSupervisor, managedShellRcSkipLines, shellRcPath } from "./install.ts";
|
|
12
13
|
import { withLock } from "./lock.ts";
|
|
13
14
|
import { errorMessage, log } from "./log.ts";
|
|
14
|
-
import { codexPaths, codexPool, codexSeatFromEnv } from "./paths.ts";
|
|
15
|
+
import { codexPaths, codexPool, codexSeatFromEnv, optionalEnv } from "./paths.ts";
|
|
15
16
|
import { isExhausted, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
|
|
16
17
|
import { StoreUnusableError, type Observation, type Provider, type SampleReport, type SeatBorrow } from "./provider.ts";
|
|
17
18
|
import { loadAccounts, loadConfig, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
|
|
@@ -19,6 +20,8 @@ import { restoreTermios, saveTermios } from "./tty.ts";
|
|
|
19
20
|
import type { Account, CodexAuthJson, CodexUsage, Config } from "./types.ts";
|
|
20
21
|
import { c } from "../cli/render.ts";
|
|
21
22
|
|
|
23
|
+
export const CODEX_SUPERVISOR_ID_ENV = "TOKENMAXXING_CODEX_SUPERVISOR_ID";
|
|
24
|
+
|
|
22
25
|
function liveId(): string | null {
|
|
23
26
|
return codexSeatFromEnv(loadAccounts(codexPool).accounts.map((a) => a.id));
|
|
24
27
|
}
|
|
@@ -251,7 +254,8 @@ export async function borrowCodexSeat(pid: number): Promise<SeatBorrow> {
|
|
|
251
254
|
const granted = await withLock(codexPool.lockFile, (): SeatBorrow => {
|
|
252
255
|
const ctx = codexPickCtx(now, null);
|
|
253
256
|
const idx = loadAccounts(codexPool);
|
|
254
|
-
const
|
|
257
|
+
const living = livingPresences(codexPaths.presenceDir);
|
|
258
|
+
const held = living.find((p) => p.id === seatId);
|
|
255
259
|
const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
|
|
256
260
|
if (heldAccount) {
|
|
257
261
|
if (heldAccount.needsReauth === true) {
|
|
@@ -262,11 +266,14 @@ export async function borrowCodexSeat(pid: number): Promise<SeatBorrow> {
|
|
|
262
266
|
}
|
|
263
267
|
return { store: ensureCodexStoreHome(heldAccount.id), id: heldAccount.id, reused: true };
|
|
264
268
|
}
|
|
265
|
-
const
|
|
269
|
+
const parent = optionalEnv(CODEX_SUPERVISOR_ID_ENV);
|
|
270
|
+
const closed = new Set(living.filter((p) => p.id === parent || p.id.startsWith(PI_PRESENCE_PREFIX)).map((p) => p.accountId));
|
|
271
|
+
const lent = countBy(living, (p) => p.accountId);
|
|
266
272
|
const usable = idx.accounts.filter(
|
|
267
|
-
(a) => a.needsReauth !== true && !isExhausted(a, ctx) &&
|
|
273
|
+
(a) => !closed.has(a.id) && a.needsReauth !== true && !isExhausted(a, ctx) && codexStoreUsable(a.id)
|
|
268
274
|
);
|
|
269
|
-
const
|
|
275
|
+
const fewest = Math.min(...usable.map((a) => lent[a.id] ?? 0));
|
|
276
|
+
const picked = pickBest(usable.filter((a) => (lent[a.id] ?? 0) === fewest), ctx);
|
|
270
277
|
if (!picked) return null;
|
|
271
278
|
const store = ensureCodexStoreHome(picked.id);
|
|
272
279
|
writePresence({ dir: codexPaths.presenceDir, id: seatId, accountId: picked.id, pid });
|
package/src/lib/opencodego.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { countBy, minBy } from "es-toolkit";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { writeFileAtomic } from "./atomic.ts";
|
|
4
5
|
import { env, HOME, opencodeGoAuthJsonFor, opencodeGoPaths, opencodeGoPool, opencodeGoStoreDirFor } from "./paths.ts";
|
|
@@ -63,10 +64,9 @@ export async function borrowOpencodeGoSeat(pid: number): Promise<SeatBorrow> {
|
|
|
63
64
|
}
|
|
64
65
|
return { store: opencodeGoStoreDirFor(heldAccount.id), id: heldAccount.id, reused: true };
|
|
65
66
|
}
|
|
66
|
-
const lent =
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const picked = open.find((_, i) => usable[i]);
|
|
67
|
+
const lent = countBy(living, (p) => p.accountId);
|
|
68
|
+
const usable = await Promise.all(accounts.map(opencodeGo.storeUsable));
|
|
69
|
+
const picked = minBy(accounts.filter((_, i) => usable[i]), (a) => lent[a.id] ?? 0);
|
|
70
70
|
if (!picked) return null;
|
|
71
71
|
writePresence({ dir: opencodeGoPaths.presenceDir, id: seatId, accountId: picked.id, pid });
|
|
72
72
|
return { store: opencodeGoStoreDirFor(picked.id), id: picked.id, reused: false };
|
package/src/lib/picker.ts
CHANGED
|
@@ -122,6 +122,7 @@ const swapPreference = (ctx: PickCtx) => [
|
|
|
122
122
|
(a: Account) => -pacePressure(a, ctx),
|
|
123
123
|
(a: Account) => weeklyExpiry(a, ctx.now),
|
|
124
124
|
(a: Account) => weeklyWindow(a)?.usedPercentage ?? 101,
|
|
125
|
+
...(ctx.seats == null ? [] : [(a: Account) => ctx.seats?.get(a.id) ?? 0]),
|
|
125
126
|
];
|
|
126
127
|
|
|
127
128
|
export function pickBest(accounts: Account[], ctx: PickCtx): Account | null {
|
package/src/lib/presence.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { pidExists, pidStartTime } from "./proc.ts";
|
|
|
7
7
|
import { readJsonFile } from "./state.ts";
|
|
8
8
|
import { ErrnoSchema, JsonTextSchema } from "./types.ts";
|
|
9
9
|
|
|
10
|
+
export const PI_PRESENCE_PREFIX = "pi-";
|
|
11
|
+
|
|
10
12
|
const PresenceSchema = z.object({
|
|
11
13
|
accountId: z.string(),
|
|
12
14
|
pid: z.number(),
|
package/src/main.ts
CHANGED
|
@@ -60,9 +60,9 @@ function printHelp(): void {
|
|
|
60
60
|
${c.cyan("tokenmaxxing doctor")} verify the install is intact
|
|
61
61
|
${c.cyan("tokenmaxxing rename")} [--codex | --grok | --opencode-go] <sel> <label>
|
|
62
62
|
${c.cyan("tokenmaxxing rm")} [--codex | --grok | --opencode-go] <sel>
|
|
63
|
-
${c.cyan("tokenmaxxing seat")} <pid> lend one pooled Claude account to an unattended consumer until <pid> exits: prints the store directory to set as CLAUDE_SECURESTORAGE_CONFIG_DIR (the directory itself, as its owner, never a copy of its credential); host sessions
|
|
64
|
-
${c.cyan("tokenmaxxing seat --codex")} <pid> borrow one pooled codex account for an unattended consumer (plugin, script): prints the CODEX_HOME to set,
|
|
65
|
-
${c.cyan("tokenmaxxing seat --opencode-go")} <pid> lend one pooled opencode-go key to an unattended consumer until <pid> exits: prints the store directory, whose auth.json is an opencode auth file (mount it at $XDG_DATA_HOME/opencode/auth.json, then run a model such as opencode-go/mimo-v2.6-pro); other borrowers
|
|
63
|
+
${c.cyan("tokenmaxxing seat")} <pid> lend one pooled Claude account to an unattended consumer until <pid> exits: prints the store directory to set as CLAUDE_SECURESTORAGE_CONFIG_DIR (the directory itself, as its owner, never a copy of its credential); host sessions and other borrowers share the account; exit 1 = none usable
|
|
64
|
+
${c.cyan("tokenmaxxing seat --codex")} <pid> borrow one pooled codex account for an unattended consumer (plugin, script): prints the CODEX_HOME to set, lent to <pid> until it exits; codex sessions and other borrowers share the account, and a refresh race between them can sign it out until auth --codex runs again; exit 1 = none usable, fall back to the ambient login
|
|
65
|
+
${c.cyan("tokenmaxxing seat --opencode-go")} <pid> lend one pooled opencode-go key to an unattended consumer until <pid> exits: prints the store directory, whose auth.json is an opencode auth file (mount it at $XDG_DATA_HOME/opencode/auth.json, then run a model such as opencode-go/mimo-v2.6-pro); other borrowers share the key; exit 1 = none usable
|
|
66
66
|
${c.cyan("tokenmaxxing serve")} serve the CLIProxyAPI-compatible usage API on http://localhost:<hub.port> (default 8317) so a dashboard such as T3 Code's "Add a CLIProxyAPI hub" shows every pooled Claude and Codex account's quota; the management key is the contents of hub-key in the state directory
|
|
67
67
|
${c.cyan("tokenmaxxing uninstall")} [--yes] print the targets, then remove supervisor + settings entries (refused without ${c.cyan("--yes")} when HOME is the login home)
|
|
68
68
|
|