whoburnedmore 0.9.19 → 0.9.21
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/README.md +1 -1
- package/dist/index.js +883 -244
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,10 +7,9 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
|
-
import {
|
|
11
|
-
import { createRequire as createRequire4 } from "node:module";
|
|
10
|
+
import { createRequire as createRequire5 } from "node:module";
|
|
12
11
|
import { platform as platform4 } from "node:os";
|
|
13
|
-
import {
|
|
12
|
+
import { pathToFileURL } from "node:url";
|
|
14
13
|
import { createInterface } from "node:readline/promises";
|
|
15
14
|
import pc2 from "picocolors";
|
|
16
15
|
|
|
@@ -24,8 +23,8 @@ function parseOrg(args) {
|
|
|
24
23
|
function parsePass(args) {
|
|
25
24
|
return parseValueFlag(args, "--pass") ?? parseValueFlag(args, "--code");
|
|
26
25
|
}
|
|
27
|
-
function
|
|
28
|
-
return
|
|
26
|
+
function hasUnsafeInstallTokenArg(args) {
|
|
27
|
+
return args.some((arg) => arg === "--token" || arg.startsWith("--token="));
|
|
29
28
|
}
|
|
30
29
|
function applyScope(payload, flags) {
|
|
31
30
|
if (flags.board) payload.board = flags.board;
|
|
@@ -66,9 +65,65 @@ function resolveCommand(args) {
|
|
|
66
65
|
return words[0] ?? "run";
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
// src/http-bounds.ts
|
|
69
|
+
async function readResponseBytesCapped(response, maxBytes) {
|
|
70
|
+
const limit = Math.max(1, maxBytes);
|
|
71
|
+
const declared = Number(response.headers.get("content-length"));
|
|
72
|
+
if (Number.isFinite(declared) && declared > limit) {
|
|
73
|
+
throw new Error("response body too large");
|
|
74
|
+
}
|
|
75
|
+
if (!response.body) throw new Error("response body missing");
|
|
76
|
+
const reader = response.body.getReader();
|
|
77
|
+
const chunks = [];
|
|
78
|
+
let total = 0;
|
|
79
|
+
try {
|
|
80
|
+
while (true) {
|
|
81
|
+
const { done, value } = await reader.read();
|
|
82
|
+
if (done) break;
|
|
83
|
+
if (!value) continue;
|
|
84
|
+
total += value.byteLength;
|
|
85
|
+
if (total > limit) {
|
|
86
|
+
await reader.cancel("response body too large").catch(() => void 0);
|
|
87
|
+
throw new Error("response body too large");
|
|
88
|
+
}
|
|
89
|
+
chunks.push(value);
|
|
90
|
+
}
|
|
91
|
+
} finally {
|
|
92
|
+
reader.releaseLock();
|
|
93
|
+
}
|
|
94
|
+
const bytes = new Uint8Array(total);
|
|
95
|
+
let offset = 0;
|
|
96
|
+
for (const chunk of chunks) {
|
|
97
|
+
bytes.set(chunk, offset);
|
|
98
|
+
offset += chunk.byteLength;
|
|
99
|
+
}
|
|
100
|
+
return bytes;
|
|
101
|
+
}
|
|
102
|
+
async function readTextResponseCapped(response, maxBytes) {
|
|
103
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
104
|
+
await readResponseBytesCapped(response, maxBytes)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
async function readJsonResponseCapped(response, maxBytes) {
|
|
108
|
+
return JSON.parse(await readTextResponseCapped(response, maxBytes));
|
|
109
|
+
}
|
|
110
|
+
|
|
69
111
|
// src/api.ts
|
|
112
|
+
var DEFAULT_API_BASE = "https://api.whoburnedmore.com";
|
|
113
|
+
var MAX_SERVER_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
70
114
|
function apiBase() {
|
|
71
|
-
|
|
115
|
+
const raw = process.env.WHOBURNEDMORE_API?.trim() || DEFAULT_API_BASE;
|
|
116
|
+
try {
|
|
117
|
+
const url = new URL(raw);
|
|
118
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
119
|
+
const trustedTransport = url.protocol === "https:" || url.protocol === "http:" && loopback;
|
|
120
|
+
if (!trustedTransport || url.username || url.password || url.pathname !== "" && url.pathname !== "/" || url.search || url.hash) {
|
|
121
|
+
throw new Error("untrusted");
|
|
122
|
+
}
|
|
123
|
+
return url.origin;
|
|
124
|
+
} catch {
|
|
125
|
+
throw new Error("WHOBURNEDMORE_API must be a trusted HTTPS or loopback HTTP origin");
|
|
126
|
+
}
|
|
72
127
|
}
|
|
73
128
|
function webBase() {
|
|
74
129
|
return process.env.WHOBURNEDMORE_WEB ?? "https://whoburnedmore.com";
|
|
@@ -88,7 +143,7 @@ function isOpenableUrl(url) {
|
|
|
88
143
|
return /^(https?|file):\/\//.test(url);
|
|
89
144
|
}
|
|
90
145
|
async function readJson(res) {
|
|
91
|
-
const text = await res
|
|
146
|
+
const text = await readTextResponseCapped(res, MAX_SERVER_RESPONSE_BYTES);
|
|
92
147
|
if (!text) return {};
|
|
93
148
|
try {
|
|
94
149
|
return JSON.parse(text);
|
|
@@ -109,7 +164,8 @@ async function send(method, path, body, token) {
|
|
|
109
164
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
110
165
|
// Bound the request so a slow/black-holing/hostile server can't hang the CLI
|
|
111
166
|
// — or the unattended 15-minute background sync — indefinitely.
|
|
112
|
-
signal: AbortSignal.timeout(3e4)
|
|
167
|
+
signal: AbortSignal.timeout(3e4),
|
|
168
|
+
redirect: "error"
|
|
113
169
|
});
|
|
114
170
|
} catch {
|
|
115
171
|
throw new Error(
|
|
@@ -160,9 +216,9 @@ async function devicePoll(deviceCode) {
|
|
|
160
216
|
}
|
|
161
217
|
return body;
|
|
162
218
|
}
|
|
163
|
-
async function refreshCliToken(
|
|
219
|
+
async function refreshCliToken(refreshToken) {
|
|
164
220
|
try {
|
|
165
|
-
const { status, body } = await post("/v1/auth/cli/refresh", {
|
|
221
|
+
const { status, body } = await post("/v1/auth/cli/refresh", { refreshToken });
|
|
166
222
|
if (status === 200 && typeof body.token === "string" && body.token) {
|
|
167
223
|
return { token: body.token, handle: body.handle ?? "" };
|
|
168
224
|
}
|
|
@@ -172,14 +228,20 @@ async function refreshCliToken(anonKey) {
|
|
|
172
228
|
}
|
|
173
229
|
async function bindDeviceKey(token, anonKey) {
|
|
174
230
|
try {
|
|
175
|
-
const { status } = await post(
|
|
231
|
+
const { status, body } = await post(
|
|
176
232
|
"/v1/me/devices/bind",
|
|
177
233
|
{ anonKey },
|
|
178
234
|
token
|
|
179
235
|
);
|
|
180
|
-
|
|
236
|
+
if (status === 200) {
|
|
237
|
+
return {
|
|
238
|
+
definitive: true,
|
|
239
|
+
...typeof body.refreshToken === "string" && body.refreshToken ? { refreshToken: body.refreshToken } : {}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return { definitive: status === 409 };
|
|
181
243
|
} catch {
|
|
182
|
-
return false;
|
|
244
|
+
return { definitive: false };
|
|
183
245
|
}
|
|
184
246
|
}
|
|
185
247
|
async function submit(token, payload) {
|
|
@@ -228,31 +290,112 @@ async function redeemServerInstall(token, anonKey) {
|
|
|
228
290
|
|
|
229
291
|
// src/autosync.ts
|
|
230
292
|
import { spawnSync } from "node:child_process";
|
|
293
|
+
import { createRequire } from "node:module";
|
|
231
294
|
import {
|
|
232
295
|
existsSync as existsSync2,
|
|
233
296
|
mkdirSync as mkdirSync2,
|
|
234
|
-
readFileSync
|
|
297
|
+
readFileSync,
|
|
235
298
|
renameSync as renameSync2,
|
|
236
299
|
rmSync as rmSync2,
|
|
237
300
|
statSync,
|
|
238
301
|
writeFileSync as writeFileSync2
|
|
239
302
|
} from "node:fs";
|
|
240
303
|
import { homedir as homedir2, platform } from "node:os";
|
|
241
|
-
import { dirname, join as join2, posix, win32 } from "node:path";
|
|
304
|
+
import { dirname as dirname2, join as join2, posix, win32 } from "node:path";
|
|
242
305
|
|
|
243
306
|
// src/config.ts
|
|
307
|
+
import { createHash, randomBytes as randomBytes2 } from "node:crypto";
|
|
308
|
+
import {
|
|
309
|
+
existsSync
|
|
310
|
+
} from "node:fs";
|
|
311
|
+
import { homedir } from "node:os";
|
|
312
|
+
import { join } from "node:path";
|
|
313
|
+
|
|
314
|
+
// src/safe-local-file.ts
|
|
244
315
|
import { randomBytes } from "node:crypto";
|
|
245
316
|
import {
|
|
246
317
|
chmodSync,
|
|
247
|
-
|
|
318
|
+
closeSync,
|
|
319
|
+
constants,
|
|
320
|
+
fstatSync,
|
|
248
321
|
mkdirSync,
|
|
249
|
-
|
|
322
|
+
openSync,
|
|
323
|
+
readSync,
|
|
250
324
|
renameSync,
|
|
251
325
|
rmSync,
|
|
252
326
|
writeFileSync
|
|
253
327
|
} from "node:fs";
|
|
254
|
-
import {
|
|
255
|
-
|
|
328
|
+
import { dirname } from "node:path";
|
|
329
|
+
function readTextFileSyncCapped(path, maxBytes) {
|
|
330
|
+
const limit = Math.max(1, maxBytes);
|
|
331
|
+
let fd = null;
|
|
332
|
+
try {
|
|
333
|
+
fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
334
|
+
const metadata = fstatSync(fd);
|
|
335
|
+
if (!metadata.isFile() || metadata.size > limit) return null;
|
|
336
|
+
const chunks = [];
|
|
337
|
+
let total = 0;
|
|
338
|
+
while (true) {
|
|
339
|
+
const allowance = Math.min(64 * 1024, limit + 1 - total);
|
|
340
|
+
if (allowance <= 0) return null;
|
|
341
|
+
const chunk = Buffer.allocUnsafe(allowance);
|
|
342
|
+
const count = readSync(fd, chunk, 0, allowance, null);
|
|
343
|
+
if (count === 0) break;
|
|
344
|
+
total += count;
|
|
345
|
+
if (total > limit) return null;
|
|
346
|
+
chunks.push(chunk.subarray(0, count));
|
|
347
|
+
}
|
|
348
|
+
return Buffer.concat(chunks, total).toString("utf8");
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
} finally {
|
|
352
|
+
if (fd !== null) {
|
|
353
|
+
try {
|
|
354
|
+
closeSync(fd);
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function writePrivateFileAtomic(path, content, maxBytes) {
|
|
361
|
+
if (Buffer.byteLength(content, "utf8") > Math.max(1, maxBytes)) return false;
|
|
362
|
+
const dir = dirname(path);
|
|
363
|
+
let tmp = null;
|
|
364
|
+
try {
|
|
365
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
366
|
+
try {
|
|
367
|
+
chmodSync(dir, 448);
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
tmp = `${path}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
|
|
371
|
+
writeFileSync(tmp, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
372
|
+
try {
|
|
373
|
+
chmodSync(tmp, 384);
|
|
374
|
+
} catch {
|
|
375
|
+
}
|
|
376
|
+
renameSync(tmp, path);
|
|
377
|
+
tmp = null;
|
|
378
|
+
return true;
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
} finally {
|
|
382
|
+
if (tmp) {
|
|
383
|
+
try {
|
|
384
|
+
rmSync(tmp, { force: true });
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/config.ts
|
|
392
|
+
var MAX_CONFIG_BYTES = 1024 * 1024;
|
|
393
|
+
function deviceKeyHash(anonKey) {
|
|
394
|
+
return createHash("sha256").update(anonKey).digest("hex");
|
|
395
|
+
}
|
|
396
|
+
function needsDeviceBind(config) {
|
|
397
|
+
return !config.deviceBoundAt || !config.refreshToken;
|
|
398
|
+
}
|
|
256
399
|
function defaultConfigDir() {
|
|
257
400
|
const override = process.env.WHOBURNEDMORE_CONFIG_DIR?.trim();
|
|
258
401
|
if (override) return override;
|
|
@@ -262,10 +405,13 @@ function loadConfig(dir = defaultConfigDir()) {
|
|
|
262
405
|
const file = join(dir, "config.json");
|
|
263
406
|
if (!existsSync(file)) return null;
|
|
264
407
|
try {
|
|
265
|
-
const
|
|
408
|
+
const content = readTextFileSyncCapped(file, MAX_CONFIG_BYTES);
|
|
409
|
+
if (content === null) return null;
|
|
410
|
+
const parsed = JSON.parse(content);
|
|
266
411
|
const config = {};
|
|
267
412
|
if (typeof parsed.anonKey === "string") config.anonKey = parsed.anonKey;
|
|
268
413
|
if (typeof parsed.cliToken === "string") config.cliToken = parsed.cliToken;
|
|
414
|
+
if (typeof parsed.refreshToken === "string") config.refreshToken = parsed.refreshToken;
|
|
269
415
|
if (typeof parsed.handle === "string") config.handle = parsed.handle;
|
|
270
416
|
if (typeof parsed.lastSyncAt === "number" && Number.isFinite(parsed.lastSyncAt))
|
|
271
417
|
config.lastSyncAt = parsed.lastSyncAt;
|
|
@@ -281,28 +427,15 @@ function loadConfig(dir = defaultConfigDir()) {
|
|
|
281
427
|
}
|
|
282
428
|
}
|
|
283
429
|
function saveConfig(dir = defaultConfigDir(), config = {}) {
|
|
284
|
-
mkdirSync(dir, { recursive: true });
|
|
285
430
|
const file = join(dir, "config.json");
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
|
|
289
|
-
try {
|
|
290
|
-
chmodSync(tmp, 384);
|
|
291
|
-
} catch {
|
|
292
|
-
}
|
|
293
|
-
renameSync(tmp, file);
|
|
294
|
-
} catch (err) {
|
|
295
|
-
try {
|
|
296
|
-
rmSync(tmp, { force: true });
|
|
297
|
-
} catch {
|
|
298
|
-
}
|
|
299
|
-
throw err;
|
|
431
|
+
if (!writePrivateFileAtomic(file, JSON.stringify(config, null, 2), MAX_CONFIG_BYTES)) {
|
|
432
|
+
throw new Error("could not save private CLI configuration");
|
|
300
433
|
}
|
|
301
434
|
}
|
|
302
435
|
function ensureAnonKey(dir = defaultConfigDir()) {
|
|
303
436
|
const config = loadConfig(dir) ?? {};
|
|
304
437
|
if (config.anonKey) return config.anonKey;
|
|
305
|
-
const anonKey =
|
|
438
|
+
const anonKey = randomBytes2(32).toString("hex");
|
|
306
439
|
saveConfig(dir, { ...config, anonKey });
|
|
307
440
|
return anonKey;
|
|
308
441
|
}
|
|
@@ -316,7 +449,12 @@ function recordDeviceBound(dir = defaultConfigDir(), when = Date.now()) {
|
|
|
316
449
|
}
|
|
317
450
|
function saveAuth(dir = defaultConfigDir(), auth = { cliToken: "" }) {
|
|
318
451
|
const config = loadConfig(dir) ?? {};
|
|
319
|
-
saveConfig(dir, {
|
|
452
|
+
saveConfig(dir, {
|
|
453
|
+
...config,
|
|
454
|
+
cliToken: auth.cliToken,
|
|
455
|
+
handle: auth.handle,
|
|
456
|
+
...auth.refreshToken ? { refreshToken: auth.refreshToken } : {}
|
|
457
|
+
});
|
|
320
458
|
}
|
|
321
459
|
function clearAuth(dir = defaultConfigDir()) {
|
|
322
460
|
const config = loadConfig(dir);
|
|
@@ -341,7 +479,9 @@ var STABLE_NPM_CANDIDATES = [
|
|
|
341
479
|
"/usr/local/bin/npm",
|
|
342
480
|
"/usr/bin/npm"
|
|
343
481
|
];
|
|
344
|
-
var
|
|
482
|
+
var require2 = createRequire(import.meta.url);
|
|
483
|
+
var CLI_VERSION = require2("../package.json").version;
|
|
484
|
+
var SCHEDULED_PACKAGE_SPEC = `whoburnedmore@${CLI_VERSION}`;
|
|
345
485
|
var SYNC_PATH_DIRS = [
|
|
346
486
|
"/opt/homebrew/bin",
|
|
347
487
|
"/usr/local/bin",
|
|
@@ -351,7 +491,7 @@ var SYNC_PATH_DIRS = [
|
|
|
351
491
|
"/sbin"
|
|
352
492
|
];
|
|
353
493
|
function syncPathEnv(npmPath = resolveNpmPath()) {
|
|
354
|
-
const dir =
|
|
494
|
+
const dir = dirname2(npmPath);
|
|
355
495
|
const dirs = [];
|
|
356
496
|
if (dir && dir !== "." && dir !== "/" && dir !== npmPath) dirs.push(dir);
|
|
357
497
|
for (const d of SYNC_PATH_DIRS) {
|
|
@@ -481,7 +621,7 @@ function syncCommandArgs(npmPath = resolveNpmPath()) {
|
|
|
481
621
|
"--yes",
|
|
482
622
|
"--ignore-scripts",
|
|
483
623
|
"--package",
|
|
484
|
-
|
|
624
|
+
SCHEDULED_PACKAGE_SPEC,
|
|
485
625
|
"--",
|
|
486
626
|
"whoburnedmore",
|
|
487
627
|
"sync"
|
|
@@ -696,8 +836,8 @@ function autoSyncInstalled() {
|
|
|
696
836
|
}
|
|
697
837
|
function readInstalledSystemd() {
|
|
698
838
|
try {
|
|
699
|
-
return `${
|
|
700
|
-
${
|
|
839
|
+
return `${readFileSync(systemdServicePath(), "utf8")}
|
|
840
|
+
${readFileSync(systemdTimerPath(), "utf8")}`;
|
|
701
841
|
} catch {
|
|
702
842
|
return null;
|
|
703
843
|
}
|
|
@@ -709,7 +849,7 @@ ${buildSystemdTimer()}`;
|
|
|
709
849
|
function readInstalledAgent() {
|
|
710
850
|
if (platform() === "darwin") {
|
|
711
851
|
const p = launchAgentPath();
|
|
712
|
-
return existsSync2(p) ?
|
|
852
|
+
return existsSync2(p) ? readFileSync(p, "utf8") : null;
|
|
713
853
|
}
|
|
714
854
|
if (platform() === "linux") {
|
|
715
855
|
const mech = linuxSyncMechanism();
|
|
@@ -797,7 +937,7 @@ async function daemonLoop(deps) {
|
|
|
797
937
|
|
|
798
938
|
// src/collect.ts
|
|
799
939
|
import { execFile } from "node:child_process";
|
|
800
|
-
import { createRequire as
|
|
940
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
801
941
|
import { dirname as dirname6, join as join13 } from "node:path";
|
|
802
942
|
import { promisify } from "node:util";
|
|
803
943
|
|
|
@@ -807,7 +947,7 @@ import { homedir as homedir4 } from "node:os";
|
|
|
807
947
|
import { join as join5 } from "node:path";
|
|
808
948
|
|
|
809
949
|
// src/native/claude.ts
|
|
810
|
-
import { readdir
|
|
950
|
+
import { readdir } from "node:fs/promises";
|
|
811
951
|
import { homedir as homedir3 } from "node:os";
|
|
812
952
|
import { join as join4 } from "node:path";
|
|
813
953
|
|
|
@@ -6248,7 +6388,12 @@ function estimateCostUSD(model, t) {
|
|
|
6248
6388
|
|
|
6249
6389
|
// ../shared/dist/index.js
|
|
6250
6390
|
var DateString = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD");
|
|
6251
|
-
var
|
|
6391
|
+
var MAX_TOKEN_COUNT = 1e11;
|
|
6392
|
+
var MAX_ROLLUP_COUNT = 1e9;
|
|
6393
|
+
var MAX_COST_USD = 1e5;
|
|
6394
|
+
var tokenCount = external_exports.number().int().nonnegative().max(MAX_TOKEN_COUNT);
|
|
6395
|
+
var rollupCount = external_exports.number().int().nonnegative().max(MAX_ROLLUP_COUNT);
|
|
6396
|
+
var costUSD = external_exports.number().nonnegative().max(MAX_COST_USD);
|
|
6252
6397
|
var ConnectorProvider = external_exports.enum([
|
|
6253
6398
|
"anthropic-api",
|
|
6254
6399
|
"openai-api",
|
|
@@ -6278,7 +6423,7 @@ var DailyUsageEntry = external_exports.object({
|
|
|
6278
6423
|
cacheCreationTokens: tokenCount,
|
|
6279
6424
|
cacheReadTokens: tokenCount,
|
|
6280
6425
|
/** Estimated cost in USD for this entry. */
|
|
6281
|
-
costUSD
|
|
6426
|
+
costUSD,
|
|
6282
6427
|
/** Where this entry came from. Defaults to the local CLI for back-compat. */
|
|
6283
6428
|
origin: UsageOrigin.default("cli"),
|
|
6284
6429
|
/** True when the numbers come from a provider's authoritative usage API. */
|
|
@@ -6293,7 +6438,7 @@ var DailyUsageEntry = external_exports.object({
|
|
|
6293
6438
|
* cannot see request ids) omit it, and an omitted fingerprint is never
|
|
6294
6439
|
* penalized.
|
|
6295
6440
|
*/
|
|
6296
|
-
requestCount:
|
|
6441
|
+
requestCount: rollupCount.optional()
|
|
6297
6442
|
});
|
|
6298
6443
|
var Timestamp = external_exports.string().min(1).max(40);
|
|
6299
6444
|
var SessionEntry = external_exports.object({
|
|
@@ -6304,49 +6449,76 @@ var SessionEntry = external_exports.object({
|
|
|
6304
6449
|
outputTokens: tokenCount,
|
|
6305
6450
|
cacheCreationTokens: tokenCount,
|
|
6306
6451
|
cacheReadTokens: tokenCount,
|
|
6307
|
-
costUSD
|
|
6452
|
+
costUSD,
|
|
6308
6453
|
lastActivity: Timestamp,
|
|
6309
6454
|
/** Number of assistant messages in this session (from transcripts). Optional. */
|
|
6310
|
-
messageCount:
|
|
6455
|
+
messageCount: rollupCount.optional()
|
|
6311
6456
|
});
|
|
6312
6457
|
var BlockEntry = external_exports.object({
|
|
6313
6458
|
startTime: Timestamp,
|
|
6314
6459
|
totalTokens: tokenCount,
|
|
6315
|
-
costUSD
|
|
6460
|
+
costUSD
|
|
6316
6461
|
});
|
|
6317
6462
|
var ToolStat = external_exports.object({
|
|
6318
6463
|
name: external_exports.string().min(1).max(128),
|
|
6319
|
-
count:
|
|
6464
|
+
count: rollupCount,
|
|
6320
6465
|
/** How many of those calls returned an error/interrupt (tool reliability). Optional. */
|
|
6321
|
-
errors:
|
|
6466
|
+
errors: rollupCount.optional(),
|
|
6322
6467
|
/** Tokens burned on turns that used this tool (turn tokens split across its tool calls). Optional. */
|
|
6323
|
-
tokens:
|
|
6468
|
+
tokens: tokenCount.optional()
|
|
6324
6469
|
});
|
|
6325
6470
|
var AgentStat = external_exports.object({
|
|
6326
6471
|
/** Total assistant messages across transcripts. */
|
|
6327
|
-
messageCount:
|
|
6472
|
+
messageCount: rollupCount,
|
|
6328
6473
|
/** Assistant messages that ran inside a subagent sidechain. */
|
|
6329
|
-
subagentMessages:
|
|
6474
|
+
subagentMessages: rollupCount,
|
|
6330
6475
|
/** Tokens spent inside subagent sidechains. */
|
|
6331
|
-
subagentTokens:
|
|
6476
|
+
subagentTokens: tokenCount,
|
|
6332
6477
|
/** Total tokens observed across transcripts (denominator for the share). */
|
|
6333
|
-
totalTokens:
|
|
6478
|
+
totalTokens: tokenCount,
|
|
6334
6479
|
/**
|
|
6335
6480
|
* Messages the human actually sent (their prompts) — non-sidechain user turns
|
|
6336
6481
|
* carrying real text, NOT tool results or injected/meta turns. Denominator for
|
|
6337
6482
|
* "avg cost per message". Optional (back-compat with older CLIs).
|
|
6338
6483
|
*/
|
|
6339
|
-
userMessageCount:
|
|
6484
|
+
userMessageCount: rollupCount.optional()
|
|
6340
6485
|
});
|
|
6341
6486
|
var SkillStat = external_exports.object({
|
|
6342
6487
|
name: external_exports.string().min(1).max(128),
|
|
6343
|
-
count:
|
|
6488
|
+
count: rollupCount,
|
|
6344
6489
|
/** Tokens burned in records produced while this skill was active. Optional. */
|
|
6345
|
-
tokens:
|
|
6490
|
+
tokens: tokenCount.optional()
|
|
6491
|
+
});
|
|
6492
|
+
var CodexReplayPriorRow = external_exports.object({
|
|
6493
|
+
model: external_exports.string().min(1).max(128),
|
|
6494
|
+
inputTokens: tokenCount,
|
|
6495
|
+
outputTokens: tokenCount,
|
|
6496
|
+
cacheCreationTokens: tokenCount,
|
|
6497
|
+
cacheReadTokens: tokenCount
|
|
6498
|
+
});
|
|
6499
|
+
var CodexReplayPriorScope = external_exports.object({
|
|
6500
|
+
date: DateString,
|
|
6501
|
+
rows: external_exports.array(CodexReplayPriorRow).min(1).max(100)
|
|
6346
6502
|
});
|
|
6347
6503
|
var SubmitPayload = external_exports.object({
|
|
6348
6504
|
cliVersion: external_exports.string().min(1).max(32),
|
|
6349
6505
|
entries: external_exports.array(DailyUsageEntry).min(1).max(2e4),
|
|
6506
|
+
/**
|
|
6507
|
+
* Dates observed only inside replayed Codex fork/subagent rollouts and absent
|
|
6508
|
+
* from a successful replay-aware parse. The API may remove an old
|
|
6509
|
+
* self-reported Codex scope for these targeted dates only when the account has
|
|
6510
|
+
* one confirmed device and the accompanying prior scope matches exactly.
|
|
6511
|
+
* Omitted on parser failure, timeout, capped payloads, and older clients.
|
|
6512
|
+
*/
|
|
6513
|
+
codexReplayTombstoneDates: external_exports.array(DateString).max(5e3).optional(),
|
|
6514
|
+
/**
|
|
6515
|
+
* Compare-and-swap proof for destructive Codex correction. Each scope is the
|
|
6516
|
+
* exact model/token snapshot the legacy native reader would have submitted.
|
|
6517
|
+
* The API corrects only when its stored scope matches these rows exactly.
|
|
6518
|
+
*/
|
|
6519
|
+
codexReplayPriorScopes: external_exports.array(CodexReplayPriorScope).max(5e3).optional(),
|
|
6520
|
+
/** sha256 of this machine's bound secret; required by the API for correction authority. */
|
|
6521
|
+
deviceKeyHash: external_exports.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
6350
6522
|
/** Optional per-conversation rollups (ccusage session). Back-compat: omittable. */
|
|
6351
6523
|
sessions: external_exports.array(SessionEntry).max(1e4).optional(),
|
|
6352
6524
|
/** Optional time-window rollups (ccusage blocks) for peak-hours analysis. */
|
|
@@ -6420,6 +6592,7 @@ function entryTotalTokens(e) {
|
|
|
6420
6592
|
}
|
|
6421
6593
|
var LeaderboardPeriod = external_exports.enum(["today", "7d", "30d", "all"]);
|
|
6422
6594
|
var LeaderboardMetric = external_exports.enum(["tokens", "cost"]);
|
|
6595
|
+
var InsightsPeriod = external_exports.enum(["7d", "30d", "all"]);
|
|
6423
6596
|
var OrgType = external_exports.enum(["company", "hackathon", "hackerhouse"]);
|
|
6424
6597
|
var MemberRole = external_exports.enum(["owner", "admin", "member"]);
|
|
6425
6598
|
var OrgBoardVisibility = external_exports.enum(["public", "members"]);
|
|
@@ -6558,18 +6731,63 @@ var OrgJoinInput = external_exports.object({
|
|
|
6558
6731
|
});
|
|
6559
6732
|
|
|
6560
6733
|
// src/native/file-cache.ts
|
|
6561
|
-
import {
|
|
6562
|
-
|
|
6563
|
-
|
|
6734
|
+
import {
|
|
6735
|
+
chmodSync as chmodSync2,
|
|
6736
|
+
constants as constants2,
|
|
6737
|
+
mkdirSync as mkdirSync3,
|
|
6738
|
+
renameSync as renameSync3,
|
|
6739
|
+
unlinkSync,
|
|
6740
|
+
writeFileSync as writeFileSync3
|
|
6741
|
+
} from "node:fs";
|
|
6742
|
+
import { open, stat } from "node:fs/promises";
|
|
6743
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
6744
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
6745
|
+
var MAX_NATIVE_FILE_BYTES = 64 * 1024 * 1024;
|
|
6746
|
+
var MAX_NATIVE_CACHE_BYTES = 128 * 1024 * 1024;
|
|
6747
|
+
var READ_CHUNK_BYTES = 64 * 1024;
|
|
6748
|
+
async function readTextFileCapped(path, opts = {}) {
|
|
6749
|
+
const maxBytes = Math.max(1, opts.maxBytes ?? MAX_NATIVE_FILE_BYTES);
|
|
6750
|
+
const deadline = opts.deadline ?? Number.POSITIVE_INFINITY;
|
|
6751
|
+
const now = opts.now ?? Date.now;
|
|
6752
|
+
let handle = null;
|
|
6753
|
+
try {
|
|
6754
|
+
handle = await open(
|
|
6755
|
+
path,
|
|
6756
|
+
constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0)
|
|
6757
|
+
);
|
|
6758
|
+
const metadata = await handle.stat();
|
|
6759
|
+
if (!metadata.isFile()) return { ok: false, reason: "unreadable" };
|
|
6760
|
+
if (metadata.size > maxBytes) return { ok: false, reason: "too-large" };
|
|
6761
|
+
const chunks = [];
|
|
6762
|
+
let total = 0;
|
|
6763
|
+
while (true) {
|
|
6764
|
+
if (now() > deadline) return { ok: false, reason: "timed-out" };
|
|
6765
|
+
const allowance = Math.min(READ_CHUNK_BYTES, maxBytes + 1 - total);
|
|
6766
|
+
if (allowance <= 0) return { ok: false, reason: "too-large" };
|
|
6767
|
+
const chunk = Buffer.allocUnsafe(allowance);
|
|
6768
|
+
const { bytesRead } = await handle.read(chunk, 0, allowance, null);
|
|
6769
|
+
if (bytesRead === 0) break;
|
|
6770
|
+
total += bytesRead;
|
|
6771
|
+
if (total > maxBytes) return { ok: false, reason: "too-large" };
|
|
6772
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
6773
|
+
}
|
|
6774
|
+
return { ok: true, content: Buffer.concat(chunks, total).toString("utf8") };
|
|
6775
|
+
} catch {
|
|
6776
|
+
return { ok: false, reason: "unreadable" };
|
|
6777
|
+
} finally {
|
|
6778
|
+
await handle?.close().catch(() => void 0);
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6564
6781
|
function nativeCachePath(reader, env = process.env) {
|
|
6565
6782
|
const override = env.WHOBURNEDMORE_CONFIG_DIR?.trim();
|
|
6566
6783
|
const dir = override || defaultConfigDir();
|
|
6567
6784
|
return join3(dir, `native-cache-${reader}.json`);
|
|
6568
6785
|
}
|
|
6569
|
-
async function loadCache(path, version) {
|
|
6786
|
+
async function loadCache(path, version, maxBytes) {
|
|
6570
6787
|
try {
|
|
6571
|
-
const
|
|
6572
|
-
|
|
6788
|
+
const read = await readTextFileCapped(path, { maxBytes });
|
|
6789
|
+
if (!read.ok) return {};
|
|
6790
|
+
const parsed = JSON.parse(read.content);
|
|
6573
6791
|
if (parsed && parsed.v === version && parsed.files && typeof parsed.files === "object") {
|
|
6574
6792
|
return parsed.files;
|
|
6575
6793
|
}
|
|
@@ -6577,18 +6795,34 @@ async function loadCache(path, version) {
|
|
|
6577
6795
|
}
|
|
6578
6796
|
return {};
|
|
6579
6797
|
}
|
|
6580
|
-
function saveCache(path, version, files) {
|
|
6798
|
+
function saveCache(path, version, files, maxBytes) {
|
|
6799
|
+
let tmp = null;
|
|
6581
6800
|
try {
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6801
|
+
const dir = dirname3(path);
|
|
6802
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
6803
|
+
chmodSync2(dir, 448);
|
|
6804
|
+
tmp = `${path}.tmp-${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
6805
|
+
const serialized = JSON.stringify({ v: version, files });
|
|
6806
|
+
if (Buffer.byteLength(serialized, "utf8") > maxBytes) return;
|
|
6807
|
+
writeFileSync3(tmp, serialized, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
6808
|
+
chmodSync2(tmp, 384);
|
|
6585
6809
|
renameSync3(tmp, path);
|
|
6810
|
+
tmp = null;
|
|
6586
6811
|
} catch {
|
|
6812
|
+
} finally {
|
|
6813
|
+
if (tmp) {
|
|
6814
|
+
try {
|
|
6815
|
+
unlinkSync(tmp);
|
|
6816
|
+
} catch {
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6587
6819
|
}
|
|
6588
6820
|
}
|
|
6589
6821
|
async function readFilesWithCache(opts) {
|
|
6590
6822
|
const now = opts.now ?? Date.now;
|
|
6591
|
-
const
|
|
6823
|
+
const maxFileBytes = Math.max(1, opts.maxFileBytes ?? MAX_NATIVE_FILE_BYTES);
|
|
6824
|
+
const maxCacheBytes = Math.max(1, opts.maxCacheBytes ?? MAX_NATIVE_CACHE_BYTES);
|
|
6825
|
+
const cached = await loadCache(opts.cachePath, opts.version, maxCacheBytes);
|
|
6592
6826
|
const fresh = {};
|
|
6593
6827
|
const itemsByFile = [];
|
|
6594
6828
|
let filesRead = 0;
|
|
@@ -6602,6 +6836,9 @@ async function readFilesWithCache(opts) {
|
|
|
6602
6836
|
} catch {
|
|
6603
6837
|
continue;
|
|
6604
6838
|
}
|
|
6839
|
+
if (size > maxFileBytes) {
|
|
6840
|
+
continue;
|
|
6841
|
+
}
|
|
6605
6842
|
const hit = cached[f];
|
|
6606
6843
|
if (hit && hit.size === size && hit.mtimeMs === mtimeMs) {
|
|
6607
6844
|
fresh[f] = hit;
|
|
@@ -6609,21 +6846,27 @@ async function readFilesWithCache(opts) {
|
|
|
6609
6846
|
continue;
|
|
6610
6847
|
}
|
|
6611
6848
|
if (now() > opts.deadline) {
|
|
6612
|
-
saveCache(opts.cachePath, opts.version, { ...cached, ...fresh });
|
|
6849
|
+
saveCache(opts.cachePath, opts.version, { ...cached, ...fresh }, maxCacheBytes);
|
|
6613
6850
|
return { itemsByFile: null, filesRead, timedOut: true };
|
|
6614
6851
|
}
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6852
|
+
const read = await readTextFileCapped(f, {
|
|
6853
|
+
maxBytes: maxFileBytes,
|
|
6854
|
+
deadline: opts.deadline,
|
|
6855
|
+
now
|
|
6856
|
+
});
|
|
6857
|
+
if (!read.ok) {
|
|
6858
|
+
if (read.reason === "timed-out") {
|
|
6859
|
+
saveCache(opts.cachePath, opts.version, { ...cached, ...fresh }, maxCacheBytes);
|
|
6860
|
+
return { itemsByFile: null, filesRead, timedOut: true };
|
|
6861
|
+
}
|
|
6619
6862
|
continue;
|
|
6620
6863
|
}
|
|
6621
|
-
const items = opts.parseFile(content, f);
|
|
6864
|
+
const items = opts.parseFile(read.content, f);
|
|
6622
6865
|
fresh[f] = { size, mtimeMs, items };
|
|
6623
6866
|
itemsByFile.push(items);
|
|
6624
6867
|
filesRead += 1;
|
|
6625
6868
|
}
|
|
6626
|
-
saveCache(opts.cachePath, opts.version, fresh);
|
|
6869
|
+
saveCache(opts.cachePath, opts.version, fresh, maxCacheBytes);
|
|
6627
6870
|
return { itemsByFile, filesRead, timedOut: false };
|
|
6628
6871
|
}
|
|
6629
6872
|
|
|
@@ -6866,13 +7109,18 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
|
|
|
6866
7109
|
if (now() > deadline) {
|
|
6867
7110
|
return { requests: [...acc.values()], found: true, timedOut: true };
|
|
6868
7111
|
}
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
7112
|
+
const read = await readTextFileCapped(f, {
|
|
7113
|
+
maxBytes: opts.maxFileBytes ?? MAX_NATIVE_FILE_BYTES,
|
|
7114
|
+
deadline,
|
|
7115
|
+
now
|
|
7116
|
+
});
|
|
7117
|
+
if (!read.ok) {
|
|
7118
|
+
if (read.reason === "timed-out") {
|
|
7119
|
+
return { requests: [...acc.values()], found: true, timedOut: true };
|
|
7120
|
+
}
|
|
6873
7121
|
continue;
|
|
6874
7122
|
}
|
|
6875
|
-
accumulateClaudeLines(acc, splitLines(content));
|
|
7123
|
+
accumulateClaudeLines(acc, splitLines(read.content));
|
|
6876
7124
|
}
|
|
6877
7125
|
return { requests: [...acc.values()], found: true };
|
|
6878
7126
|
}
|
|
@@ -7277,28 +7525,54 @@ function parseContinueJsonl(content) {
|
|
|
7277
7525
|
}
|
|
7278
7526
|
return mapContinueRecords(records);
|
|
7279
7527
|
}
|
|
7280
|
-
async function listJsonl2(
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7528
|
+
async function listJsonl2(root, opts) {
|
|
7529
|
+
const queue = [{ dir: root, depth: 0 }];
|
|
7530
|
+
let queueIndex = 0;
|
|
7531
|
+
const files = [];
|
|
7532
|
+
let visitedEntries = 0;
|
|
7533
|
+
const maxEntries = Math.max(1e3, opts.maxFiles * 20);
|
|
7534
|
+
while (queueIndex < queue.length) {
|
|
7535
|
+
if (opts.now() > opts.deadline) return { files: [], aborted: true };
|
|
7536
|
+
const current = queue[queueIndex++];
|
|
7537
|
+
let dirents;
|
|
7538
|
+
try {
|
|
7539
|
+
dirents = await readdir2(current.dir, { withFileTypes: true });
|
|
7540
|
+
} catch {
|
|
7541
|
+
continue;
|
|
7542
|
+
}
|
|
7543
|
+
for (const d of dirents) {
|
|
7544
|
+
visitedEntries += 1;
|
|
7545
|
+
if (visitedEntries > maxEntries || opts.now() > opts.deadline) {
|
|
7546
|
+
return { files: [], aborted: true };
|
|
7547
|
+
}
|
|
7548
|
+
const full = join6(current.dir, d.name);
|
|
7549
|
+
if (d.isDirectory()) {
|
|
7550
|
+
if (current.depth >= 32) return { files: [], aborted: true };
|
|
7551
|
+
queue.push({ dir: full, depth: current.depth + 1 });
|
|
7552
|
+
} else if (d.isFile() && d.name === "tokensGenerated.jsonl") {
|
|
7553
|
+
files.push(full);
|
|
7554
|
+
if (files.length > opts.maxFiles) return { files: [], aborted: true };
|
|
7555
|
+
}
|
|
7556
|
+
}
|
|
7292
7557
|
}
|
|
7293
|
-
return
|
|
7558
|
+
return { files, aborted: false };
|
|
7294
7559
|
}
|
|
7295
7560
|
var CONTINUE_CACHE_VERSION = 2;
|
|
7296
7561
|
async function collectContinue(opts = {}) {
|
|
7297
7562
|
const env = opts.env ?? process.env;
|
|
7298
7563
|
const home = opts.continueDir ?? join6(env.HOME || homedir5(), ".continue");
|
|
7299
|
-
const files = await listJsonl2(join6(home, "dev_data"));
|
|
7300
|
-
if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
|
|
7301
7564
|
const now = opts.now ?? Date.now;
|
|
7565
|
+
const deadline = now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS);
|
|
7566
|
+
const discovery = await listJsonl2(join6(home, "dev_data"), {
|
|
7567
|
+
deadline,
|
|
7568
|
+
now,
|
|
7569
|
+
maxFiles: Math.max(1, opts.maxFiles ?? 1e4)
|
|
7570
|
+
});
|
|
7571
|
+
if (discovery.aborted) {
|
|
7572
|
+
return { entries: [], found: false, filesScanned: 0, timedOut: true };
|
|
7573
|
+
}
|
|
7574
|
+
const files = discovery.files;
|
|
7575
|
+
if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
|
|
7302
7576
|
const res = await readFilesWithCache({
|
|
7303
7577
|
files,
|
|
7304
7578
|
cachePath: opts.cachePath ?? nativeCachePath("continue", env),
|
|
@@ -7311,7 +7585,7 @@ async function collectContinue(opts = {}) {
|
|
|
7311
7585
|
e.costUSD,
|
|
7312
7586
|
e.requestCount ?? 0
|
|
7313
7587
|
]),
|
|
7314
|
-
deadline
|
|
7588
|
+
deadline,
|
|
7315
7589
|
now
|
|
7316
7590
|
});
|
|
7317
7591
|
if (!res.itemsByFile) {
|
|
@@ -7350,15 +7624,15 @@ async function collectContinue(opts = {}) {
|
|
|
7350
7624
|
|
|
7351
7625
|
// src/cursor.ts
|
|
7352
7626
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
7353
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
7354
|
-
import { createRequire as
|
|
7627
|
+
import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
|
|
7628
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
7355
7629
|
import { homedir as homedir6, platform as platform2 } from "node:os";
|
|
7356
7630
|
import { join as join8 } from "node:path";
|
|
7357
7631
|
|
|
7358
7632
|
// src/tokscale.ts
|
|
7359
7633
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
7360
|
-
import { createRequire } from "node:module";
|
|
7361
|
-
import { dirname as
|
|
7634
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
7635
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
7362
7636
|
var LOOKBACK_DAYS = 30;
|
|
7363
7637
|
function num3(n) {
|
|
7364
7638
|
const v = Math.round(Number(n));
|
|
@@ -7377,9 +7651,9 @@ function mapTokscaleDay(date, json) {
|
|
|
7377
7651
|
const outputTokens = num3(e.output) + num3(e.reasoning);
|
|
7378
7652
|
const cacheCreationTokens = num3(e.cacheWrite);
|
|
7379
7653
|
const cacheReadTokens = num3(e.cacheRead);
|
|
7380
|
-
const
|
|
7654
|
+
const costUSD2 = numCost(e.cost);
|
|
7381
7655
|
const total = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
|
|
7382
|
-
if (total === 0 &&
|
|
7656
|
+
if (total === 0 && costUSD2 === 0) continue;
|
|
7383
7657
|
out.push({
|
|
7384
7658
|
date,
|
|
7385
7659
|
tool: "cursor",
|
|
@@ -7388,7 +7662,7 @@ function mapTokscaleDay(date, json) {
|
|
|
7388
7662
|
outputTokens,
|
|
7389
7663
|
cacheCreationTokens,
|
|
7390
7664
|
cacheReadTokens,
|
|
7391
|
-
costUSD,
|
|
7665
|
+
costUSD: costUSD2,
|
|
7392
7666
|
origin: "cli",
|
|
7393
7667
|
verified: false
|
|
7394
7668
|
});
|
|
@@ -7397,12 +7671,12 @@ function mapTokscaleDay(date, json) {
|
|
|
7397
7671
|
}
|
|
7398
7672
|
function resolveTokscaleBin() {
|
|
7399
7673
|
try {
|
|
7400
|
-
const
|
|
7401
|
-
const pkgPath =
|
|
7402
|
-
const pkg =
|
|
7674
|
+
const require4 = createRequire2(import.meta.url);
|
|
7675
|
+
const pkgPath = require4.resolve("tokscale/package.json");
|
|
7676
|
+
const pkg = require4("tokscale/package.json");
|
|
7403
7677
|
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
|
|
7404
7678
|
if (!rel) return null;
|
|
7405
|
-
const binPath = join7(
|
|
7679
|
+
const binPath = join7(dirname4(pkgPath), rel);
|
|
7406
7680
|
if (/\.(c|m)?js$/.test(binPath)) {
|
|
7407
7681
|
return { cmd: process.execPath, prefixArgs: [binPath] };
|
|
7408
7682
|
}
|
|
@@ -7459,31 +7733,47 @@ function collectCursorViaTokscale(lookbackDays = LOOKBACK_DAYS) {
|
|
|
7459
7733
|
|
|
7460
7734
|
// src/cursor.ts
|
|
7461
7735
|
var EVENTS_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
|
|
7736
|
+
var MAX_CURSOR_PAGE_BYTES = 4 * 1024 * 1024;
|
|
7462
7737
|
function cursorDbPath() {
|
|
7463
7738
|
const home = homedir6();
|
|
7464
7739
|
const os = platform2();
|
|
7465
7740
|
const p = os === "darwin" ? join8(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join8(process.env.APPDATA ?? join8(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join8(process.env.XDG_CONFIG_HOME ?? join8(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
|
|
7466
7741
|
return existsSync3(p) ? p : null;
|
|
7467
7742
|
}
|
|
7743
|
+
function trustedSqliteBinaries() {
|
|
7744
|
+
const candidates = platform2() === "darwin" ? ["/usr/bin/sqlite3", "/opt/homebrew/bin/sqlite3", "/usr/local/bin/sqlite3"] : platform2() === "linux" ? ["/usr/bin/sqlite3", "/usr/local/bin/sqlite3"] : [];
|
|
7745
|
+
const trusted = [];
|
|
7746
|
+
for (const candidate of candidates) {
|
|
7747
|
+
try {
|
|
7748
|
+
const resolved = realpathSync(candidate);
|
|
7749
|
+
const stat2 = statSync3(resolved);
|
|
7750
|
+
if (stat2.isFile() && (stat2.mode & 18) === 0) trusted.push(resolved);
|
|
7751
|
+
} catch {
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
return [...new Set(trusted)];
|
|
7755
|
+
}
|
|
7468
7756
|
function readCursorToken(db) {
|
|
7469
|
-
const
|
|
7757
|
+
const require4 = createRequire3(import.meta.url);
|
|
7470
7758
|
try {
|
|
7471
|
-
const { DatabaseSync } =
|
|
7759
|
+
const { DatabaseSync } = require4("node:sqlite");
|
|
7472
7760
|
const d = new DatabaseSync(db, { readOnly: true });
|
|
7473
7761
|
const row = d.prepare("SELECT value FROM ItemTable WHERE key = ?").get("cursorAuth/accessToken");
|
|
7474
7762
|
d.close();
|
|
7475
7763
|
if (row?.value) return String(row.value);
|
|
7476
7764
|
} catch {
|
|
7477
7765
|
}
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7766
|
+
for (const sqlite3 of trustedSqliteBinaries()) {
|
|
7767
|
+
try {
|
|
7768
|
+
const res = spawnSync3(
|
|
7769
|
+
sqlite3,
|
|
7770
|
+
[db, "SELECT value FROM ItemTable WHERE key='cursorAuth/accessToken';"],
|
|
7771
|
+
{ encoding: "utf8", timeout: 1e4 }
|
|
7772
|
+
);
|
|
7773
|
+
const out = res.stdout?.trim();
|
|
7774
|
+
if (res.status === 0 && out) return out;
|
|
7775
|
+
} catch {
|
|
7776
|
+
}
|
|
7487
7777
|
}
|
|
7488
7778
|
return null;
|
|
7489
7779
|
}
|
|
@@ -7570,24 +7860,28 @@ async function fetchCursorEvents(cookie, maxPages = 30, pageSize = 500) {
|
|
|
7570
7860
|
Cookie: cookie
|
|
7571
7861
|
},
|
|
7572
7862
|
body: JSON.stringify({ page, pageSize }),
|
|
7573
|
-
signal: AbortSignal.timeout(2e4)
|
|
7863
|
+
signal: AbortSignal.timeout(2e4),
|
|
7864
|
+
redirect: "error"
|
|
7574
7865
|
});
|
|
7575
7866
|
if (!res.ok) {
|
|
7576
7867
|
throw new Error(`cursor usage page ${page} failed (HTTP ${res.status})`);
|
|
7577
7868
|
}
|
|
7578
|
-
const body = await
|
|
7869
|
+
const body = await readJsonResponseCapped(
|
|
7870
|
+
res,
|
|
7871
|
+
MAX_CURSOR_PAGE_BYTES
|
|
7872
|
+
);
|
|
7579
7873
|
const batch = body.usageEventsDisplay ?? [];
|
|
7580
7874
|
all.push(...batch);
|
|
7581
7875
|
if (batch.length < pageSize) break;
|
|
7582
7876
|
}
|
|
7583
7877
|
return all;
|
|
7584
7878
|
}
|
|
7585
|
-
async function collectCursor() {
|
|
7879
|
+
async function collectCursor(opts) {
|
|
7586
7880
|
try {
|
|
7587
7881
|
const db = cursorDbPath();
|
|
7588
7882
|
const token = db ? readCursorToken(db) : null;
|
|
7589
7883
|
const cookie = token ? cursorCookie(token) : null;
|
|
7590
|
-
if (cookie) {
|
|
7884
|
+
if (cookie && !opts?.offline) {
|
|
7591
7885
|
const events = await fetchCursorEvents(cookie);
|
|
7592
7886
|
const { entries, blocks } = mapCursorEvents(events);
|
|
7593
7887
|
if (entries.length > 0) return { entries, blocks, found: true };
|
|
@@ -7605,7 +7899,7 @@ async function collectCursor() {
|
|
|
7605
7899
|
// src/native/codex.ts
|
|
7606
7900
|
import { readdir as readdir3 } from "node:fs/promises";
|
|
7607
7901
|
import { homedir as homedir7 } from "node:os";
|
|
7608
|
-
import { join as join9, resolve } from "node:path";
|
|
7902
|
+
import { basename, join as join9, resolve } from "node:path";
|
|
7609
7903
|
function num5(n) {
|
|
7610
7904
|
const v = Math.round(Number(n));
|
|
7611
7905
|
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
@@ -7622,8 +7916,10 @@ function readTokenFields(payload) {
|
|
|
7622
7916
|
output: num5(output)
|
|
7623
7917
|
};
|
|
7624
7918
|
}
|
|
7625
|
-
function
|
|
7919
|
+
function inspectCodexRollout(lines) {
|
|
7626
7920
|
let model = "unknown";
|
|
7921
|
+
let sawSessionMeta = false;
|
|
7922
|
+
let replayedRollout = false;
|
|
7627
7923
|
const perDay = /* @__PURE__ */ new Map();
|
|
7628
7924
|
let lastSeenDate = null;
|
|
7629
7925
|
for (const raw of lines) {
|
|
@@ -7638,6 +7934,13 @@ function parseCodexRollout(lines) {
|
|
|
7638
7934
|
const payload = obj.payload;
|
|
7639
7935
|
if (!payload || typeof payload !== "object") continue;
|
|
7640
7936
|
const kind = obj.type;
|
|
7937
|
+
if (kind === "session_meta" && !sawSessionMeta) {
|
|
7938
|
+
sawSessionMeta = true;
|
|
7939
|
+
const source = payload.source;
|
|
7940
|
+
if (typeof payload.forked_from_id === "string" || typeof payload.parent_thread_id === "string" || source && typeof source === "object" && source.subagent !== void 0) {
|
|
7941
|
+
replayedRollout = true;
|
|
7942
|
+
}
|
|
7943
|
+
}
|
|
7641
7944
|
if (kind === "session_meta" || kind === "turn_context") {
|
|
7642
7945
|
if (typeof payload.model === "string" && payload.model) model = payload.model;
|
|
7643
7946
|
}
|
|
@@ -7657,7 +7960,8 @@ function parseCodexRollout(lines) {
|
|
|
7657
7960
|
}
|
|
7658
7961
|
}
|
|
7659
7962
|
}
|
|
7660
|
-
if (perDay.size === 0)
|
|
7963
|
+
if (perDay.size === 0)
|
|
7964
|
+
return { sessions: [], replaySessions: [], replayCandidateDates: [] };
|
|
7661
7965
|
const dates = [...perDay.keys()].sort();
|
|
7662
7966
|
const out = [];
|
|
7663
7967
|
let prev = { input: 0, cached: 0, output: 0 };
|
|
@@ -7667,8 +7971,8 @@ function parseCodexRollout(lines) {
|
|
|
7667
7971
|
const dCached = Math.max(0, cum.cached - prev.cached);
|
|
7668
7972
|
const dOutput = Math.max(0, cum.output - prev.output);
|
|
7669
7973
|
prev = cum;
|
|
7670
|
-
const cacheReadTokens = dCached;
|
|
7671
|
-
const inputTokens =
|
|
7974
|
+
const cacheReadTokens = Math.min(dCached, dInput);
|
|
7975
|
+
const inputTokens = dInput - cacheReadTokens;
|
|
7672
7976
|
const outputTokens = dOutput;
|
|
7673
7977
|
if (inputTokens + outputTokens + cacheReadTokens === 0) continue;
|
|
7674
7978
|
out.push({
|
|
@@ -7681,7 +7985,11 @@ function parseCodexRollout(lines) {
|
|
|
7681
7985
|
turnCount: turns
|
|
7682
7986
|
});
|
|
7683
7987
|
}
|
|
7684
|
-
return
|
|
7988
|
+
return {
|
|
7989
|
+
sessions: replayedRollout ? [] : out,
|
|
7990
|
+
replaySessions: replayedRollout ? out : [],
|
|
7991
|
+
replayCandidateDates: replayedRollout ? dates : []
|
|
7992
|
+
};
|
|
7685
7993
|
}
|
|
7686
7994
|
function foldCodexSessions(acc, sessions) {
|
|
7687
7995
|
for (const s of sessions) {
|
|
@@ -7729,7 +8037,7 @@ function resolveCodexHome(env = process.env) {
|
|
|
7729
8037
|
return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join9(homedir7(), ".codex");
|
|
7730
8038
|
}
|
|
7731
8039
|
function resolveCodexSessionsDirs(env = process.env) {
|
|
7732
|
-
const home = resolveCodexHome(env);
|
|
8040
|
+
const home = resolve(resolveCodexHome(env));
|
|
7733
8041
|
return [join9(home, "sessions"), join9(home, "archived_sessions")];
|
|
7734
8042
|
}
|
|
7735
8043
|
async function listJsonl3(dir) {
|
|
@@ -7758,9 +8066,10 @@ function* splitLines3(content) {
|
|
|
7758
8066
|
if (start < content.length) yield content.slice(start);
|
|
7759
8067
|
}
|
|
7760
8068
|
var NATIVE_READ_BUDGET_MS2 = 45e3;
|
|
7761
|
-
var CODEX_CACHE_VERSION =
|
|
7762
|
-
function toCachedSession(s) {
|
|
8069
|
+
var CODEX_CACHE_VERSION = 7;
|
|
8070
|
+
function toCachedSession(s, kind = "usage") {
|
|
7763
8071
|
return [
|
|
8072
|
+
kind,
|
|
7764
8073
|
s.date,
|
|
7765
8074
|
s.model,
|
|
7766
8075
|
s.inputTokens,
|
|
@@ -7772,25 +8081,53 @@ function toCachedSession(s) {
|
|
|
7772
8081
|
}
|
|
7773
8082
|
function fromCachedSession(t) {
|
|
7774
8083
|
return {
|
|
7775
|
-
date: t[
|
|
7776
|
-
model: t[
|
|
7777
|
-
inputTokens: t[
|
|
7778
|
-
outputTokens: t[
|
|
7779
|
-
cacheCreationTokens: t[
|
|
7780
|
-
cacheReadTokens: t[
|
|
7781
|
-
turnCount: t[
|
|
8084
|
+
date: t[1],
|
|
8085
|
+
model: t[2],
|
|
8086
|
+
inputTokens: t[3],
|
|
8087
|
+
outputTokens: t[4],
|
|
8088
|
+
cacheCreationTokens: t[5],
|
|
8089
|
+
cacheReadTokens: t[6],
|
|
8090
|
+
turnCount: t[7]
|
|
7782
8091
|
};
|
|
7783
8092
|
}
|
|
7784
8093
|
async function collectCodexNative(env = process.env, opts = {}) {
|
|
7785
8094
|
const dirs = resolveCodexSessionsDirs(env);
|
|
7786
|
-
const files =
|
|
8095
|
+
const files = [];
|
|
8096
|
+
const primaryFiles = /* @__PURE__ */ new Set();
|
|
8097
|
+
const seenIdentities = /* @__PURE__ */ new Set();
|
|
8098
|
+
for (const dir of dirs) {
|
|
8099
|
+
for (const file of await listJsonl3(dir)) {
|
|
8100
|
+
const key = basename(file);
|
|
8101
|
+
files.push(file);
|
|
8102
|
+
if (!seenIdentities.has(key)) {
|
|
8103
|
+
seenIdentities.add(key);
|
|
8104
|
+
primaryFiles.add(file);
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
8107
|
+
}
|
|
7787
8108
|
if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
|
|
7788
8109
|
const now = opts.now ?? Date.now;
|
|
7789
8110
|
const res = await readFilesWithCache({
|
|
7790
8111
|
files,
|
|
7791
8112
|
cachePath: opts.cachePath ?? nativeCachePath("codex", env),
|
|
7792
8113
|
version: CODEX_CACHE_VERSION,
|
|
7793
|
-
parseFile: (content) =>
|
|
8114
|
+
parseFile: (content, path) => {
|
|
8115
|
+
const inspected = inspectCodexRollout(splitLines3(content));
|
|
8116
|
+
const primary = primaryFiles.has(path);
|
|
8117
|
+
return [
|
|
8118
|
+
// Do not pass toCachedSession directly to map: Array.map's numeric
|
|
8119
|
+
// index would be supplied as the optional `kind` argument.
|
|
8120
|
+
...inspected.sessions.map(
|
|
8121
|
+
(session) => toCachedSession(session, primary ? "usage" : "duplicate")
|
|
8122
|
+
),
|
|
8123
|
+
...inspected.replaySessions.map(
|
|
8124
|
+
(session) => toCachedSession(
|
|
8125
|
+
session,
|
|
8126
|
+
primary ? "replay" : "replay-duplicate"
|
|
8127
|
+
)
|
|
8128
|
+
)
|
|
8129
|
+
];
|
|
8130
|
+
},
|
|
7794
8131
|
deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS2),
|
|
7795
8132
|
now
|
|
7796
8133
|
});
|
|
@@ -7803,13 +8140,24 @@ async function collectCodexNative(env = process.env, opts = {}) {
|
|
|
7803
8140
|
};
|
|
7804
8141
|
}
|
|
7805
8142
|
const acc = /* @__PURE__ */ new Map();
|
|
8143
|
+
const legacyAcc = /* @__PURE__ */ new Map();
|
|
8144
|
+
const replayCandidateDates = /* @__PURE__ */ new Set();
|
|
7806
8145
|
for (const items of res.itemsByFile) {
|
|
7807
|
-
|
|
8146
|
+
const usage = items.filter((item) => item[0] === "usage").map(fromCachedSession);
|
|
8147
|
+
const legacy = items.map(fromCachedSession);
|
|
8148
|
+
const replay = items.filter(
|
|
8149
|
+
(item) => item[0] === "replay" || item[0] === "replay-duplicate"
|
|
8150
|
+
).map(fromCachedSession);
|
|
8151
|
+
foldCodexSessions(acc, usage);
|
|
8152
|
+
foldCodexSessions(legacyAcc, legacy);
|
|
8153
|
+
for (const session of replay) replayCandidateDates.add(session.date);
|
|
7808
8154
|
}
|
|
7809
8155
|
return {
|
|
7810
8156
|
entries: finalizeCodexEntries(acc),
|
|
7811
8157
|
found: true,
|
|
7812
|
-
filesScanned: res.filesRead
|
|
8158
|
+
filesScanned: res.filesRead,
|
|
8159
|
+
replayCandidateDates: [...replayCandidateDates].sort(),
|
|
8160
|
+
legacyEntries: finalizeCodexEntries(legacyAcc)
|
|
7813
8161
|
};
|
|
7814
8162
|
}
|
|
7815
8163
|
|
|
@@ -8015,16 +8363,20 @@ async function collectVscodeAgent(opts) {
|
|
|
8015
8363
|
}
|
|
8016
8364
|
|
|
8017
8365
|
// src/pricing-live.ts
|
|
8018
|
-
import {
|
|
8019
|
-
import {
|
|
8366
|
+
import { chmod, mkdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
8367
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
8368
|
+
import { dirname as dirname5, join as join11 } from "node:path";
|
|
8020
8369
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8021
8370
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
8371
|
+
var MAX_PRICING_BYTES = 32 * 1024 * 1024;
|
|
8022
8372
|
function pricingCachePath(dir = defaultConfigDir()) {
|
|
8023
8373
|
return join11(dir, "pricing-cache.json");
|
|
8024
8374
|
}
|
|
8025
8375
|
async function readCache(path) {
|
|
8026
8376
|
try {
|
|
8027
|
-
const
|
|
8377
|
+
const read = await readTextFileCapped(path, { maxBytes: MAX_PRICING_BYTES });
|
|
8378
|
+
if (!read.ok) return null;
|
|
8379
|
+
const parsed = JSON.parse(read.content);
|
|
8028
8380
|
if (typeof parsed?.fetchedAt === "number" && parsed.table && typeof parsed.table === "object") {
|
|
8029
8381
|
return parsed;
|
|
8030
8382
|
}
|
|
@@ -8035,10 +8387,11 @@ async function readCache(path) {
|
|
|
8035
8387
|
async function fetchLiveTable(url) {
|
|
8036
8388
|
try {
|
|
8037
8389
|
const res = await fetch(url, {
|
|
8038
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
8390
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
8391
|
+
redirect: "error"
|
|
8039
8392
|
});
|
|
8040
8393
|
if (!res.ok) return null;
|
|
8041
|
-
const table = litellmToTable(await res
|
|
8394
|
+
const table = litellmToTable(await readJsonResponseCapped(res, MAX_PRICING_BYTES));
|
|
8042
8395
|
return Object.keys(table).length > 0 ? table : null;
|
|
8043
8396
|
} catch {
|
|
8044
8397
|
return null;
|
|
@@ -8056,10 +8409,20 @@ async function loadLivePricing(env = process.env, now = Date.now, cachePath = pr
|
|
|
8056
8409
|
if (live) {
|
|
8057
8410
|
setLivePricing(live);
|
|
8058
8411
|
try {
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
await
|
|
8062
|
-
|
|
8412
|
+
const dir = dirname5(path);
|
|
8413
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
8414
|
+
await chmod(dir, 448);
|
|
8415
|
+
const serialized = JSON.stringify({ fetchedAt: now(), table: live });
|
|
8416
|
+
if (Buffer.byteLength(serialized, "utf8") <= MAX_PRICING_BYTES) {
|
|
8417
|
+
const tmp = `${path}.${process.pid}.${randomBytes4(8).toString("hex")}.tmp`;
|
|
8418
|
+
try {
|
|
8419
|
+
await writeFile(tmp, serialized, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
8420
|
+
await chmod(tmp, 384);
|
|
8421
|
+
await rename(tmp, path);
|
|
8422
|
+
} finally {
|
|
8423
|
+
await unlink(tmp).catch(() => void 0);
|
|
8424
|
+
}
|
|
8425
|
+
}
|
|
8063
8426
|
} catch {
|
|
8064
8427
|
}
|
|
8065
8428
|
return "live";
|
|
@@ -8072,8 +8435,8 @@ async function loadLivePricing(env = process.env, now = Date.now, cachePath = pr
|
|
|
8072
8435
|
}
|
|
8073
8436
|
|
|
8074
8437
|
// src/provenance-store.ts
|
|
8075
|
-
import {
|
|
8076
|
-
|
|
8438
|
+
import { join as join12 } from "node:path";
|
|
8439
|
+
var MAX_PROVENANCE_STORE_BYTES = 4 * 1024 * 1024;
|
|
8077
8440
|
var PROVENANCE_STORE_VERSION = 1;
|
|
8078
8441
|
var KEY_SEP = "|";
|
|
8079
8442
|
var keyOf = (date, tool) => `${date}${KEY_SEP}${tool}`;
|
|
@@ -8087,7 +8450,9 @@ function provenanceStorePath(env = process.env) {
|
|
|
8087
8450
|
}
|
|
8088
8451
|
function loadProvenanceStore(path) {
|
|
8089
8452
|
try {
|
|
8090
|
-
const
|
|
8453
|
+
const content = readTextFileSyncCapped(path, MAX_PROVENANCE_STORE_BYTES);
|
|
8454
|
+
if (content === null) return null;
|
|
8455
|
+
const parsed = JSON.parse(content);
|
|
8091
8456
|
if (parsed && parsed.v === PROVENANCE_STORE_VERSION && parsed.req && typeof parsed.req === "object") {
|
|
8092
8457
|
return {
|
|
8093
8458
|
v: PROVENANCE_STORE_VERSION,
|
|
@@ -8101,10 +8466,7 @@ function loadProvenanceStore(path) {
|
|
|
8101
8466
|
}
|
|
8102
8467
|
function saveProvenanceStore(path, store) {
|
|
8103
8468
|
try {
|
|
8104
|
-
|
|
8105
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
8106
|
-
writeFileSync4(tmp, JSON.stringify(store));
|
|
8107
|
-
renameSync4(tmp, path);
|
|
8469
|
+
writePrivateFileAtomic(path, JSON.stringify(store), MAX_PROVENANCE_STORE_BYTES);
|
|
8108
8470
|
} catch {
|
|
8109
8471
|
}
|
|
8110
8472
|
}
|
|
@@ -8158,12 +8520,131 @@ function reconcileProvenance(entries, agent, complete, store) {
|
|
|
8158
8520
|
};
|
|
8159
8521
|
}
|
|
8160
8522
|
|
|
8523
|
+
// src/wire-sanitize.ts
|
|
8524
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
8525
|
+
var MACHINE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:/@+~-]*$/;
|
|
8526
|
+
function sanitizeMachineLabel(value, kind, maxLength) {
|
|
8527
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
8528
|
+
if (raw.length > 0 && raw.length <= maxLength && MACHINE_IDENTIFIER.test(raw)) {
|
|
8529
|
+
return raw;
|
|
8530
|
+
}
|
|
8531
|
+
const digest = createHash2("sha256").update(`${kind}\0`).update(raw).digest("hex").slice(0, 12);
|
|
8532
|
+
return `${kind}-${digest}`;
|
|
8533
|
+
}
|
|
8534
|
+
function sanitizeDailyEntries(rows) {
|
|
8535
|
+
const safe = [];
|
|
8536
|
+
for (const raw of rows) {
|
|
8537
|
+
if (!raw || typeof raw !== "object") continue;
|
|
8538
|
+
const row = raw;
|
|
8539
|
+
const parsed = DailyUsageEntry.safeParse({
|
|
8540
|
+
...row,
|
|
8541
|
+
tool: sanitizeMachineLabel(row.tool, "agent", 64),
|
|
8542
|
+
model: sanitizeMachineLabel(row.model, "model", 128),
|
|
8543
|
+
// Only a server-side connector may assert stronger provenance.
|
|
8544
|
+
origin: "cli",
|
|
8545
|
+
verified: false
|
|
8546
|
+
});
|
|
8547
|
+
if (parsed.success) safe.push(parsed.data);
|
|
8548
|
+
}
|
|
8549
|
+
return safe;
|
|
8550
|
+
}
|
|
8551
|
+
function sanitizeSessions(rows) {
|
|
8552
|
+
const safe = [];
|
|
8553
|
+
for (const raw of rows) {
|
|
8554
|
+
if (!raw || typeof raw !== "object") continue;
|
|
8555
|
+
const row = raw;
|
|
8556
|
+
const sessionId = createHash2("sha256").update("session\0").update(String(row.sessionId ?? "")).digest("hex");
|
|
8557
|
+
const parsed = SessionEntry.safeParse({
|
|
8558
|
+
...row,
|
|
8559
|
+
sessionId: `local-${sessionId.slice(0, 24)}`,
|
|
8560
|
+
tool: sanitizeMachineLabel(row.tool, "agent", 64),
|
|
8561
|
+
model: sanitizeMachineLabel(row.model, "model", 128)
|
|
8562
|
+
});
|
|
8563
|
+
if (parsed.success) safe.push(parsed.data);
|
|
8564
|
+
}
|
|
8565
|
+
return safe;
|
|
8566
|
+
}
|
|
8567
|
+
function sanitizeBlocks(rows) {
|
|
8568
|
+
return rows.flatMap((row) => {
|
|
8569
|
+
const parsed = BlockEntry.safeParse(row);
|
|
8570
|
+
return parsed.success ? [parsed.data] : [];
|
|
8571
|
+
});
|
|
8572
|
+
}
|
|
8573
|
+
function sanitizeToolStats(rows) {
|
|
8574
|
+
return rows.flatMap((raw) => {
|
|
8575
|
+
if (!raw || typeof raw !== "object") return [];
|
|
8576
|
+
const row = raw;
|
|
8577
|
+
const parsed = ToolStat.safeParse({
|
|
8578
|
+
...row,
|
|
8579
|
+
name: sanitizeMachineLabel(row.name, "tool", 128)
|
|
8580
|
+
});
|
|
8581
|
+
return parsed.success ? [parsed.data] : [];
|
|
8582
|
+
});
|
|
8583
|
+
}
|
|
8584
|
+
function sanitizeSkillStats(rows) {
|
|
8585
|
+
return rows.flatMap((raw) => {
|
|
8586
|
+
if (!raw || typeof raw !== "object") return [];
|
|
8587
|
+
const row = raw;
|
|
8588
|
+
const parsed = SkillStat.safeParse({
|
|
8589
|
+
...row,
|
|
8590
|
+
name: sanitizeMachineLabel(row.name, "skill", 128)
|
|
8591
|
+
});
|
|
8592
|
+
return parsed.success ? [parsed.data] : [];
|
|
8593
|
+
});
|
|
8594
|
+
}
|
|
8595
|
+
var EMPTY_AGENT = {
|
|
8596
|
+
messageCount: 0,
|
|
8597
|
+
subagentMessages: 0,
|
|
8598
|
+
subagentTokens: 0,
|
|
8599
|
+
totalTokens: 0,
|
|
8600
|
+
userMessageCount: 0
|
|
8601
|
+
};
|
|
8602
|
+
function sanitizeAgentStat(value) {
|
|
8603
|
+
const parsed = AgentStat.safeParse(value);
|
|
8604
|
+
return parsed.success ? parsed.data : { ...EMPTY_AGENT };
|
|
8605
|
+
}
|
|
8606
|
+
function sanitizeCodexReplayScopes(scopes) {
|
|
8607
|
+
return scopes.flatMap((raw) => {
|
|
8608
|
+
if (!raw || typeof raw !== "object") return [];
|
|
8609
|
+
const scope = raw;
|
|
8610
|
+
const rows = Array.isArray(scope.rows) ? scope.rows.map((row) => {
|
|
8611
|
+
if (!row || typeof row !== "object") return row;
|
|
8612
|
+
const value = row;
|
|
8613
|
+
return {
|
|
8614
|
+
...value,
|
|
8615
|
+
model: sanitizeMachineLabel(value.model, "model", 128)
|
|
8616
|
+
};
|
|
8617
|
+
}) : scope.rows;
|
|
8618
|
+
const parsed = CodexReplayPriorScope.safeParse({ ...scope, rows });
|
|
8619
|
+
return parsed.success ? [parsed.data] : [];
|
|
8620
|
+
});
|
|
8621
|
+
}
|
|
8622
|
+
|
|
8161
8623
|
// src/collect.ts
|
|
8162
8624
|
var execFileAsync = promisify(execFile);
|
|
8163
8625
|
var NATIVE_COVERED_SOURCES = /* @__PURE__ */ new Set(["claude", "codex"]);
|
|
8164
8626
|
var CCUSAGE_TIMEOUT_MS = 25e3;
|
|
8165
8627
|
var CCUSAGE_FALLBACK_TIMEOUT_MS = NATIVE_READ_BUDGET_MS;
|
|
8166
8628
|
var CCUSAGE_AGGREGATE_TIMEOUT_MS = 18e4;
|
|
8629
|
+
var CCUSAGE_MAX_CONCURRENCY = 4;
|
|
8630
|
+
var CCUSAGE_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
|
|
8631
|
+
async function mapConcurrent(items, concurrency, fn) {
|
|
8632
|
+
const out = new Array(items.length);
|
|
8633
|
+
let next = 0;
|
|
8634
|
+
const worker = async () => {
|
|
8635
|
+
while (true) {
|
|
8636
|
+
const index = next++;
|
|
8637
|
+
if (index >= items.length) return;
|
|
8638
|
+
out[index] = await fn(items[index], index);
|
|
8639
|
+
}
|
|
8640
|
+
};
|
|
8641
|
+
const workers = Math.min(
|
|
8642
|
+
items.length,
|
|
8643
|
+
Math.max(1, Math.floor(concurrency) || 1)
|
|
8644
|
+
);
|
|
8645
|
+
await Promise.all(Array.from({ length: workers }, () => worker()));
|
|
8646
|
+
return out;
|
|
8647
|
+
}
|
|
8167
8648
|
var SOURCES = [
|
|
8168
8649
|
"claude",
|
|
8169
8650
|
"codex",
|
|
@@ -8291,16 +8772,16 @@ function mapCcusageBlocks(json) {
|
|
|
8291
8772
|
if (b.isGap === true) continue;
|
|
8292
8773
|
if (typeof b.startTime !== "string") continue;
|
|
8293
8774
|
const totalTokens = norm(b.totalTokens);
|
|
8294
|
-
const
|
|
8295
|
-
if (totalTokens === 0 &&
|
|
8296
|
-
out.push({ startTime: b.startTime, totalTokens, costUSD });
|
|
8775
|
+
const costUSD2 = normCost(b.costUSD);
|
|
8776
|
+
if (totalTokens === 0 && costUSD2 === 0) continue;
|
|
8777
|
+
out.push({ startTime: b.startTime, totalTokens, costUSD: costUSD2 });
|
|
8297
8778
|
}
|
|
8298
8779
|
return out;
|
|
8299
8780
|
}
|
|
8300
8781
|
function resolveCcusageBin() {
|
|
8301
|
-
const
|
|
8302
|
-
const pkgPath =
|
|
8303
|
-
const pkg =
|
|
8782
|
+
const require4 = createRequire4(import.meta.url);
|
|
8783
|
+
const pkgPath = require4.resolve("ccusage/package.json");
|
|
8784
|
+
const pkg = require4("ccusage/package.json");
|
|
8304
8785
|
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.ccusage ?? "ccusage";
|
|
8305
8786
|
const binPath = join13(dirname6(pkgPath), rel);
|
|
8306
8787
|
if (/\.(c|m)?js$/.test(binPath)) {
|
|
@@ -8308,6 +8789,68 @@ function resolveCcusageBin() {
|
|
|
8308
8789
|
}
|
|
8309
8790
|
return { cmd: binPath, prefixArgs: [] };
|
|
8310
8791
|
}
|
|
8792
|
+
function codexReplayTombstoneDates(native, correctedEntries, replayReadSucceeded) {
|
|
8793
|
+
if (!replayReadSucceeded || native.timedOut) return [];
|
|
8794
|
+
const correctedDates = new Set(
|
|
8795
|
+
correctedEntries.filter((entry) => entry.tool === "codex").map((entry) => entry.date)
|
|
8796
|
+
);
|
|
8797
|
+
return [...new Set(native.replayCandidateDates ?? [])].filter((date) => !correctedDates.has(date)).sort().slice(0, 5e3);
|
|
8798
|
+
}
|
|
8799
|
+
function codexRowsByDate(entries) {
|
|
8800
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
8801
|
+
for (const entry of entries) {
|
|
8802
|
+
if (entry.tool !== "codex" || entry.origin !== "cli" || entry.verified === true)
|
|
8803
|
+
continue;
|
|
8804
|
+
const byModel = grouped.get(entry.date) ?? /* @__PURE__ */ new Map();
|
|
8805
|
+
const row = byModel.get(entry.model) ?? {
|
|
8806
|
+
model: entry.model,
|
|
8807
|
+
inputTokens: 0,
|
|
8808
|
+
outputTokens: 0,
|
|
8809
|
+
cacheCreationTokens: 0,
|
|
8810
|
+
cacheReadTokens: 0
|
|
8811
|
+
};
|
|
8812
|
+
row.inputTokens += entry.inputTokens;
|
|
8813
|
+
row.outputTokens += entry.outputTokens;
|
|
8814
|
+
row.cacheCreationTokens += entry.cacheCreationTokens;
|
|
8815
|
+
row.cacheReadTokens += entry.cacheReadTokens;
|
|
8816
|
+
byModel.set(entry.model, row);
|
|
8817
|
+
grouped.set(entry.date, byModel);
|
|
8818
|
+
}
|
|
8819
|
+
return new Map(
|
|
8820
|
+
[...grouped].map(([date, byModel]) => [
|
|
8821
|
+
date,
|
|
8822
|
+
[...byModel.values()].sort((a, b) => a.model.localeCompare(b.model))
|
|
8823
|
+
])
|
|
8824
|
+
);
|
|
8825
|
+
}
|
|
8826
|
+
function codexRowsEqual(left, right) {
|
|
8827
|
+
return JSON.stringify(left ?? []) === JSON.stringify(right ?? []);
|
|
8828
|
+
}
|
|
8829
|
+
function codexReplayCorrectionMetadata(native, correctedEntries, replayReadSucceeded) {
|
|
8830
|
+
if (!replayReadSucceeded || native.timedOut || !native.legacyEntries) {
|
|
8831
|
+
return { tombstoneDates: [], priorScopes: [] };
|
|
8832
|
+
}
|
|
8833
|
+
const tombstoneDates = codexReplayTombstoneDates(
|
|
8834
|
+
native,
|
|
8835
|
+
correctedEntries,
|
|
8836
|
+
replayReadSucceeded
|
|
8837
|
+
);
|
|
8838
|
+
const legacyByDate = codexRowsByDate(native.legacyEntries);
|
|
8839
|
+
const correctedByDate = codexRowsByDate(correctedEntries);
|
|
8840
|
+
const changedDates = new Set(tombstoneDates);
|
|
8841
|
+
for (const [date, rows] of legacyByDate) {
|
|
8842
|
+
if (!codexRowsEqual(rows, correctedByDate.get(date))) changedDates.add(date);
|
|
8843
|
+
}
|
|
8844
|
+
const priorScopes = [...changedDates].sort().flatMap((date) => {
|
|
8845
|
+
const rows = legacyByDate.get(date);
|
|
8846
|
+
return rows && rows.length > 0 && rows.length <= 100 ? [{ date, rows }] : [];
|
|
8847
|
+
}).slice(0, 5e3);
|
|
8848
|
+
const provedDates = new Set(priorScopes.map((scope) => scope.date));
|
|
8849
|
+
return {
|
|
8850
|
+
tombstoneDates: tombstoneDates.filter((date) => provedDates.has(date)),
|
|
8851
|
+
priorScopes
|
|
8852
|
+
};
|
|
8853
|
+
}
|
|
8311
8854
|
function dedupeDaily(entries) {
|
|
8312
8855
|
const byKey = /* @__PURE__ */ new Map();
|
|
8313
8856
|
for (const e of entries) {
|
|
@@ -8361,8 +8904,7 @@ function dedupeBlocks(blocks) {
|
|
|
8361
8904
|
function selectSourceEntries(source, ccusageEntries, native) {
|
|
8362
8905
|
if (source === "claude" && nativeReaderWon(native.claude))
|
|
8363
8906
|
return native.claude.entries;
|
|
8364
|
-
if (source === "codex"
|
|
8365
|
-
return native.codex.entries;
|
|
8907
|
+
if (source === "codex") return ccusageEntries;
|
|
8366
8908
|
return ccusageEntries;
|
|
8367
8909
|
}
|
|
8368
8910
|
function nativeReaderWon(result) {
|
|
@@ -8370,7 +8912,7 @@ function nativeReaderWon(result) {
|
|
|
8370
8912
|
}
|
|
8371
8913
|
function ccusageFallbackSources(native) {
|
|
8372
8914
|
return SOURCES.filter(
|
|
8373
|
-
(s) =>
|
|
8915
|
+
(s) => s === "codex" || s === "claude" && !nativeReaderWon(native.claude)
|
|
8374
8916
|
);
|
|
8375
8917
|
}
|
|
8376
8918
|
function ccusageClaudeEnv(env = process.env) {
|
|
@@ -8386,7 +8928,7 @@ async function runCcusageOnce(cmd, args, env, timeoutMs = CCUSAGE_TIMEOUT_MS) {
|
|
|
8386
8928
|
try {
|
|
8387
8929
|
const { stdout } = await execFileAsync(cmd, args, {
|
|
8388
8930
|
encoding: "utf8",
|
|
8389
|
-
maxBuffer:
|
|
8931
|
+
maxBuffer: CCUSAGE_MAX_BUFFER_BYTES,
|
|
8390
8932
|
// A single source shouldn't be able to hang the whole run: a hung source
|
|
8391
8933
|
// gets killed and (if transient) retried once below rather than stalling
|
|
8392
8934
|
// everything for minutes. The claude/codex fallback passes a longer cap —
|
|
@@ -8413,8 +8955,10 @@ var COLLECT_STAGES = SOURCES.length + 4 + VSCODE_AGENTS.length + 1;
|
|
|
8413
8955
|
function isAuthoritativeScan(attributionComplete, ...fingerprintReaders) {
|
|
8414
8956
|
return attributionComplete && fingerprintReaders.every((reader) => reader.timedOut !== true);
|
|
8415
8957
|
}
|
|
8416
|
-
async function collectAll(onProgress) {
|
|
8417
|
-
await loadLivePricing(
|
|
8958
|
+
async function collectAll(onProgress, opts) {
|
|
8959
|
+
await loadLivePricing(
|
|
8960
|
+
opts?.offline ? { ...process.env, WHOBURNEDMORE_PRICING_OFFLINE: "1" } : process.env
|
|
8961
|
+
).catch(() => {
|
|
8418
8962
|
});
|
|
8419
8963
|
const { cmd, prefixArgs } = resolveCcusageBin();
|
|
8420
8964
|
let done = 0;
|
|
@@ -8425,22 +8969,26 @@ async function collectAll(onProgress) {
|
|
|
8425
8969
|
const nativeCodexTask = collectCodexNative().catch(
|
|
8426
8970
|
() => ({ entries: [], found: false, filesScanned: 0, timedOut: true })
|
|
8427
8971
|
);
|
|
8428
|
-
const sourceTasks =
|
|
8429
|
-
|
|
8430
|
-
|
|
8972
|
+
const sourceTasks = mapConcurrent(
|
|
8973
|
+
SOURCES,
|
|
8974
|
+
CCUSAGE_MAX_CONCURRENCY,
|
|
8975
|
+
async (source) => {
|
|
8976
|
+
if (NATIVE_COVERED_SOURCES.has(source)) {
|
|
8977
|
+
await (source === "claude" ? nativeClaudeTask : nativeCodexTask);
|
|
8978
|
+
tick();
|
|
8979
|
+
return { source, mapped: [] };
|
|
8980
|
+
}
|
|
8981
|
+
const json = await runCcusage(cmd, [
|
|
8982
|
+
...prefixArgs,
|
|
8983
|
+
source,
|
|
8984
|
+
"daily",
|
|
8985
|
+
"--json",
|
|
8986
|
+
"--offline"
|
|
8987
|
+
]);
|
|
8431
8988
|
tick();
|
|
8432
|
-
return { source, mapped: [] };
|
|
8989
|
+
return { source, mapped: json ? mapCcusageDaily(source, json) : [] };
|
|
8433
8990
|
}
|
|
8434
|
-
|
|
8435
|
-
...prefixArgs,
|
|
8436
|
-
source,
|
|
8437
|
-
"daily",
|
|
8438
|
-
"--json",
|
|
8439
|
-
"--offline"
|
|
8440
|
-
]);
|
|
8441
|
-
tick();
|
|
8442
|
-
return { source, mapped: json ? mapCcusageDaily(source, json) : [] };
|
|
8443
|
-
});
|
|
8991
|
+
);
|
|
8444
8992
|
const sessionTask = runCcusage(
|
|
8445
8993
|
cmd,
|
|
8446
8994
|
[...prefixArgs, "session", "--json", "--offline"],
|
|
@@ -8459,7 +9007,7 @@ async function collectAll(onProgress) {
|
|
|
8459
9007
|
tick();
|
|
8460
9008
|
return json ? mapCcusageBlocks(json) : [];
|
|
8461
9009
|
});
|
|
8462
|
-
const cursorTask = collectCursor().then((c) => {
|
|
9010
|
+
const cursorTask = collectCursor({ offline: opts?.offline }).then((c) => {
|
|
8463
9011
|
tick();
|
|
8464
9012
|
return c;
|
|
8465
9013
|
});
|
|
@@ -8488,7 +9036,7 @@ async function collectAll(onProgress) {
|
|
|
8488
9036
|
vscodeResults,
|
|
8489
9037
|
continueResult
|
|
8490
9038
|
] = await Promise.all([
|
|
8491
|
-
|
|
9039
|
+
sourceTasks,
|
|
8492
9040
|
sessionTask,
|
|
8493
9041
|
blockTask,
|
|
8494
9042
|
cursorTask,
|
|
@@ -8500,6 +9048,7 @@ async function collectAll(onProgress) {
|
|
|
8500
9048
|
]);
|
|
8501
9049
|
const native = { claude: nativeClaude, codex: nativeCodex };
|
|
8502
9050
|
const fallbacks = /* @__PURE__ */ new Map();
|
|
9051
|
+
let codexReplayReadSucceeded = false;
|
|
8503
9052
|
for (const source of ccusageFallbackSources(native)) {
|
|
8504
9053
|
const json = await runCcusage(
|
|
8505
9054
|
cmd,
|
|
@@ -8508,8 +9057,15 @@ async function collectAll(onProgress) {
|
|
|
8508
9057
|
source === "claude" ? ccusageClaudeEnv() : void 0,
|
|
8509
9058
|
CCUSAGE_FALLBACK_TIMEOUT_MS
|
|
8510
9059
|
);
|
|
9060
|
+
if (source === "codex") codexReplayReadSucceeded = json !== null;
|
|
8511
9061
|
fallbacks.set(source, json ? mapCcusageDaily(source, json) : []);
|
|
8512
9062
|
}
|
|
9063
|
+
const codexReplayEntries = fallbacks.get("codex") ?? [];
|
|
9064
|
+
const replayCorrection = codexReplayCorrectionMetadata(
|
|
9065
|
+
nativeCodex,
|
|
9066
|
+
codexReplayEntries,
|
|
9067
|
+
codexReplayReadSucceeded
|
|
9068
|
+
);
|
|
8513
9069
|
const entries = [];
|
|
8514
9070
|
const toolsFound = [];
|
|
8515
9071
|
for (const { source, mapped } of sourceResults) {
|
|
@@ -8536,13 +9092,16 @@ async function collectAll(onProgress) {
|
|
|
8536
9092
|
}
|
|
8537
9093
|
const { tools, skills, agent, sessionMessages, complete } = attribution;
|
|
8538
9094
|
onProgress?.(COLLECT_STAGES, COLLECT_STAGES, "");
|
|
8539
|
-
const
|
|
9095
|
+
const localSessions = dedupeSessions(sessions).map((s) => {
|
|
8540
9096
|
const messageCount = sessionMessages.get(s.sessionId);
|
|
8541
9097
|
return {
|
|
8542
9098
|
...s,
|
|
8543
9099
|
...messageCount ? { messageCount } : {}
|
|
8544
9100
|
};
|
|
8545
9101
|
});
|
|
9102
|
+
const dedupedSessions = sanitizeSessions(localSessions);
|
|
9103
|
+
const boundaryEntries = sanitizeDailyEntries(entries);
|
|
9104
|
+
const boundaryAgent = sanitizeAgentStat(agent);
|
|
8546
9105
|
const scanComplete = isAuthoritativeScan(
|
|
8547
9106
|
complete,
|
|
8548
9107
|
nativeClaude,
|
|
@@ -8552,25 +9111,40 @@ async function collectAll(onProgress) {
|
|
|
8552
9111
|
);
|
|
8553
9112
|
const storePath = provenanceStorePath();
|
|
8554
9113
|
const reconciled = reconcileProvenance(
|
|
8555
|
-
dedupeDaily(
|
|
8556
|
-
|
|
9114
|
+
dedupeDaily(boundaryEntries),
|
|
9115
|
+
boundaryAgent,
|
|
8557
9116
|
scanComplete,
|
|
8558
9117
|
loadProvenanceStore(storePath)
|
|
8559
9118
|
);
|
|
9119
|
+
const reconciledEntries = sanitizeDailyEntries(reconciled.entries);
|
|
8560
9120
|
saveProvenanceStore(storePath, reconciled.store);
|
|
9121
|
+
const cappedEntries = capByTokens(reconciledEntries, 2e4, entryTokens2);
|
|
9122
|
+
const payloadReplayCorrection = boundaryEntries.length === entries.length && reconciledEntries.length === reconciled.entries.length && cappedEntries.length === reconciledEntries.length ? replayCorrection : { tombstoneDates: [], priorScopes: [] };
|
|
9123
|
+
const safeReplayScopes = sanitizeCodexReplayScopes(
|
|
9124
|
+
payloadReplayCorrection.priorScopes
|
|
9125
|
+
);
|
|
9126
|
+
const safeReplayDates = new Set(safeReplayScopes.map((scope) => scope.date));
|
|
8561
9127
|
return {
|
|
8562
9128
|
// Cap each array to the server's accepted maximum (the shared SubmitPayload
|
|
8563
9129
|
// schema: entries ≤ 20000, sessions/blocks ≤ 10000), keeping the
|
|
8564
9130
|
// highest-token rows. Without this, a power user with >10000 distinct sessions
|
|
8565
9131
|
// would have their ENTIRE submit rejected with a 400 instead of a capped one.
|
|
8566
9132
|
// tools/skills are already bounded upstream (attribution caps).
|
|
8567
|
-
entries:
|
|
9133
|
+
entries: cappedEntries,
|
|
9134
|
+
codexReplayTombstoneDates: payloadReplayCorrection.tombstoneDates.filter(
|
|
9135
|
+
(date) => safeReplayDates.has(date)
|
|
9136
|
+
),
|
|
9137
|
+
codexReplayPriorScopes: safeReplayScopes,
|
|
8568
9138
|
sessions: capByTokens(dedupedSessions, 1e4, entryTokens2),
|
|
8569
|
-
blocks: capByTokens(
|
|
9139
|
+
blocks: capByTokens(
|
|
9140
|
+
dedupeBlocks(sanitizeBlocks(blocks)),
|
|
9141
|
+
1e4,
|
|
9142
|
+
(b) => b.totalTokens
|
|
9143
|
+
),
|
|
8570
9144
|
toolsFound,
|
|
8571
|
-
tools,
|
|
8572
|
-
skills,
|
|
8573
|
-
agent: reconciled.agent,
|
|
9145
|
+
tools: sanitizeToolStats(tools),
|
|
9146
|
+
skills: sanitizeSkillStats(skills),
|
|
9147
|
+
agent: sanitizeAgentStat(reconciled.agent),
|
|
8574
9148
|
attributionComplete: complete
|
|
8575
9149
|
};
|
|
8576
9150
|
}
|
|
@@ -8595,7 +9169,7 @@ function antigravityNoticeLines() {
|
|
|
8595
9169
|
}
|
|
8596
9170
|
|
|
8597
9171
|
// src/verify-upload.ts
|
|
8598
|
-
import { createHash } from "node:crypto";
|
|
9172
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
8599
9173
|
function prepareVerifyUpload(requests, scanTimedOut, cap = 5e4) {
|
|
8600
9174
|
const sorted = requests.slice().sort((a, b) => b.ts - a.ts);
|
|
8601
9175
|
const truncated = scanTimedOut || sorted.length > cap;
|
|
@@ -8609,7 +9183,7 @@ function prepareVerifyUpload(requests, scanTimedOut, cap = 5e4) {
|
|
|
8609
9183
|
outputTokens: r.outputTokens,
|
|
8610
9184
|
cacheCreationTokens: r.cacheCreationTokens,
|
|
8611
9185
|
cacheReadTokens: r.cacheReadTokens,
|
|
8612
|
-
reqHash:
|
|
9186
|
+
reqHash: createHash3("sha256").update(r.key).digest("hex").slice(0, 32)
|
|
8613
9187
|
}));
|
|
8614
9188
|
return { records, truncated };
|
|
8615
9189
|
}
|
|
@@ -8676,6 +9250,9 @@ function agentStatusReport(now = Date.now()) {
|
|
|
8676
9250
|
});
|
|
8677
9251
|
}
|
|
8678
9252
|
|
|
9253
|
+
// src/local-dashboard.ts
|
|
9254
|
+
import { join as join15 } from "node:path";
|
|
9255
|
+
|
|
8679
9256
|
// src/output.ts
|
|
8680
9257
|
function formatTokens(n) {
|
|
8681
9258
|
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
@@ -8704,6 +9281,13 @@ function signedInNextStepLines(result) {
|
|
|
8704
9281
|
` \u2192 Get a friend on it \u2014 have them run: npx whoburnedmore --board=${sanitizeServerText(code)}`
|
|
8705
9282
|
];
|
|
8706
9283
|
}
|
|
9284
|
+
if (result.needsSocial) {
|
|
9285
|
+
return [
|
|
9286
|
+
` Your dashboard: ${sanitizeServerText(result.profileUrl)}`,
|
|
9287
|
+
" \u2192 You're synced, but not on the public leaderboard yet.",
|
|
9288
|
+
" \u2192 Add a social handle (X, GitHub, or Instagram) on your dashboard to appear."
|
|
9289
|
+
];
|
|
9290
|
+
}
|
|
8707
9291
|
return [
|
|
8708
9292
|
` Your dashboard: ${sanitizeServerText(result.profileUrl)}`,
|
|
8709
9293
|
" \u2192 Open it to see your rank and share your profile."
|
|
@@ -8711,6 +9295,14 @@ function signedInNextStepLines(result) {
|
|
|
8711
9295
|
}
|
|
8712
9296
|
|
|
8713
9297
|
// src/local-dashboard.ts
|
|
9298
|
+
var MAX_LOCAL_DASHBOARD_BYTES = 16 * 1024 * 1024;
|
|
9299
|
+
function writeLocalDashboard(dir, html) {
|
|
9300
|
+
const file = join15(dir, "dashboard.html");
|
|
9301
|
+
if (!writePrivateFileAtomic(file, html, MAX_LOCAL_DASHBOARD_BYTES)) {
|
|
9302
|
+
throw new Error("could not write the private local dashboard");
|
|
9303
|
+
}
|
|
9304
|
+
return file;
|
|
9305
|
+
}
|
|
8714
9306
|
function esc(s) {
|
|
8715
9307
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
8716
9308
|
}
|
|
@@ -8941,8 +9533,8 @@ async function publishLocal(payload, deps) {
|
|
|
8941
9533
|
}
|
|
8942
9534
|
|
|
8943
9535
|
// src/index.ts
|
|
8944
|
-
var
|
|
8945
|
-
var VERSION =
|
|
9536
|
+
var require3 = createRequire5(import.meta.url);
|
|
9537
|
+
var VERSION = require3("../package.json").version;
|
|
8946
9538
|
var LOADING_VIBES = [
|
|
8947
9539
|
"counting up your token usage, right here on your machine\u2026",
|
|
8948
9540
|
"tallying tokens across every coding agent you use\u2026",
|
|
@@ -9014,16 +9606,15 @@ async function confirm(question) {
|
|
|
9014
9606
|
}
|
|
9015
9607
|
function showLocalDashboard(payload) {
|
|
9016
9608
|
const dir = defaultConfigDir();
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
writeFileSync5(
|
|
9020
|
-
file,
|
|
9609
|
+
const file = writeLocalDashboard(
|
|
9610
|
+
dir,
|
|
9021
9611
|
renderDashboardHtml(payload.entries, /* @__PURE__ */ new Date(), { webBaseUrl: webBase() })
|
|
9022
9612
|
);
|
|
9613
|
+
const fileUrl = pathToFileURL(file).href;
|
|
9023
9614
|
console.log();
|
|
9024
|
-
console.log(` Local dashboard: ${pc2.cyan(
|
|
9615
|
+
console.log(` Local dashboard: ${pc2.cyan(fileUrl)}`);
|
|
9025
9616
|
console.log(pc2.dim(" Re-run `npx whoburnedmore --local` to refresh it. Nothing left your machine."));
|
|
9026
|
-
openBrowser(
|
|
9617
|
+
openBrowser(fileUrl);
|
|
9027
9618
|
}
|
|
9028
9619
|
async function run(flags) {
|
|
9029
9620
|
if (!flags.quiet) {
|
|
@@ -9043,11 +9634,20 @@ async function run(flags) {
|
|
|
9043
9634
|
} } : startProgress();
|
|
9044
9635
|
let collected;
|
|
9045
9636
|
try {
|
|
9046
|
-
collected = await collectAll(progress.onProgress);
|
|
9637
|
+
collected = await collectAll(progress.onProgress, { offline: flags.local });
|
|
9047
9638
|
} finally {
|
|
9048
9639
|
progress.stop();
|
|
9049
9640
|
}
|
|
9050
|
-
const {
|
|
9641
|
+
const {
|
|
9642
|
+
entries,
|
|
9643
|
+
codexReplayTombstoneDates: codexReplayTombstoneDates2,
|
|
9644
|
+
codexReplayPriorScopes,
|
|
9645
|
+
blocks,
|
|
9646
|
+
tools,
|
|
9647
|
+
skills,
|
|
9648
|
+
agent,
|
|
9649
|
+
attributionComplete
|
|
9650
|
+
} = collected;
|
|
9051
9651
|
if (entries.length === 0) {
|
|
9052
9652
|
console.log();
|
|
9053
9653
|
console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
|
|
@@ -9059,8 +9659,14 @@ async function run(flags) {
|
|
|
9059
9659
|
return;
|
|
9060
9660
|
}
|
|
9061
9661
|
const payload = { cliVersion: VERSION, entries };
|
|
9662
|
+
if (codexReplayTombstoneDates2.length > 0)
|
|
9663
|
+
payload.codexReplayTombstoneDates = codexReplayTombstoneDates2;
|
|
9664
|
+
if (codexReplayPriorScopes.length > 0)
|
|
9665
|
+
payload.codexReplayPriorScopes = codexReplayPriorScopes;
|
|
9666
|
+
const configuredMachineKey = loadConfig()?.anonKey;
|
|
9667
|
+
if (configuredMachineKey)
|
|
9668
|
+
payload.deviceKeyHash = deviceKeyHash(configuredMachineKey);
|
|
9062
9669
|
payload.tzOffsetMinutes = -(/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
9063
|
-
if (sessions.length > 0) payload.sessions = sessions;
|
|
9064
9670
|
if (blocks.length > 0) payload.blocks = blocks;
|
|
9065
9671
|
if (tools.length > 0) payload.tools = tools;
|
|
9066
9672
|
if (skills.length > 0) payload.skills = skills;
|
|
@@ -9080,19 +9686,11 @@ async function run(flags) {
|
|
|
9080
9686
|
confirm,
|
|
9081
9687
|
signIn: ensureSignedIn,
|
|
9082
9688
|
submit: async (token, p) => {
|
|
9083
|
-
const result = await
|
|
9689
|
+
const result = await submitFromBoundDevice(token, p);
|
|
9084
9690
|
try {
|
|
9085
9691
|
recordSync();
|
|
9086
9692
|
} catch {
|
|
9087
9693
|
}
|
|
9088
|
-
try {
|
|
9089
|
-
const cfgNow = loadConfig();
|
|
9090
|
-
if (!cfgNow?.deviceBoundAt) {
|
|
9091
|
-
const key = ensureAnonKey();
|
|
9092
|
-
if (await bindDeviceKey(token, key)) recordDeviceBound();
|
|
9093
|
-
}
|
|
9094
|
-
} catch {
|
|
9095
|
-
}
|
|
9096
9694
|
return result;
|
|
9097
9695
|
},
|
|
9098
9696
|
openBrowser,
|
|
@@ -9111,7 +9709,7 @@ async function run(flags) {
|
|
|
9111
9709
|
await submitSignedIn(cfg.cliToken, payload, flags, canSignIn);
|
|
9112
9710
|
return;
|
|
9113
9711
|
}
|
|
9114
|
-
const healed = cfg?.
|
|
9712
|
+
const healed = cfg?.refreshToken ? await refreshCliToken(cfg.refreshToken) : null;
|
|
9115
9713
|
if (healed) {
|
|
9116
9714
|
saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
|
|
9117
9715
|
await submitSignedIn(healed.token, payload, flags, canSignIn);
|
|
@@ -9123,7 +9721,7 @@ async function run(flags) {
|
|
|
9123
9721
|
console.log(pc2.yellow(" Sign in to put your usage on the leaderboard."));
|
|
9124
9722
|
console.log(
|
|
9125
9723
|
pc2.dim(
|
|
9126
|
-
" Run `npx whoburnedmore` in an interactive terminal to sign in, or `npx whoburnedmore link
|
|
9724
|
+
" Run `npx whoburnedmore` in an interactive terminal to sign in, or generate a one-time `npx whoburnedmore link` code from your profile for servers/CI."
|
|
9127
9725
|
)
|
|
9128
9726
|
);
|
|
9129
9727
|
} else {
|
|
@@ -9135,8 +9733,8 @@ function sleep(ms) {
|
|
|
9135
9733
|
}
|
|
9136
9734
|
async function refreshCliTokenFromConfig() {
|
|
9137
9735
|
const cfg = loadConfig();
|
|
9138
|
-
if (!cfg?.
|
|
9139
|
-
return refreshCliToken(cfg.
|
|
9736
|
+
if (!cfg?.refreshToken) return null;
|
|
9737
|
+
return refreshCliToken(cfg.refreshToken);
|
|
9140
9738
|
}
|
|
9141
9739
|
async function ensureSignedIn() {
|
|
9142
9740
|
const cfg = loadConfig();
|
|
@@ -9165,7 +9763,11 @@ async function ensureSignedIn() {
|
|
|
9165
9763
|
continue;
|
|
9166
9764
|
}
|
|
9167
9765
|
if (res.status === "ok") {
|
|
9168
|
-
saveAuth(void 0, {
|
|
9766
|
+
saveAuth(void 0, {
|
|
9767
|
+
cliToken: res.token,
|
|
9768
|
+
handle: res.handle,
|
|
9769
|
+
refreshToken: res.refreshToken
|
|
9770
|
+
});
|
|
9169
9771
|
console.log(pc2.green(` \u2713 Signed in as @${sanitizeServerText(res.handle)}.`));
|
|
9170
9772
|
return { token: res.token, handle: res.handle };
|
|
9171
9773
|
}
|
|
@@ -9177,24 +9779,21 @@ async function ensureSignedIn() {
|
|
|
9177
9779
|
return null;
|
|
9178
9780
|
}
|
|
9179
9781
|
async function submitSignedIn(token, payload, flags, interactive) {
|
|
9180
|
-
let activeToken = token;
|
|
9181
9782
|
let result;
|
|
9182
9783
|
try {
|
|
9183
|
-
result = await
|
|
9784
|
+
result = await submitFromBoundDevice(token, payload);
|
|
9184
9785
|
} catch (err) {
|
|
9185
9786
|
if (err instanceof UnauthorizedError) {
|
|
9186
9787
|
const healed = await refreshCliTokenFromConfig();
|
|
9187
9788
|
if (healed) {
|
|
9188
9789
|
saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
|
|
9189
|
-
|
|
9190
|
-
result = await submit(healed.token, payload);
|
|
9790
|
+
result = await submitFromBoundDevice(healed.token, payload);
|
|
9191
9791
|
} else {
|
|
9192
9792
|
clearAuth();
|
|
9193
9793
|
if (!interactive) return;
|
|
9194
9794
|
const auth = await ensureSignedIn();
|
|
9195
9795
|
if (!auth) return;
|
|
9196
|
-
|
|
9197
|
-
result = await submit(auth.token, payload);
|
|
9796
|
+
result = await submitFromBoundDevice(auth.token, payload);
|
|
9198
9797
|
}
|
|
9199
9798
|
} else {
|
|
9200
9799
|
throw err;
|
|
@@ -9204,14 +9803,6 @@ async function submitSignedIn(token, payload, flags, interactive) {
|
|
|
9204
9803
|
recordSync();
|
|
9205
9804
|
} catch {
|
|
9206
9805
|
}
|
|
9207
|
-
try {
|
|
9208
|
-
const cfgNow = loadConfig();
|
|
9209
|
-
if (!cfgNow?.deviceBoundAt) {
|
|
9210
|
-
const key = ensureAnonKey();
|
|
9211
|
-
if (await bindDeviceKey(activeToken, key)) recordDeviceBound();
|
|
9212
|
-
}
|
|
9213
|
-
} catch {
|
|
9214
|
-
}
|
|
9215
9806
|
const baseUrl = result.orgBoardUrl ?? result.boardUrl ?? result.profileUrl;
|
|
9216
9807
|
if (!flags.quiet) {
|
|
9217
9808
|
console.log(
|
|
@@ -9270,6 +9861,29 @@ async function submitSignedIn(token, payload, flags, interactive) {
|
|
|
9270
9861
|
}
|
|
9271
9862
|
afterSubmitChores(flags);
|
|
9272
9863
|
}
|
|
9864
|
+
async function submitFromBoundDevice(token, payload) {
|
|
9865
|
+
let machineKey = loadConfig()?.anonKey;
|
|
9866
|
+
try {
|
|
9867
|
+
const cfg = loadConfig();
|
|
9868
|
+
if (!cfg || needsDeviceBind(cfg)) {
|
|
9869
|
+
machineKey = ensureAnonKey();
|
|
9870
|
+
const bound = await bindDeviceKey(token, machineKey);
|
|
9871
|
+
if (bound.refreshToken) {
|
|
9872
|
+
saveAuth(void 0, {
|
|
9873
|
+
cliToken: token,
|
|
9874
|
+
handle: cfg?.handle,
|
|
9875
|
+
refreshToken: bound.refreshToken
|
|
9876
|
+
});
|
|
9877
|
+
}
|
|
9878
|
+
if (bound.definitive) recordDeviceBound();
|
|
9879
|
+
}
|
|
9880
|
+
} catch {
|
|
9881
|
+
}
|
|
9882
|
+
if (machineKey) {
|
|
9883
|
+
payload.deviceKeyHash = deviceKeyHash(machineKey);
|
|
9884
|
+
}
|
|
9885
|
+
return submit(token, payload);
|
|
9886
|
+
}
|
|
9273
9887
|
function afterSubmitChores(flags) {
|
|
9274
9888
|
if (flags.quiet) {
|
|
9275
9889
|
try {
|
|
@@ -9287,14 +9901,36 @@ function afterSubmitChores(flags) {
|
|
|
9287
9901
|
autoSyncInstalled() ? pc2.dim(" Background sync is on \u2014 your page updates automatically every 15 min (`npx whoburnedmore uninstall-sync` to stop).") : pc2.dim(" Re-run anytime to update your page.")
|
|
9288
9902
|
);
|
|
9289
9903
|
}
|
|
9904
|
+
async function readServerInstallToken() {
|
|
9905
|
+
const fromEnvironment = process.env.WHOBURNEDMORE_INSTALL_TOKEN?.trim();
|
|
9906
|
+
if (fromEnvironment) return fromEnvironment;
|
|
9907
|
+
if (process.stdin.isTTY) {
|
|
9908
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9909
|
+
const value2 = (await rl.question(" Paste the one-time link code: ")).trim();
|
|
9910
|
+
rl.close();
|
|
9911
|
+
return value2 || void 0;
|
|
9912
|
+
}
|
|
9913
|
+
let value = "";
|
|
9914
|
+
for await (const chunk of process.stdin) {
|
|
9915
|
+
value += String(chunk);
|
|
9916
|
+
if (value.length > 512) {
|
|
9917
|
+
throw new Error("install code input is too large");
|
|
9918
|
+
}
|
|
9919
|
+
}
|
|
9920
|
+
return value.trim() || void 0;
|
|
9921
|
+
}
|
|
9290
9922
|
async function linkServerInstall(token) {
|
|
9291
9923
|
if (!token) {
|
|
9292
|
-
throw new Error("missing install
|
|
9924
|
+
throw new Error("missing install code \u2014 generate one from your signed-in profile, then paste it when prompted");
|
|
9293
9925
|
}
|
|
9294
9926
|
const anonKey = ensureAnonKey();
|
|
9295
9927
|
const linked = await redeemServerInstall(token, anonKey);
|
|
9296
9928
|
if (linked.cliToken) {
|
|
9297
|
-
saveAuth(void 0, {
|
|
9929
|
+
saveAuth(void 0, {
|
|
9930
|
+
cliToken: linked.cliToken,
|
|
9931
|
+
handle: linked.handle,
|
|
9932
|
+
refreshToken: linked.refreshToken
|
|
9933
|
+
});
|
|
9298
9934
|
}
|
|
9299
9935
|
const handle = sanitizeServerText(linked.handle);
|
|
9300
9936
|
console.log(
|
|
@@ -9367,7 +10003,7 @@ async function runDaemon() {
|
|
|
9367
10003
|
await run({ dryRun: false, noSubmit: false, local: false, quiet: true });
|
|
9368
10004
|
if (!loadConfig()?.cliToken) {
|
|
9369
10005
|
throw new Error(
|
|
9370
|
-
"not linked \u2014 nothing submitted (
|
|
10006
|
+
"not linked \u2014 nothing submitted (generate a one-time link code from your signed-in profile first)"
|
|
9371
10007
|
);
|
|
9372
10008
|
}
|
|
9373
10009
|
}
|
|
@@ -9586,7 +10222,10 @@ async function main() {
|
|
|
9586
10222
|
break;
|
|
9587
10223
|
}
|
|
9588
10224
|
case "link":
|
|
9589
|
-
|
|
10225
|
+
if (hasUnsafeInstallTokenArg(args)) {
|
|
10226
|
+
throw new Error("`--token` is no longer accepted because command-line secrets leak into shell history and process listings; run `npx whoburnedmore link` and paste the code when prompted");
|
|
10227
|
+
}
|
|
10228
|
+
await linkServerInstall(await readServerInstallToken());
|
|
9590
10229
|
break;
|
|
9591
10230
|
case "daemon":
|
|
9592
10231
|
await runDaemon();
|
|
@@ -9639,7 +10278,7 @@ function printHelp() {
|
|
|
9639
10278
|
npx whoburnedmore --local build the dashboard on your machine and open it (offline)
|
|
9640
10279
|
npx whoburnedmore --dry-run print exactly what would be sent, send nothing
|
|
9641
10280
|
npx whoburnedmore --no-submit collect locally, send nothing (no dashboard)
|
|
9642
|
-
npx whoburnedmore link
|
|
10281
|
+
npx whoburnedmore link link this server/VM (prompts for a one-time code)
|
|
9643
10282
|
npx whoburnedmore daemon keep syncing in the foreground (VMs/containers with no cron)
|
|
9644
10283
|
npx whoburnedmore private take yourself off the public leaderboard
|
|
9645
10284
|
npx whoburnedmore public put yourself back on it
|