tokenmaxxing 1.77.0 → 1.79.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.77.0",
3
+ "version": "1.79.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/doctor.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import { verifyRealClaude } from "../lib/claudebin.ts";
2
+ import { CLAUDE_BIN, verifyRealBin } from "../lib/claudebin.ts";
3
3
  import { checkSettings, installedBin } from "../lib/settings.ts";
4
- import { checkTimerHealthy, codexStoreHookTrust, findClaudeShadowers, hubServiceHealthy, isBinDirAhead, shellRcPath, skipImperativeHub, timerActivationHint } from "../lib/install.ts";
4
+ import { activationHint, CHECK_JOB, codexStoreHookTrust, findClaudeShadowers, HUB_JOB, isBinDirAhead, jobHealthy, shellRcPath } from "../lib/install.ts";
5
5
  import { errorMessage } from "../lib/log.ts";
6
6
  import { claudePool, codexPool, paths } from "../lib/paths.ts";
7
7
  import { loadAccounts, loadConfig } from "../lib/state.ts";
@@ -29,10 +29,10 @@ export async function cmdDoctor(): Promise<number> {
29
29
  check(s.stopOk, "Stop hook installed in settings.json", "run `tokenmaxxing init`");
30
30
  check(s.stopFailureOk, "StopFailure hook installed in settings.json", "run `tokenmaxxing init`");
31
31
  check(s.sessionStartOk, "SessionStart hook installed in settings.json", "run `tokenmaxxing init`");
32
- check(checkTimerHealthy(), "periodic check timer active", timerActivationHint());
32
+ check(jobHealthy(CHECK_JOB), "periodic check timer active", activationHint(CHECK_JOB));
33
33
  if (existsSync(paths.supervisorLink)) {
34
- if (skipImperativeHub()) note("usage hub service is owned outside init (TOKENMAXXING_SKIP_HUB set) - not verified here");
35
- else check(hubServiceHealthy(), "usage hub service active", "run `tokenmaxxing init` (it writes the unit and activates it)");
34
+ if (HUB_JOB.skip()) note("usage hub service is owned outside init (TOKENMAXXING_SKIP_HUB set) - not verified here");
35
+ else check(jobHealthy(HUB_JOB), "usage hub service active", "run `tokenmaxxing init` (it writes the unit and activates it)");
36
36
  }
37
37
 
38
38
  const idx = loadAccounts(claudePool);
@@ -95,7 +95,7 @@ export async function cmdDoctor(): Promise<number> {
95
95
  const cfg = loadConfig();
96
96
  check(!!cfg.claudeBin && existsSync(cfg.claudeBin), "real claude binary resolved", "set claudeBin in config.json");
97
97
  if (cfg.claudeBin && existsSync(cfg.claudeBin)) {
98
- const fail = verifyRealClaude(cfg.claudeBin);
98
+ const fail = verifyRealBin({ ...CLAUDE_BIN, bin: cfg.claudeBin });
99
99
  check(fail === null, "claudeBin launches the real claude", fail ?? undefined);
100
100
  }
101
101
 
package/src/cli/serve.ts CHANGED
@@ -7,7 +7,7 @@ import { claude } from "../lib/claude.ts";
7
7
  import { codex, observeCodex } from "../lib/codex.ts";
8
8
  import { CODEX_USAGE_URL } from "../lib/codexusage.ts";
9
9
  import { errorMessage, log } from "../lib/log.ts";
10
- import { hubDeactivationHint } from "../lib/install.ts";
10
+ import { deactivationHint, HUB_JOB } from "../lib/install.ts";
11
11
  import { paths } from "../lib/paths.ts";
12
12
  import { isExhausted, thresholdBars } from "../lib/picker.ts";
13
13
  import type { Provider } from "../lib/provider.ts";
@@ -215,7 +215,7 @@ export async function cmdServe(args: string[]): Promise<number> {
215
215
  },
216
216
  });
217
217
  } catch (e) {
218
- emitError({ message: `cannot bind localhost:${cfg.hub.port}: ${errorMessage(e)} (another \`tokenmaxxing serve\` or the usage hub service may already hold the port - stop it first: ${hubDeactivationHint()})` });
218
+ emitError({ message: `cannot bind localhost:${cfg.hub.port}: ${errorMessage(e)} (another \`tokenmaxxing serve\` or the usage hub service may already hold the port - stop it first: ${deactivationHint(HUB_JOB)})` });
219
219
  return 1;
220
220
  }
221
221
  console.log(`${c.green("✓")} serving the CLIProxyAPI-compatible usage API at http://localhost:${server.port}/v0/management`);
@@ -2,9 +2,8 @@ import { join } from "node:path";
2
2
  import type { z } from "zod";
3
3
  import { codexPaths, optionalEnv } from "../lib/paths.ts";
4
4
  import { writeFileAtomic } from "../lib/atomic.ts";
5
- import { MAX_WRAP_DEPTH, WRAP_DEPTH_ENV } from "../lib/claudebin.ts";
5
+ import { CODEX_BIN, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealBin } from "../lib/claudebin.ts";
6
6
  import { codex, codexPickCtx } from "../lib/codex.ts";
7
- import { resolveRealCodex } from "../lib/codexbin.ts";
8
7
  import { compactCodexThread } from "../lib/compact.ts";
9
8
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
10
9
  import { isExhausted } from "../lib/picker.ts";
@@ -26,7 +25,7 @@ async function compactBeforeMove(input: { sessionId: string | null; now: number
26
25
  const env: Record<string, string | undefined> = { ...process.env, TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH) };
27
26
  delete env[CODEX_SUPERVISOR_ID_ENV];
28
27
  log("codexstop.compact_start", { thread: sessionId.slice(0, 8), account: live.id.slice(0, 8) });
29
- await compactCodexThread({ real: resolveRealCodex(), threadId: sessionId, env });
28
+ await compactCodexThread({ real: resolveRealBin(CODEX_BIN), threadId: sessionId, env });
30
29
  }
31
30
 
32
31
  async function handleCodexStop(rawStdin: string): Promise<void> {
@@ -3,8 +3,7 @@ import { join } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { codexPaths, codexPool } from "../lib/paths.ts";
5
5
  import { withLock } from "../lib/lock.ts";
6
- import { UNMANAGED_ENV, WRAP_DEPTH_ENV, wrapDepth } from "../lib/claudebin.ts";
7
- import { resolveRealCodex } from "../lib/codexbin.ts";
6
+ import { CODEX_BIN, UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealBin, wrapDepth } from "../lib/claudebin.ts";
8
7
  import { codexStoreUsable, ensureCodexStoreHome } from "../lib/codexauth.ts";
9
8
  import { codexPickCtx, pickCodexSeat } from "../lib/codex.ts";
10
9
  import { clearPresence, livingPresences } from "../lib/presence.ts";
@@ -74,7 +73,7 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
74
73
  const { argv } = input;
75
74
  if (loopGuardTripped("codex")) return 1;
76
75
 
77
- const real = resolveRealCodex();
76
+ const real = resolveRealBin(CODEX_BIN);
78
77
  const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
79
78
 
80
79
  if (!shouldManageCodex({ argv }) || process.env[UNMANAGED_ENV]) {
@@ -4,7 +4,7 @@ import type { FileSink, Subprocess } from "bun";
4
4
  import { maxBy } from "es-toolkit";
5
5
  import { z } from "zod";
6
6
  import { claudePool, paths, storeDirFor } from "../lib/paths.ts";
7
- import { UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealClaude, wrapDepth } from "../lib/claudebin.ts";
7
+ import { CLAUDE_BIN, UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealBin, wrapDepth } from "../lib/claudebin.ts";
8
8
  import { claude, pickSeat } from "../lib/claude.ts";
9
9
  import { compactClaudeSession } from "../lib/compact.ts";
10
10
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
@@ -353,7 +353,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
353
353
  }
354
354
  let real: string;
355
355
  try {
356
- real = resolveRealClaude();
356
+ real = resolveRealBin(CLAUDE_BIN);
357
357
  } catch (e) {
358
358
  if (!earlyStream) throw e;
359
359
  process.stdout.write(systemLine(earlySid, `tokenmaxxing: ${errorMessage(e)}`));
package/src/lib/claude.ts CHANGED
@@ -2,8 +2,8 @@ import { mkdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { readItem, writeItem, deleteItem, isolatedTarget, readStore, storeTarget, claudeAiOauthOnly } from "./credstore.ts";
5
- import { resolveRealClaude, resolveVerifiedClaude } from "./claudebin.ts";
6
- import { ensurePathInRc, hubActivationHint, installSupervisor, managedShellRcSkipLines, shellRcPath, timerActivationHint } from "./install.ts";
5
+ import { CLAUDE_BIN, resolveRealBin, resolveVerifiedClaude } from "./claudebin.ts";
6
+ import { activationHint, CHECK_JOB, ensurePathInRc, HUB_JOB, installSupervisor, managedShellRcSkipLines, shellRcPath } from "./install.ts";
7
7
  import { withLock } from "./lock.ts";
8
8
  import { errorMessage, log } from "./log.ts";
9
9
  import { claudeTierLabel, describeIdentity, fetchTokenIdentity, isDeadCredential, InvalidGrantError } from "./oauth.ts";
@@ -151,7 +151,7 @@ async function login(): Promise<Harvest | null> {
151
151
  const iso = isolatedTarget(onboardDir);
152
152
  await deleteItem(iso);
153
153
  const cjPath = join(onboardDir, ".claude.json");
154
- const real = resolveRealClaude();
154
+ const real = resolveRealBin(CLAUDE_BIN);
155
155
 
156
156
  const savedTermios = saveTermios();
157
157
  const env = scrubCredentialEnv({ ...process.env, CLAUDE_CONFIG_DIR: onboardDir, TOKENMAXXING_PROBE: "1", TOKENMAXXING_SUPERVISED: "" });
@@ -256,9 +256,9 @@ function install(): void {
256
256
  const out = installSupervisor();
257
257
  console.log(`${c.green("✓")} installed ${c.bold("claude")} supervisor + statusLine/Stop/StopFailure/SessionStart hooks`);
258
258
  if (out.timerLoaded) console.log(`${c.green("✓")} periodic check timer active (every ${out.checkIntervalS}s)`);
259
- else console.log(c.yellow(`⚠ check timer written but not activated - run: ${timerActivationHint()}`));
259
+ else console.log(c.yellow(`⚠ check timer written but not activated - run: ${activationHint(CHECK_JOB)}`));
260
260
  if (out.hubLoaded) console.log(`${c.green("✓")} usage hub service active`);
261
- else console.log(c.yellow(`⚠ usage hub service written but not activated - run: ${hubActivationHint()}`));
261
+ else console.log(c.yellow(`⚠ usage hub service written but not activated - run: ${activationHint(HUB_JOB)}`));
262
262
  if (!out.pathAhead) {
263
263
  console.log();
264
264
  ensurePathAhead();
@@ -4,7 +4,7 @@ import { uniq } from "es-toolkit";
4
4
  import { z } from "zod";
5
5
  import { errorMessage } from "./log.ts";
6
6
  import { paths } from "./paths.ts";
7
- import { loadConfig } from "./state.ts";
7
+ import { loadConfig, type BinKey } from "./state.ts";
8
8
  import { writeFileAtomic } from "./atomic.ts";
9
9
 
10
10
  export const WRAP_DEPTH_ENV = "TOKENMAXXING_WRAP_DEPTH";
@@ -49,75 +49,70 @@ export function pointsBackAtUs(bin: string): boolean {
49
49
  return resolved != null && binDir != null && dirname(resolved) === binDir;
50
50
  }
51
51
 
52
- function scanPathForClaudeCandidates(): string[] {
52
+ function scanPathCandidates(name: string): string[] {
53
53
  const seen = new Set<string>();
54
54
  const out: string[] = [];
55
55
  for (const d of (process.env.PATH ?? "").split(":")) {
56
56
  if (!d) continue;
57
- const cand = join(d, "claude");
58
- try {
59
- if (existsSync(cand) && statSync(cand).isFile() && !pointsBackAtUs(cand)) {
60
- const key = realpathOrNull(cand) ?? cand;
61
- if (!seen.has(key)) {
62
- seen.add(key);
63
- out.push(cand);
64
- }
65
- }
66
- } catch { }
57
+ const cand = join(d, name);
58
+ if (!existsSync(cand) || !statSync(cand, { throwIfNoEntry: false })?.isFile() || pointsBackAtUs(cand)) continue;
59
+ const key = realpathOrNull(cand) ?? cand;
60
+ if (seen.has(key)) continue;
61
+ seen.add(key);
62
+ out.push(cand);
67
63
  }
68
64
  return out;
69
65
  }
70
66
 
71
- export function scanPathForClaude(): string | null {
72
- return scanPathForClaudeCandidates()[0] ?? null;
73
- }
74
-
75
- export function resolveRealClaude(): string {
76
- const cfg = loadConfig();
77
- if (cfg.claudeBin) {
78
- if (!existsSync(cfg.claudeBin)) {
79
- throw new Error(`configured claudeBin does not exist: ${cfg.claudeBin} - fix config.json`);
67
+ export function resolveRealBin(input: { name: string; key: BinKey }): string {
68
+ const configured = loadConfig()[input.key];
69
+ if (configured) {
70
+ if (!existsSync(configured)) {
71
+ throw new Error(`configured ${input.key} does not exist: ${configured} - fix config.json`);
80
72
  }
81
- if (pointsBackAtUs(cfg.claudeBin)) {
73
+ if (pointsBackAtUs(configured)) {
82
74
  throw new Error(
83
- `configured claudeBin (${cfg.claudeBin}) is tokenmaxxing's own wrapper - spawning it recurses. Point claudeBin at the real claude binary in ${paths.configJson}`,
75
+ `configured ${input.key} (${configured}) is tokenmaxxing's own wrapper - spawning it recurses. Point ${input.key} at the real ${input.name} binary in ${paths.configJson}`,
84
76
  );
85
77
  }
86
- return cfg.claudeBin;
78
+ return configured;
87
79
  }
88
- const scanned = scanPathForClaude();
80
+ const scanned = scanPathCandidates(input.name)[0];
89
81
  if (scanned) return scanned;
90
- throw new Error("could not locate the real `claude` binary (set claudeBin in config.json)");
82
+ throw new Error(`could not locate the real \`${input.name}\` binary (set ${input.key} in config.json)`);
91
83
  }
92
84
 
93
- export function verifyRealClaude(bin: string): string | null {
85
+ export function verifyRealBin(input: { bin: string; name: string; versionOk: (versionOutput: string) => boolean }): string | null {
94
86
  const env = { ...process.env, [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH), TOKENMAXXING_PROBE: "1" };
95
87
  let p;
96
88
  try {
97
- p = Bun.spawnSync([bin, "--version"], { env, stdout: "pipe", stderr: "pipe", timeout: 15_000, killSignal: "SIGKILL" });
89
+ p = Bun.spawnSync([input.bin, "--version"], { env, stdout: "pipe", stderr: "pipe", timeout: 15_000, killSignal: "SIGKILL" });
98
90
  } catch (e) {
99
91
  return errorMessage(e);
100
92
  }
101
93
  const outText = p.stdout.toString().trim();
102
94
  const err = p.stderr.toString().trim();
103
95
  if (p.exitCode === 0) {
104
- if (/claude/i.test(outText)) return null;
105
- return `--version output does not identify claude: "${outText.slice(0, 80)}"`;
96
+ if (input.versionOk(outText)) return null;
97
+ return `--version output does not identify ${input.name}: "${outText.slice(0, 80)}"`;
106
98
  }
107
99
  if (err.includes(LOOP_DIAGNOSIS)) return "it leads back into the tokenmaxxing wrapper (recursion)";
108
100
  return `--version exited ${p.exitCode ?? "on signal/timeout"}: ${(err || outText).slice(0, 160)}`;
109
101
  }
110
102
 
103
+ export const CLAUDE_BIN = { name: "claude", key: "claudeBin", versionOk: (out: string) => out.toLowerCase().includes("claude") } as const;
104
+ export const CODEX_BIN = { name: "codex", key: "codexBin", versionOk: (out: string) => out.toLowerCase().includes("codex") } as const;
105
+
111
106
  export function resolveVerifiedClaude(): string {
112
107
  const candidates: string[] = [];
113
108
  try {
114
- candidates.push(resolveRealClaude());
109
+ candidates.push(resolveRealBin(CLAUDE_BIN));
115
110
  } catch { }
116
- candidates.push(...scanPathForClaudeCandidates());
111
+ candidates.push(...scanPathCandidates(CLAUDE_BIN.name));
117
112
 
118
113
  const failures: string[] = [];
119
114
  for (const cand of uniq(candidates)) {
120
- const fail = verifyRealClaude(cand);
115
+ const fail = verifyRealBin({ ...CLAUDE_BIN, bin: cand });
121
116
  if (fail === null) return cand;
122
117
  failures.push(`${cand}: ${fail}`);
123
118
  }
package/src/lib/codex.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { existsSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { writeFileAtomic } from "./atomic.ts";
4
- import { MAX_WRAP_DEPTH, WRAP_DEPTH_ENV } from "./claudebin.ts";
4
+ import { CODEX_BIN, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealBin, verifyRealBin } from "./claudebin.ts";
5
5
  import { codexIdentityOf, codexStoreUsable, deleteCodexStoreAuth, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
6
- import { resolveRealCodex, verifyRealCodex } from "./codexbin.ts";
7
6
  import { CodexInvalidGrantError, CodexRefreshFailedError, refreshCodexAuth } from "./codexoauth.ts";
8
7
  import { seatCounts } from "./presence.ts";
9
8
  import { CodexUsageReadError, codexLimitLabel, fetchCodexUsage } from "./codexusage.ts";
@@ -113,7 +112,7 @@ async function prepareMove(target: Account): Promise<void> {
113
112
  }
114
113
 
115
114
  async function login(): Promise<Harvest | null> {
116
- const real = resolveRealCodex();
115
+ const real = resolveRealBin(CODEX_BIN);
117
116
  const onboardDir = codexPaths.onboardDir;
118
117
  rmSync(onboardDir, { recursive: true, force: true });
119
118
  writeFileAtomic(join(onboardDir, "config.toml"), 'cli_auth_credentials_store = "file"\n');
@@ -179,8 +178,8 @@ function storePinnedAwayFromFile(): boolean {
179
178
  }
180
179
 
181
180
  function preflight(): void {
182
- const real = resolveRealCodex();
183
- const fail = verifyRealCodex({ bin: real });
181
+ const real = resolveRealBin(CODEX_BIN);
182
+ const fail = verifyRealBin({ ...CODEX_BIN, bin: real });
184
183
  if (fail !== null) throw new Error(`codex binary failed verification: ${real}: ${fail}`);
185
184
  pinBinOverride({ key: "codexBin", bin: real });
186
185
  if (storePinnedAwayFromFile()) {
package/src/lib/grok.ts CHANGED
@@ -133,7 +133,7 @@ const base = statusOnlyProvider({
133
133
  liveAuthPath: () => join(grokPaths.home, "auth.json"),
134
134
  readAuth,
135
135
  liveId: () => grokSeatFromEnv(loadAccounts(grokPool).accounts.map((a) => a.id)),
136
- versionOk: (out) => out.trim().toLowerCase().includes("grok"),
136
+ versionOk: (out) => out.toLowerCase().includes("grok"),
137
137
  importIntro: "Opening an isolated grok login for your first pooled account - the login you already have stays as it is.",
138
138
  foundLive: (count) => `found ${count} grok login(s) in ${grokPaths.home} - pooling the first; use \`tokenmaxxing add --grok\` for the rest.`,
139
139
  installNotice: "grok pool is status-only: no supervisor or hooks installed. Use `tokenmaxxing status` to view pooled grok accounts.",
@@ -6,7 +6,7 @@ import { z } from "zod";
6
6
  import { codexPaths, codexStoreDirFor, HOME, optionalEnv, paths } from "./paths.ts";
7
7
  import { writeFileAtomic } from "./atomic.ts";
8
8
  import { installedBin, installSettings, isOurHookCommand, uninstallSettings } from "./settings.ts";
9
- import { resolveRealClaude } from "./claudebin.ts";
9
+ import { CLAUDE_BIN, resolveRealBin } from "./claudebin.ts";
10
10
  import { loadConfig } from "./state.ts";
11
11
  import { ErrnoSchema } from "./types.ts";
12
12
 
@@ -17,7 +17,7 @@ export function isBinDirAhead(): boolean {
17
17
  const ourIdx = dirs.indexOf(paths.binDir);
18
18
  if (ourIdx < 0) return false;
19
19
  try {
20
- const realDir = dirname(resolveRealClaude());
20
+ const realDir = dirname(resolveRealBin(CLAUDE_BIN));
21
21
  const realIdx = dirs.indexOf(realDir);
22
22
  return realIdx < 0 || ourIdx < realIdx;
23
23
  } catch {
@@ -36,11 +36,11 @@ export function isNixPackaged(): boolean {
36
36
  }
37
37
  }
38
38
 
39
- export function skipImperativeTimer(): boolean {
39
+ function skipImperativeTimer(): boolean {
40
40
  return envFlag("TOKENMAXXING_SKIP_TIMER");
41
41
  }
42
42
 
43
- export function skipImperativeHub(): boolean {
43
+ function skipImperativeHub(): boolean {
44
44
  return envFlag("TOKENMAXXING_SKIP_HUB");
45
45
  }
46
46
 
@@ -106,8 +106,8 @@ export function installSupervisor(): InstallOutcome {
106
106
  claudeWrapper: paths.supervisorLink,
107
107
  installedBin: target,
108
108
  pathAhead: isBinDirAhead(),
109
- timerLoaded: installCheckTimer(checkIntervalS),
110
- hubLoaded: installHubService(),
109
+ timerLoaded: installJob(CHECK_JOB, checkIntervalS),
110
+ hubLoaded: installJob(HUB_JOB, checkIntervalS),
111
111
  checkIntervalS,
112
112
  };
113
113
  }
@@ -205,9 +205,67 @@ export function uninstallCodexSupervisor(): void {
205
205
  if (existsSync(codexSupervisorLink())) rmSync(codexSupervisorLink(), { force: true });
206
206
  }
207
207
 
208
- const LAUNCHD_LABEL = "com.tokenmaxxing.check";
209
- const LAUNCHD_HUB_LABEL = "com.tokenmaxxing.hub";
210
- const HUB_UNIT = "tokenmaxxing-hub.service";
208
+ export type UnitJob = {
209
+ label: string;
210
+ arg: string;
211
+ log: string;
212
+ skip: () => boolean;
213
+ unit: string;
214
+ launchd: (intervalS: number) => string;
215
+ systemd: Record<string, (exec: string, intervalS: number) => string>;
216
+ };
217
+
218
+ export const CHECK_JOB: UnitJob = {
219
+ label: "com.tokenmaxxing.check",
220
+ arg: "check",
221
+ log: "check.stderr.log",
222
+ skip: skipImperativeTimer,
223
+ unit: "tokenmaxxing-check.timer",
224
+ launchd: (intervalS) => ` <key>StartInterval</key><integer>${intervalS}</integer>\n`,
225
+ systemd: {
226
+ "tokenmaxxing-check.service": (exec) => `[Unit]
227
+ Description=tokenmaxxing account-switch check
228
+
229
+ [Service]
230
+ Type=oneshot
231
+ ExecStart=${exec}
232
+ ${systemdEnv()}`,
233
+ "tokenmaxxing-check.timer": (_exec, intervalS) => `[Unit]
234
+ Description=tokenmaxxing periodic account-switch check
235
+
236
+ [Timer]
237
+ OnBootSec=${intervalS}
238
+ OnUnitActiveSec=${intervalS}
239
+ AccuracySec=${Math.max(1, Math.floor(intervalS / 12))}
240
+
241
+ [Install]
242
+ WantedBy=timers.target
243
+ `,
244
+ },
245
+ };
246
+
247
+ export const HUB_JOB: UnitJob = {
248
+ label: "com.tokenmaxxing.hub",
249
+ arg: "serve",
250
+ log: "hub.stderr.log",
251
+ skip: skipImperativeHub,
252
+ unit: "tokenmaxxing-hub.service",
253
+ launchd: () => ` <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n <key>RunAtLoad</key><true/>\n`,
254
+ systemd: {
255
+ "tokenmaxxing-hub.service": (exec) => `[Unit]
256
+ Description=tokenmaxxing usage hub
257
+ StartLimitIntervalSec=0
258
+
259
+ [Service]
260
+ ExecStart=${exec}
261
+ ${systemdEnv()}Restart=on-failure
262
+ RestartSec=5
263
+
264
+ [Install]
265
+ WantedBy=default.target
266
+ `,
267
+ },
268
+ };
211
269
 
212
270
  function launchdPlist(label: string): string {
213
271
  return join(paths.launchdAgentsDir, `${label}.plist`);
@@ -233,110 +291,56 @@ function launchdEnv(): string {
233
291
  return ` <key>EnvironmentVariables</key><dict><key>TOKENMAXXING_HOME</key><string>${escape(paths.home)}</string></dict>\n`;
234
292
  }
235
293
 
236
- function installCheckTimer(intervalS: number): boolean {
237
- if (skipImperativeTimer()) return true;
294
+ function installJob(job: UnitJob, intervalS: number): boolean {
295
+ if (job.skip()) return true;
238
296
 
239
297
  if (process.platform === "darwin") {
240
- const plist = launchdPlist(LAUNCHD_LABEL);
298
+ const plist = launchdPlist(job.label);
241
299
  writeFileAtomic(
242
300
  plist,
243
301
  `<?xml version="1.0" encoding="UTF-8"?>
244
302
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
245
303
  <plist version="1.0">
246
304
  <dict>
247
- <key>Label</key><string>${LAUNCHD_LABEL}</string>
248
- <key>ProgramArguments</key><array><string>${escape(installedBin())}</string><string>check</string></array>
249
- <key>StartInterval</key><integer>${intervalS}</integer>
250
- ${launchdEnv()} <key>StandardOutPath</key><string>/dev/null</string>
251
- <key>StandardErrorPath</key><string>${escape(join(paths.home, "check.stderr.log"))}</string>
305
+ <key>Label</key><string>${job.label}</string>
306
+ <key>ProgramArguments</key><array><string>${escape(installedBin())}</string><string>${job.arg}</string></array>
307
+ ${job.launchd(intervalS)}${launchdEnv()} <key>StandardOutPath</key><string>/dev/null</string>
308
+ <key>StandardErrorPath</key><string>${escape(join(paths.home, job.log))}</string>
252
309
  </dict>
253
310
  </plist>
254
311
  `,
255
312
  0o644,
256
313
  );
257
314
  const domain = launchdDomain();
258
- run(["launchctl", "bootout", `${domain}/${LAUNCHD_LABEL}`]);
259
- return run(["launchctl", "bootstrap", domain, plist]) || checkTimerHealthy();
260
- }
261
-
262
- const exec = `"${installedBin().replaceAll("%", "%%")}" check`;
263
- writeFileAtomic(
264
- join(paths.systemdUserDir, "tokenmaxxing-check.service"),
265
- `[Unit]
266
- Description=tokenmaxxing account-switch check
267
-
268
- [Service]
269
- Type=oneshot
270
- ExecStart=${exec}
271
- ${systemdEnv()}`,
272
- 0o644,
273
- );
274
- writeFileAtomic(
275
- join(paths.systemdUserDir, "tokenmaxxing-check.timer"),
276
- `[Unit]
277
- Description=tokenmaxxing periodic account-switch check
278
-
279
- [Timer]
280
- OnBootSec=${intervalS}
281
- OnUnitActiveSec=${intervalS}
282
- AccuracySec=${Math.max(1, Math.floor(intervalS / 12))}
283
-
284
- [Install]
285
- WantedBy=timers.target
286
- `,
287
- 0o644,
288
- );
289
- return (
290
- run(["systemctl", "--user", "daemon-reload"]) &&
291
- run(["systemctl", "--user", "enable", "--now", "tokenmaxxing-check.timer"])
292
- );
293
- }
294
-
295
- export function timerActivationHint(): string {
296
- if (process.platform === "darwin") {
297
- return `launchctl bootstrap gui/$(id -u) ${launchdPlist(LAUNCHD_LABEL)}`;
298
- }
299
- return "systemctl --user daemon-reload && systemctl --user enable --now tokenmaxxing-check.timer";
300
- }
301
-
302
- export function hubActivationHint(): string {
303
- if (process.platform === "darwin") {
304
- return `launchctl bootstrap gui/$(id -u) ${launchdPlist(LAUNCHD_HUB_LABEL)}`;
315
+ run(["launchctl", "bootout", `${domain}/${job.label}`]);
316
+ return run(["launchctl", "bootstrap", domain, plist]) || jobHealthy(job);
305
317
  }
306
- return `systemctl --user daemon-reload && systemctl --user enable --now ${HUB_UNIT}`;
307
- }
308
318
 
309
- export function checkTimerHealthy(): boolean {
310
- if (skipImperativeTimer()) return true;
311
- if (process.platform === "darwin") {
312
- return existsSync(launchdPlist(LAUNCHD_LABEL)) && run(["launchctl", "print", `${launchdDomain()}/${LAUNCHD_LABEL}`]);
313
- }
314
- return (
315
- existsSync(join(paths.systemdUserDir, "tokenmaxxing-check.timer")) &&
316
- run(["systemctl", "--user", "is-active", "--quiet", "tokenmaxxing-check.timer"])
317
- );
319
+ const exec = `"${installedBin().replaceAll("%", "%%")}" ${job.arg}`;
320
+ for (const [file, text] of Object.entries(job.systemd)) writeFileAtomic(join(paths.systemdUserDir, file), text(exec, intervalS), 0o644);
321
+ return run(["systemctl", "--user", "daemon-reload"]) && run(["systemctl", "--user", "enable", "--now", job.unit]);
318
322
  }
319
323
 
320
- export function hubServiceHealthy(): boolean {
321
- if (skipImperativeHub()) return true;
324
+ export function activationHint(job: UnitJob): string {
322
325
  if (process.platform === "darwin") {
323
- return existsSync(launchdPlist(LAUNCHD_HUB_LABEL)) && run(["launchctl", "print", `${launchdDomain()}/${LAUNCHD_HUB_LABEL}`]);
326
+ return `launchctl bootstrap gui/$(id -u) ${launchdPlist(job.label)}`;
324
327
  }
325
- return existsSync(join(paths.systemdUserDir, HUB_UNIT)) && run(["systemctl", "--user", "is-active", "--quiet", HUB_UNIT]);
328
+ return `systemctl --user daemon-reload && systemctl --user enable --now ${job.unit}`;
326
329
  }
327
330
 
328
- export function timerDeactivationHint(): string {
331
+ export function jobHealthy(job: UnitJob): boolean {
332
+ if (job.skip()) return true;
329
333
  if (process.platform === "darwin") {
330
- return `launchctl bootout gui/$(id -u)/${LAUNCHD_LABEL}`;
334
+ return existsSync(launchdPlist(job.label)) && run(["launchctl", "print", `${launchdDomain()}/${job.label}`]);
331
335
  }
332
- return "systemctl --user disable --now tokenmaxxing-check.timer";
336
+ return existsSync(join(paths.systemdUserDir, job.unit)) && run(["systemctl", "--user", "is-active", "--quiet", job.unit]);
333
337
  }
334
338
 
335
- export function hubDeactivationHint(): string {
339
+ export function deactivationHint(job: UnitJob): string {
336
340
  if (process.platform === "darwin") {
337
- return `launchctl bootout gui/$(id -u)/${LAUNCHD_HUB_LABEL}`;
341
+ return `launchctl bootout gui/$(id -u)/${job.label}`;
338
342
  }
339
- return `systemctl --user disable --now ${HUB_UNIT}`;
343
+ return `systemctl --user disable --now ${job.unit}`;
340
344
  }
341
345
 
342
346
  function launchdJobLoaded(label: string): "loaded" | "not-loaded" | "unavailable" {
@@ -361,108 +365,42 @@ function systemdUnitActive(unit: string): "active" | "not-active" | "unavailable
361
365
  }
362
366
  }
363
367
 
364
- function removeTimerUnits(): void {
365
- if (process.platform === "darwin") {
366
- rmSync(launchdPlist(LAUNCHD_LABEL), { force: true });
367
- return;
368
- }
369
- rmSync(join(paths.systemdUserDir, "tokenmaxxing-check.timer"), { force: true });
370
- rmSync(join(paths.systemdUserDir, "tokenmaxxing-check.service"), { force: true });
368
+ function jobFiles(job: UnitJob): string[] {
369
+ if (process.platform === "darwin") return [launchdPlist(job.label)];
370
+ return Object.keys(job.systemd).map((file) => join(paths.systemdUserDir, file));
371
+ }
372
+
373
+ function removeJobFiles(job: UnitJob): void {
374
+ for (const file of jobFiles(job)) rmSync(file, { force: true });
371
375
  }
372
376
 
373
377
  type UnitOutcome = "removed" | "skipped" | "still-loaded";
374
378
 
375
- function uninstallCheckTimer(live: boolean): UnitOutcome {
376
- if (skipImperativeTimer()) return "skipped";
379
+ function uninstallJob(job: UnitJob, live: boolean): UnitOutcome {
380
+ if (job.skip()) return "skipped";
377
381
  if (!live) {
378
- removeTimerUnits();
382
+ removeJobFiles(job);
379
383
  return "removed";
380
384
  }
381
385
  if (process.platform === "darwin") {
382
- const loaded = launchdJobLoaded(LAUNCHD_LABEL);
383
- const deactivated = loaded === "loaded" ? run(["launchctl", "bootout", `${launchdDomain()}/${LAUNCHD_LABEL}`]) : loaded === "not-loaded";
384
- removeTimerUnits();
386
+ const loaded = launchdJobLoaded(job.label);
387
+ const deactivated = loaded === "loaded" ? run(["launchctl", "bootout", `${launchdDomain()}/${job.label}`]) : loaded === "not-loaded";
388
+ removeJobFiles(job);
385
389
  return deactivated ? "removed" : "still-loaded";
386
390
  }
387
- const active = systemdUnitActive("tokenmaxxing-check.timer");
388
- const deactivated = active !== "unavailable" && (run(["systemctl", "--user", "disable", "--now", "tokenmaxxing-check.timer"]) || active === "not-active");
389
- removeTimerUnits();
391
+ const active = systemdUnitActive(job.unit);
392
+ const deactivated = active !== "unavailable" && (run(["systemctl", "--user", "disable", "--now", job.unit]) || active === "not-active");
393
+ removeJobFiles(job);
390
394
  run(["systemctl", "--user", "daemon-reload"]);
391
395
  return deactivated ? "removed" : "still-loaded";
392
396
  }
393
397
 
394
- function installHubService(): boolean {
395
- if (skipImperativeHub()) return true;
396
-
397
- if (process.platform === "darwin") {
398
- const plist = launchdPlist(LAUNCHD_HUB_LABEL);
399
- writeFileAtomic(
400
- plist,
401
- `<?xml version="1.0" encoding="UTF-8"?>
402
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
403
- <plist version="1.0">
404
- <dict>
405
- <key>Label</key><string>${LAUNCHD_HUB_LABEL}</string>
406
- <key>ProgramArguments</key><array><string>${escape(installedBin())}</string><string>serve</string></array>
407
- <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
408
- <key>RunAtLoad</key><true/>
409
- ${launchdEnv()} <key>StandardOutPath</key><string>/dev/null</string>
410
- <key>StandardErrorPath</key><string>${escape(join(paths.home, "hub.stderr.log"))}</string>
411
- </dict>
412
- </plist>
413
- `,
414
- 0o644,
415
- );
416
- const domain = launchdDomain();
417
- run(["launchctl", "bootout", `${domain}/${LAUNCHD_HUB_LABEL}`]);
418
- return run(["launchctl", "bootstrap", domain, plist]) || hubServiceHealthy();
419
- }
420
-
421
- const exec = `"${installedBin().replaceAll("%", "%%")}" serve`;
422
- writeFileAtomic(
423
- join(paths.systemdUserDir, HUB_UNIT),
424
- `[Unit]
425
- Description=tokenmaxxing usage hub
426
- StartLimitIntervalSec=0
427
-
428
- [Service]
429
- ExecStart=${exec}
430
- ${systemdEnv()}Restart=on-failure
431
- RestartSec=5
432
-
433
- [Install]
434
- WantedBy=default.target
435
- `,
436
- 0o644,
437
- );
438
- return run(["systemctl", "--user", "daemon-reload"]) && run(["systemctl", "--user", "enable", "--now", HUB_UNIT]);
439
- }
440
-
441
- function removeHubUnits(): void {
442
- if (process.platform === "darwin") {
443
- rmSync(launchdPlist(LAUNCHD_HUB_LABEL), { force: true });
444
- return;
445
- }
446
- rmSync(join(paths.systemdUserDir, HUB_UNIT), { force: true });
447
- }
448
-
449
- function uninstallHubService(live: boolean): UnitOutcome {
450
- if (skipImperativeHub()) return "skipped";
451
- if (!live) {
452
- removeHubUnits();
453
- return "removed";
454
- }
455
- if (process.platform === "darwin") {
456
- const loaded = launchdJobLoaded(LAUNCHD_HUB_LABEL);
457
- const deactivated = loaded === "loaded" ? run(["launchctl", "bootout", `${launchdDomain()}/${LAUNCHD_HUB_LABEL}`]) : loaded === "not-loaded";
458
- removeHubUnits();
459
- return deactivated ? "removed" : "still-loaded";
460
- }
461
- const active = systemdUnitActive(HUB_UNIT);
462
- const deactivated = active !== "unavailable" && (run(["systemctl", "--user", "disable", "--now", HUB_UNIT]) || active === "not-active");
463
- removeHubUnits();
464
- run(["systemctl", "--user", "daemon-reload"]);
465
- return deactivated ? "removed" : "still-loaded";
398
+ function uninstallTarget(job: UnitJob, live: boolean): string {
399
+ const files = jobFiles(job).join(" and ");
400
+ if (!live) return files;
401
+ return process.platform === "darwin"
402
+ ? `${files} (and launchctl bootout ${launchdDomain()}/${job.label})`
403
+ : `${files} (and systemctl --user disable --now ${job.unit})`;
466
404
  }
467
405
 
468
406
  export function shellRcPath(): string | null {
@@ -563,20 +501,11 @@ export function onLoginHome(): boolean {
563
501
  }
564
502
 
565
503
  export function uninstallTargets(live: boolean): string[] {
566
- const timer =
567
- process.platform === "darwin"
568
- ? `${launchdPlist(LAUNCHD_LABEL)}${live ? ` (and launchctl bootout ${launchdDomain()}/${LAUNCHD_LABEL})` : ""}`
569
- : `${join(paths.systemdUserDir, "tokenmaxxing-check.timer")} and .service${live ? " (and systemctl --user disable --now tokenmaxxing-check.timer)" : ""}`;
570
- const hub =
571
- process.platform === "darwin"
572
- ? `${launchdPlist(LAUNCHD_HUB_LABEL)}${live ? ` (and launchctl bootout ${launchdDomain()}/${LAUNCHD_HUB_LABEL})` : ""}`
573
- : `${join(paths.systemdUserDir, HUB_UNIT)}${live ? ` (and systemctl --user disable --now ${HUB_UNIT})` : ""}`;
574
504
  const rc = shellRcPath();
575
505
  return [
576
506
  `${paths.claudeSettings}: the hook and statusline entries`,
577
507
  `${codexPaths.hooksJson}: the codex Stop hook entry`,
578
- ...(skipImperativeTimer() ? [] : [timer]),
579
- ...(skipImperativeHub() ? [] : [hub]),
508
+ ...[CHECK_JOB, HUB_JOB].filter((job) => !job.skip()).map((job) => uninstallTarget(job, live)),
580
509
  paths.supervisorLink,
581
510
  codexSupervisorLink(),
582
511
  join(paths.binDir, "xx"),
@@ -587,8 +516,8 @@ export function uninstallTargets(live: boolean): string[] {
587
516
 
588
517
  export function uninstallSupervisor(input: { live: boolean }): UninstallOutcome {
589
518
  uninstallSettings();
590
- const timer = uninstallCheckTimer(input.live);
591
- const hub = uninstallHubService(input.live);
519
+ const timer = uninstallJob(CHECK_JOB, input.live);
520
+ const hub = uninstallJob(HUB_JOB, input.live);
592
521
  uninstallCodexSupervisor();
593
522
  for (const f of [paths.supervisorLink, join(paths.binDir, "xx"), installedBin()]) {
594
523
  if (existsSync(f)) rmSync(f, { force: true });
@@ -63,7 +63,7 @@ export const opencodeGo: Provider = statusOnlyProvider({
63
63
  liveAuthPath,
64
64
  readAuth,
65
65
  liveId: () => null,
66
- versionOk: (out) => out.trim() !== "",
66
+ versionOk: (out) => out !== "",
67
67
  importIntro: "Pooling your opencode-go API key - your existing opencode auth stays as it is.",
68
68
  foundLive: () => `found an opencode-go credential in ${liveAuthPath()} - pooling it; use \`tokenmaxxing add --opencode-go\` for more keys.`,
69
69
  installNotice: "opencode-go pool is status-only: no supervisor or hooks installed. Use `tokenmaxxing status` to view pooled keys.",
package/src/lib/state.ts CHANGED
@@ -39,7 +39,9 @@ export function loadConfig(): Config {
39
39
  return cfg;
40
40
  }
41
41
 
42
- export function pinBinOverride(input: { key: "claudeBin" | "codexBin" | "grokBin" | "opencodeBin"; bin: string }): void {
42
+ export type BinKey = "claudeBin" | "codexBin" | "grokBin" | "opencodeBin";
43
+
44
+ export function pinBinOverride(input: { key: BinKey; bin: string }): void {
43
45
  let raw: Record<string, unknown> = {};
44
46
  if (existsSync(paths.configJson)) {
45
47
  raw = z.record(z.string(), z.unknown()).parse(JSON.parse(readFileSync(paths.configJson, "utf8")));
@@ -1,8 +1,9 @@
1
- import { existsSync, mkdirSync, rmSync } from "node:fs";
1
+ import { mkdirSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { resolveRealBin, verifyRealBin } from "./claudebin.ts";
3
4
  import type { PoolPaths } from "./paths.ts";
4
5
  import type { Provider, SampleReport } from "./provider.ts";
5
- import { loadConfig, pinBinOverride, type Harvest } from "./state.ts";
6
+ import { pinBinOverride, type Harvest } from "./state.ts";
6
7
  import type { Account } from "./types.ts";
7
8
  import { c } from "../cli/render.ts";
8
9
 
@@ -31,23 +32,7 @@ export type StatusOnlySpec = {
31
32
  };
32
33
 
33
34
  export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
34
- function resolveBin(): string {
35
- const configured = loadConfig()[spec.binKey];
36
- if (configured) {
37
- if (!existsSync(configured)) throw new Error(`configured ${spec.binKey} does not exist: ${configured} - fix config.json`);
38
- return configured;
39
- }
40
- for (const d of (process.env.PATH ?? "").split(":")) {
41
- if (!d) continue;
42
- const cand = join(d, spec.binName);
43
- try {
44
- if (existsSync(cand)) return cand;
45
- } catch {
46
- continue;
47
- }
48
- }
49
- throw new Error(`could not locate the real \`${spec.binName}\` binary (set ${spec.binKey} in config.json)`);
50
- }
35
+ const bin = { name: spec.binName, key: spec.binKey, versionOk: spec.versionOk };
51
36
 
52
37
  async function storeUsable(a: Account): Promise<boolean> {
53
38
  try {
@@ -66,7 +51,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
66
51
  }
67
52
 
68
53
  async function login(): Promise<Harvest | null> {
69
- const real = resolveBin();
54
+ const real = resolveRealBin(bin);
70
55
  const onboardDir = spec.onboardDir;
71
56
  rmSync(onboardDir, { recursive: true, force: true });
72
57
  mkdirSync(onboardDir, { recursive: true });
@@ -102,11 +87,9 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
102
87
  }
103
88
 
104
89
  function preflight(): void {
105
- const real = resolveBin();
106
- const p = Bun.spawnSync([real, "--version"], { stdout: "pipe", stderr: "pipe", timeout: 15_000 });
107
- if (p.exitCode !== 0 || !spec.versionOk(p.stdout.toString())) {
108
- throw new Error(`${spec.binName} binary failed verification: ${real}`);
109
- }
90
+ const real = resolveRealBin(bin);
91
+ const fail = verifyRealBin({ ...bin, bin: real });
92
+ if (fail !== null) throw new Error(`${spec.binName} binary failed verification: ${real}: ${fail}`);
110
93
  pinBinOverride({ key: spec.binKey, bin: real });
111
94
  }
112
95
 
package/src/main.ts CHANGED
@@ -23,7 +23,7 @@ import { cmdCheck } from "./cli/check.ts";
23
23
  import { cmdConfig } from "./cli/config.ts";
24
24
  import { cmdSeat } from "./cli/seat.ts";
25
25
  import { cmdServe } from "./cli/serve.ts";
26
- import { hubDeactivationHint, onLoginHome, timerDeactivationHint, uninstallSupervisor, uninstallTargets } from "./lib/install.ts";
26
+ import { CHECK_JOB, deactivationHint, HUB_JOB, onLoginHome, uninstallSupervisor, uninstallTargets } from "./lib/install.ts";
27
27
  import { errorMessage } from "./lib/log.ts";
28
28
  import { HOME } from "./lib/paths.ts";
29
29
  import { c, emitError } from "./cli/render.ts";
@@ -177,8 +177,8 @@ async function main(): Promise<number> {
177
177
  ...(out.pathLineRemoved ? ["rc PATH line"] : []),
178
178
  ];
179
179
  console.log(`removed ${removed.join(", ")}`);
180
- if (out.timer === "still-loaded") console.log(c.yellow(`⚠ the check job may still be loaded - run: ${timerDeactivationHint()}`));
181
- if (out.hub === "still-loaded") console.log(c.yellow(`⚠ the usage hub job may still be loaded - run: ${hubDeactivationHint()}`));
180
+ if (out.timer === "still-loaded") console.log(c.yellow(`⚠ the check job may still be loaded - run: ${deactivationHint(CHECK_JOB)}`));
181
+ if (out.hub === "still-loaded") console.log(c.yellow(`⚠ the usage hub job may still be loaded - run: ${deactivationHint(HUB_JOB)}`));
182
182
  if (!out.pathLineRemoved) console.log(c.dim("(no tokenmaxxing PATH line found in the shell rc)"));
183
183
  console.log(`kept: accounts.json, config.json, and every account credential store (claude: stores/ and its keychain items on macOS; codex: codex-stores/; grok: grok-stores/; opencode-go: opencode-go-stores/) - remove accounts with \`xx rm\` to delete their credentials`);
184
184
  return 0;
@@ -1,55 +0,0 @@
1
- import { existsSync, statSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { LOOP_DIAGNOSIS, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, pointsBackAtUs } from "./claudebin.ts";
4
- import { errorMessage } from "./log.ts";
5
- import { loadConfig } from "./state.ts";
6
- import { paths } from "./paths.ts";
7
-
8
- function scanPathForCodex(): string | null {
9
- for (const d of (process.env.PATH ?? "").split(":")) {
10
- if (!d) continue;
11
- const cand = join(d, "codex");
12
- try {
13
- if (existsSync(cand) && statSync(cand).isFile() && !pointsBackAtUs(cand)) return cand;
14
- } catch {
15
- continue;
16
- }
17
- }
18
- return null;
19
- }
20
-
21
- export function resolveRealCodex(): string {
22
- const cfg = loadConfig();
23
- if (cfg.codexBin) {
24
- if (!existsSync(cfg.codexBin)) {
25
- throw new Error(`configured codexBin does not exist: ${cfg.codexBin} - fix config.json`);
26
- }
27
- if (pointsBackAtUs(cfg.codexBin)) {
28
- throw new Error(
29
- `configured codexBin (${cfg.codexBin}) is tokenmaxxing's own wrapper - spawning it recurses. Point codexBin at the real codex binary in ${paths.configJson}`,
30
- );
31
- }
32
- return cfg.codexBin;
33
- }
34
- const scanned = scanPathForCodex();
35
- if (scanned) return scanned;
36
- throw new Error("could not locate the real `codex` binary (set codexBin in config.json)");
37
- }
38
-
39
- export function verifyRealCodex(input: { bin: string }): string | null {
40
- const env = { ...process.env, [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH), TOKENMAXXING_PROBE: "1" };
41
- let p;
42
- try {
43
- p = Bun.spawnSync([input.bin, "--version"], { env, stdout: "pipe", stderr: "pipe", timeout: 15_000, killSignal: "SIGKILL" });
44
- } catch (e) {
45
- return errorMessage(e);
46
- }
47
- const outText = p.stdout.toString().trim();
48
- const err = p.stderr.toString().trim();
49
- if (p.exitCode === 0) {
50
- if (outText.toLowerCase().includes("codex")) return null;
51
- return `--version output does not identify codex: "${outText.slice(0, 80)}"`;
52
- }
53
- if (err.includes(LOOP_DIAGNOSIS)) return "it leads back into the tokenmaxxing wrapper (recursion)";
54
- return `--version exited ${p.exitCode ?? "on signal/timeout"}: ${(err || outText).slice(0, 160)}`;
55
- }