tokenmaxxing 1.106.0 → 1.108.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.106.0",
3
+ "version": "1.108.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,15 +1,36 @@
1
+ import { borrowClaudeSeat } from "../lib/claude.ts";
1
2
  import { borrowCodexSeat } from "../lib/codex.ts";
3
+ import { borrowOpencodeGoSeat } from "../lib/opencodego.ts";
2
4
  import { emitError } from "./render.ts";
3
5
 
4
- export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Promise<number> {
6
+ const POOLS = {
7
+ claude: {
8
+ borrow: borrowClaudeSeat,
9
+ usage: "usage: tokenmaxxing seat <pid> - print the CLAUDE_SECURESTORAGE_CONFIG_DIR of one pooled claude account, lent to <pid> alone until it exits",
10
+ none: "no usable claude account (pool empty, every account lent to another borrower, or every account at a limit or flagged for reauthentication)",
11
+ },
12
+ codex: {
13
+ borrow: borrowCodexSeat,
14
+ usage: "usage: tokenmaxxing seat --codex <pid> - print the CODEX_HOME of one pooled account, reserved until <pid> exits",
15
+ none: "no usable codex account (pool empty, every account live in another session, or every account at a limit) - use the ambient codex login",
16
+ },
17
+ "opencode-go": {
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> alone until it exits",
20
+ none: "no usable opencode-go key (pool empty, every key lent to another borrower, or no store holds a usable key)",
21
+ },
22
+ };
23
+
24
+ export async function cmdSeat(pool: keyof typeof POOLS, pidRaw: string | undefined, extra: string[]): Promise<number> {
25
+ const seat = POOLS[pool];
5
26
  const pid = pidRaw != null && /^(0|[1-9][0-9]*)$/.test(pidRaw) ? Number(pidRaw) : NaN;
6
27
  if (!Number.isSafeInteger(pid) || pid < 2 || extra.length > 0) {
7
- emitError({ message: "usage: tokenmaxxing seat --codex <pid> - print the CODEX_HOME of one pooled account, reserved until <pid> exits" });
28
+ emitError({ message: seat.usage });
8
29
  return 2;
9
30
  }
10
- const granted = await borrowCodexSeat(pid);
31
+ const granted = await seat.borrow(pid);
11
32
  if (!granted) {
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" });
33
+ emitError({ message: seat.none });
13
34
  return 1;
14
35
  }
15
36
  if ("denied" in granted) {
@@ -1,3 +1,5 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { basename, dirname, join } from "node:path";
1
3
  import { z } from "zod";
2
4
  import { claude } from "../lib/claude.ts";
3
5
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
@@ -20,10 +22,18 @@ const StopFailureStdin = z.looseObject({
20
22
  const ROW_WAIT_MS = 3_000;
21
23
  const ROW_POLL_MS = 200;
22
24
 
23
- async function awaitEnforcedRow(input: { transcriptPath: string; error: string; lastAssistantMessage: string | undefined }): Promise<TranscriptRow | null> {
25
+ function subagentTranscript(transcriptPath: string, agentId: string): string {
26
+ const dir = join(dirname(transcriptPath), basename(transcriptPath, ".jsonl"), "subagents");
27
+ const name = `agent-${agentId}.jsonl`;
28
+ const found = existsSync(dir) ? readdirSync(dir, { recursive: true, encoding: "utf8" }).find((p) => basename(p) === name) : undefined;
29
+ return join(dir, found ?? name);
30
+ }
31
+
32
+ async function awaitEnforcedRow(input: { transcriptPath: string; agentId: string | undefined; error: string; lastAssistantMessage: string | undefined }): Promise<TranscriptRow | null> {
24
33
  const deadline = Date.now() + ROW_WAIT_MS;
25
34
  while (true) {
26
- const row = findEnforcedRow({ rows: await readTranscriptTail(input.transcriptPath), error: input.error, lastAssistantMessage: input.lastAssistantMessage });
35
+ const path = input.agentId === undefined ? input.transcriptPath : subagentTranscript(input.transcriptPath, input.agentId);
36
+ const row = findEnforcedRow({ rows: await readTranscriptTail(path), error: input.error, lastAssistantMessage: input.lastAssistantMessage });
27
37
  if (row || Date.now() >= deadline) return row;
28
38
  await Bun.sleep(ROW_POLL_MS);
29
39
  }
@@ -52,7 +62,7 @@ export async function runStopFailureHook(): Promise<number> {
52
62
  const canRespawn = session != null && mainLoop;
53
63
  const cfg = loadConfig();
54
64
  const row = stdin.transcript_path
55
- ? await awaitEnforcedRow({ transcriptPath: stdin.transcript_path, error, lastAssistantMessage: stdin.last_assistant_message })
65
+ ? await awaitEnforcedRow({ transcriptPath: stdin.transcript_path, agentId: stdin.agent_id, error, lastAssistantMessage: stdin.last_assistant_message })
56
66
  : null;
57
67
  const limit = row ? classifyEnforcedLimit(row, cfg.policy.switchModels) : null;
58
68
 
package/src/lib/claude.ts CHANGED
@@ -11,8 +11,8 @@ import { claudeTierLabel, describeIdentity, fetchTokenIdentity, isDeadCredential
11
11
  import { claudePool, env, paths, seatFromEnv, storeDirFor } from "./paths.ts";
12
12
  import { landWindows, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
13
13
  import { deletePiStore } from "./piauth.ts";
14
- import { livingPresences } from "./presence.ts";
15
- import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
14
+ import { livingPresences, writePresence } from "./presence.ts";
15
+ import { StoreUnusableError, type Observation, type Provider, type SampleReport, type SeatBorrow } from "./provider.ts";
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";
@@ -142,6 +142,40 @@ export function pickSeat(now: number, model: ModelInfo | null, eligible: (a: Acc
142
142
  return pickBest(candidates, ctx) ?? pickEarliestReset(candidates, ctx)?.account ?? null;
143
143
  }
144
144
 
145
+ export async function borrowClaudeSeat(pid: number): Promise<SeatBorrow> {
146
+ const seatId = `seat-${pid}`;
147
+ const cfg = loadConfig();
148
+ const bars = thresholdBars(cfg);
149
+ const granted = await withLock(claudePool.lockFile, async (): Promise<SeatBorrow> => {
150
+ const idx = loadAccounts(claudePool);
151
+ let dirty = false;
152
+ for (const a of idx.accounts) dirty = foldTee(a, bars) || dirty;
153
+ if (dirty) saveAccounts(claudePool, idx);
154
+ const living = livingPresences(paths.presenceDir);
155
+ const held = living.find((p) => p.id === seatId);
156
+ const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
157
+ if (heldAccount) {
158
+ if (heldAccount.needsReauth === true) {
159
+ return { denied: "the account this pid holds needs reauthentication - run `tokenmaxxing auth` and borrow again" };
160
+ }
161
+ if (!(await storeUsable(heldAccount))) {
162
+ return { denied: "the account this pid holds has no usable credential in its store - refusing to hand back a credential-less seat" };
163
+ }
164
+ return { store: storeDirFor(heldAccount.id), id: heldAccount.id, reused: true };
165
+ }
166
+ const lent = new Set(living.filter((p) => p.id.startsWith("seat-")).map((p) => p.accountId));
167
+ const open = idx.accounts.filter((a) => a.needsReauth !== true && !lent.has(a.id));
168
+ const usable = await Promise.all(open.map(storeUsable));
169
+ const ctx: PickCtx = { now: Date.now(), thresholds: bars, currentId: null, families: gatedFamilies(null, cfg.policy.switchModels), seats: presence() };
170
+ const picked = pickBest(open.filter((_, i) => usable[i]), ctx);
171
+ if (!picked) return null;
172
+ writePresence({ dir: paths.presenceDir, id: seatId, accountId: picked.id, pid });
173
+ return { store: storeDirFor(picked.id), id: picked.id, reused: false };
174
+ });
175
+ if (granted && !("denied" in granted)) log("seat.grant", { account: granted.id.slice(0, 8), pid, reused: granted.reused });
176
+ return granted;
177
+ }
178
+
145
179
  async function removeCredentials(a: Account): Promise<void> {
146
180
  await deleteItem(storeTarget(a.id));
147
181
  rmSync(storeDirFor(a.id), { recursive: true, force: true });
package/src/lib/codex.ts CHANGED
@@ -13,7 +13,7 @@ import { withLock } from "./lock.ts";
13
13
  import { errorMessage, log } from "./log.ts";
14
14
  import { codexPaths, codexPool, codexSeatFromEnv } from "./paths.ts";
15
15
  import { isExhausted, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
16
- import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
16
+ import { StoreUnusableError, type Observation, type Provider, type SampleReport, type SeatBorrow } from "./provider.ts";
17
17
  import { loadAccounts, loadConfig, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
18
18
  import { restoreTermios, saveTermios } from "./tty.ts";
19
19
  import type { Account, CodexAuthJson, CodexUsage, Config } from "./types.ts";
@@ -239,9 +239,7 @@ export function pickCodexSeat(now: number, eligible: (a: Account) => boolean = (
239
239
  return pickBest(open.filter((a) => !isExhausted(a, ctx)), ctx) ?? pickEarliestReset(open, ctx)?.account ?? null;
240
240
  }
241
241
 
242
- export type CodexBorrow = { store: string; id: string; reused: boolean } | { denied: string } | null;
243
-
244
- export async function borrowCodexSeat(pid: number): Promise<CodexBorrow> {
242
+ export async function borrowCodexSeat(pid: number): Promise<SeatBorrow> {
245
243
  const now = Date.now();
246
244
  const seatId = `seat-${pid}`;
247
245
  const cfg = loadConfig();
@@ -250,7 +248,7 @@ export async function borrowCodexSeat(pid: number): Promise<CodexBorrow> {
250
248
  .accounts.filter((a) => a.needsReauth !== true && codexStoreUsable(a.id))
251
249
  .map((a) => observeCodex(a, cfg, now, { probe: true, refresh: false })),
252
250
  );
253
- const granted = await withLock(codexPool.lockFile, (): CodexBorrow => {
251
+ const granted = await withLock(codexPool.lockFile, (): SeatBorrow => {
254
252
  const ctx = codexPickCtx(now, null);
255
253
  const idx = loadAccounts(codexPool);
256
254
  const held = livingPresences(codexPaths.presenceDir).find((p) => p.id === seatId);
package/src/lib/grok.ts CHANGED
@@ -131,6 +131,7 @@ const base = statusOnlyProvider({
131
131
  liveAuthPath: () => join(grokPaths.home, "auth.json"),
132
132
  readAuth,
133
133
  liveId: () => grokSeatFromEnv(loadAccounts(grokPool).accounts.map((a) => a.id)),
134
+ presence: () => new Map(),
134
135
  versionOk: (out) => out.toLowerCase().includes("grok"),
135
136
  importIntro: "Opening an isolated grok login for your first pooled account - the login you already have stays as it is.",
136
137
  foundLive: (count) => `found ${count} grok login(s) in ${grokPaths.home} - pooling the first; use \`tokenmaxxing add --grok\` for the rest.`,
@@ -2,8 +2,11 @@ import { join } from "node:path";
2
2
  import { z } from "zod";
3
3
  import { writeFileAtomic } from "./atomic.ts";
4
4
  import { env, HOME, opencodeGoAuthJsonFor, opencodeGoPaths, opencodeGoPool, opencodeGoStoreDirFor } from "./paths.ts";
5
- import type { Provider } from "./provider.ts";
6
- import { readJsonFile, type Harvest } from "./state.ts";
5
+ import { withLock } from "./lock.ts";
6
+ import { log } from "./log.ts";
7
+ import { livingPresences, seatCounts, writePresence } from "./presence.ts";
8
+ import type { Provider, SeatBorrow } from "./provider.ts";
9
+ import { loadAccounts, readJsonFile, type Harvest } from "./state.ts";
7
10
  import { statusOnlyProvider, type AuthEntry } from "./statusonly.ts";
8
11
  import { ErrnoSchema } from "./types.ts";
9
12
  import { c } from "../cli/render.ts";
@@ -47,6 +50,31 @@ function readAuth(path: string): AuthEntry[] {
47
50
  return [{ id: idOfKey(entry.data.key), usable: true, harvest: harvestOf(entry.data.key) }];
48
51
  }
49
52
 
53
+ export async function borrowOpencodeGoSeat(pid: number): Promise<SeatBorrow> {
54
+ const seatId = `seat-${pid}`;
55
+ const granted = await withLock(opencodeGoPool.lockFile, async (): Promise<SeatBorrow> => {
56
+ const accounts = loadAccounts(opencodeGoPool).accounts;
57
+ const living = livingPresences(opencodeGoPaths.presenceDir);
58
+ const held = living.find((p) => p.id === seatId);
59
+ const heldAccount = held ? (accounts.find((a) => a.id === held.accountId) ?? null) : null;
60
+ if (heldAccount) {
61
+ if (!(await opencodeGo.storeUsable(heldAccount))) {
62
+ return { denied: "the key this pid holds is no longer usable in its store - run `tokenmaxxing auth --opencode-go` and borrow again" };
63
+ }
64
+ return { store: opencodeGoStoreDirFor(heldAccount.id), id: heldAccount.id, reused: true };
65
+ }
66
+ const lent = new Set(living.map((p) => p.accountId));
67
+ const open = accounts.filter((a) => !lent.has(a.id));
68
+ const usable = await Promise.all(open.map(opencodeGo.storeUsable));
69
+ const picked = open.find((_, i) => usable[i]);
70
+ if (!picked) return null;
71
+ writePresence({ dir: opencodeGoPaths.presenceDir, id: seatId, accountId: picked.id, pid });
72
+ return { store: opencodeGoStoreDirFor(picked.id), id: picked.id, reused: false };
73
+ });
74
+ if (granted && !("denied" in granted)) log("seat.grant", { account: granted.id.slice(0, 8), pid, reused: granted.reused });
75
+ return granted;
76
+ }
77
+
50
78
  export const opencodeGo: Provider = statusOnlyProvider({
51
79
  name: "opencode-go",
52
80
  flag: " --opencode-go",
@@ -62,6 +90,7 @@ export const opencodeGo: Provider = statusOnlyProvider({
62
90
  liveAuthPath,
63
91
  readAuth,
64
92
  liveId: () => null,
93
+ presence: () => seatCounts(opencodeGoPaths.presenceDir),
65
94
  versionOk: (out) => out !== "",
66
95
  importIntro: "Pooling your opencode-go API key - your existing opencode auth stays as it is.",
67
96
  foundLive: () => `found an opencode-go credential in ${liveAuthPath()} - pooling it; use \`tokenmaxxing add --opencode-go\` for more keys.`,
package/src/lib/paths.ts CHANGED
@@ -133,6 +133,7 @@ export function grokSeatFromEnv(accountIds: string[], env: Record<string, string
133
133
  export const opencodeGoPaths = {
134
134
  storesDir: join(TM_HOME, "opencode-go-stores"),
135
135
  onboardDir: join(TM_HOME, "opencode-go-onboard"),
136
+ presenceDir: join(TM_HOME, "opencode-go-live"),
136
137
  } as const;
137
138
 
138
139
  export function opencodeGoStoreDirFor(accountId: string): string {
@@ -8,6 +8,8 @@ export type SampleReport = { ok: true; source: "statusline" | "probe" | "cached"
8
8
 
9
9
  export type SwapFailure = "dead-grant" | "skip" | "fatal";
10
10
 
11
+ export type SeatBorrow = { store: string; id: string; reused: boolean } | { denied: string } | null;
12
+
11
13
  export class StoreUnusableError extends Error {
12
14
  constructor(message: string) {
13
15
  super(message);
@@ -60,7 +60,7 @@ function listDir(dir: string, root: string): Dirent[] {
60
60
  }
61
61
 
62
62
  function tmpSweepDirs(root: string): string[] {
63
- const dirs = [paths.home, paths.usageDir, paths.presenceDir, paths.respawnDir, sessionsDir(), paths.binDir, codexPaths.presenceDir, codexPaths.respawnDir, codexPaths.onboardDir];
63
+ const dirs = [paths.home, paths.usageDir, paths.presenceDir, paths.respawnDir, sessionsDir(), paths.binDir, codexPaths.presenceDir, codexPaths.respawnDir, codexPaths.onboardDir, opencodeGoPaths.presenceDir];
64
64
  for (const parent of STORE_PARENTS) {
65
65
  for (const child of listDir(parent, root)) {
66
66
  if (child.isDirectory()) dirs.push(join(parent, child.name));
@@ -24,6 +24,7 @@ export type StatusOnlySpec = {
24
24
  liveAuthPath: () => string;
25
25
  readAuth: (path: string) => AuthEntry[];
26
26
  liveId: () => string | null;
27
+ presence: () => Map<string, number>;
27
28
  versionOk: (versionOutput: string) => boolean;
28
29
  importIntro: string;
29
30
  foundLive: (count: number) => string;
@@ -101,7 +102,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
101
102
  waitsWhenDepleted: false,
102
103
  statusOnly: true,
103
104
  liveId: spec.liveId,
104
- presence: () => new Map(),
105
+ presence: spec.presence,
105
106
  gatedFamilies: () => null,
106
107
  observeLive: async (account) => (account.lastUsageAt != null ? { windows: account.windows, at: account.lastUsageAt } : null),
107
108
  samplePool,
package/src/main.ts CHANGED
@@ -39,6 +39,7 @@ const PI_FLAG = "--pi";
39
39
  const JSON_COMMANDS = new Set(["status", "config", "check"]);
40
40
  const CODEX_COMMANDS = new Set(["init", "add", "auth", "rm", "rename", "seat"]);
41
41
  const STATUS_ONLY_COMMANDS = new Set(["init", "add", "auth", "rm", "rename"]);
42
+ const OPENCODE_GO_COMMANDS = new Set([...STATUS_ONLY_COMMANDS, "seat"]);
42
43
 
43
44
  function printHelp(): void {
44
45
  console.log(`${c.bold("tokenmaxxing")} - automatic Claude Code account switching
@@ -59,7 +60,9 @@ function printHelp(): void {
59
60
  ${c.cyan("tokenmaxxing doctor")} verify the install is intact
60
61
  ${c.cyan("tokenmaxxing rename")} [--codex | --grok | --opencode-go] <sel> <label>
61
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 keep using the account, other borrowers do not; exit 1 = none usable
62
64
  ${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
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 do not get the key; exit 1 = none usable
63
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
64
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)
65
68
 
@@ -127,10 +130,13 @@ async function main(): Promise<number> {
127
130
  return 2;
128
131
  }
129
132
 
130
- if ((provider === grok || provider === opencodeGo) && (sub == null || !STATUS_ONLY_COMMANDS.has(sub))) {
133
+ if (provider === grok || provider === opencodeGo) {
131
134
  const flag = provider === grok ? GROK_FLAG : OPENCODE_GO_FLAG;
132
- emitError({ json, message: `${flag} applies to ${[...STATUS_ONLY_COMMANDS].join(", ")}, not ${sub ?? "status"}` });
133
- return 2;
135
+ const commands = provider === grok ? STATUS_ONLY_COMMANDS : OPENCODE_GO_COMMANDS;
136
+ if (sub == null || !commands.has(sub)) {
137
+ emitError({ json, message: `${flag} applies to ${[...commands].join(", ")}, not ${sub ?? "status"}` });
138
+ return 2;
139
+ }
134
140
  }
135
141
 
136
142
  if (json && sub != null && !JSON_COMMANDS.has(sub)) {
@@ -167,13 +173,7 @@ async function main(): Promise<number> {
167
173
  case "doctor": return cmdDoctor();
168
174
  case "rm": return cmdRm(provider, args[1]);
169
175
  case "rename": return cmdRename(provider, args.slice(1));
170
- case "seat": {
171
- if (provider !== codex) {
172
- emitError({ message: `seat borrows a pooled codex account - pass ${CODEX_FLAG} (usage: tokenmaxxing seat ${CODEX_FLAG} <pid>)` });
173
- return 2;
174
- }
175
- return cmdSeat(args[1], args.slice(2));
176
- }
176
+ case "seat": return cmdSeat(provider === codex ? "codex" : provider === opencodeGo ? "opencode-go" : "claude", args[1], args.slice(2));
177
177
  case "serve": return cmdServe(args.slice(1));
178
178
  case "uninstall": {
179
179
  if (args.length > 1) {