tokenmaxxing 1.57.0 → 1.59.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/DESIGN.md +6 -6
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/cli/auth.ts +6 -17
- package/src/cli/check.ts +2 -2
- package/src/cli/cloudrun.ts +12 -19
- package/src/cli/config.ts +2 -1
- package/src/cli/doctor.ts +5 -4
- package/src/cli/init.ts +0 -1
- package/src/cli/render.ts +24 -9
- package/src/cli/rm.ts +0 -4
- package/src/cli/status.ts +37 -55
- package/src/entries/codexstophook.ts +11 -24
- package/src/entries/codexsupervisor.ts +30 -72
- package/src/entries/statusline.ts +17 -26
- package/src/entries/stopfailurehook.ts +4 -5
- package/src/entries/stophook.ts +12 -10
- package/src/entries/subagentstatusline.ts +4 -10
- package/src/entries/supervisor.ts +71 -113
- package/src/lib/claude.ts +18 -19
- package/src/lib/claudebin.ts +3 -2
- package/src/lib/claudelock.ts +2 -2
- package/src/lib/cloud.ts +4 -8
- package/src/lib/codex.ts +3 -3
- package/src/lib/codexauth.ts +2 -11
- package/src/lib/codexbin.ts +2 -1
- package/src/lib/codexoauth.ts +3 -8
- package/src/lib/codexusage.ts +5 -5
- package/src/lib/compact.ts +5 -18
- package/src/lib/credstore.ts +1 -6
- package/src/lib/decide.ts +12 -29
- package/src/lib/grok.ts +38 -152
- package/src/lib/http.ts +1 -2
- package/src/lib/install.ts +20 -38
- package/src/lib/keychain.ts +1 -4
- package/src/lib/log.ts +5 -5
- package/src/lib/oauth.ts +6 -5
- package/src/lib/opencodego.ts +31 -140
- package/src/lib/paths.ts +6 -22
- package/src/lib/picker.ts +9 -20
- package/src/lib/presence.ts +4 -5
- package/src/lib/proc.ts +22 -0
- package/src/lib/provider.ts +1 -1
- package/src/lib/sample.ts +25 -24
- package/src/lib/sessions.ts +5 -6
- package/src/lib/settings.ts +1 -8
- package/src/lib/state.ts +6 -29
- package/src/lib/statusonly.ts +143 -0
- package/src/lib/supervise.ts +111 -0
- package/src/lib/types.ts +27 -36
- package/src/lib/update.ts +6 -7
- package/src/lib/usage.ts +45 -188
- package/src/main.ts +5 -5
- 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:
|
|
12
|
+
resetsAt: w.resets_at ?? null,
|
|
28
13
|
});
|
|
29
14
|
|
|
30
15
|
export function parseStatusLineStdin(obj: unknown): UsageWindows | null {
|
|
@@ -59,65 +44,12 @@ export function gatedFamilies(model: ModelInfo | null, families: string[]): stri
|
|
|
59
44
|
return family ? [family] : [];
|
|
60
45
|
}
|
|
61
46
|
|
|
62
|
-
const
|
|
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
|
-
}
|
|
47
|
+
const MODEL_FAMILIES = ["sonnet", "opus", "haiku", "fable"];
|
|
110
48
|
|
|
111
|
-
export function
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
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`;
|
|
49
|
+
export function modelFromFlag(value: string | null): ModelInfo | null {
|
|
50
|
+
const name = value?.split("[")[0]?.trim() ?? "";
|
|
51
|
+
const tokens = familyTokens(name);
|
|
52
|
+
return MODEL_FAMILIES.some((f) => tokens.includes(f)) ? { id: name, display: name } : null;
|
|
121
53
|
}
|
|
122
54
|
|
|
123
55
|
const TranscriptBlockSchema = z.looseObject({ type: z.string().optional(), text: z.string().optional() });
|
|
@@ -128,7 +60,7 @@ export const TranscriptRowSchema = z.looseObject({
|
|
|
128
60
|
apiErrorIsTransient: z.boolean().optional(),
|
|
129
61
|
error: z.string().optional(),
|
|
130
62
|
errorDetails: z.string().optional(),
|
|
131
|
-
quotaLimits: z.looseObject({ rateLimitType: z.string().optional(), resetsAt:
|
|
63
|
+
quotaLimits: z.looseObject({ rateLimitType: z.string().optional(), resetsAt: EpochSecondsSchema.optional() }).optional(),
|
|
132
64
|
message: z.looseObject({ content: z.unknown().optional() }).optional(),
|
|
133
65
|
});
|
|
134
66
|
export type TranscriptRow = z.infer<typeof TranscriptRowSchema>;
|
|
@@ -154,11 +86,8 @@ export function readTranscriptTail(path: string, maxBytes = TRANSCRIPT_TAIL_BYTE
|
|
|
154
86
|
}
|
|
155
87
|
const rows: TranscriptRow[] = [];
|
|
156
88
|
for (const line of text.split("\n")) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const parsed = TranscriptRowSchema.safeParse(JSON.parse(line));
|
|
160
|
-
if (parsed.success) rows.push(parsed.data);
|
|
161
|
-
} catch {}
|
|
89
|
+
const parsed = TranscriptRowSchema.safeParse(JsonTextSchema.safeParse(line).data);
|
|
90
|
+
if (parsed.success) rows.push(parsed.data);
|
|
162
91
|
}
|
|
163
92
|
return rows;
|
|
164
93
|
}
|
|
@@ -184,12 +113,10 @@ export function findEnforcedRow(input: { rows: TranscriptRow[]; lastAssistantMes
|
|
|
184
113
|
return null;
|
|
185
114
|
}
|
|
186
115
|
|
|
187
|
-
export
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
]);
|
|
192
|
-
export type EnforcedClass = z.infer<typeof EnforcedClassSchema>;
|
|
116
|
+
export type EnforcedClass =
|
|
117
|
+
| { kind: "session"; resetsAt: number | null }
|
|
118
|
+
| { kind: "weekly"; resetsAt: number | null }
|
|
119
|
+
| { kind: "model"; family: string; resetsAt: number | null };
|
|
193
120
|
|
|
194
121
|
const ErrorBodySchema = z.looseObject({
|
|
195
122
|
error: z.looseObject({ type: z.string().optional(), details: z.looseObject({ error_code: z.string().optional() }).optional() }).optional(),
|
|
@@ -201,18 +128,14 @@ export function parseErrorBody(errorDetails: string | undefined): z.infer<typeof
|
|
|
201
128
|
if (!errorDetails) return null;
|
|
202
129
|
const at = errorDetails.indexOf("{");
|
|
203
130
|
if (at < 0) return null;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return body.success ? body.data : null;
|
|
207
|
-
} catch {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
131
|
+
const body = ErrorBodySchema.safeParse(JsonTextSchema.safeParse(errorDetails.slice(at)).data);
|
|
132
|
+
return body.success ? body.data : null;
|
|
210
133
|
}
|
|
211
134
|
|
|
212
135
|
export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]): EnforcedClass | null {
|
|
213
136
|
const q = row.quotaLimits;
|
|
214
137
|
if (q) {
|
|
215
|
-
const resetsAt = q.resetsAt
|
|
138
|
+
const resetsAt = q.resetsAt ?? null;
|
|
216
139
|
const type = q.rateLimitType ?? "";
|
|
217
140
|
if (type === "five_hour") return { kind: "session", resetsAt };
|
|
218
141
|
if (type === "seven_day") return { kind: "weekly", resetsAt };
|
|
@@ -225,22 +148,6 @@ export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]
|
|
|
225
148
|
return family ? { kind: "model", family, resetsAt: null } : null;
|
|
226
149
|
}
|
|
227
150
|
|
|
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
151
|
const FIVE_HOURS_S = 5 * 3600;
|
|
245
152
|
const WEEK_S = 7 * 24 * 3600;
|
|
246
153
|
|
|
@@ -273,97 +180,46 @@ export const CRED_ENV_OVERRIDES = [
|
|
|
273
180
|
const PROBE_KILL_MS = 60_000;
|
|
274
181
|
const PIPE_GRACE_MS = 2_000;
|
|
275
182
|
|
|
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
183
|
export function scrubCredentialEnv(env: Record<string, string>): Record<string, string> {
|
|
331
184
|
const scrubbed = { ...env };
|
|
332
185
|
for (const k of CRED_ENV_OVERRIDES) delete scrubbed[k];
|
|
333
186
|
return scrubbed;
|
|
334
187
|
}
|
|
335
188
|
|
|
336
|
-
export type ProbeTarget = { configDir: string; store
|
|
189
|
+
export type ProbeTarget = { configDir: string; store: string };
|
|
337
190
|
|
|
338
|
-
function
|
|
191
|
+
export async function refreshViaProbe(target: ProbeTarget): Promise<boolean> {
|
|
339
192
|
const env = scrubCredentialEnv({ ...process.env, TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH) });
|
|
340
193
|
env.CLAUDE_CONFIG_DIR = target.configDir;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return null;
|
|
194
|
+
env.CLAUDE_SECURESTORAGE_CONFIG_DIR = target.store;
|
|
195
|
+
try {
|
|
196
|
+
const p = Bun.spawn([resolveRealClaude(), "-p", "/usage", "--output-format", "json"], { env, stdout: "ignore", stderr: "pipe", timeout: PROBE_KILL_MS, killSignal: "SIGKILL" });
|
|
197
|
+
const stderr = await Promise.race([new Response(p.stderr).text(), p.exited.then(() => delay(PIPE_GRACE_MS)).then(() => null)]);
|
|
198
|
+
await p.exited;
|
|
199
|
+
if (stderr === null) {
|
|
200
|
+
log("usage.probe_failed", { err: "output pipes still open after child exit (leaked descendant)" });
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (p.exitCode !== 0) {
|
|
204
|
+
log("usage.probe_failed", { exit: p.exitCode ?? "signal", stderr: stderr.trim().slice(0, 200) });
|
|
205
|
+
return false;
|
|
354
206
|
}
|
|
355
|
-
|
|
207
|
+
return true;
|
|
208
|
+
} catch (e) {
|
|
209
|
+
log("usage.probe_failed", { err: errorMessage(e) });
|
|
210
|
+
return false;
|
|
356
211
|
}
|
|
357
212
|
}
|
|
358
213
|
|
|
359
214
|
const USAGE_URL = env("TOKENMAXXING_OAUTH_USAGE_URL", "https://api.anthropic.com/api/oauth/usage");
|
|
360
215
|
const USAGE_DEADLINE_MS = 10_000;
|
|
361
216
|
|
|
362
|
-
const
|
|
217
|
+
const ResetsAtSchema = z.iso.datetime({ offset: true }).transform((iso) => Date.parse(iso)).nullish();
|
|
218
|
+
const UsageLimitSchema = z.looseObject({ utilization: z.number(), resets_at: ResetsAtSchema });
|
|
363
219
|
const UsageScopedLimitSchema = z.looseObject({
|
|
364
220
|
kind: z.string(),
|
|
365
221
|
percent: z.number(),
|
|
366
|
-
resets_at:
|
|
222
|
+
resets_at: ResetsAtSchema,
|
|
367
223
|
scope: z.looseObject({ model: z.looseObject({ display_name: z.string() }) }),
|
|
368
224
|
});
|
|
369
225
|
const UsageResponseSchema = z.looseObject({
|
|
@@ -377,7 +233,7 @@ function scopedRows(limits: unknown[]): Record<string, UsageWindow> {
|
|
|
377
233
|
for (const raw of limits) {
|
|
378
234
|
const row = UsageScopedLimitSchema.safeParse(raw);
|
|
379
235
|
if (!row.success || row.data.kind !== "weekly_scoped") continue;
|
|
380
|
-
perModel[row.data.scope.model.display_name] = { usedPercentage: row.data.percent, resetsAt:
|
|
236
|
+
perModel[row.data.scope.model.display_name] = { usedPercentage: row.data.percent, resetsAt: row.data.resets_at ?? null };
|
|
381
237
|
}
|
|
382
238
|
return perModel;
|
|
383
239
|
}
|
|
@@ -385,12 +241,13 @@ function scopedRows(limits: unknown[]): Record<string, UsageWindow> {
|
|
|
385
241
|
export async function fetchUsageDirect(accessToken: string): Promise<UsageWindows | null> {
|
|
386
242
|
let res: Response;
|
|
387
243
|
try {
|
|
388
|
-
res = await http.get(
|
|
244
|
+
res = await http.get(USAGE_URL, {
|
|
245
|
+
searchParams: { at_wall: 1, skip_spend: 1 },
|
|
389
246
|
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
390
247
|
signal: AbortSignal.timeout(USAGE_DEADLINE_MS),
|
|
391
248
|
});
|
|
392
249
|
} catch (e) {
|
|
393
|
-
log("usage.get_failed", { err:
|
|
250
|
+
log("usage.get_failed", { err: errorMessage(e) });
|
|
394
251
|
return null;
|
|
395
252
|
}
|
|
396
253
|
if (!res.ok) {
|
|
@@ -399,9 +256,9 @@ export async function fetchUsageDirect(accessToken: string): Promise<UsageWindow
|
|
|
399
256
|
}
|
|
400
257
|
const parsed = UsageResponseSchema.safeParse(JsonTextSchema.safeParse(await res.text()).data);
|
|
401
258
|
if (!parsed.success || !parsed.data.five_hour || !parsed.data.seven_day) {
|
|
402
|
-
log("usage.get_incomplete", { ok: parsed.success });
|
|
259
|
+
log("usage.get_incomplete", { ok: parsed.success, issue: parsed.success ? undefined : z.prettifyError(parsed.error).slice(0, 200) });
|
|
403
260
|
return null;
|
|
404
261
|
}
|
|
405
|
-
const win = (w: z.infer<typeof UsageLimitSchema>): UsageWindow => ({ usedPercentage: w.utilization, resetsAt:
|
|
262
|
+
const win = (w: z.infer<typeof UsageLimitSchema>): UsageWindow => ({ usedPercentage: w.utilization, resetsAt: w.resets_at ?? null });
|
|
406
263
|
return { fiveHour: win(parsed.data.five_hour), sevenDay: win(parsed.data.seven_day), perModel: scopedRows(parsed.data.limits ?? []) };
|
|
407
264
|
}
|
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 {
|
|
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
|
|
127
|
+
case "__stop-hook": return runBoundaryHook("stop");
|
|
128
128
|
case "__stop-failure-hook": return runStopFailureHook();
|
|
129
|
-
case "__session-start": return
|
|
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:
|
|
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
|
-
}
|