tokenmaxxing 1.56.0 → 1.58.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.
Files changed (53) hide show
  1. package/DESIGN.md +7 -7
  2. package/README.md +3 -3
  3. package/package.json +1 -1
  4. package/src/cli/auth.ts +6 -17
  5. package/src/cli/check.ts +2 -2
  6. package/src/cli/cloudrun.ts +12 -19
  7. package/src/cli/config.ts +2 -1
  8. package/src/cli/doctor.ts +5 -4
  9. package/src/cli/init.ts +0 -1
  10. package/src/cli/render.ts +24 -9
  11. package/src/cli/rm.ts +0 -4
  12. package/src/cli/status.ts +34 -57
  13. package/src/entries/codexstophook.ts +10 -23
  14. package/src/entries/codexsupervisor.ts +30 -72
  15. package/src/entries/statusline.ts +17 -26
  16. package/src/entries/stopfailurehook.ts +4 -5
  17. package/src/entries/stophook.ts +12 -10
  18. package/src/entries/subagentstatusline.ts +4 -10
  19. package/src/entries/supervisor.ts +91 -116
  20. package/src/lib/claude.ts +11 -12
  21. package/src/lib/claudebin.ts +3 -2
  22. package/src/lib/claudelock.ts +2 -2
  23. package/src/lib/cloud.ts +4 -8
  24. package/src/lib/codex.ts +3 -3
  25. package/src/lib/codexauth.ts +2 -11
  26. package/src/lib/codexbin.ts +2 -1
  27. package/src/lib/codexoauth.ts +3 -8
  28. package/src/lib/codexusage.ts +5 -5
  29. package/src/lib/compact.ts +5 -18
  30. package/src/lib/credstore.ts +1 -6
  31. package/src/lib/decide.ts +6 -24
  32. package/src/lib/grok.ts +38 -152
  33. package/src/lib/http.ts +1 -2
  34. package/src/lib/install.ts +20 -38
  35. package/src/lib/keychain.ts +1 -4
  36. package/src/lib/log.ts +5 -5
  37. package/src/lib/oauth.ts +6 -5
  38. package/src/lib/opencodego.ts +31 -140
  39. package/src/lib/paths.ts +6 -22
  40. package/src/lib/picker.ts +9 -20
  41. package/src/lib/presence.ts +4 -5
  42. package/src/lib/proc.ts +22 -0
  43. package/src/lib/sample.ts +25 -24
  44. package/src/lib/sessions.ts +5 -6
  45. package/src/lib/settings.ts +1 -8
  46. package/src/lib/state.ts +6 -29
  47. package/src/lib/statusonly.ts +143 -0
  48. package/src/lib/supervise.ts +111 -0
  49. package/src/lib/types.ts +27 -36
  50. package/src/lib/update.ts +6 -7
  51. package/src/lib/usage.ts +40 -191
  52. package/src/main.ts +5 -5
  53. package/src/entries/sessionstart.ts +0 -33
@@ -1,17 +1,18 @@
1
1
  import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { z } from "zod";
4
- import { codexPaths, codexPool, paths } from "../lib/paths.ts";
4
+ import { codexPaths, codexPool } from "../lib/paths.ts";
5
5
  import { withLock } from "../lib/lock.ts";
6
- import { LOOP_DIAGNOSIS, MAX_WRAP_DEPTH, UNMANAGED_ENV, WRAP_DEPTH_ENV, WRAP_RATE_MAX, WRAP_RATE_WINDOW_MS, wrapDepth, wrapperEntryRateTripped } from "../lib/claudebin.ts";
6
+ import { UNMANAGED_ENV, WRAP_DEPTH_ENV, wrapDepth } from "../lib/claudebin.ts";
7
7
  import { resolveRealCodex } from "../lib/codexbin.ts";
8
8
  import { ensureCodexStoreHome } from "../lib/codexauth.ts";
9
9
  import { pickCodexSeat } from "../lib/codex.ts";
10
- import { clearPresence, writePresence } from "../lib/presence.ts";
11
- import { saveTermios, restoreTermios } from "../lib/tty.ts";
10
+ import { clearPresence } from "../lib/presence.ts";
11
+ import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
12
+ import { saveTermios } from "../lib/tty.ts";
12
13
  import { loadAccounts } from "../lib/state.ts";
13
14
  import { CodexRespawnMarkerSchema } from "../lib/types.ts";
14
- import { log } from "../lib/log.ts";
15
+ import { errorMessage, log } from "../lib/log.ts";
15
16
 
16
17
  export const CODEX_SUPERVISOR_ID_ENV = "TOKENMAXXING_CODEX_SUPERVISOR_ID";
17
18
 
@@ -27,7 +28,7 @@ function readCodexMarker(marker: string): z.infer<typeof CodexRespawnMarkerSchem
27
28
  try {
28
29
  return CodexRespawnMarkerSchema.parse(JSON.parse(readFileSync(marker, "utf8")));
29
30
  } catch (e) {
30
- log("codexsupervisor.marker_invalid", { err: e instanceof Error ? e.message : String(e) });
31
+ log("codexsupervisor.marker_invalid", { err: errorMessage(e) });
31
32
  throw new Error(
32
33
  `${marker} is corrupt (unparsable JSON or off-schema) - the codex session was stopped instead of resumed on a stale target; inspect and remove the marker, then run \`codex resume\``,
33
34
  );
@@ -54,52 +55,17 @@ export function shouldManageCodex(input: { argv: string[] }): boolean {
54
55
  return firstPositional === null || !NONINTERACTIVE_SUBCMDS.has(firstPositional);
55
56
  }
56
57
 
57
- async function recordCodexPresence(child: { pid: number; exitCode: number | null; signalCode: string | null; kill: () => void; exited: Promise<unknown> }, supervisorId: string, seat: { id: string }, savedTermios: string | null): Promise<void> {
58
- for (let attempt = 0; ; attempt++) {
59
- try {
60
- writePresence({ dir: codexPaths.presenceDir, id: supervisorId, accountId: seat.id, pid: child.pid });
61
- return;
62
- } catch (e) {
63
- if (child.exitCode !== null || child.signalCode !== null) return;
64
- if (attempt >= 9) {
65
- log("codexsupervisor.presence_failed", { err: e instanceof Error ? e.message : String(e) });
66
- child.kill();
67
- await child.exited;
68
- restoreTermios(savedTermios);
69
- throw new Error("could not write the codex presence file - refusing to run an unprotected session (its account would look like a swap target)");
70
- }
71
- await Bun.sleep(100);
72
- }
73
- }
74
- }
75
-
76
58
  export async function runCodexSupervisor(input: { argv: string[] }): Promise<number> {
77
59
  const { argv } = input;
78
- const depth = wrapDepth();
79
- if (depth >= MAX_WRAP_DEPTH) {
80
- console.error(
81
- `tokenmaxxing: ${LOOP_DIAGNOSIS} (depth ${depth}) - codexBin in ${paths.configJson} does not launch the real codex binary. Fix codexBin, then run \`tokenmaxxing doctor\`.`,
82
- );
83
- log("codexsupervisor.loop_abort", { depth });
84
- return 1;
85
- }
86
- if (wrapperEntryRateTripped(Date.now())) {
87
- console.error(
88
- `tokenmaxxing: ${LOOP_DIAGNOSIS} (over ${WRAP_RATE_MAX} wrapper entries in ${WRAP_RATE_WINDOW_MS / 1000}s) - codexBin in ${paths.configJson} does not launch the real codex binary. Fix codexBin, then run \`tokenmaxxing doctor\`.`,
89
- );
90
- log("codexsupervisor.rate_abort", { max: WRAP_RATE_MAX });
91
- return 1;
92
- }
60
+ if (loopGuardTripped("codex")) return 1;
93
61
 
94
62
  const real = resolveRealCodex();
95
- const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(depth + 1) };
63
+ const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
96
64
 
97
65
  if (!shouldManageCodex({ argv }) || process.env[UNMANAGED_ENV]) {
98
66
  const passthroughEnv: Record<string, string | undefined> = { ...childEnv };
99
67
  delete passthroughEnv[CODEX_SUPERVISOR_ID_ENV];
100
- const p = Bun.spawn([real, ...argv], { stdin: "inherit", stdout: "inherit", stderr: "inherit", env: passthroughEnv });
101
- await p.exited;
102
- return p.exitCode ?? (p.signalCode ? 1 : 0);
68
+ return runPassthrough({ real, argv, env: passthroughEnv });
103
69
  }
104
70
 
105
71
  const supervisorId = crypto.randomUUID();
@@ -131,37 +97,29 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
131
97
  ...(store ? { CODEX_HOME: store } : {}),
132
98
  },
133
99
  });
134
- if (picked) await recordCodexPresence(spawned, supervisorId, picked, savedTermios);
100
+ if (picked) {
101
+ await recordPresenceOrStop({
102
+ child: spawned,
103
+ dir: codexPaths.presenceDir,
104
+ id: supervisorId,
105
+ accountId: picked.id,
106
+ event: "codexsupervisor.presence_failed",
107
+ message: "could not write the codex presence file - refusing to run an unprotected session (its account would look like a swap target)",
108
+ savedTermios,
109
+ });
110
+ }
135
111
  return { child: spawned, seat: picked };
136
112
  });
137
113
 
138
- let done = false;
139
- const markerWatch = (async () => {
140
- while (!done) {
141
- if (existsSync(marker)) {
142
- readCodexMarker(marker);
143
- return true;
144
- }
145
- await Bun.sleep(150);
146
- }
147
- return false;
148
- })();
149
- const exited = child.exited.then(() => {
150
- done = true;
151
- return "exit";
114
+ await raceMarkerOrExit({
115
+ child,
116
+ tick: () => {
117
+ if (!existsSync(marker)) return false;
118
+ readCodexMarker(marker);
119
+ return true;
120
+ },
121
+ savedTermios,
152
122
  });
153
- try {
154
- const winner = await Promise.race([exited, markerWatch.then((found) => (found ? "marker" : "exit"))]);
155
- if (winner === "marker") child.kill();
156
- } catch (e) {
157
- child.kill();
158
- throw e;
159
- } finally {
160
- await child.exited;
161
- done = true;
162
- await markerWatch.catch(() => false);
163
- restoreTermios(savedTermios);
164
- }
165
123
 
166
124
  const payload = existsSync(marker) ? readCodexMarker(marker) : null;
167
125
  if (payload) {
@@ -175,6 +133,6 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
175
133
  }
176
134
  clearPresence({ dir: codexPaths.presenceDir, id: supervisorId });
177
135
  log("codexsupervisor.exit", { supervisorId: supervisorId.slice(0, 8), respawns, code: child.exitCode, signal: child.signalCode });
178
- return child.exitCode ?? (child.signalCode ? 1 : 0);
136
+ return exitStatus(child);
179
137
  }
180
138
  }
@@ -1,39 +1,32 @@
1
1
  import { sortBy } from "es-toolkit";
2
- import { z } from "zod";
3
2
  import { claudePool, seatFromEnv } from "../lib/paths.ts";
3
+ import { errorMessage } from "../lib/log.ts";
4
4
  import { loadAccounts, loadConfig, writeUsage } from "../lib/state.ts";
5
5
  import { familyTokens, matchedFamily, parseStatusLineStdin, parseStatusLineModel } from "../lib/usage.ts";
6
6
  import { earliestReset, limitWindows, weeklyExpiry, weeklyWindow } from "../lib/picker.ts";
7
+ import { readStdin } from "../lib/proc.ts";
7
8
  import { worktreeName } from "../lib/worktree.ts";
8
- import { makeColors, makeUsagePaint } from "../cli/render.ts";
9
- import { fmtResetShort } from "../lib/usage.ts";
9
+ import { fmtResetShort, makeColors, makeUsagePaint, statuslineColor } from "../cli/render.ts";
10
10
  import {
11
- AccountsIndexSchema,
12
11
  JsonTextSchema,
13
12
  StatusLineStdinSchema,
14
- WindowSchema,
15
13
  type Account,
14
+ type AccountsIndex,
16
15
  type UsageState,
17
16
  type UsageWindow,
17
+ type Window,
18
18
  } from "../lib/types.ts";
19
19
 
20
- const RenderCtxSchema = z.object({
21
- accounts: AccountsIndexSchema,
22
- perModel: z.record(z.string(), WindowSchema),
23
- switchModels: z.array(z.string()),
24
- worktree: z.string().nullable(),
25
- liveAccount: z.string().nullable(),
26
- now: z.number(),
27
- color: z.boolean(),
28
- truecolor: z.boolean(),
29
- });
30
- export type RenderCtx = z.infer<typeof RenderCtxSchema>;
31
-
32
- export async function readStdin(): Promise<string> {
33
- const chunks: Uint8Array[] = [];
34
- for await (const c of Bun.stdin.stream()) chunks.push(c);
35
- return Buffer.concat(chunks).toString("utf8");
36
- }
20
+ export type RenderCtx = {
21
+ accounts: AccountsIndex;
22
+ perModel: Record<string, Window>;
23
+ switchModels: string[];
24
+ worktree: string | null;
25
+ liveAccount: string | null;
26
+ now: number;
27
+ color: boolean;
28
+ truecolor: boolean;
29
+ };
37
30
 
38
31
  export function renderStatusline(stdinObj: unknown, ctx: RenderCtx): string {
39
32
  const col = makeColors(ctx.color);
@@ -134,7 +127,6 @@ export async function runStatusline(): Promise<number> {
134
127
  const accounts = loadAccounts(claudePool);
135
128
  const stdin = StatusLineStdinSchema.safeParse(obj);
136
129
  const dir = stdin.success ? (stdin.data.workspace?.current_dir ?? stdin.data.workspace?.project_dir ?? null) : null;
137
- const colorterm = z.string().optional().parse(process.env.COLORTERM);
138
130
  const live = accounts.accounts.find((a) => a.id === account);
139
131
  const ctx: RenderCtx = {
140
132
  accounts,
@@ -143,12 +135,11 @@ export async function runStatusline(): Promise<number> {
143
135
  worktree: dir == null ? null : worktreeName(dir),
144
136
  liveAccount: account,
145
137
  now,
146
- color: !process.env.NO_COLOR,
147
- truecolor: colorterm != null && (colorterm.includes("truecolor") || colorterm.includes("24bit")),
138
+ ...statuslineColor(),
148
139
  };
149
140
  line = renderStatusline(obj, ctx);
150
141
  } catch (e) {
151
- line = `tokenmaxxing: ${e instanceof Error ? e.message : String(e)}`;
142
+ line = `tokenmaxxing: ${errorMessage(e)}`;
152
143
  }
153
144
  process.stdout.write(line + "\n");
154
145
  return 0;
@@ -1,13 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import { claude } from "../lib/claude.ts";
3
3
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
4
+ import { readStdin } from "../lib/proc.ts";
4
5
  import { supervisedSession, writeRespawnMarker } from "../lib/sessions.ts";
5
6
  import { loadConfig } from "../lib/state.ts";
6
7
  import { classifyEnforcedLimit, findEnforcedRow, parseErrorBody, readTranscriptTail, type TranscriptRow } from "../lib/usage.ts";
7
8
  import { paths } from "../lib/paths.ts";
8
9
  import { JsonTextSchema, type EnforcedLimit } from "../lib/types.ts";
9
- import { log } from "../lib/log.ts";
10
- import { readStdin } from "./statusline.ts";
10
+ import { errorMessage, log } from "../lib/log.ts";
11
11
 
12
12
  const StopFailureStdin = z.looseObject({
13
13
  session_id: z.uuid().optional().catch(undefined),
@@ -34,8 +34,7 @@ export async function runStopFailureHook(): Promise<number> {
34
34
 
35
35
  const account = claude.liveId();
36
36
  const now = Date.now();
37
- const raw = await readStdin();
38
- const parsed = StopFailureStdin.safeParse(JsonTextSchema.safeParse(raw).data);
37
+ const parsed = StopFailureStdin.safeParse(JsonTextSchema.safeParse(await readStdin()).data);
39
38
  const stdin = parsed.success ? parsed.data : {};
40
39
  if (stdin.error !== undefined && stdin.error !== "rate_limit") return 0;
41
40
 
@@ -84,7 +83,7 @@ export async function runStopFailureHook(): Promise<number> {
84
83
  log("stopfailure.decision", { reason: decision.reason, swapped: decision.swapped, account: decision.account?.id.slice(0, 8), waitUntil: decision.waitUntil });
85
84
  }
86
85
  } catch (e) {
87
- log("stopfailure.error", { err: e instanceof Error ? e.message : String(e) });
86
+ log("stopfailure.error", { err: errorMessage(e) });
88
87
  }
89
88
  return 0;
90
89
  }
@@ -1,29 +1,31 @@
1
1
  import { z } from "zod";
2
2
  import { claude } from "../lib/claude.ts";
3
3
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
4
+ import { readStdin } from "../lib/proc.ts";
4
5
  import { supervisedSession, writeRespawnMarker } from "../lib/sessions.ts";
5
6
  import { JsonTextSchema } from "../lib/types.ts";
6
- import { log } from "../lib/log.ts";
7
- import { readStdin } from "./statusline.ts";
7
+ import { errorMessage, log } from "../lib/log.ts";
8
8
 
9
- const StopStdin = z.looseObject({ session_id: z.uuid().optional().catch(undefined) });
9
+ const BoundaryStdin = z.looseObject({
10
+ source: z.string().optional(),
11
+ session_id: z.uuid().optional().catch(undefined),
12
+ });
10
13
 
11
- export async function runStopHook(): Promise<number> {
14
+ export async function runBoundaryHook(event: "stop" | "sessionstart"): Promise<number> {
12
15
  if (process.env.TOKENMAXXING_PROBE) return 0;
13
16
 
14
- const raw = await readStdin();
15
- const parsed = StopStdin.safeParse(JsonTextSchema.safeParse(raw).data);
16
- const stdinSid = parsed.success ? parsed.data.session_id : undefined;
17
+ const parsed = BoundaryStdin.safeParse(JsonTextSchema.safeParse(await readStdin()).data);
18
+ const stdin = parsed.success ? parsed.data : {};
17
19
  const session = supervisedSession();
18
20
 
19
21
  try {
20
22
  const decision = await evaluateAndMaybeSwap(claude, Date.now(), session != null);
21
23
  if (session && decision.account && (decision.swapped || decision.waitUntil !== undefined)) {
22
- writeRespawnMarker({ session, sessionId: stdinSid ?? session.sid, accountId: decision.account.id, waitUntil: decision.waitUntil ?? Date.now(), compact: true });
23
- log(decision.waitUntil !== undefined ? "stop.wait" : "stop.move", { account: decision.account.id.slice(0, 8), waitUntil: decision.waitUntil, session: session.sid.slice(0, 8) });
24
+ writeRespawnMarker({ session, sessionId: stdin.session_id ?? session.sid, accountId: decision.account.id, waitUntil: decision.waitUntil ?? Date.now(), compact: true });
25
+ log(`${event}.${decision.waitUntil !== undefined ? "wait" : "move"}`, { source: stdin.source, account: decision.account.id.slice(0, 8), waitUntil: decision.waitUntil, session: session.sid.slice(0, 8) });
24
26
  }
25
27
  } catch (e) {
26
- log("stop.error", { err: e instanceof Error ? e.message : String(e) });
28
+ log(`${event}.error`, { err: errorMessage(e) });
27
29
  }
28
30
  return 0;
29
31
  }
@@ -1,10 +1,8 @@
1
- import { z } from "zod";
2
- import { makeColors, makeUsagePaint } from "../cli/render.ts";
3
- import { readStdin } from "./statusline.ts";
1
+ import { makeColors, makeUsagePaint, statuslineColor } from "../cli/render.ts";
2
+ import { readStdin } from "../lib/proc.ts";
4
3
  import { JsonTextSchema, SubagentStatusLineStdinSchema } from "../lib/types.ts";
5
4
 
6
- const RowCtxSchema = z.object({ color: z.boolean(), truecolor: z.boolean() });
7
- export type RowCtx = z.infer<typeof RowCtxSchema>;
5
+ export type RowCtx = { color: boolean; truecolor: boolean };
8
6
 
9
7
  function modelFamily(id: string): string {
10
8
  const [head, family] = id.split("-");
@@ -44,11 +42,7 @@ export function renderSubagentRows(stdinObj: unknown, ctx: RowCtx): string[] {
44
42
 
45
43
  export async function runSubagentStatusline(): Promise<number> {
46
44
  const obj = JsonTextSchema.safeParse(await readStdin()).data ?? null;
47
- const colorterm = z.string().optional().parse(process.env.COLORTERM);
48
- const rows = renderSubagentRows(obj, {
49
- color: !process.env.NO_COLOR,
50
- truecolor: colorterm != null && (colorterm.includes("truecolor") || colorterm.includes("24bit")),
51
- });
45
+ const rows = renderSubagentRows(obj, statuslineColor());
52
46
  if (rows.length > 0) process.stdout.write(rows.join("\n") + "\n");
53
47
  return 0;
54
48
  }