whoburnedmore 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +379 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { spawn } from "node:child_process";
|
|
|
10
10
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
11
11
|
import { createRequire as createRequire4 } from "node:module";
|
|
12
12
|
import { platform as platform3 } from "node:os";
|
|
13
|
-
import { join as
|
|
13
|
+
import { join as join7 } from "node:path";
|
|
14
14
|
import { createInterface } from "node:readline/promises";
|
|
15
15
|
import pc2 from "picocolors";
|
|
16
16
|
|
|
@@ -29,16 +29,34 @@ function parseBoard(args) {
|
|
|
29
29
|
function apiBase() {
|
|
30
30
|
return process.env.WHOBURNEDMORE_API ?? "https://api.whoburnedmore.com";
|
|
31
31
|
}
|
|
32
|
+
async function readJson(res) {
|
|
33
|
+
const text = await res.text();
|
|
34
|
+
if (!text) return {};
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(text);
|
|
37
|
+
} catch {
|
|
38
|
+
return {
|
|
39
|
+
error: res.status >= 500 ? "the leaderboard server is temporarily unavailable \u2014 try again in a minute" : `unexpected response from the server (HTTP ${res.status})`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
32
43
|
async function post(path, body, token) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
let res;
|
|
45
|
+
try {
|
|
46
|
+
res = await fetch(`${apiBase()}${path}`, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify(body)
|
|
53
|
+
});
|
|
54
|
+
} catch {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"couldn't reach the leaderboard server \u2014 check your connection and try again"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return { status: res.status, body: await readJson(res) };
|
|
42
60
|
}
|
|
43
61
|
async function deviceStart() {
|
|
44
62
|
const { status, body } = await post("/v1/auth/device", {});
|
|
@@ -268,19 +286,231 @@ function autoSyncInstalled() {
|
|
|
268
286
|
// src/collect.ts
|
|
269
287
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
270
288
|
import { createRequire as createRequire3 } from "node:module";
|
|
271
|
-
import { dirname as dirname2, join as
|
|
289
|
+
import { dirname as dirname2, join as join6 } from "node:path";
|
|
290
|
+
|
|
291
|
+
// src/attribution.ts
|
|
292
|
+
import { readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
|
|
293
|
+
import { homedir as homedir3 } from "node:os";
|
|
294
|
+
import { basename, join as join3 } from "node:path";
|
|
295
|
+
|
|
296
|
+
// src/pricing.ts
|
|
297
|
+
var TABLE = [
|
|
298
|
+
{ match: /opus/i, price: { in: 15, out: 75, cacheWrite: 18.75, cacheRead: 1.5 } },
|
|
299
|
+
{ match: /sonnet/i, price: { in: 3, out: 15, cacheWrite: 3.75, cacheRead: 0.3 } },
|
|
300
|
+
{ match: /haiku/i, price: { in: 0.8, out: 4, cacheWrite: 1, cacheRead: 0.08 } },
|
|
301
|
+
{ match: /fable/i, price: { in: 15, out: 75, cacheWrite: 18.75, cacheRead: 1.5 } },
|
|
302
|
+
{ match: /gpt-4o|gpt-4\.1/i, price: { in: 2.5, out: 10, cacheWrite: 2.5, cacheRead: 1.25 } },
|
|
303
|
+
{ match: /gpt-5|o3|o4|codex/i, price: { in: 1.25, out: 10, cacheWrite: 1.25, cacheRead: 0.125 } },
|
|
304
|
+
{ match: /gemini.*flash/i, price: { in: 0.15, out: 0.6, cacheWrite: 0.15, cacheRead: 0.0375 } },
|
|
305
|
+
{ match: /gemini/i, price: { in: 1.25, out: 5, cacheWrite: 1.25, cacheRead: 0.31 } }
|
|
306
|
+
];
|
|
307
|
+
function estimateCostUSD(model, t) {
|
|
308
|
+
const row = TABLE.find((r) => r.match.test(model));
|
|
309
|
+
if (!row) return 0;
|
|
310
|
+
const p = row.price;
|
|
311
|
+
const usd = (t.inputTokens * p.in + t.outputTokens * p.out + t.cacheCreationTokens * p.cacheWrite + t.cacheReadTokens * p.cacheRead) / 1e6;
|
|
312
|
+
return usd > 0 ? usd : 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/attribution.ts
|
|
316
|
+
var CLAUDE_PROJECTS = join3(homedir3(), ".claude", "projects");
|
|
317
|
+
var MAX_FILES = 5e3;
|
|
318
|
+
var MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
319
|
+
var TIME_BUDGET_MS = 3e4;
|
|
320
|
+
var MAX_STATS = 300;
|
|
321
|
+
var MAX_PROJECTS = 500;
|
|
322
|
+
function createAccumulator() {
|
|
323
|
+
return {
|
|
324
|
+
tools: /* @__PURE__ */ new Map(),
|
|
325
|
+
skills: /* @__PURE__ */ new Map(),
|
|
326
|
+
projects: /* @__PURE__ */ new Map(),
|
|
327
|
+
agent: {
|
|
328
|
+
messageCount: 0,
|
|
329
|
+
subagentMessages: 0,
|
|
330
|
+
subagentTokens: 0,
|
|
331
|
+
totalTokens: 0
|
|
332
|
+
},
|
|
333
|
+
titles: /* @__PURE__ */ new Map(),
|
|
334
|
+
sessionMessages: /* @__PURE__ */ new Map()
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function createFileContext() {
|
|
338
|
+
return { toolNames: /* @__PURE__ */ new Map() };
|
|
339
|
+
}
|
|
340
|
+
function recordTokens(usage) {
|
|
341
|
+
if (!usage || typeof usage !== "object") return 0;
|
|
342
|
+
const u = usage;
|
|
343
|
+
const n = (v) => {
|
|
344
|
+
const x = Math.round(Number(v));
|
|
345
|
+
return Number.isFinite(x) && x > 0 ? x : 0;
|
|
346
|
+
};
|
|
347
|
+
return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens) + n(u.cache_read_input_tokens);
|
|
348
|
+
}
|
|
349
|
+
function processRecord(rec, acc, ctx) {
|
|
350
|
+
if (!rec || typeof rec !== "object") return;
|
|
351
|
+
const r = rec;
|
|
352
|
+
if (typeof r.attributionSkill === "string" && r.attributionSkill) {
|
|
353
|
+
const s = r.attributionSkill.slice(0, 128);
|
|
354
|
+
acc.skills.set(s, (acc.skills.get(s) ?? 0) + 1);
|
|
355
|
+
}
|
|
356
|
+
if (r.type === "ai-title" && typeof r.aiTitle === "string" && r.aiTitle && typeof r.sessionId === "string" && r.sessionId) {
|
|
357
|
+
acc.titles.set(r.sessionId, r.aiTitle.slice(0, 200));
|
|
358
|
+
}
|
|
359
|
+
const content = r.message?.content;
|
|
360
|
+
if (!Array.isArray(content)) return;
|
|
361
|
+
const isAssistant = r.type === "assistant" || r.message?.role === "assistant";
|
|
362
|
+
const isUser = r.type === "user" || r.message?.role === "user";
|
|
363
|
+
if (isAssistant) {
|
|
364
|
+
for (const block of content) {
|
|
365
|
+
if (block && typeof block === "object" && block.type === "tool_use" && typeof block.name === "string") {
|
|
366
|
+
const name = block.name.slice(0, 128);
|
|
367
|
+
if (!name) continue;
|
|
368
|
+
const t = acc.tools.get(name) ?? { count: 0, errors: 0 };
|
|
369
|
+
t.count += 1;
|
|
370
|
+
acc.tools.set(name, t);
|
|
371
|
+
const id = block.id;
|
|
372
|
+
if (typeof id === "string" && id) ctx.toolNames.set(id, name);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const tokens = recordTokens(r.message?.usage);
|
|
376
|
+
acc.agent.messageCount += 1;
|
|
377
|
+
acc.agent.totalTokens += tokens;
|
|
378
|
+
const sidechain = r.isSidechain === true;
|
|
379
|
+
if (sidechain) {
|
|
380
|
+
acc.agent.subagentMessages += 1;
|
|
381
|
+
acc.agent.subagentTokens += tokens;
|
|
382
|
+
}
|
|
383
|
+
if (typeof r.sessionId === "string" && r.sessionId) {
|
|
384
|
+
acc.sessionMessages.set(
|
|
385
|
+
r.sessionId,
|
|
386
|
+
(acc.sessionMessages.get(r.sessionId) ?? 0) + 1
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
if (typeof r.cwd === "string" && r.cwd && tokens > 0) {
|
|
390
|
+
const name = basename(r.cwd).slice(0, 128) || "unknown";
|
|
391
|
+
const model = typeof r.message?.model === "string" ? r.message.model : "unknown";
|
|
392
|
+
const u = r.message?.usage;
|
|
393
|
+
const num3 = (v) => {
|
|
394
|
+
const x = Math.round(Number(v));
|
|
395
|
+
return Number.isFinite(x) && x > 0 ? x : 0;
|
|
396
|
+
};
|
|
397
|
+
const cost = estimateCostUSD(model, {
|
|
398
|
+
inputTokens: num3(u?.input_tokens),
|
|
399
|
+
outputTokens: num3(u?.output_tokens),
|
|
400
|
+
cacheCreationTokens: num3(u?.cache_creation_input_tokens),
|
|
401
|
+
cacheReadTokens: num3(u?.cache_read_input_tokens)
|
|
402
|
+
});
|
|
403
|
+
const p = acc.projects.get(name) ?? { tokens: 0, costUSD: 0 };
|
|
404
|
+
p.tokens += tokens;
|
|
405
|
+
p.costUSD += cost;
|
|
406
|
+
acc.projects.set(name, p);
|
|
407
|
+
}
|
|
408
|
+
} else if (isUser) {
|
|
409
|
+
for (const block of content) {
|
|
410
|
+
if (block && typeof block === "object" && block.type === "tool_result" && block.is_error === true) {
|
|
411
|
+
const id = block.tool_use_id;
|
|
412
|
+
const name = typeof id === "string" ? ctx.toolNames.get(id) : void 0;
|
|
413
|
+
if (name) {
|
|
414
|
+
const t = acc.tools.get(name) ?? { count: 0, errors: 0 };
|
|
415
|
+
t.errors += 1;
|
|
416
|
+
acc.tools.set(name, t);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function toSkillStats(map) {
|
|
423
|
+
return [...map.entries()].map(([name, count]) => ({ name, count })).filter((s) => s.count > 0).sort((a, b) => b.count - a.count).slice(0, MAX_STATS);
|
|
424
|
+
}
|
|
425
|
+
function toToolStats(map) {
|
|
426
|
+
return [...map.entries()].map(([name, v]) => ({ name, count: v.count, errors: v.errors })).filter((s) => s.count > 0).sort((a, b) => b.count - a.count).slice(0, MAX_STATS).map((s) => s.errors > 0 ? s : { name: s.name, count: s.count });
|
|
427
|
+
}
|
|
428
|
+
function toProjectStats(map) {
|
|
429
|
+
return [...map.entries()].map(([name, v]) => ({
|
|
430
|
+
name,
|
|
431
|
+
tokens: v.tokens,
|
|
432
|
+
costUSD: Number(v.costUSD.toFixed(6))
|
|
433
|
+
})).filter((p) => p.tokens > 0).sort((a, b) => b.tokens - a.tokens).slice(0, MAX_PROJECTS);
|
|
434
|
+
}
|
|
435
|
+
function accumulatorToResult(acc) {
|
|
436
|
+
return {
|
|
437
|
+
tools: toToolStats(acc.tools),
|
|
438
|
+
skills: toSkillStats(acc.skills),
|
|
439
|
+
projects: toProjectStats(acc.projects),
|
|
440
|
+
agent: { ...acc.agent },
|
|
441
|
+
titles: acc.titles,
|
|
442
|
+
sessionMessages: acc.sessionMessages
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function listTranscripts(dir) {
|
|
446
|
+
const out = [];
|
|
447
|
+
const walk = (d) => {
|
|
448
|
+
if (out.length >= MAX_FILES) return;
|
|
449
|
+
let entries;
|
|
450
|
+
try {
|
|
451
|
+
entries = readdirSync(d, { withFileTypes: true });
|
|
452
|
+
} catch {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
for (const e of entries) {
|
|
456
|
+
if (out.length >= MAX_FILES) return;
|
|
457
|
+
const p = join3(d, e.name);
|
|
458
|
+
if (e.isDirectory()) walk(p);
|
|
459
|
+
else if (e.isFile() && e.name.endsWith(".jsonl")) {
|
|
460
|
+
try {
|
|
461
|
+
out.push({ path: p, mtime: statSync(p).mtimeMs });
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
walk(dir);
|
|
468
|
+
return out.sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES).map((f) => f.path);
|
|
469
|
+
}
|
|
470
|
+
function collectAttribution() {
|
|
471
|
+
const acc = createAccumulator();
|
|
472
|
+
const deadline = Date.now() + TIME_BUDGET_MS;
|
|
473
|
+
try {
|
|
474
|
+
for (const file of listTranscripts(CLAUDE_PROJECTS)) {
|
|
475
|
+
if (Date.now() > deadline) break;
|
|
476
|
+
let size = 0;
|
|
477
|
+
try {
|
|
478
|
+
size = statSync(file).size;
|
|
479
|
+
} catch {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (size > MAX_FILE_BYTES) continue;
|
|
483
|
+
let text;
|
|
484
|
+
try {
|
|
485
|
+
text = readFileSync2(file, "utf8");
|
|
486
|
+
} catch {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const ctx = createFileContext();
|
|
490
|
+
for (const line of text.split("\n")) {
|
|
491
|
+
if (!line) continue;
|
|
492
|
+
try {
|
|
493
|
+
processRecord(JSON.parse(line), acc, ctx);
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} catch {
|
|
499
|
+
}
|
|
500
|
+
return accumulatorToResult(acc);
|
|
501
|
+
}
|
|
272
502
|
|
|
273
503
|
// src/cursor.ts
|
|
274
504
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
275
505
|
import { existsSync as existsSync3 } from "node:fs";
|
|
276
506
|
import { createRequire as createRequire2 } from "node:module";
|
|
277
|
-
import { homedir as
|
|
278
|
-
import { join as
|
|
507
|
+
import { homedir as homedir4, platform as platform2 } from "node:os";
|
|
508
|
+
import { join as join5 } from "node:path";
|
|
279
509
|
|
|
280
510
|
// src/tokscale.ts
|
|
281
511
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
282
512
|
import { createRequire } from "node:module";
|
|
283
|
-
import { dirname, join as
|
|
513
|
+
import { dirname, join as join4 } from "node:path";
|
|
284
514
|
var LOOKBACK_DAYS = 30;
|
|
285
515
|
function num(n) {
|
|
286
516
|
const v = Math.round(Number(n));
|
|
@@ -324,7 +554,7 @@ function resolveTokscaleBin() {
|
|
|
324
554
|
const pkg = require3("tokscale/package.json");
|
|
325
555
|
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
|
|
326
556
|
if (!rel) return null;
|
|
327
|
-
const binPath =
|
|
557
|
+
const binPath = join4(dirname(pkgPath), rel);
|
|
328
558
|
if (/\.(c|m)?js$/.test(binPath)) {
|
|
329
559
|
return { cmd: process.execPath, prefixArgs: [binPath] };
|
|
330
560
|
}
|
|
@@ -379,9 +609,9 @@ function collectCursorViaTokscale(lookbackDays = LOOKBACK_DAYS) {
|
|
|
379
609
|
// src/cursor.ts
|
|
380
610
|
var EVENTS_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
|
|
381
611
|
function cursorDbPath() {
|
|
382
|
-
const home =
|
|
612
|
+
const home = homedir4();
|
|
383
613
|
const os = platform2();
|
|
384
|
-
const p = os === "darwin" ?
|
|
614
|
+
const p = os === "darwin" ? join5(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join5(process.env.APPDATA ?? join5(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join5(process.env.XDG_CONFIG_HOME ?? join5(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
|
|
385
615
|
return existsSync3(p) ? p : null;
|
|
386
616
|
}
|
|
387
617
|
function readCursorToken(db) {
|
|
@@ -654,12 +884,52 @@ function resolveCcusageBin() {
|
|
|
654
884
|
const pkgPath = require3.resolve("ccusage/package.json");
|
|
655
885
|
const pkg = require3("ccusage/package.json");
|
|
656
886
|
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.ccusage ?? "ccusage";
|
|
657
|
-
const binPath =
|
|
887
|
+
const binPath = join6(dirname2(pkgPath), rel);
|
|
658
888
|
if (/\.(c|m)?js$/.test(binPath)) {
|
|
659
889
|
return { cmd: process.execPath, prefixArgs: [binPath] };
|
|
660
890
|
}
|
|
661
891
|
return { cmd: binPath, prefixArgs: [] };
|
|
662
892
|
}
|
|
893
|
+
function dedupeDaily(entries) {
|
|
894
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
895
|
+
for (const e of entries) {
|
|
896
|
+
const key = `${e.date}|${e.tool}|${e.model}|${e.origin}`;
|
|
897
|
+
const prev = byKey.get(key);
|
|
898
|
+
if (!prev) {
|
|
899
|
+
byKey.set(key, { ...e });
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
prev.inputTokens += e.inputTokens;
|
|
903
|
+
prev.outputTokens += e.outputTokens;
|
|
904
|
+
prev.cacheCreationTokens += e.cacheCreationTokens;
|
|
905
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
906
|
+
prev.costUSD = Number((prev.costUSD + e.costUSD).toFixed(6));
|
|
907
|
+
prev.verified = prev.verified && e.verified;
|
|
908
|
+
}
|
|
909
|
+
return [...byKey.values()];
|
|
910
|
+
}
|
|
911
|
+
function dedupeSessions(sessions) {
|
|
912
|
+
const byId = /* @__PURE__ */ new Map();
|
|
913
|
+
const total = (s) => s.inputTokens + s.outputTokens + s.cacheCreationTokens + s.cacheReadTokens;
|
|
914
|
+
for (const s of sessions) {
|
|
915
|
+
const prev = byId.get(s.sessionId);
|
|
916
|
+
if (!prev || total(s) >= total(prev)) byId.set(s.sessionId, s);
|
|
917
|
+
}
|
|
918
|
+
return [...byId.values()];
|
|
919
|
+
}
|
|
920
|
+
function dedupeBlocks(blocks) {
|
|
921
|
+
const byStart = /* @__PURE__ */ new Map();
|
|
922
|
+
for (const b of blocks) {
|
|
923
|
+
const prev = byStart.get(b.startTime);
|
|
924
|
+
if (!prev) {
|
|
925
|
+
byStart.set(b.startTime, { ...b });
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
prev.totalTokens += b.totalTokens;
|
|
929
|
+
prev.costUSD = Number((prev.costUSD + b.costUSD).toFixed(6));
|
|
930
|
+
}
|
|
931
|
+
return [...byStart.values()];
|
|
932
|
+
}
|
|
663
933
|
function runCcusage(cmd, args) {
|
|
664
934
|
const res = spawnSync4(cmd, args, {
|
|
665
935
|
encoding: "utf8",
|
|
@@ -702,7 +972,26 @@ async function collectAll() {
|
|
|
702
972
|
blocks.push(...cursor.blocks);
|
|
703
973
|
toolsFound.push("cursor");
|
|
704
974
|
}
|
|
705
|
-
|
|
975
|
+
const { tools, skills, projects, agent, titles, sessionMessages } = collectAttribution();
|
|
976
|
+
const dedupedSessions = dedupeSessions(sessions).map((s) => {
|
|
977
|
+
const title = titles.get(s.sessionId);
|
|
978
|
+
const messageCount = sessionMessages.get(s.sessionId);
|
|
979
|
+
return {
|
|
980
|
+
...s,
|
|
981
|
+
...title ? { title } : {},
|
|
982
|
+
...messageCount ? { messageCount } : {}
|
|
983
|
+
};
|
|
984
|
+
});
|
|
985
|
+
return {
|
|
986
|
+
entries: dedupeDaily(entries),
|
|
987
|
+
sessions: dedupedSessions,
|
|
988
|
+
blocks: dedupeBlocks(blocks),
|
|
989
|
+
toolsFound,
|
|
990
|
+
tools,
|
|
991
|
+
skills,
|
|
992
|
+
projects,
|
|
993
|
+
agent
|
|
994
|
+
};
|
|
706
995
|
}
|
|
707
996
|
|
|
708
997
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
@@ -4792,13 +5081,42 @@ var SessionEntry = external_exports.object({
|
|
|
4792
5081
|
cacheCreationTokens: tokenCount,
|
|
4793
5082
|
cacheReadTokens: tokenCount,
|
|
4794
5083
|
costUSD: external_exports.number().nonnegative(),
|
|
4795
|
-
lastActivity: Timestamp
|
|
5084
|
+
lastActivity: Timestamp,
|
|
5085
|
+
/** Human-readable AI-generated session title (from transcripts). Optional. */
|
|
5086
|
+
title: external_exports.string().max(200).optional(),
|
|
5087
|
+
/** Number of assistant messages in this session (from transcripts). Optional. */
|
|
5088
|
+
messageCount: external_exports.number().int().nonnegative().optional()
|
|
4796
5089
|
});
|
|
4797
5090
|
var BlockEntry = external_exports.object({
|
|
4798
5091
|
startTime: Timestamp,
|
|
4799
5092
|
totalTokens: tokenCount,
|
|
4800
5093
|
costUSD: external_exports.number().nonnegative()
|
|
4801
5094
|
});
|
|
5095
|
+
var ToolStat = external_exports.object({
|
|
5096
|
+
name: external_exports.string().min(1).max(128),
|
|
5097
|
+
count: external_exports.number().int().nonnegative(),
|
|
5098
|
+
/** How many of those calls returned an error/interrupt (tool reliability). Optional. */
|
|
5099
|
+
errors: external_exports.number().int().nonnegative().optional()
|
|
5100
|
+
});
|
|
5101
|
+
var ProjectStat = external_exports.object({
|
|
5102
|
+
name: external_exports.string().min(1).max(128),
|
|
5103
|
+
tokens: external_exports.number().int().nonnegative(),
|
|
5104
|
+
costUSD: external_exports.number().nonnegative()
|
|
5105
|
+
});
|
|
5106
|
+
var AgentStat = external_exports.object({
|
|
5107
|
+
/** Total assistant messages across transcripts. */
|
|
5108
|
+
messageCount: external_exports.number().int().nonnegative(),
|
|
5109
|
+
/** Assistant messages that ran inside a subagent sidechain. */
|
|
5110
|
+
subagentMessages: external_exports.number().int().nonnegative(),
|
|
5111
|
+
/** Tokens spent inside subagent sidechains. */
|
|
5112
|
+
subagentTokens: external_exports.number().int().nonnegative(),
|
|
5113
|
+
/** Total tokens observed across transcripts (denominator for the share). */
|
|
5114
|
+
totalTokens: external_exports.number().int().nonnegative()
|
|
5115
|
+
});
|
|
5116
|
+
var SkillStat = external_exports.object({
|
|
5117
|
+
name: external_exports.string().min(1).max(128),
|
|
5118
|
+
count: external_exports.number().int().nonnegative()
|
|
5119
|
+
});
|
|
4802
5120
|
var SubmitPayload = external_exports.object({
|
|
4803
5121
|
cliVersion: external_exports.string().min(1).max(32),
|
|
4804
5122
|
entries: external_exports.array(DailyUsageEntry).min(1).max(2e4),
|
|
@@ -4806,6 +5124,14 @@ var SubmitPayload = external_exports.object({
|
|
|
4806
5124
|
sessions: external_exports.array(SessionEntry).max(1e4).optional(),
|
|
4807
5125
|
/** Optional time-window rollups (ccusage blocks) for peak-hours analysis. */
|
|
4808
5126
|
blocks: external_exports.array(BlockEntry).max(1e4).optional(),
|
|
5127
|
+
/** Optional tool-call frequencies parsed from local transcripts (names + counts). */
|
|
5128
|
+
tools: external_exports.array(ToolStat).max(300).optional(),
|
|
5129
|
+
/** Optional skill-usage frequencies parsed from local transcripts. */
|
|
5130
|
+
skills: external_exports.array(SkillStat).max(300).optional(),
|
|
5131
|
+
/** Optional per-project usage totals parsed from local transcripts. */
|
|
5132
|
+
projects: external_exports.array(ProjectStat).max(500).optional(),
|
|
5133
|
+
/** Optional subagent-vs-main rollup parsed from local transcripts. */
|
|
5134
|
+
agent: AgentStat.optional(),
|
|
4809
5135
|
/** Optional friends-board code (from `--board=<code>`): auto-join this board on submit. */
|
|
4810
5136
|
board: external_exports.string().min(1).max(32).optional()
|
|
4811
5137
|
});
|
|
@@ -5036,6 +5362,25 @@ async function publishLocal(payload, deps) {
|
|
|
5036
5362
|
// src/index.ts
|
|
5037
5363
|
var require2 = createRequire4(import.meta.url);
|
|
5038
5364
|
var VERSION = require2("../package.json").version;
|
|
5365
|
+
function startSpinner(label) {
|
|
5366
|
+
if (!process.stdout.isTTY) {
|
|
5367
|
+
console.log(pc2.dim(` ${label}`));
|
|
5368
|
+
return () => {
|
|
5369
|
+
};
|
|
5370
|
+
}
|
|
5371
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
5372
|
+
let i = 0;
|
|
5373
|
+
process.stdout.write("\x1B[?25l");
|
|
5374
|
+
const timer = setInterval(() => {
|
|
5375
|
+
i = (i + 1) % frames.length;
|
|
5376
|
+
process.stdout.write(`\r ${pc2.yellow(frames[i])} ${pc2.dim(label)}`);
|
|
5377
|
+
}, 80);
|
|
5378
|
+
return () => {
|
|
5379
|
+
clearInterval(timer);
|
|
5380
|
+
process.stdout.write("\r\x1B[2K");
|
|
5381
|
+
process.stdout.write("\x1B[?25h");
|
|
5382
|
+
};
|
|
5383
|
+
}
|
|
5039
5384
|
function openBrowser(url) {
|
|
5040
5385
|
const os = platform3();
|
|
5041
5386
|
const [cmd, args] = os === "darwin" ? ["open", [url]] : os === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
@@ -5072,7 +5417,7 @@ async function confirm(question) {
|
|
|
5072
5417
|
function showLocalDashboard(entries) {
|
|
5073
5418
|
const dir = defaultConfigDir();
|
|
5074
5419
|
mkdirSync3(dir, { recursive: true });
|
|
5075
|
-
const file =
|
|
5420
|
+
const file = join7(dir, "dashboard.html");
|
|
5076
5421
|
writeFileSync3(file, renderDashboardHtml(entries));
|
|
5077
5422
|
console.log();
|
|
5078
5423
|
console.log(` Local dashboard: ${pc2.cyan(`file://${file}`)}`);
|
|
@@ -5082,9 +5427,16 @@ function showLocalDashboard(entries) {
|
|
|
5082
5427
|
async function run(flags) {
|
|
5083
5428
|
if (!flags.quiet) {
|
|
5084
5429
|
console.log(pc2.dim(`whoburnedmore v${VERSION} \xB7 ${flags.local ? "local mode" : apiBase()}`));
|
|
5085
|
-
console.log(pc2.dim(" Calculating your burn from local usage\u2026"));
|
|
5086
5430
|
}
|
|
5087
|
-
const
|
|
5431
|
+
const stop = flags.quiet ? () => {
|
|
5432
|
+
} : startSpinner("Calculating your burn from local usage\u2026");
|
|
5433
|
+
let collected;
|
|
5434
|
+
try {
|
|
5435
|
+
collected = await collectAll();
|
|
5436
|
+
} finally {
|
|
5437
|
+
stop();
|
|
5438
|
+
}
|
|
5439
|
+
const { entries, sessions, blocks, toolsFound, tools, skills, projects, agent } = collected;
|
|
5088
5440
|
if (entries.length === 0) {
|
|
5089
5441
|
console.log();
|
|
5090
5442
|
console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
|
|
@@ -5094,6 +5446,10 @@ async function run(flags) {
|
|
|
5094
5446
|
const payload = { cliVersion: VERSION, entries };
|
|
5095
5447
|
if (sessions.length > 0) payload.sessions = sessions;
|
|
5096
5448
|
if (blocks.length > 0) payload.blocks = blocks;
|
|
5449
|
+
if (tools.length > 0) payload.tools = tools;
|
|
5450
|
+
if (skills.length > 0) payload.skills = skills;
|
|
5451
|
+
if (projects.length > 0) payload.projects = projects;
|
|
5452
|
+
if (agent.messageCount > 0) payload.agent = agent;
|
|
5097
5453
|
if (flags.board) payload.board = flags.board;
|
|
5098
5454
|
if (flags.dryRun) {
|
|
5099
5455
|
console.log(pc2.dim("\n --dry-run: this exact payload would be sent, nothing else:\n"));
|