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
package/src/lib/usage.ts CHANGED
@@ -3,28 +3,13 @@ import { delay } from "es-toolkit";
3
3
  import { z } from "zod";
4
4
  import { MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealClaude } from "./claudebin.ts";
5
5
  import { http } from "./http.ts";
6
- import { log } from "./log.ts";
6
+ import { errorMessage, log } from "./log.ts";
7
7
  import { env } from "./paths.ts";
8
- import { JsonTextSchema, RateLimitsStdinSchema, type ModelInfo, type UsageWindow, type UsageWindows, type Window } from "./types.ts";
9
-
10
- export function normalizeResetsAt(v: unknown): number | null {
11
- const num = z.number().finite().safeParse(v);
12
- if (num.success) {
13
- return num.data < 1e12 ? Math.round(num.data * 1000) : Math.round(num.data);
14
- }
15
- const str = z.string().safeParse(v);
16
- if (str.success && str.data.trim() !== "") {
17
- const n = Number(str.data);
18
- if (Number.isFinite(n)) return normalizeResetsAt(n);
19
- const t = Date.parse(str.data);
20
- return Number.isFinite(t) ? t : null;
21
- }
22
- return null;
23
- }
8
+ import { EpochSecondsSchema, JsonTextSchema, RateLimitsStdinSchema, type ModelInfo, type UsageWindow, type UsageWindows, type Window } from "./types.ts";
24
9
 
25
10
  const win = (w: { used_percentage: number; resets_at?: number | null }): UsageWindow => ({
26
11
  usedPercentage: w.used_percentage,
27
- resetsAt: normalizeResetsAt(w.resets_at),
12
+ resetsAt: w.resets_at ?? null,
28
13
  });
29
14
 
30
15
  export function parseStatusLineStdin(obj: unknown): UsageWindows | null {
@@ -59,67 +44,6 @@ export function gatedFamilies(model: ModelInfo | null, families: string[]): stri
59
44
  return family ? [family] : [];
60
45
  }
61
46
 
62
- const MONTHS: Record<string, number> = {
63
- jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11,
64
- };
65
-
66
- function tzOffsetMs(utcMs: number, tz: string): number {
67
- const parts = new Intl.DateTimeFormat("en-US", {
68
- timeZone: tz,
69
- hourCycle: "h23",
70
- year: "numeric", month: "2-digit", day: "2-digit",
71
- hour: "2-digit", minute: "2-digit", second: "2-digit",
72
- }).formatToParts(new Date(utcMs));
73
- const p: Record<string, number> = {};
74
- for (const part of parts) if (part.type !== "literal") p[part.type] = Number(part.value);
75
- const asUTC = Date.UTC(p.year!, p.month! - 1, p.day!, p.hour!, p.minute!, p.second!);
76
- return asUTC - utcMs;
77
- }
78
-
79
- function zonedWallToEpoch(y: number, mon: number, day: number, hour: number, min: number, tz: string): number {
80
- const guess = Date.UTC(y, mon, day, hour, min);
81
- const off1 = tzOffsetMs(guess, tz);
82
- const off2 = tzOffsetMs(guess - off1, tz);
83
- return guess - off2;
84
- }
85
-
86
- export function parseResetClock(clock: string, now = Date.now()): number | null {
87
- const m = clock.match(/\b([A-Za-z]{3,9})\s+(\d{1,2})(?:[^\S\n]+at[^\S\n]+|,[^\S\n]*)(\d{1,2})(?::(\d{2}))?\s*([ap])m\s*\(([^)]+)\)/i);
88
- if (!m) return null;
89
- const mon = MONTHS[m[1]!.slice(0, 3).toLowerCase()];
90
- if (mon === undefined) return null;
91
- const day = Number(m[2]);
92
- let hour = Number(m[3]) % 12;
93
- if (m[5]!.toLowerCase() === "p") hour += 12;
94
- const min = m[4] ? Number(m[4]) : 0;
95
- const tz = m[6]!.trim();
96
-
97
- const baseYear = new Date(now).getUTCFullYear();
98
- let best: number | null = null;
99
- for (const y of [baseYear - 1, baseYear, baseYear + 1]) {
100
- let epoch: number;
101
- try {
102
- epoch = zonedWallToEpoch(y, mon, day, hour, min, tz);
103
- } catch {
104
- return null;
105
- }
106
- if (best === null || Math.abs(epoch - now) < Math.abs(best - now)) best = epoch;
107
- }
108
- return best;
109
- }
110
-
111
- export function fmtResetShort(epochMs: number | null | undefined, now = Date.now()): string {
112
- if (epochMs == null) return "";
113
- const dsec = Math.round((epochMs - now) / 1000);
114
- if (dsec <= 0) return "";
115
- const d = Math.floor(dsec / 86400);
116
- const h = Math.floor((dsec % 86400) / 3600);
117
- const m = Math.floor((dsec % 3600) / 60);
118
- if (d > 0) return `${d}d`;
119
- if (h > 0) return `${h}h`;
120
- return `${Math.max(m, 1)}m`;
121
- }
122
-
123
47
  const TranscriptBlockSchema = z.looseObject({ type: z.string().optional(), text: z.string().optional() });
124
48
  export const TranscriptRowSchema = z.looseObject({
125
49
  type: z.string().optional(),
@@ -128,7 +52,7 @@ export const TranscriptRowSchema = z.looseObject({
128
52
  apiErrorIsTransient: z.boolean().optional(),
129
53
  error: z.string().optional(),
130
54
  errorDetails: z.string().optional(),
131
- quotaLimits: z.looseObject({ rateLimitType: z.string().optional(), resetsAt: z.number().optional() }).optional(),
55
+ quotaLimits: z.looseObject({ rateLimitType: z.string().optional(), resetsAt: EpochSecondsSchema.optional() }).optional(),
132
56
  message: z.looseObject({ content: z.unknown().optional() }).optional(),
133
57
  });
134
58
  export type TranscriptRow = z.infer<typeof TranscriptRowSchema>;
@@ -154,11 +78,8 @@ export function readTranscriptTail(path: string, maxBytes = TRANSCRIPT_TAIL_BYTE
154
78
  }
155
79
  const rows: TranscriptRow[] = [];
156
80
  for (const line of text.split("\n")) {
157
- if (line.trim() === "") continue;
158
- try {
159
- const parsed = TranscriptRowSchema.safeParse(JSON.parse(line));
160
- if (parsed.success) rows.push(parsed.data);
161
- } catch {}
81
+ const parsed = TranscriptRowSchema.safeParse(JsonTextSchema.safeParse(line).data);
82
+ if (parsed.success) rows.push(parsed.data);
162
83
  }
163
84
  return rows;
164
85
  }
@@ -184,12 +105,10 @@ export function findEnforcedRow(input: { rows: TranscriptRow[]; lastAssistantMes
184
105
  return null;
185
106
  }
186
107
 
187
- export const EnforcedClassSchema = z.discriminatedUnion("kind", [
188
- z.object({ kind: z.literal("session"), resetsAt: z.number().nullable() }),
189
- z.object({ kind: z.literal("weekly"), resetsAt: z.number().nullable() }),
190
- z.object({ kind: z.literal("model"), family: z.string(), resetsAt: z.number().nullable() }),
191
- ]);
192
- export type EnforcedClass = z.infer<typeof EnforcedClassSchema>;
108
+ export type EnforcedClass =
109
+ | { kind: "session"; resetsAt: number | null }
110
+ | { kind: "weekly"; resetsAt: number | null }
111
+ | { kind: "model"; family: string; resetsAt: number | null };
193
112
 
194
113
  const ErrorBodySchema = z.looseObject({
195
114
  error: z.looseObject({ type: z.string().optional(), details: z.looseObject({ error_code: z.string().optional() }).optional() }).optional(),
@@ -201,18 +120,14 @@ export function parseErrorBody(errorDetails: string | undefined): z.infer<typeof
201
120
  if (!errorDetails) return null;
202
121
  const at = errorDetails.indexOf("{");
203
122
  if (at < 0) return null;
204
- try {
205
- const body = ErrorBodySchema.safeParse(JSON.parse(errorDetails.slice(at)));
206
- return body.success ? body.data : null;
207
- } catch {
208
- return null;
209
- }
123
+ const body = ErrorBodySchema.safeParse(JsonTextSchema.safeParse(errorDetails.slice(at)).data);
124
+ return body.success ? body.data : null;
210
125
  }
211
126
 
212
127
  export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]): EnforcedClass | null {
213
128
  const q = row.quotaLimits;
214
129
  if (q) {
215
- const resetsAt = q.resetsAt != null ? normalizeResetsAt(q.resetsAt) : null;
130
+ const resetsAt = q.resetsAt ?? null;
216
131
  const type = q.rateLimitType ?? "";
217
132
  if (type === "five_hour") return { kind: "session", resetsAt };
218
133
  if (type === "seven_day") return { kind: "weekly", resetsAt };
@@ -225,22 +140,6 @@ export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]
225
140
  return family ? { kind: "model", family, resetsAt: null } : null;
226
141
  }
227
142
 
228
- export function parseUsageText(text: string, now = Date.now()): UsageWindows | null {
229
- if (!text) return null;
230
- const re = /current (session|week \(([^)]+)\)):\s*(\d+)\s*%(?:(?:(?!current)[^\n])*?\bresets\s+([A-Z][a-z]{2,8}\s+\d{1,2}(?:[^\S\n]+at[^\S\n]+|,[^\S\n]*)\d{1,2}(?::\d{2})?\s*[ap]m\s*\([^)]+\)))?/gi;
231
- let fiveHour: UsageWindow | null = null;
232
- let sevenDay: UsageWindow | null = null;
233
- const perModel: Record<string, UsageWindow> = {};
234
- for (let m = re.exec(text); m !== null; m = re.exec(text)) {
235
- const window: UsageWindow = { usedPercentage: Number(m[3]), resetsAt: m[4] ? parseResetClock(m[4], now) : null };
236
- if (m[1]!.toLowerCase() === "session") fiveHour = window;
237
- else if (/^all models$/i.test(m[2]!.trim())) sevenDay = window;
238
- else perModel[m[2]!.trim()] = window;
239
- }
240
- if (!fiveHour || !sevenDay) return null;
241
- return { fiveHour, sevenDay, perModel };
242
- }
243
-
244
143
  const FIVE_HOURS_S = 5 * 3600;
245
144
  const WEEK_S = 7 * 24 * 3600;
246
145
 
@@ -273,97 +172,46 @@ export const CRED_ENV_OVERRIDES = [
273
172
  const PROBE_KILL_MS = 60_000;
274
173
  const PIPE_GRACE_MS = 2_000;
275
174
 
276
- const SpawnResultSchema = z.object({
277
- exitCode: z.number().nullable(),
278
- stdout: z.string(),
279
- stderr: z.string(),
280
- });
281
- type SpawnResult = z.infer<typeof SpawnResultSchema>;
282
-
283
- async function spawnClaudeBounded(cmd: string[], env: Record<string, string>): Promise<SpawnResult | null> {
284
- const p = Bun.spawn(cmd, { env, stdout: "pipe", stderr: "pipe", timeout: PROBE_KILL_MS, killSignal: "SIGKILL" });
285
- const reads = Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text()]);
286
- const settled = await Promise.race([
287
- reads,
288
- p.exited.then(() => delay(PIPE_GRACE_MS)).then(() => null),
289
- ]);
290
- if (settled === null) return null;
291
- const [stdout, stderr] = settled;
292
- await p.exited;
293
- return { exitCode: p.exitCode, stdout, stderr };
294
- }
295
-
296
- async function probeUsageOnce(env: Record<string, string>, now: number): Promise<UsageWindows | null> {
297
- let out: string;
298
- try {
299
- const r = await spawnClaudeBounded([resolveRealClaude(), "-p", "/usage", "--output-format", "json"], env);
300
- if (r === null) {
301
- log("usage.probe_failed", { err: "output pipes still open after child exit (leaked descendant)" });
302
- return null;
303
- }
304
- if (r.exitCode !== 0) {
305
- log("usage.probe_failed", { exit: r.exitCode ?? "signal", stderr: r.stderr.trim().slice(0, 200) });
306
- return null;
307
- }
308
- out = r.stdout;
309
- } catch (e) {
310
- log("usage.probe_failed", { err: e instanceof Error ? e.message : String(e) });
311
- return null;
312
- }
313
-
314
- const j = z.object({ result: z.string() }).safeParse(JsonTextSchema.safeParse(out).data);
315
- const text = j.success ? j.data.result : out;
316
- const full = parseUsageText(text, now);
317
- if (!full) {
318
- log("usage.probe_unparsed", { sample: text.trim().slice(0, 200) });
319
- } else {
320
- const clockLine = text.split("\n").find((l) => /^current /i.test(l) && /\bresets\b/i.test(l));
321
- if (clockLine && [full.fiveHour, full.sevenDay, ...Object.values(full.perModel)].every((w) => w.resetsAt === null)) {
322
- log("usage.reset_clock_unparsed", { sample: clockLine.slice(0, 120) });
323
- }
324
- }
325
- return full;
326
- }
327
-
328
- const PROBE_RETRY_DELAYS_MS = [2000, 5000];
329
-
330
175
  export function scrubCredentialEnv(env: Record<string, string>): Record<string, string> {
331
176
  const scrubbed = { ...env };
332
177
  for (const k of CRED_ENV_OVERRIDES) delete scrubbed[k];
333
178
  return scrubbed;
334
179
  }
335
180
 
336
- export type ProbeTarget = { configDir: string; store?: string };
181
+ export type ProbeTarget = { configDir: string; store: string };
337
182
 
338
- function probeEnv(target: ProbeTarget): Record<string, string> {
183
+ export async function refreshViaProbe(target: ProbeTarget): Promise<boolean> {
339
184
  const env = scrubCredentialEnv({ ...process.env, TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH) });
340
185
  env.CLAUDE_CONFIG_DIR = target.configDir;
341
- if (target.store) env.CLAUDE_SECURESTORAGE_CONFIG_DIR = target.store;
342
- return env;
343
- }
344
-
345
- export async function probeUsage(target: ProbeTarget, now = Date.now(), opts: { retries?: number } = {}): Promise<UsageWindows | null> {
346
- const probe = probeEnv(target);
347
-
348
- for (let attempt = 0; ; attempt++) {
349
- const full = await probeUsageOnce(probe, now);
350
- if (full) return full;
351
- if (attempt >= (opts.retries ?? PROBE_RETRY_DELAYS_MS.length)) {
352
- log("usage.probe_gave_up", { attempts: attempt + 1 });
353
- return null;
186
+ env.CLAUDE_SECURESTORAGE_CONFIG_DIR = target.store;
187
+ try {
188
+ const p = Bun.spawn([resolveRealClaude(), "-p", "/usage", "--output-format", "json"], { env, stdout: "ignore", stderr: "pipe", timeout: PROBE_KILL_MS, killSignal: "SIGKILL" });
189
+ const stderr = await Promise.race([new Response(p.stderr).text(), p.exited.then(() => delay(PIPE_GRACE_MS)).then(() => null)]);
190
+ await p.exited;
191
+ if (stderr === null) {
192
+ log("usage.probe_failed", { err: "output pipes still open after child exit (leaked descendant)" });
193
+ return false;
194
+ }
195
+ if (p.exitCode !== 0) {
196
+ log("usage.probe_failed", { exit: p.exitCode ?? "signal", stderr: stderr.trim().slice(0, 200) });
197
+ return false;
354
198
  }
355
- await delay(PROBE_RETRY_DELAYS_MS[Math.min(attempt, PROBE_RETRY_DELAYS_MS.length - 1)]!);
199
+ return true;
200
+ } catch (e) {
201
+ log("usage.probe_failed", { err: errorMessage(e) });
202
+ return false;
356
203
  }
357
204
  }
358
205
 
359
206
  const USAGE_URL = env("TOKENMAXXING_OAUTH_USAGE_URL", "https://api.anthropic.com/api/oauth/usage");
360
207
  const USAGE_DEADLINE_MS = 10_000;
361
208
 
362
- const UsageLimitSchema = z.looseObject({ utilization: z.number(), resets_at: z.unknown() });
209
+ const ResetsAtSchema = z.iso.datetime({ offset: true }).transform((iso) => Date.parse(iso)).nullish();
210
+ const UsageLimitSchema = z.looseObject({ utilization: z.number(), resets_at: ResetsAtSchema });
363
211
  const UsageScopedLimitSchema = z.looseObject({
364
212
  kind: z.string(),
365
213
  percent: z.number(),
366
- resets_at: z.unknown(),
214
+ resets_at: ResetsAtSchema,
367
215
  scope: z.looseObject({ model: z.looseObject({ display_name: z.string() }) }),
368
216
  });
369
217
  const UsageResponseSchema = z.looseObject({
@@ -377,7 +225,7 @@ function scopedRows(limits: unknown[]): Record<string, UsageWindow> {
377
225
  for (const raw of limits) {
378
226
  const row = UsageScopedLimitSchema.safeParse(raw);
379
227
  if (!row.success || row.data.kind !== "weekly_scoped") continue;
380
- perModel[row.data.scope.model.display_name] = { usedPercentage: row.data.percent, resetsAt: normalizeResetsAt(row.data.resets_at) };
228
+ perModel[row.data.scope.model.display_name] = { usedPercentage: row.data.percent, resetsAt: row.data.resets_at ?? null };
381
229
  }
382
230
  return perModel;
383
231
  }
@@ -385,12 +233,13 @@ function scopedRows(limits: unknown[]): Record<string, UsageWindow> {
385
233
  export async function fetchUsageDirect(accessToken: string): Promise<UsageWindows | null> {
386
234
  let res: Response;
387
235
  try {
388
- res = await http.get(`${USAGE_URL}?at_wall=1&skip_spend=1`, {
236
+ res = await http.get(USAGE_URL, {
237
+ searchParams: { at_wall: 1, skip_spend: 1 },
389
238
  headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
390
239
  signal: AbortSignal.timeout(USAGE_DEADLINE_MS),
391
240
  });
392
241
  } catch (e) {
393
- log("usage.get_failed", { err: e instanceof Error ? e.message : String(e) });
242
+ log("usage.get_failed", { err: errorMessage(e) });
394
243
  return null;
395
244
  }
396
245
  if (!res.ok) {
@@ -399,9 +248,9 @@ export async function fetchUsageDirect(accessToken: string): Promise<UsageWindow
399
248
  }
400
249
  const parsed = UsageResponseSchema.safeParse(JsonTextSchema.safeParse(await res.text()).data);
401
250
  if (!parsed.success || !parsed.data.five_hour || !parsed.data.seven_day) {
402
- log("usage.get_incomplete", { ok: parsed.success });
251
+ log("usage.get_incomplete", { ok: parsed.success, issue: parsed.success ? undefined : z.prettifyError(parsed.error).slice(0, 200) });
403
252
  return null;
404
253
  }
405
- const win = (w: z.infer<typeof UsageLimitSchema>): UsageWindow => ({ usedPercentage: w.utilization, resetsAt: normalizeResetsAt(w.resets_at) });
254
+ const win = (w: z.infer<typeof UsageLimitSchema>): UsageWindow => ({ usedPercentage: w.utilization, resetsAt: w.resets_at ?? null });
406
255
  return { fiveHour: win(parsed.data.five_hour), sevenDay: win(parsed.data.seven_day), perModel: scopedRows(parsed.data.limits ?? []) };
407
256
  }
package/src/main.ts CHANGED
@@ -4,9 +4,8 @@ import { basename } from "node:path";
4
4
  import { runSupervisor } from "./entries/supervisor.ts";
5
5
  import { runStatusline } from "./entries/statusline.ts";
6
6
  import { runSubagentStatusline } from "./entries/subagentstatusline.ts";
7
- import { runStopHook } from "./entries/stophook.ts";
7
+ import { runBoundaryHook } from "./entries/stophook.ts";
8
8
  import { runStopFailureHook } from "./entries/stopfailurehook.ts";
9
- import { runSessionStart } from "./entries/sessionstart.ts";
10
9
  import { runCodexSupervisor } from "./entries/codexsupervisor.ts";
11
10
  import { runCodexStopHook } from "./entries/codexstophook.ts";
12
11
  import { claude } from "./lib/claude.ts";
@@ -26,6 +25,7 @@ import { cmdSetupToken } from "./cli/setuptoken.ts";
26
25
  import { cmdCloudRun } from "./cli/cloudrun.ts";
27
26
  import { cmdCursorInit } from "./cli/cursorinit.ts";
28
27
  import { onLoginHome, timerDeactivationHint, uninstallSupervisor, uninstallTargets } from "./lib/install.ts";
28
+ import { errorMessage } from "./lib/log.ts";
29
29
  import { HOME } from "./lib/paths.ts";
30
30
  import { c, emitError } from "./cli/render.ts";
31
31
 
@@ -124,9 +124,9 @@ async function main(): Promise<number> {
124
124
  switch (sub) {
125
125
  case "__statusline": return runStatusline();
126
126
  case "__subagent-statusline": return runSubagentStatusline();
127
- case "__stop-hook": return runStopHook();
127
+ case "__stop-hook": return runBoundaryHook("stop");
128
128
  case "__stop-failure-hook": return runStopFailureHook();
129
- case "__session-start": return runSessionStart();
129
+ case "__session-start": return runBoundaryHook("sessionstart");
130
130
  case "__codex-stop-hook": return runCodexStopHook();
131
131
  case undefined:
132
132
  case "status": {
@@ -201,6 +201,6 @@ async function main(): Promise<number> {
201
201
  try {
202
202
  process.exit(await main());
203
203
  } catch (e) {
204
- emitError({ json: jsonMode, message: e instanceof Error ? e.message : String(e) });
204
+ emitError({ json: jsonMode, message: errorMessage(e) });
205
205
  process.exit(1);
206
206
  }
@@ -1,33 +0,0 @@
1
- import { z } from "zod";
2
- import { claude } from "../lib/claude.ts";
3
- import { evaluateAndMaybeSwap } from "../lib/decide.ts";
4
- import { supervisedSession, writeRespawnMarker } from "../lib/sessions.ts";
5
- import { log } from "../lib/log.ts";
6
- import { readStdin } from "./statusline.ts";
7
- import { JsonTextSchema } from "../lib/types.ts";
8
-
9
- const SessionStartStdin = z.looseObject({
10
- source: z.string().optional(),
11
- session_id: z.uuid().optional().catch(undefined),
12
- });
13
-
14
- export async function runSessionStart(): Promise<number> {
15
- if (process.env.TOKENMAXXING_PROBE) return 0;
16
-
17
- const raw = await readStdin();
18
- const parsed = SessionStartStdin.safeParse(JsonTextSchema.safeParse(raw).data);
19
- const source = parsed.success ? parsed.data.source : undefined;
20
- const stdinSid = parsed.success ? parsed.data.session_id : undefined;
21
- const session = supervisedSession();
22
-
23
- try {
24
- const decision = await evaluateAndMaybeSwap(claude, Date.now(), session != null);
25
- if (session && decision.account && (decision.swapped || decision.waitUntil !== undefined)) {
26
- writeRespawnMarker({ session, sessionId: stdinSid ?? session.sid, accountId: decision.account.id, waitUntil: decision.waitUntil ?? Date.now(), compact: true });
27
- log("sessionstart.marker", { source, account: decision.account.id.slice(0, 8), waitUntil: decision.waitUntil });
28
- }
29
- } catch (e) {
30
- log("sessionstart.error", { err: e instanceof Error ? e.message : String(e) });
31
- }
32
- return 0;
33
- }