whoburnedmore 0.9.20 → 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 +864 -236
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,10 +7,8 @@ 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 { join as join15 } from "node:path";
|
|
14
12
|
import { pathToFileURL } from "node:url";
|
|
15
13
|
import { createInterface } from "node:readline/promises";
|
|
16
14
|
import pc2 from "picocolors";
|
|
@@ -25,8 +23,8 @@ function parseOrg(args) {
|
|
|
25
23
|
function parsePass(args) {
|
|
26
24
|
return parseValueFlag(args, "--pass") ?? parseValueFlag(args, "--code");
|
|
27
25
|
}
|
|
28
|
-
function
|
|
29
|
-
return
|
|
26
|
+
function hasUnsafeInstallTokenArg(args) {
|
|
27
|
+
return args.some((arg) => arg === "--token" || arg.startsWith("--token="));
|
|
30
28
|
}
|
|
31
29
|
function applyScope(payload, flags) {
|
|
32
30
|
if (flags.board) payload.board = flags.board;
|
|
@@ -67,9 +65,65 @@ function resolveCommand(args) {
|
|
|
67
65
|
return words[0] ?? "run";
|
|
68
66
|
}
|
|
69
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
|
+
|
|
70
111
|
// src/api.ts
|
|
112
|
+
var DEFAULT_API_BASE = "https://api.whoburnedmore.com";
|
|
113
|
+
var MAX_SERVER_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
71
114
|
function apiBase() {
|
|
72
|
-
|
|
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
|
+
}
|
|
73
127
|
}
|
|
74
128
|
function webBase() {
|
|
75
129
|
return process.env.WHOBURNEDMORE_WEB ?? "https://whoburnedmore.com";
|
|
@@ -89,7 +143,7 @@ function isOpenableUrl(url) {
|
|
|
89
143
|
return /^(https?|file):\/\//.test(url);
|
|
90
144
|
}
|
|
91
145
|
async function readJson(res) {
|
|
92
|
-
const text = await res
|
|
146
|
+
const text = await readTextResponseCapped(res, MAX_SERVER_RESPONSE_BYTES);
|
|
93
147
|
if (!text) return {};
|
|
94
148
|
try {
|
|
95
149
|
return JSON.parse(text);
|
|
@@ -110,7 +164,8 @@ async function send(method, path, body, token) {
|
|
|
110
164
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
111
165
|
// Bound the request so a slow/black-holing/hostile server can't hang the CLI
|
|
112
166
|
// — or the unattended 15-minute background sync — indefinitely.
|
|
113
|
-
signal: AbortSignal.timeout(3e4)
|
|
167
|
+
signal: AbortSignal.timeout(3e4),
|
|
168
|
+
redirect: "error"
|
|
114
169
|
});
|
|
115
170
|
} catch {
|
|
116
171
|
throw new Error(
|
|
@@ -161,9 +216,9 @@ async function devicePoll(deviceCode) {
|
|
|
161
216
|
}
|
|
162
217
|
return body;
|
|
163
218
|
}
|
|
164
|
-
async function refreshCliToken(
|
|
219
|
+
async function refreshCliToken(refreshToken) {
|
|
165
220
|
try {
|
|
166
|
-
const { status, body } = await post("/v1/auth/cli/refresh", {
|
|
221
|
+
const { status, body } = await post("/v1/auth/cli/refresh", { refreshToken });
|
|
167
222
|
if (status === 200 && typeof body.token === "string" && body.token) {
|
|
168
223
|
return { token: body.token, handle: body.handle ?? "" };
|
|
169
224
|
}
|
|
@@ -173,14 +228,20 @@ async function refreshCliToken(anonKey) {
|
|
|
173
228
|
}
|
|
174
229
|
async function bindDeviceKey(token, anonKey) {
|
|
175
230
|
try {
|
|
176
|
-
const { status } = await post(
|
|
231
|
+
const { status, body } = await post(
|
|
177
232
|
"/v1/me/devices/bind",
|
|
178
233
|
{ anonKey },
|
|
179
234
|
token
|
|
180
235
|
);
|
|
181
|
-
|
|
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 };
|
|
182
243
|
} catch {
|
|
183
|
-
return false;
|
|
244
|
+
return { definitive: false };
|
|
184
245
|
}
|
|
185
246
|
}
|
|
186
247
|
async function submit(token, payload) {
|
|
@@ -229,31 +290,112 @@ async function redeemServerInstall(token, anonKey) {
|
|
|
229
290
|
|
|
230
291
|
// src/autosync.ts
|
|
231
292
|
import { spawnSync } from "node:child_process";
|
|
293
|
+
import { createRequire } from "node:module";
|
|
232
294
|
import {
|
|
233
295
|
existsSync as existsSync2,
|
|
234
296
|
mkdirSync as mkdirSync2,
|
|
235
|
-
readFileSync
|
|
297
|
+
readFileSync,
|
|
236
298
|
renameSync as renameSync2,
|
|
237
299
|
rmSync as rmSync2,
|
|
238
300
|
statSync,
|
|
239
301
|
writeFileSync as writeFileSync2
|
|
240
302
|
} from "node:fs";
|
|
241
303
|
import { homedir as homedir2, platform } from "node:os";
|
|
242
|
-
import { dirname, join as join2, posix, win32 } from "node:path";
|
|
304
|
+
import { dirname as dirname2, join as join2, posix, win32 } from "node:path";
|
|
243
305
|
|
|
244
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
|
|
245
315
|
import { randomBytes } from "node:crypto";
|
|
246
316
|
import {
|
|
247
317
|
chmodSync,
|
|
248
|
-
|
|
318
|
+
closeSync,
|
|
319
|
+
constants,
|
|
320
|
+
fstatSync,
|
|
249
321
|
mkdirSync,
|
|
250
|
-
|
|
322
|
+
openSync,
|
|
323
|
+
readSync,
|
|
251
324
|
renameSync,
|
|
252
325
|
rmSync,
|
|
253
326
|
writeFileSync
|
|
254
327
|
} from "node:fs";
|
|
255
|
-
import {
|
|
256
|
-
|
|
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
|
+
}
|
|
257
399
|
function defaultConfigDir() {
|
|
258
400
|
const override = process.env.WHOBURNEDMORE_CONFIG_DIR?.trim();
|
|
259
401
|
if (override) return override;
|
|
@@ -263,10 +405,13 @@ function loadConfig(dir = defaultConfigDir()) {
|
|
|
263
405
|
const file = join(dir, "config.json");
|
|
264
406
|
if (!existsSync(file)) return null;
|
|
265
407
|
try {
|
|
266
|
-
const
|
|
408
|
+
const content = readTextFileSyncCapped(file, MAX_CONFIG_BYTES);
|
|
409
|
+
if (content === null) return null;
|
|
410
|
+
const parsed = JSON.parse(content);
|
|
267
411
|
const config = {};
|
|
268
412
|
if (typeof parsed.anonKey === "string") config.anonKey = parsed.anonKey;
|
|
269
413
|
if (typeof parsed.cliToken === "string") config.cliToken = parsed.cliToken;
|
|
414
|
+
if (typeof parsed.refreshToken === "string") config.refreshToken = parsed.refreshToken;
|
|
270
415
|
if (typeof parsed.handle === "string") config.handle = parsed.handle;
|
|
271
416
|
if (typeof parsed.lastSyncAt === "number" && Number.isFinite(parsed.lastSyncAt))
|
|
272
417
|
config.lastSyncAt = parsed.lastSyncAt;
|
|
@@ -282,28 +427,15 @@ function loadConfig(dir = defaultConfigDir()) {
|
|
|
282
427
|
}
|
|
283
428
|
}
|
|
284
429
|
function saveConfig(dir = defaultConfigDir(), config = {}) {
|
|
285
|
-
mkdirSync(dir, { recursive: true });
|
|
286
430
|
const file = join(dir, "config.json");
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
|
|
290
|
-
try {
|
|
291
|
-
chmodSync(tmp, 384);
|
|
292
|
-
} catch {
|
|
293
|
-
}
|
|
294
|
-
renameSync(tmp, file);
|
|
295
|
-
} catch (err) {
|
|
296
|
-
try {
|
|
297
|
-
rmSync(tmp, { force: true });
|
|
298
|
-
} catch {
|
|
299
|
-
}
|
|
300
|
-
throw err;
|
|
431
|
+
if (!writePrivateFileAtomic(file, JSON.stringify(config, null, 2), MAX_CONFIG_BYTES)) {
|
|
432
|
+
throw new Error("could not save private CLI configuration");
|
|
301
433
|
}
|
|
302
434
|
}
|
|
303
435
|
function ensureAnonKey(dir = defaultConfigDir()) {
|
|
304
436
|
const config = loadConfig(dir) ?? {};
|
|
305
437
|
if (config.anonKey) return config.anonKey;
|
|
306
|
-
const anonKey =
|
|
438
|
+
const anonKey = randomBytes2(32).toString("hex");
|
|
307
439
|
saveConfig(dir, { ...config, anonKey });
|
|
308
440
|
return anonKey;
|
|
309
441
|
}
|
|
@@ -317,7 +449,12 @@ function recordDeviceBound(dir = defaultConfigDir(), when = Date.now()) {
|
|
|
317
449
|
}
|
|
318
450
|
function saveAuth(dir = defaultConfigDir(), auth = { cliToken: "" }) {
|
|
319
451
|
const config = loadConfig(dir) ?? {};
|
|
320
|
-
saveConfig(dir, {
|
|
452
|
+
saveConfig(dir, {
|
|
453
|
+
...config,
|
|
454
|
+
cliToken: auth.cliToken,
|
|
455
|
+
handle: auth.handle,
|
|
456
|
+
...auth.refreshToken ? { refreshToken: auth.refreshToken } : {}
|
|
457
|
+
});
|
|
321
458
|
}
|
|
322
459
|
function clearAuth(dir = defaultConfigDir()) {
|
|
323
460
|
const config = loadConfig(dir);
|
|
@@ -342,7 +479,9 @@ var STABLE_NPM_CANDIDATES = [
|
|
|
342
479
|
"/usr/local/bin/npm",
|
|
343
480
|
"/usr/bin/npm"
|
|
344
481
|
];
|
|
345
|
-
var
|
|
482
|
+
var require2 = createRequire(import.meta.url);
|
|
483
|
+
var CLI_VERSION = require2("../package.json").version;
|
|
484
|
+
var SCHEDULED_PACKAGE_SPEC = `whoburnedmore@${CLI_VERSION}`;
|
|
346
485
|
var SYNC_PATH_DIRS = [
|
|
347
486
|
"/opt/homebrew/bin",
|
|
348
487
|
"/usr/local/bin",
|
|
@@ -352,7 +491,7 @@ var SYNC_PATH_DIRS = [
|
|
|
352
491
|
"/sbin"
|
|
353
492
|
];
|
|
354
493
|
function syncPathEnv(npmPath = resolveNpmPath()) {
|
|
355
|
-
const dir =
|
|
494
|
+
const dir = dirname2(npmPath);
|
|
356
495
|
const dirs = [];
|
|
357
496
|
if (dir && dir !== "." && dir !== "/" && dir !== npmPath) dirs.push(dir);
|
|
358
497
|
for (const d of SYNC_PATH_DIRS) {
|
|
@@ -482,7 +621,7 @@ function syncCommandArgs(npmPath = resolveNpmPath()) {
|
|
|
482
621
|
"--yes",
|
|
483
622
|
"--ignore-scripts",
|
|
484
623
|
"--package",
|
|
485
|
-
|
|
624
|
+
SCHEDULED_PACKAGE_SPEC,
|
|
486
625
|
"--",
|
|
487
626
|
"whoburnedmore",
|
|
488
627
|
"sync"
|
|
@@ -697,8 +836,8 @@ function autoSyncInstalled() {
|
|
|
697
836
|
}
|
|
698
837
|
function readInstalledSystemd() {
|
|
699
838
|
try {
|
|
700
|
-
return `${
|
|
701
|
-
${
|
|
839
|
+
return `${readFileSync(systemdServicePath(), "utf8")}
|
|
840
|
+
${readFileSync(systemdTimerPath(), "utf8")}`;
|
|
702
841
|
} catch {
|
|
703
842
|
return null;
|
|
704
843
|
}
|
|
@@ -710,7 +849,7 @@ ${buildSystemdTimer()}`;
|
|
|
710
849
|
function readInstalledAgent() {
|
|
711
850
|
if (platform() === "darwin") {
|
|
712
851
|
const p = launchAgentPath();
|
|
713
|
-
return existsSync2(p) ?
|
|
852
|
+
return existsSync2(p) ? readFileSync(p, "utf8") : null;
|
|
714
853
|
}
|
|
715
854
|
if (platform() === "linux") {
|
|
716
855
|
const mech = linuxSyncMechanism();
|
|
@@ -798,7 +937,7 @@ async function daemonLoop(deps) {
|
|
|
798
937
|
|
|
799
938
|
// src/collect.ts
|
|
800
939
|
import { execFile } from "node:child_process";
|
|
801
|
-
import { createRequire as
|
|
940
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
802
941
|
import { dirname as dirname6, join as join13 } from "node:path";
|
|
803
942
|
import { promisify } from "node:util";
|
|
804
943
|
|
|
@@ -808,7 +947,7 @@ import { homedir as homedir4 } from "node:os";
|
|
|
808
947
|
import { join as join5 } from "node:path";
|
|
809
948
|
|
|
810
949
|
// src/native/claude.ts
|
|
811
|
-
import { readdir
|
|
950
|
+
import { readdir } from "node:fs/promises";
|
|
812
951
|
import { homedir as homedir3 } from "node:os";
|
|
813
952
|
import { join as join4 } from "node:path";
|
|
814
953
|
|
|
@@ -6249,7 +6388,12 @@ function estimateCostUSD(model, t) {
|
|
|
6249
6388
|
|
|
6250
6389
|
// ../shared/dist/index.js
|
|
6251
6390
|
var DateString = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD");
|
|
6252
|
-
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);
|
|
6253
6397
|
var ConnectorProvider = external_exports.enum([
|
|
6254
6398
|
"anthropic-api",
|
|
6255
6399
|
"openai-api",
|
|
@@ -6279,7 +6423,7 @@ var DailyUsageEntry = external_exports.object({
|
|
|
6279
6423
|
cacheCreationTokens: tokenCount,
|
|
6280
6424
|
cacheReadTokens: tokenCount,
|
|
6281
6425
|
/** Estimated cost in USD for this entry. */
|
|
6282
|
-
costUSD
|
|
6426
|
+
costUSD,
|
|
6283
6427
|
/** Where this entry came from. Defaults to the local CLI for back-compat. */
|
|
6284
6428
|
origin: UsageOrigin.default("cli"),
|
|
6285
6429
|
/** True when the numbers come from a provider's authoritative usage API. */
|
|
@@ -6294,7 +6438,7 @@ var DailyUsageEntry = external_exports.object({
|
|
|
6294
6438
|
* cannot see request ids) omit it, and an omitted fingerprint is never
|
|
6295
6439
|
* penalized.
|
|
6296
6440
|
*/
|
|
6297
|
-
requestCount:
|
|
6441
|
+
requestCount: rollupCount.optional()
|
|
6298
6442
|
});
|
|
6299
6443
|
var Timestamp = external_exports.string().min(1).max(40);
|
|
6300
6444
|
var SessionEntry = external_exports.object({
|
|
@@ -6305,49 +6449,76 @@ var SessionEntry = external_exports.object({
|
|
|
6305
6449
|
outputTokens: tokenCount,
|
|
6306
6450
|
cacheCreationTokens: tokenCount,
|
|
6307
6451
|
cacheReadTokens: tokenCount,
|
|
6308
|
-
costUSD
|
|
6452
|
+
costUSD,
|
|
6309
6453
|
lastActivity: Timestamp,
|
|
6310
6454
|
/** Number of assistant messages in this session (from transcripts). Optional. */
|
|
6311
|
-
messageCount:
|
|
6455
|
+
messageCount: rollupCount.optional()
|
|
6312
6456
|
});
|
|
6313
6457
|
var BlockEntry = external_exports.object({
|
|
6314
6458
|
startTime: Timestamp,
|
|
6315
6459
|
totalTokens: tokenCount,
|
|
6316
|
-
costUSD
|
|
6460
|
+
costUSD
|
|
6317
6461
|
});
|
|
6318
6462
|
var ToolStat = external_exports.object({
|
|
6319
6463
|
name: external_exports.string().min(1).max(128),
|
|
6320
|
-
count:
|
|
6464
|
+
count: rollupCount,
|
|
6321
6465
|
/** How many of those calls returned an error/interrupt (tool reliability). Optional. */
|
|
6322
|
-
errors:
|
|
6466
|
+
errors: rollupCount.optional(),
|
|
6323
6467
|
/** Tokens burned on turns that used this tool (turn tokens split across its tool calls). Optional. */
|
|
6324
|
-
tokens:
|
|
6468
|
+
tokens: tokenCount.optional()
|
|
6325
6469
|
});
|
|
6326
6470
|
var AgentStat = external_exports.object({
|
|
6327
6471
|
/** Total assistant messages across transcripts. */
|
|
6328
|
-
messageCount:
|
|
6472
|
+
messageCount: rollupCount,
|
|
6329
6473
|
/** Assistant messages that ran inside a subagent sidechain. */
|
|
6330
|
-
subagentMessages:
|
|
6474
|
+
subagentMessages: rollupCount,
|
|
6331
6475
|
/** Tokens spent inside subagent sidechains. */
|
|
6332
|
-
subagentTokens:
|
|
6476
|
+
subagentTokens: tokenCount,
|
|
6333
6477
|
/** Total tokens observed across transcripts (denominator for the share). */
|
|
6334
|
-
totalTokens:
|
|
6478
|
+
totalTokens: tokenCount,
|
|
6335
6479
|
/**
|
|
6336
6480
|
* Messages the human actually sent (their prompts) — non-sidechain user turns
|
|
6337
6481
|
* carrying real text, NOT tool results or injected/meta turns. Denominator for
|
|
6338
6482
|
* "avg cost per message". Optional (back-compat with older CLIs).
|
|
6339
6483
|
*/
|
|
6340
|
-
userMessageCount:
|
|
6484
|
+
userMessageCount: rollupCount.optional()
|
|
6341
6485
|
});
|
|
6342
6486
|
var SkillStat = external_exports.object({
|
|
6343
6487
|
name: external_exports.string().min(1).max(128),
|
|
6344
|
-
count:
|
|
6488
|
+
count: rollupCount,
|
|
6345
6489
|
/** Tokens burned in records produced while this skill was active. Optional. */
|
|
6346
|
-
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)
|
|
6347
6502
|
});
|
|
6348
6503
|
var SubmitPayload = external_exports.object({
|
|
6349
6504
|
cliVersion: external_exports.string().min(1).max(32),
|
|
6350
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(),
|
|
6351
6522
|
/** Optional per-conversation rollups (ccusage session). Back-compat: omittable. */
|
|
6352
6523
|
sessions: external_exports.array(SessionEntry).max(1e4).optional(),
|
|
6353
6524
|
/** Optional time-window rollups (ccusage blocks) for peak-hours analysis. */
|
|
@@ -6421,6 +6592,7 @@ function entryTotalTokens(e) {
|
|
|
6421
6592
|
}
|
|
6422
6593
|
var LeaderboardPeriod = external_exports.enum(["today", "7d", "30d", "all"]);
|
|
6423
6594
|
var LeaderboardMetric = external_exports.enum(["tokens", "cost"]);
|
|
6595
|
+
var InsightsPeriod = external_exports.enum(["7d", "30d", "all"]);
|
|
6424
6596
|
var OrgType = external_exports.enum(["company", "hackathon", "hackerhouse"]);
|
|
6425
6597
|
var MemberRole = external_exports.enum(["owner", "admin", "member"]);
|
|
6426
6598
|
var OrgBoardVisibility = external_exports.enum(["public", "members"]);
|
|
@@ -6559,18 +6731,63 @@ var OrgJoinInput = external_exports.object({
|
|
|
6559
6731
|
});
|
|
6560
6732
|
|
|
6561
6733
|
// src/native/file-cache.ts
|
|
6562
|
-
import {
|
|
6563
|
-
|
|
6564
|
-
|
|
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
|
+
}
|
|
6565
6781
|
function nativeCachePath(reader, env = process.env) {
|
|
6566
6782
|
const override = env.WHOBURNEDMORE_CONFIG_DIR?.trim();
|
|
6567
6783
|
const dir = override || defaultConfigDir();
|
|
6568
6784
|
return join3(dir, `native-cache-${reader}.json`);
|
|
6569
6785
|
}
|
|
6570
|
-
async function loadCache(path, version) {
|
|
6786
|
+
async function loadCache(path, version, maxBytes) {
|
|
6571
6787
|
try {
|
|
6572
|
-
const
|
|
6573
|
-
|
|
6788
|
+
const read = await readTextFileCapped(path, { maxBytes });
|
|
6789
|
+
if (!read.ok) return {};
|
|
6790
|
+
const parsed = JSON.parse(read.content);
|
|
6574
6791
|
if (parsed && parsed.v === version && parsed.files && typeof parsed.files === "object") {
|
|
6575
6792
|
return parsed.files;
|
|
6576
6793
|
}
|
|
@@ -6578,18 +6795,34 @@ async function loadCache(path, version) {
|
|
|
6578
6795
|
}
|
|
6579
6796
|
return {};
|
|
6580
6797
|
}
|
|
6581
|
-
function saveCache(path, version, files) {
|
|
6798
|
+
function saveCache(path, version, files, maxBytes) {
|
|
6799
|
+
let tmp = null;
|
|
6582
6800
|
try {
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
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);
|
|
6586
6809
|
renameSync3(tmp, path);
|
|
6810
|
+
tmp = null;
|
|
6587
6811
|
} catch {
|
|
6812
|
+
} finally {
|
|
6813
|
+
if (tmp) {
|
|
6814
|
+
try {
|
|
6815
|
+
unlinkSync(tmp);
|
|
6816
|
+
} catch {
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6588
6819
|
}
|
|
6589
6820
|
}
|
|
6590
6821
|
async function readFilesWithCache(opts) {
|
|
6591
6822
|
const now = opts.now ?? Date.now;
|
|
6592
|
-
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);
|
|
6593
6826
|
const fresh = {};
|
|
6594
6827
|
const itemsByFile = [];
|
|
6595
6828
|
let filesRead = 0;
|
|
@@ -6603,6 +6836,9 @@ async function readFilesWithCache(opts) {
|
|
|
6603
6836
|
} catch {
|
|
6604
6837
|
continue;
|
|
6605
6838
|
}
|
|
6839
|
+
if (size > maxFileBytes) {
|
|
6840
|
+
continue;
|
|
6841
|
+
}
|
|
6606
6842
|
const hit = cached[f];
|
|
6607
6843
|
if (hit && hit.size === size && hit.mtimeMs === mtimeMs) {
|
|
6608
6844
|
fresh[f] = hit;
|
|
@@ -6610,21 +6846,27 @@ async function readFilesWithCache(opts) {
|
|
|
6610
6846
|
continue;
|
|
6611
6847
|
}
|
|
6612
6848
|
if (now() > opts.deadline) {
|
|
6613
|
-
saveCache(opts.cachePath, opts.version, { ...cached, ...fresh });
|
|
6849
|
+
saveCache(opts.cachePath, opts.version, { ...cached, ...fresh }, maxCacheBytes);
|
|
6614
6850
|
return { itemsByFile: null, filesRead, timedOut: true };
|
|
6615
6851
|
}
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
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
|
+
}
|
|
6620
6862
|
continue;
|
|
6621
6863
|
}
|
|
6622
|
-
const items = opts.parseFile(content, f);
|
|
6864
|
+
const items = opts.parseFile(read.content, f);
|
|
6623
6865
|
fresh[f] = { size, mtimeMs, items };
|
|
6624
6866
|
itemsByFile.push(items);
|
|
6625
6867
|
filesRead += 1;
|
|
6626
6868
|
}
|
|
6627
|
-
saveCache(opts.cachePath, opts.version, fresh);
|
|
6869
|
+
saveCache(opts.cachePath, opts.version, fresh, maxCacheBytes);
|
|
6628
6870
|
return { itemsByFile, filesRead, timedOut: false };
|
|
6629
6871
|
}
|
|
6630
6872
|
|
|
@@ -6867,13 +7109,18 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
|
|
|
6867
7109
|
if (now() > deadline) {
|
|
6868
7110
|
return { requests: [...acc.values()], found: true, timedOut: true };
|
|
6869
7111
|
}
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
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
|
+
}
|
|
6874
7121
|
continue;
|
|
6875
7122
|
}
|
|
6876
|
-
accumulateClaudeLines(acc, splitLines(content));
|
|
7123
|
+
accumulateClaudeLines(acc, splitLines(read.content));
|
|
6877
7124
|
}
|
|
6878
7125
|
return { requests: [...acc.values()], found: true };
|
|
6879
7126
|
}
|
|
@@ -7278,28 +7525,54 @@ function parseContinueJsonl(content) {
|
|
|
7278
7525
|
}
|
|
7279
7526
|
return mapContinueRecords(records);
|
|
7280
7527
|
}
|
|
7281
|
-
async function listJsonl2(
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
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
|
+
}
|
|
7293
7557
|
}
|
|
7294
|
-
return
|
|
7558
|
+
return { files, aborted: false };
|
|
7295
7559
|
}
|
|
7296
7560
|
var CONTINUE_CACHE_VERSION = 2;
|
|
7297
7561
|
async function collectContinue(opts = {}) {
|
|
7298
7562
|
const env = opts.env ?? process.env;
|
|
7299
7563
|
const home = opts.continueDir ?? join6(env.HOME || homedir5(), ".continue");
|
|
7300
|
-
const files = await listJsonl2(join6(home, "dev_data"));
|
|
7301
|
-
if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
|
|
7302
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 };
|
|
7303
7576
|
const res = await readFilesWithCache({
|
|
7304
7577
|
files,
|
|
7305
7578
|
cachePath: opts.cachePath ?? nativeCachePath("continue", env),
|
|
@@ -7312,7 +7585,7 @@ async function collectContinue(opts = {}) {
|
|
|
7312
7585
|
e.costUSD,
|
|
7313
7586
|
e.requestCount ?? 0
|
|
7314
7587
|
]),
|
|
7315
|
-
deadline
|
|
7588
|
+
deadline,
|
|
7316
7589
|
now
|
|
7317
7590
|
});
|
|
7318
7591
|
if (!res.itemsByFile) {
|
|
@@ -7351,15 +7624,15 @@ async function collectContinue(opts = {}) {
|
|
|
7351
7624
|
|
|
7352
7625
|
// src/cursor.ts
|
|
7353
7626
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
7354
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
7355
|
-
import { createRequire as
|
|
7627
|
+
import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
|
|
7628
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
7356
7629
|
import { homedir as homedir6, platform as platform2 } from "node:os";
|
|
7357
7630
|
import { join as join8 } from "node:path";
|
|
7358
7631
|
|
|
7359
7632
|
// src/tokscale.ts
|
|
7360
7633
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
7361
|
-
import { createRequire } from "node:module";
|
|
7362
|
-
import { dirname as
|
|
7634
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
7635
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
7363
7636
|
var LOOKBACK_DAYS = 30;
|
|
7364
7637
|
function num3(n) {
|
|
7365
7638
|
const v = Math.round(Number(n));
|
|
@@ -7378,9 +7651,9 @@ function mapTokscaleDay(date, json) {
|
|
|
7378
7651
|
const outputTokens = num3(e.output) + num3(e.reasoning);
|
|
7379
7652
|
const cacheCreationTokens = num3(e.cacheWrite);
|
|
7380
7653
|
const cacheReadTokens = num3(e.cacheRead);
|
|
7381
|
-
const
|
|
7654
|
+
const costUSD2 = numCost(e.cost);
|
|
7382
7655
|
const total = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
|
|
7383
|
-
if (total === 0 &&
|
|
7656
|
+
if (total === 0 && costUSD2 === 0) continue;
|
|
7384
7657
|
out.push({
|
|
7385
7658
|
date,
|
|
7386
7659
|
tool: "cursor",
|
|
@@ -7389,7 +7662,7 @@ function mapTokscaleDay(date, json) {
|
|
|
7389
7662
|
outputTokens,
|
|
7390
7663
|
cacheCreationTokens,
|
|
7391
7664
|
cacheReadTokens,
|
|
7392
|
-
costUSD,
|
|
7665
|
+
costUSD: costUSD2,
|
|
7393
7666
|
origin: "cli",
|
|
7394
7667
|
verified: false
|
|
7395
7668
|
});
|
|
@@ -7398,12 +7671,12 @@ function mapTokscaleDay(date, json) {
|
|
|
7398
7671
|
}
|
|
7399
7672
|
function resolveTokscaleBin() {
|
|
7400
7673
|
try {
|
|
7401
|
-
const
|
|
7402
|
-
const pkgPath =
|
|
7403
|
-
const pkg =
|
|
7674
|
+
const require4 = createRequire2(import.meta.url);
|
|
7675
|
+
const pkgPath = require4.resolve("tokscale/package.json");
|
|
7676
|
+
const pkg = require4("tokscale/package.json");
|
|
7404
7677
|
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
|
|
7405
7678
|
if (!rel) return null;
|
|
7406
|
-
const binPath = join7(
|
|
7679
|
+
const binPath = join7(dirname4(pkgPath), rel);
|
|
7407
7680
|
if (/\.(c|m)?js$/.test(binPath)) {
|
|
7408
7681
|
return { cmd: process.execPath, prefixArgs: [binPath] };
|
|
7409
7682
|
}
|
|
@@ -7460,31 +7733,47 @@ function collectCursorViaTokscale(lookbackDays = LOOKBACK_DAYS) {
|
|
|
7460
7733
|
|
|
7461
7734
|
// src/cursor.ts
|
|
7462
7735
|
var EVENTS_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
|
|
7736
|
+
var MAX_CURSOR_PAGE_BYTES = 4 * 1024 * 1024;
|
|
7463
7737
|
function cursorDbPath() {
|
|
7464
7738
|
const home = homedir6();
|
|
7465
7739
|
const os = platform2();
|
|
7466
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");
|
|
7467
7741
|
return existsSync3(p) ? p : null;
|
|
7468
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
|
+
}
|
|
7469
7756
|
function readCursorToken(db) {
|
|
7470
|
-
const
|
|
7757
|
+
const require4 = createRequire3(import.meta.url);
|
|
7471
7758
|
try {
|
|
7472
|
-
const { DatabaseSync } =
|
|
7759
|
+
const { DatabaseSync } = require4("node:sqlite");
|
|
7473
7760
|
const d = new DatabaseSync(db, { readOnly: true });
|
|
7474
7761
|
const row = d.prepare("SELECT value FROM ItemTable WHERE key = ?").get("cursorAuth/accessToken");
|
|
7475
7762
|
d.close();
|
|
7476
7763
|
if (row?.value) return String(row.value);
|
|
7477
7764
|
} catch {
|
|
7478
7765
|
}
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
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
|
+
}
|
|
7488
7777
|
}
|
|
7489
7778
|
return null;
|
|
7490
7779
|
}
|
|
@@ -7571,12 +7860,16 @@ async function fetchCursorEvents(cookie, maxPages = 30, pageSize = 500) {
|
|
|
7571
7860
|
Cookie: cookie
|
|
7572
7861
|
},
|
|
7573
7862
|
body: JSON.stringify({ page, pageSize }),
|
|
7574
|
-
signal: AbortSignal.timeout(2e4)
|
|
7863
|
+
signal: AbortSignal.timeout(2e4),
|
|
7864
|
+
redirect: "error"
|
|
7575
7865
|
});
|
|
7576
7866
|
if (!res.ok) {
|
|
7577
7867
|
throw new Error(`cursor usage page ${page} failed (HTTP ${res.status})`);
|
|
7578
7868
|
}
|
|
7579
|
-
const body = await
|
|
7869
|
+
const body = await readJsonResponseCapped(
|
|
7870
|
+
res,
|
|
7871
|
+
MAX_CURSOR_PAGE_BYTES
|
|
7872
|
+
);
|
|
7580
7873
|
const batch = body.usageEventsDisplay ?? [];
|
|
7581
7874
|
all.push(...batch);
|
|
7582
7875
|
if (batch.length < pageSize) break;
|
|
@@ -7606,7 +7899,7 @@ async function collectCursor(opts) {
|
|
|
7606
7899
|
// src/native/codex.ts
|
|
7607
7900
|
import { readdir as readdir3 } from "node:fs/promises";
|
|
7608
7901
|
import { homedir as homedir7 } from "node:os";
|
|
7609
|
-
import { join as join9, resolve } from "node:path";
|
|
7902
|
+
import { basename, join as join9, resolve } from "node:path";
|
|
7610
7903
|
function num5(n) {
|
|
7611
7904
|
const v = Math.round(Number(n));
|
|
7612
7905
|
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
@@ -7623,8 +7916,10 @@ function readTokenFields(payload) {
|
|
|
7623
7916
|
output: num5(output)
|
|
7624
7917
|
};
|
|
7625
7918
|
}
|
|
7626
|
-
function
|
|
7919
|
+
function inspectCodexRollout(lines) {
|
|
7627
7920
|
let model = "unknown";
|
|
7921
|
+
let sawSessionMeta = false;
|
|
7922
|
+
let replayedRollout = false;
|
|
7628
7923
|
const perDay = /* @__PURE__ */ new Map();
|
|
7629
7924
|
let lastSeenDate = null;
|
|
7630
7925
|
for (const raw of lines) {
|
|
@@ -7639,6 +7934,13 @@ function parseCodexRollout(lines) {
|
|
|
7639
7934
|
const payload = obj.payload;
|
|
7640
7935
|
if (!payload || typeof payload !== "object") continue;
|
|
7641
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
|
+
}
|
|
7642
7944
|
if (kind === "session_meta" || kind === "turn_context") {
|
|
7643
7945
|
if (typeof payload.model === "string" && payload.model) model = payload.model;
|
|
7644
7946
|
}
|
|
@@ -7658,7 +7960,8 @@ function parseCodexRollout(lines) {
|
|
|
7658
7960
|
}
|
|
7659
7961
|
}
|
|
7660
7962
|
}
|
|
7661
|
-
if (perDay.size === 0)
|
|
7963
|
+
if (perDay.size === 0)
|
|
7964
|
+
return { sessions: [], replaySessions: [], replayCandidateDates: [] };
|
|
7662
7965
|
const dates = [...perDay.keys()].sort();
|
|
7663
7966
|
const out = [];
|
|
7664
7967
|
let prev = { input: 0, cached: 0, output: 0 };
|
|
@@ -7668,8 +7971,8 @@ function parseCodexRollout(lines) {
|
|
|
7668
7971
|
const dCached = Math.max(0, cum.cached - prev.cached);
|
|
7669
7972
|
const dOutput = Math.max(0, cum.output - prev.output);
|
|
7670
7973
|
prev = cum;
|
|
7671
|
-
const cacheReadTokens = dCached;
|
|
7672
|
-
const inputTokens =
|
|
7974
|
+
const cacheReadTokens = Math.min(dCached, dInput);
|
|
7975
|
+
const inputTokens = dInput - cacheReadTokens;
|
|
7673
7976
|
const outputTokens = dOutput;
|
|
7674
7977
|
if (inputTokens + outputTokens + cacheReadTokens === 0) continue;
|
|
7675
7978
|
out.push({
|
|
@@ -7682,7 +7985,11 @@ function parseCodexRollout(lines) {
|
|
|
7682
7985
|
turnCount: turns
|
|
7683
7986
|
});
|
|
7684
7987
|
}
|
|
7685
|
-
return
|
|
7988
|
+
return {
|
|
7989
|
+
sessions: replayedRollout ? [] : out,
|
|
7990
|
+
replaySessions: replayedRollout ? out : [],
|
|
7991
|
+
replayCandidateDates: replayedRollout ? dates : []
|
|
7992
|
+
};
|
|
7686
7993
|
}
|
|
7687
7994
|
function foldCodexSessions(acc, sessions) {
|
|
7688
7995
|
for (const s of sessions) {
|
|
@@ -7730,7 +8037,7 @@ function resolveCodexHome(env = process.env) {
|
|
|
7730
8037
|
return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join9(homedir7(), ".codex");
|
|
7731
8038
|
}
|
|
7732
8039
|
function resolveCodexSessionsDirs(env = process.env) {
|
|
7733
|
-
const home = resolveCodexHome(env);
|
|
8040
|
+
const home = resolve(resolveCodexHome(env));
|
|
7734
8041
|
return [join9(home, "sessions"), join9(home, "archived_sessions")];
|
|
7735
8042
|
}
|
|
7736
8043
|
async function listJsonl3(dir) {
|
|
@@ -7759,9 +8066,10 @@ function* splitLines3(content) {
|
|
|
7759
8066
|
if (start < content.length) yield content.slice(start);
|
|
7760
8067
|
}
|
|
7761
8068
|
var NATIVE_READ_BUDGET_MS2 = 45e3;
|
|
7762
|
-
var CODEX_CACHE_VERSION =
|
|
7763
|
-
function toCachedSession(s) {
|
|
8069
|
+
var CODEX_CACHE_VERSION = 7;
|
|
8070
|
+
function toCachedSession(s, kind = "usage") {
|
|
7764
8071
|
return [
|
|
8072
|
+
kind,
|
|
7765
8073
|
s.date,
|
|
7766
8074
|
s.model,
|
|
7767
8075
|
s.inputTokens,
|
|
@@ -7773,25 +8081,53 @@ function toCachedSession(s) {
|
|
|
7773
8081
|
}
|
|
7774
8082
|
function fromCachedSession(t) {
|
|
7775
8083
|
return {
|
|
7776
|
-
date: t[
|
|
7777
|
-
model: t[
|
|
7778
|
-
inputTokens: t[
|
|
7779
|
-
outputTokens: t[
|
|
7780
|
-
cacheCreationTokens: t[
|
|
7781
|
-
cacheReadTokens: t[
|
|
7782
|
-
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]
|
|
7783
8091
|
};
|
|
7784
8092
|
}
|
|
7785
8093
|
async function collectCodexNative(env = process.env, opts = {}) {
|
|
7786
8094
|
const dirs = resolveCodexSessionsDirs(env);
|
|
7787
|
-
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
|
+
}
|
|
7788
8108
|
if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
|
|
7789
8109
|
const now = opts.now ?? Date.now;
|
|
7790
8110
|
const res = await readFilesWithCache({
|
|
7791
8111
|
files,
|
|
7792
8112
|
cachePath: opts.cachePath ?? nativeCachePath("codex", env),
|
|
7793
8113
|
version: CODEX_CACHE_VERSION,
|
|
7794
|
-
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
|
+
},
|
|
7795
8131
|
deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS2),
|
|
7796
8132
|
now
|
|
7797
8133
|
});
|
|
@@ -7804,13 +8140,24 @@ async function collectCodexNative(env = process.env, opts = {}) {
|
|
|
7804
8140
|
};
|
|
7805
8141
|
}
|
|
7806
8142
|
const acc = /* @__PURE__ */ new Map();
|
|
8143
|
+
const legacyAcc = /* @__PURE__ */ new Map();
|
|
8144
|
+
const replayCandidateDates = /* @__PURE__ */ new Set();
|
|
7807
8145
|
for (const items of res.itemsByFile) {
|
|
7808
|
-
|
|
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);
|
|
7809
8154
|
}
|
|
7810
8155
|
return {
|
|
7811
8156
|
entries: finalizeCodexEntries(acc),
|
|
7812
8157
|
found: true,
|
|
7813
|
-
filesScanned: res.filesRead
|
|
8158
|
+
filesScanned: res.filesRead,
|
|
8159
|
+
replayCandidateDates: [...replayCandidateDates].sort(),
|
|
8160
|
+
legacyEntries: finalizeCodexEntries(legacyAcc)
|
|
7814
8161
|
};
|
|
7815
8162
|
}
|
|
7816
8163
|
|
|
@@ -8016,16 +8363,20 @@ async function collectVscodeAgent(opts) {
|
|
|
8016
8363
|
}
|
|
8017
8364
|
|
|
8018
8365
|
// src/pricing-live.ts
|
|
8019
|
-
import {
|
|
8020
|
-
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";
|
|
8021
8369
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8022
8370
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
8371
|
+
var MAX_PRICING_BYTES = 32 * 1024 * 1024;
|
|
8023
8372
|
function pricingCachePath(dir = defaultConfigDir()) {
|
|
8024
8373
|
return join11(dir, "pricing-cache.json");
|
|
8025
8374
|
}
|
|
8026
8375
|
async function readCache(path) {
|
|
8027
8376
|
try {
|
|
8028
|
-
const
|
|
8377
|
+
const read = await readTextFileCapped(path, { maxBytes: MAX_PRICING_BYTES });
|
|
8378
|
+
if (!read.ok) return null;
|
|
8379
|
+
const parsed = JSON.parse(read.content);
|
|
8029
8380
|
if (typeof parsed?.fetchedAt === "number" && parsed.table && typeof parsed.table === "object") {
|
|
8030
8381
|
return parsed;
|
|
8031
8382
|
}
|
|
@@ -8036,10 +8387,11 @@ async function readCache(path) {
|
|
|
8036
8387
|
async function fetchLiveTable(url) {
|
|
8037
8388
|
try {
|
|
8038
8389
|
const res = await fetch(url, {
|
|
8039
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
8390
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
8391
|
+
redirect: "error"
|
|
8040
8392
|
});
|
|
8041
8393
|
if (!res.ok) return null;
|
|
8042
|
-
const table = litellmToTable(await res
|
|
8394
|
+
const table = litellmToTable(await readJsonResponseCapped(res, MAX_PRICING_BYTES));
|
|
8043
8395
|
return Object.keys(table).length > 0 ? table : null;
|
|
8044
8396
|
} catch {
|
|
8045
8397
|
return null;
|
|
@@ -8057,10 +8409,20 @@ async function loadLivePricing(env = process.env, now = Date.now, cachePath = pr
|
|
|
8057
8409
|
if (live) {
|
|
8058
8410
|
setLivePricing(live);
|
|
8059
8411
|
try {
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
await
|
|
8063
|
-
|
|
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
|
+
}
|
|
8064
8426
|
} catch {
|
|
8065
8427
|
}
|
|
8066
8428
|
return "live";
|
|
@@ -8073,8 +8435,8 @@ async function loadLivePricing(env = process.env, now = Date.now, cachePath = pr
|
|
|
8073
8435
|
}
|
|
8074
8436
|
|
|
8075
8437
|
// src/provenance-store.ts
|
|
8076
|
-
import {
|
|
8077
|
-
|
|
8438
|
+
import { join as join12 } from "node:path";
|
|
8439
|
+
var MAX_PROVENANCE_STORE_BYTES = 4 * 1024 * 1024;
|
|
8078
8440
|
var PROVENANCE_STORE_VERSION = 1;
|
|
8079
8441
|
var KEY_SEP = "|";
|
|
8080
8442
|
var keyOf = (date, tool) => `${date}${KEY_SEP}${tool}`;
|
|
@@ -8088,7 +8450,9 @@ function provenanceStorePath(env = process.env) {
|
|
|
8088
8450
|
}
|
|
8089
8451
|
function loadProvenanceStore(path) {
|
|
8090
8452
|
try {
|
|
8091
|
-
const
|
|
8453
|
+
const content = readTextFileSyncCapped(path, MAX_PROVENANCE_STORE_BYTES);
|
|
8454
|
+
if (content === null) return null;
|
|
8455
|
+
const parsed = JSON.parse(content);
|
|
8092
8456
|
if (parsed && parsed.v === PROVENANCE_STORE_VERSION && parsed.req && typeof parsed.req === "object") {
|
|
8093
8457
|
return {
|
|
8094
8458
|
v: PROVENANCE_STORE_VERSION,
|
|
@@ -8102,10 +8466,7 @@ function loadProvenanceStore(path) {
|
|
|
8102
8466
|
}
|
|
8103
8467
|
function saveProvenanceStore(path, store) {
|
|
8104
8468
|
try {
|
|
8105
|
-
|
|
8106
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
8107
|
-
writeFileSync4(tmp, JSON.stringify(store));
|
|
8108
|
-
renameSync4(tmp, path);
|
|
8469
|
+
writePrivateFileAtomic(path, JSON.stringify(store), MAX_PROVENANCE_STORE_BYTES);
|
|
8109
8470
|
} catch {
|
|
8110
8471
|
}
|
|
8111
8472
|
}
|
|
@@ -8159,12 +8520,131 @@ function reconcileProvenance(entries, agent, complete, store) {
|
|
|
8159
8520
|
};
|
|
8160
8521
|
}
|
|
8161
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
|
+
|
|
8162
8623
|
// src/collect.ts
|
|
8163
8624
|
var execFileAsync = promisify(execFile);
|
|
8164
8625
|
var NATIVE_COVERED_SOURCES = /* @__PURE__ */ new Set(["claude", "codex"]);
|
|
8165
8626
|
var CCUSAGE_TIMEOUT_MS = 25e3;
|
|
8166
8627
|
var CCUSAGE_FALLBACK_TIMEOUT_MS = NATIVE_READ_BUDGET_MS;
|
|
8167
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
|
+
}
|
|
8168
8648
|
var SOURCES = [
|
|
8169
8649
|
"claude",
|
|
8170
8650
|
"codex",
|
|
@@ -8292,16 +8772,16 @@ function mapCcusageBlocks(json) {
|
|
|
8292
8772
|
if (b.isGap === true) continue;
|
|
8293
8773
|
if (typeof b.startTime !== "string") continue;
|
|
8294
8774
|
const totalTokens = norm(b.totalTokens);
|
|
8295
|
-
const
|
|
8296
|
-
if (totalTokens === 0 &&
|
|
8297
|
-
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 });
|
|
8298
8778
|
}
|
|
8299
8779
|
return out;
|
|
8300
8780
|
}
|
|
8301
8781
|
function resolveCcusageBin() {
|
|
8302
|
-
const
|
|
8303
|
-
const pkgPath =
|
|
8304
|
-
const pkg =
|
|
8782
|
+
const require4 = createRequire4(import.meta.url);
|
|
8783
|
+
const pkgPath = require4.resolve("ccusage/package.json");
|
|
8784
|
+
const pkg = require4("ccusage/package.json");
|
|
8305
8785
|
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.ccusage ?? "ccusage";
|
|
8306
8786
|
const binPath = join13(dirname6(pkgPath), rel);
|
|
8307
8787
|
if (/\.(c|m)?js$/.test(binPath)) {
|
|
@@ -8309,6 +8789,68 @@ function resolveCcusageBin() {
|
|
|
8309
8789
|
}
|
|
8310
8790
|
return { cmd: binPath, prefixArgs: [] };
|
|
8311
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
|
+
}
|
|
8312
8854
|
function dedupeDaily(entries) {
|
|
8313
8855
|
const byKey = /* @__PURE__ */ new Map();
|
|
8314
8856
|
for (const e of entries) {
|
|
@@ -8362,8 +8904,7 @@ function dedupeBlocks(blocks) {
|
|
|
8362
8904
|
function selectSourceEntries(source, ccusageEntries, native) {
|
|
8363
8905
|
if (source === "claude" && nativeReaderWon(native.claude))
|
|
8364
8906
|
return native.claude.entries;
|
|
8365
|
-
if (source === "codex"
|
|
8366
|
-
return native.codex.entries;
|
|
8907
|
+
if (source === "codex") return ccusageEntries;
|
|
8367
8908
|
return ccusageEntries;
|
|
8368
8909
|
}
|
|
8369
8910
|
function nativeReaderWon(result) {
|
|
@@ -8371,7 +8912,7 @@ function nativeReaderWon(result) {
|
|
|
8371
8912
|
}
|
|
8372
8913
|
function ccusageFallbackSources(native) {
|
|
8373
8914
|
return SOURCES.filter(
|
|
8374
|
-
(s) =>
|
|
8915
|
+
(s) => s === "codex" || s === "claude" && !nativeReaderWon(native.claude)
|
|
8375
8916
|
);
|
|
8376
8917
|
}
|
|
8377
8918
|
function ccusageClaudeEnv(env = process.env) {
|
|
@@ -8387,7 +8928,7 @@ async function runCcusageOnce(cmd, args, env, timeoutMs = CCUSAGE_TIMEOUT_MS) {
|
|
|
8387
8928
|
try {
|
|
8388
8929
|
const { stdout } = await execFileAsync(cmd, args, {
|
|
8389
8930
|
encoding: "utf8",
|
|
8390
|
-
maxBuffer:
|
|
8931
|
+
maxBuffer: CCUSAGE_MAX_BUFFER_BYTES,
|
|
8391
8932
|
// A single source shouldn't be able to hang the whole run: a hung source
|
|
8392
8933
|
// gets killed and (if transient) retried once below rather than stalling
|
|
8393
8934
|
// everything for minutes. The claude/codex fallback passes a longer cap —
|
|
@@ -8428,22 +8969,26 @@ async function collectAll(onProgress, opts) {
|
|
|
8428
8969
|
const nativeCodexTask = collectCodexNative().catch(
|
|
8429
8970
|
() => ({ entries: [], found: false, filesScanned: 0, timedOut: true })
|
|
8430
8971
|
);
|
|
8431
|
-
const sourceTasks =
|
|
8432
|
-
|
|
8433
|
-
|
|
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
|
+
]);
|
|
8434
8988
|
tick();
|
|
8435
|
-
return { source, mapped: [] };
|
|
8989
|
+
return { source, mapped: json ? mapCcusageDaily(source, json) : [] };
|
|
8436
8990
|
}
|
|
8437
|
-
|
|
8438
|
-
...prefixArgs,
|
|
8439
|
-
source,
|
|
8440
|
-
"daily",
|
|
8441
|
-
"--json",
|
|
8442
|
-
"--offline"
|
|
8443
|
-
]);
|
|
8444
|
-
tick();
|
|
8445
|
-
return { source, mapped: json ? mapCcusageDaily(source, json) : [] };
|
|
8446
|
-
});
|
|
8991
|
+
);
|
|
8447
8992
|
const sessionTask = runCcusage(
|
|
8448
8993
|
cmd,
|
|
8449
8994
|
[...prefixArgs, "session", "--json", "--offline"],
|
|
@@ -8491,7 +9036,7 @@ async function collectAll(onProgress, opts) {
|
|
|
8491
9036
|
vscodeResults,
|
|
8492
9037
|
continueResult
|
|
8493
9038
|
] = await Promise.all([
|
|
8494
|
-
|
|
9039
|
+
sourceTasks,
|
|
8495
9040
|
sessionTask,
|
|
8496
9041
|
blockTask,
|
|
8497
9042
|
cursorTask,
|
|
@@ -8503,6 +9048,7 @@ async function collectAll(onProgress, opts) {
|
|
|
8503
9048
|
]);
|
|
8504
9049
|
const native = { claude: nativeClaude, codex: nativeCodex };
|
|
8505
9050
|
const fallbacks = /* @__PURE__ */ new Map();
|
|
9051
|
+
let codexReplayReadSucceeded = false;
|
|
8506
9052
|
for (const source of ccusageFallbackSources(native)) {
|
|
8507
9053
|
const json = await runCcusage(
|
|
8508
9054
|
cmd,
|
|
@@ -8511,8 +9057,15 @@ async function collectAll(onProgress, opts) {
|
|
|
8511
9057
|
source === "claude" ? ccusageClaudeEnv() : void 0,
|
|
8512
9058
|
CCUSAGE_FALLBACK_TIMEOUT_MS
|
|
8513
9059
|
);
|
|
9060
|
+
if (source === "codex") codexReplayReadSucceeded = json !== null;
|
|
8514
9061
|
fallbacks.set(source, json ? mapCcusageDaily(source, json) : []);
|
|
8515
9062
|
}
|
|
9063
|
+
const codexReplayEntries = fallbacks.get("codex") ?? [];
|
|
9064
|
+
const replayCorrection = codexReplayCorrectionMetadata(
|
|
9065
|
+
nativeCodex,
|
|
9066
|
+
codexReplayEntries,
|
|
9067
|
+
codexReplayReadSucceeded
|
|
9068
|
+
);
|
|
8516
9069
|
const entries = [];
|
|
8517
9070
|
const toolsFound = [];
|
|
8518
9071
|
for (const { source, mapped } of sourceResults) {
|
|
@@ -8539,13 +9092,16 @@ async function collectAll(onProgress, opts) {
|
|
|
8539
9092
|
}
|
|
8540
9093
|
const { tools, skills, agent, sessionMessages, complete } = attribution;
|
|
8541
9094
|
onProgress?.(COLLECT_STAGES, COLLECT_STAGES, "");
|
|
8542
|
-
const
|
|
9095
|
+
const localSessions = dedupeSessions(sessions).map((s) => {
|
|
8543
9096
|
const messageCount = sessionMessages.get(s.sessionId);
|
|
8544
9097
|
return {
|
|
8545
9098
|
...s,
|
|
8546
9099
|
...messageCount ? { messageCount } : {}
|
|
8547
9100
|
};
|
|
8548
9101
|
});
|
|
9102
|
+
const dedupedSessions = sanitizeSessions(localSessions);
|
|
9103
|
+
const boundaryEntries = sanitizeDailyEntries(entries);
|
|
9104
|
+
const boundaryAgent = sanitizeAgentStat(agent);
|
|
8549
9105
|
const scanComplete = isAuthoritativeScan(
|
|
8550
9106
|
complete,
|
|
8551
9107
|
nativeClaude,
|
|
@@ -8555,25 +9111,40 @@ async function collectAll(onProgress, opts) {
|
|
|
8555
9111
|
);
|
|
8556
9112
|
const storePath = provenanceStorePath();
|
|
8557
9113
|
const reconciled = reconcileProvenance(
|
|
8558
|
-
dedupeDaily(
|
|
8559
|
-
|
|
9114
|
+
dedupeDaily(boundaryEntries),
|
|
9115
|
+
boundaryAgent,
|
|
8560
9116
|
scanComplete,
|
|
8561
9117
|
loadProvenanceStore(storePath)
|
|
8562
9118
|
);
|
|
9119
|
+
const reconciledEntries = sanitizeDailyEntries(reconciled.entries);
|
|
8563
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));
|
|
8564
9127
|
return {
|
|
8565
9128
|
// Cap each array to the server's accepted maximum (the shared SubmitPayload
|
|
8566
9129
|
// schema: entries ≤ 20000, sessions/blocks ≤ 10000), keeping the
|
|
8567
9130
|
// highest-token rows. Without this, a power user with >10000 distinct sessions
|
|
8568
9131
|
// would have their ENTIRE submit rejected with a 400 instead of a capped one.
|
|
8569
9132
|
// tools/skills are already bounded upstream (attribution caps).
|
|
8570
|
-
entries:
|
|
9133
|
+
entries: cappedEntries,
|
|
9134
|
+
codexReplayTombstoneDates: payloadReplayCorrection.tombstoneDates.filter(
|
|
9135
|
+
(date) => safeReplayDates.has(date)
|
|
9136
|
+
),
|
|
9137
|
+
codexReplayPriorScopes: safeReplayScopes,
|
|
8571
9138
|
sessions: capByTokens(dedupedSessions, 1e4, entryTokens2),
|
|
8572
|
-
blocks: capByTokens(
|
|
9139
|
+
blocks: capByTokens(
|
|
9140
|
+
dedupeBlocks(sanitizeBlocks(blocks)),
|
|
9141
|
+
1e4,
|
|
9142
|
+
(b) => b.totalTokens
|
|
9143
|
+
),
|
|
8573
9144
|
toolsFound,
|
|
8574
|
-
tools,
|
|
8575
|
-
skills,
|
|
8576
|
-
agent: reconciled.agent,
|
|
9145
|
+
tools: sanitizeToolStats(tools),
|
|
9146
|
+
skills: sanitizeSkillStats(skills),
|
|
9147
|
+
agent: sanitizeAgentStat(reconciled.agent),
|
|
8577
9148
|
attributionComplete: complete
|
|
8578
9149
|
};
|
|
8579
9150
|
}
|
|
@@ -8598,7 +9169,7 @@ function antigravityNoticeLines() {
|
|
|
8598
9169
|
}
|
|
8599
9170
|
|
|
8600
9171
|
// src/verify-upload.ts
|
|
8601
|
-
import { createHash } from "node:crypto";
|
|
9172
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
8602
9173
|
function prepareVerifyUpload(requests, scanTimedOut, cap = 5e4) {
|
|
8603
9174
|
const sorted = requests.slice().sort((a, b) => b.ts - a.ts);
|
|
8604
9175
|
const truncated = scanTimedOut || sorted.length > cap;
|
|
@@ -8612,7 +9183,7 @@ function prepareVerifyUpload(requests, scanTimedOut, cap = 5e4) {
|
|
|
8612
9183
|
outputTokens: r.outputTokens,
|
|
8613
9184
|
cacheCreationTokens: r.cacheCreationTokens,
|
|
8614
9185
|
cacheReadTokens: r.cacheReadTokens,
|
|
8615
|
-
reqHash:
|
|
9186
|
+
reqHash: createHash3("sha256").update(r.key).digest("hex").slice(0, 32)
|
|
8616
9187
|
}));
|
|
8617
9188
|
return { records, truncated };
|
|
8618
9189
|
}
|
|
@@ -8679,6 +9250,9 @@ function agentStatusReport(now = Date.now()) {
|
|
|
8679
9250
|
});
|
|
8680
9251
|
}
|
|
8681
9252
|
|
|
9253
|
+
// src/local-dashboard.ts
|
|
9254
|
+
import { join as join15 } from "node:path";
|
|
9255
|
+
|
|
8682
9256
|
// src/output.ts
|
|
8683
9257
|
function formatTokens(n) {
|
|
8684
9258
|
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
@@ -8721,6 +9295,14 @@ function signedInNextStepLines(result) {
|
|
|
8721
9295
|
}
|
|
8722
9296
|
|
|
8723
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
|
+
}
|
|
8724
9306
|
function esc(s) {
|
|
8725
9307
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
8726
9308
|
}
|
|
@@ -8951,8 +9533,8 @@ async function publishLocal(payload, deps) {
|
|
|
8951
9533
|
}
|
|
8952
9534
|
|
|
8953
9535
|
// src/index.ts
|
|
8954
|
-
var
|
|
8955
|
-
var VERSION =
|
|
9536
|
+
var require3 = createRequire5(import.meta.url);
|
|
9537
|
+
var VERSION = require3("../package.json").version;
|
|
8956
9538
|
var LOADING_VIBES = [
|
|
8957
9539
|
"counting up your token usage, right here on your machine\u2026",
|
|
8958
9540
|
"tallying tokens across every coding agent you use\u2026",
|
|
@@ -9024,10 +9606,8 @@ async function confirm(question) {
|
|
|
9024
9606
|
}
|
|
9025
9607
|
function showLocalDashboard(payload) {
|
|
9026
9608
|
const dir = defaultConfigDir();
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
writeFileSync5(
|
|
9030
|
-
file,
|
|
9609
|
+
const file = writeLocalDashboard(
|
|
9610
|
+
dir,
|
|
9031
9611
|
renderDashboardHtml(payload.entries, /* @__PURE__ */ new Date(), { webBaseUrl: webBase() })
|
|
9032
9612
|
);
|
|
9033
9613
|
const fileUrl = pathToFileURL(file).href;
|
|
@@ -9058,7 +9638,16 @@ async function run(flags) {
|
|
|
9058
9638
|
} finally {
|
|
9059
9639
|
progress.stop();
|
|
9060
9640
|
}
|
|
9061
|
-
const {
|
|
9641
|
+
const {
|
|
9642
|
+
entries,
|
|
9643
|
+
codexReplayTombstoneDates: codexReplayTombstoneDates2,
|
|
9644
|
+
codexReplayPriorScopes,
|
|
9645
|
+
blocks,
|
|
9646
|
+
tools,
|
|
9647
|
+
skills,
|
|
9648
|
+
agent,
|
|
9649
|
+
attributionComplete
|
|
9650
|
+
} = collected;
|
|
9062
9651
|
if (entries.length === 0) {
|
|
9063
9652
|
console.log();
|
|
9064
9653
|
console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
|
|
@@ -9070,8 +9659,14 @@ async function run(flags) {
|
|
|
9070
9659
|
return;
|
|
9071
9660
|
}
|
|
9072
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);
|
|
9073
9669
|
payload.tzOffsetMinutes = -(/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
9074
|
-
if (sessions.length > 0) payload.sessions = sessions;
|
|
9075
9670
|
if (blocks.length > 0) payload.blocks = blocks;
|
|
9076
9671
|
if (tools.length > 0) payload.tools = tools;
|
|
9077
9672
|
if (skills.length > 0) payload.skills = skills;
|
|
@@ -9091,19 +9686,11 @@ async function run(flags) {
|
|
|
9091
9686
|
confirm,
|
|
9092
9687
|
signIn: ensureSignedIn,
|
|
9093
9688
|
submit: async (token, p) => {
|
|
9094
|
-
const result = await
|
|
9689
|
+
const result = await submitFromBoundDevice(token, p);
|
|
9095
9690
|
try {
|
|
9096
9691
|
recordSync();
|
|
9097
9692
|
} catch {
|
|
9098
9693
|
}
|
|
9099
|
-
try {
|
|
9100
|
-
const cfgNow = loadConfig();
|
|
9101
|
-
if (!cfgNow?.deviceBoundAt) {
|
|
9102
|
-
const key = ensureAnonKey();
|
|
9103
|
-
if (await bindDeviceKey(token, key)) recordDeviceBound();
|
|
9104
|
-
}
|
|
9105
|
-
} catch {
|
|
9106
|
-
}
|
|
9107
9694
|
return result;
|
|
9108
9695
|
},
|
|
9109
9696
|
openBrowser,
|
|
@@ -9122,7 +9709,7 @@ async function run(flags) {
|
|
|
9122
9709
|
await submitSignedIn(cfg.cliToken, payload, flags, canSignIn);
|
|
9123
9710
|
return;
|
|
9124
9711
|
}
|
|
9125
|
-
const healed = cfg?.
|
|
9712
|
+
const healed = cfg?.refreshToken ? await refreshCliToken(cfg.refreshToken) : null;
|
|
9126
9713
|
if (healed) {
|
|
9127
9714
|
saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
|
|
9128
9715
|
await submitSignedIn(healed.token, payload, flags, canSignIn);
|
|
@@ -9134,7 +9721,7 @@ async function run(flags) {
|
|
|
9134
9721
|
console.log(pc2.yellow(" Sign in to put your usage on the leaderboard."));
|
|
9135
9722
|
console.log(
|
|
9136
9723
|
pc2.dim(
|
|
9137
|
-
" 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."
|
|
9138
9725
|
)
|
|
9139
9726
|
);
|
|
9140
9727
|
} else {
|
|
@@ -9146,8 +9733,8 @@ function sleep(ms) {
|
|
|
9146
9733
|
}
|
|
9147
9734
|
async function refreshCliTokenFromConfig() {
|
|
9148
9735
|
const cfg = loadConfig();
|
|
9149
|
-
if (!cfg?.
|
|
9150
|
-
return refreshCliToken(cfg.
|
|
9736
|
+
if (!cfg?.refreshToken) return null;
|
|
9737
|
+
return refreshCliToken(cfg.refreshToken);
|
|
9151
9738
|
}
|
|
9152
9739
|
async function ensureSignedIn() {
|
|
9153
9740
|
const cfg = loadConfig();
|
|
@@ -9176,7 +9763,11 @@ async function ensureSignedIn() {
|
|
|
9176
9763
|
continue;
|
|
9177
9764
|
}
|
|
9178
9765
|
if (res.status === "ok") {
|
|
9179
|
-
saveAuth(void 0, {
|
|
9766
|
+
saveAuth(void 0, {
|
|
9767
|
+
cliToken: res.token,
|
|
9768
|
+
handle: res.handle,
|
|
9769
|
+
refreshToken: res.refreshToken
|
|
9770
|
+
});
|
|
9180
9771
|
console.log(pc2.green(` \u2713 Signed in as @${sanitizeServerText(res.handle)}.`));
|
|
9181
9772
|
return { token: res.token, handle: res.handle };
|
|
9182
9773
|
}
|
|
@@ -9188,24 +9779,21 @@ async function ensureSignedIn() {
|
|
|
9188
9779
|
return null;
|
|
9189
9780
|
}
|
|
9190
9781
|
async function submitSignedIn(token, payload, flags, interactive) {
|
|
9191
|
-
let activeToken = token;
|
|
9192
9782
|
let result;
|
|
9193
9783
|
try {
|
|
9194
|
-
result = await
|
|
9784
|
+
result = await submitFromBoundDevice(token, payload);
|
|
9195
9785
|
} catch (err) {
|
|
9196
9786
|
if (err instanceof UnauthorizedError) {
|
|
9197
9787
|
const healed = await refreshCliTokenFromConfig();
|
|
9198
9788
|
if (healed) {
|
|
9199
9789
|
saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
|
|
9200
|
-
|
|
9201
|
-
result = await submit(healed.token, payload);
|
|
9790
|
+
result = await submitFromBoundDevice(healed.token, payload);
|
|
9202
9791
|
} else {
|
|
9203
9792
|
clearAuth();
|
|
9204
9793
|
if (!interactive) return;
|
|
9205
9794
|
const auth = await ensureSignedIn();
|
|
9206
9795
|
if (!auth) return;
|
|
9207
|
-
|
|
9208
|
-
result = await submit(auth.token, payload);
|
|
9796
|
+
result = await submitFromBoundDevice(auth.token, payload);
|
|
9209
9797
|
}
|
|
9210
9798
|
} else {
|
|
9211
9799
|
throw err;
|
|
@@ -9215,14 +9803,6 @@ async function submitSignedIn(token, payload, flags, interactive) {
|
|
|
9215
9803
|
recordSync();
|
|
9216
9804
|
} catch {
|
|
9217
9805
|
}
|
|
9218
|
-
try {
|
|
9219
|
-
const cfgNow = loadConfig();
|
|
9220
|
-
if (!cfgNow?.deviceBoundAt) {
|
|
9221
|
-
const key = ensureAnonKey();
|
|
9222
|
-
if (await bindDeviceKey(activeToken, key)) recordDeviceBound();
|
|
9223
|
-
}
|
|
9224
|
-
} catch {
|
|
9225
|
-
}
|
|
9226
9806
|
const baseUrl = result.orgBoardUrl ?? result.boardUrl ?? result.profileUrl;
|
|
9227
9807
|
if (!flags.quiet) {
|
|
9228
9808
|
console.log(
|
|
@@ -9281,6 +9861,29 @@ async function submitSignedIn(token, payload, flags, interactive) {
|
|
|
9281
9861
|
}
|
|
9282
9862
|
afterSubmitChores(flags);
|
|
9283
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
|
+
}
|
|
9284
9887
|
function afterSubmitChores(flags) {
|
|
9285
9888
|
if (flags.quiet) {
|
|
9286
9889
|
try {
|
|
@@ -9298,14 +9901,36 @@ function afterSubmitChores(flags) {
|
|
|
9298
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.")
|
|
9299
9902
|
);
|
|
9300
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
|
+
}
|
|
9301
9922
|
async function linkServerInstall(token) {
|
|
9302
9923
|
if (!token) {
|
|
9303
|
-
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");
|
|
9304
9925
|
}
|
|
9305
9926
|
const anonKey = ensureAnonKey();
|
|
9306
9927
|
const linked = await redeemServerInstall(token, anonKey);
|
|
9307
9928
|
if (linked.cliToken) {
|
|
9308
|
-
saveAuth(void 0, {
|
|
9929
|
+
saveAuth(void 0, {
|
|
9930
|
+
cliToken: linked.cliToken,
|
|
9931
|
+
handle: linked.handle,
|
|
9932
|
+
refreshToken: linked.refreshToken
|
|
9933
|
+
});
|
|
9309
9934
|
}
|
|
9310
9935
|
const handle = sanitizeServerText(linked.handle);
|
|
9311
9936
|
console.log(
|
|
@@ -9378,7 +10003,7 @@ async function runDaemon() {
|
|
|
9378
10003
|
await run({ dryRun: false, noSubmit: false, local: false, quiet: true });
|
|
9379
10004
|
if (!loadConfig()?.cliToken) {
|
|
9380
10005
|
throw new Error(
|
|
9381
|
-
"not linked \u2014 nothing submitted (
|
|
10006
|
+
"not linked \u2014 nothing submitted (generate a one-time link code from your signed-in profile first)"
|
|
9382
10007
|
);
|
|
9383
10008
|
}
|
|
9384
10009
|
}
|
|
@@ -9597,7 +10222,10 @@ async function main() {
|
|
|
9597
10222
|
break;
|
|
9598
10223
|
}
|
|
9599
10224
|
case "link":
|
|
9600
|
-
|
|
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());
|
|
9601
10229
|
break;
|
|
9602
10230
|
case "daemon":
|
|
9603
10231
|
await runDaemon();
|
|
@@ -9650,7 +10278,7 @@ function printHelp() {
|
|
|
9650
10278
|
npx whoburnedmore --local build the dashboard on your machine and open it (offline)
|
|
9651
10279
|
npx whoburnedmore --dry-run print exactly what would be sent, send nothing
|
|
9652
10280
|
npx whoburnedmore --no-submit collect locally, send nothing (no dashboard)
|
|
9653
|
-
npx whoburnedmore link
|
|
10281
|
+
npx whoburnedmore link link this server/VM (prompts for a one-time code)
|
|
9654
10282
|
npx whoburnedmore daemon keep syncing in the foreground (VMs/containers with no cron)
|
|
9655
10283
|
npx whoburnedmore private take yourself off the public leaderboard
|
|
9656
10284
|
npx whoburnedmore public put yourself back on it
|