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.
- package/DESIGN.md +7 -7
- 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 +34 -57
- package/src/entries/codexstophook.ts +10 -23
- 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 +91 -116
- package/src/lib/claude.ts +11 -12
- 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 +6 -24
- 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/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 +40 -191
- 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,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:
|
|
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
|
-
|
|
158
|
-
|
|
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
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
|
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
|
|
181
|
+
export type ProbeTarget = { configDir: string; store: string };
|
|
337
182
|
|
|
338
|
-
function
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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 {
|
|
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
|
-
}
|