whoburnedmore 0.2.0 → 0.4.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 +381 -37
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -8,9 +8,9 @@ var __export = (target, all) => {
|
|
|
8
8
|
// src/index.ts
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
11
|
-
import { createRequire as
|
|
12
|
-
import { platform as
|
|
13
|
-
import { join as
|
|
11
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
12
|
+
import { platform as platform3 } from "node:os";
|
|
13
|
+
import { join as join6 } 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", {});
|
|
@@ -266,9 +284,259 @@ function autoSyncInstalled() {
|
|
|
266
284
|
}
|
|
267
285
|
|
|
268
286
|
// src/collect.ts
|
|
287
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
288
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
289
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
290
|
+
|
|
291
|
+
// src/cursor.ts
|
|
292
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
293
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
294
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
295
|
+
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
296
|
+
import { join as join4 } from "node:path";
|
|
297
|
+
|
|
298
|
+
// src/tokscale.ts
|
|
269
299
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
270
300
|
import { createRequire } from "node:module";
|
|
271
301
|
import { dirname, join as join3 } from "node:path";
|
|
302
|
+
var LOOKBACK_DAYS = 30;
|
|
303
|
+
function num(n) {
|
|
304
|
+
const v = Math.round(Number(n));
|
|
305
|
+
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
306
|
+
}
|
|
307
|
+
function numCost(n) {
|
|
308
|
+
const v = Number(n);
|
|
309
|
+
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
310
|
+
}
|
|
311
|
+
function mapTokscaleDay(date, json) {
|
|
312
|
+
const entries = json?.entries;
|
|
313
|
+
if (!Array.isArray(entries)) return [];
|
|
314
|
+
const out = [];
|
|
315
|
+
for (const e of entries) {
|
|
316
|
+
const inputTokens = num(e.input);
|
|
317
|
+
const outputTokens = num(e.output) + num(e.reasoning);
|
|
318
|
+
const cacheCreationTokens = num(e.cacheWrite);
|
|
319
|
+
const cacheReadTokens = num(e.cacheRead);
|
|
320
|
+
const costUSD = numCost(e.cost);
|
|
321
|
+
const total = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
|
|
322
|
+
if (total === 0 && costUSD === 0) continue;
|
|
323
|
+
out.push({
|
|
324
|
+
date,
|
|
325
|
+
tool: "cursor",
|
|
326
|
+
model: typeof e.model === "string" && e.model ? e.model : "cursor",
|
|
327
|
+
inputTokens,
|
|
328
|
+
outputTokens,
|
|
329
|
+
cacheCreationTokens,
|
|
330
|
+
cacheReadTokens,
|
|
331
|
+
costUSD,
|
|
332
|
+
origin: "cli",
|
|
333
|
+
verified: false
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
function resolveTokscaleBin() {
|
|
339
|
+
try {
|
|
340
|
+
const require3 = createRequire(import.meta.url);
|
|
341
|
+
const pkgPath = require3.resolve("tokscale/package.json");
|
|
342
|
+
const pkg = require3("tokscale/package.json");
|
|
343
|
+
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
|
|
344
|
+
if (!rel) return null;
|
|
345
|
+
const binPath = join3(dirname(pkgPath), rel);
|
|
346
|
+
if (/\.(c|m)?js$/.test(binPath)) {
|
|
347
|
+
return { cmd: process.execPath, prefixArgs: [binPath] };
|
|
348
|
+
}
|
|
349
|
+
return { cmd: binPath, prefixArgs: [] };
|
|
350
|
+
} catch {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function runTokscaleDay(bin, day) {
|
|
355
|
+
const res = spawnSync2(
|
|
356
|
+
bin.cmd,
|
|
357
|
+
[
|
|
358
|
+
...bin.prefixArgs,
|
|
359
|
+
"--client",
|
|
360
|
+
"cursor",
|
|
361
|
+
"--json",
|
|
362
|
+
"--since",
|
|
363
|
+
day,
|
|
364
|
+
"--until",
|
|
365
|
+
day,
|
|
366
|
+
"--group-by",
|
|
367
|
+
"model",
|
|
368
|
+
"--no-spinner"
|
|
369
|
+
],
|
|
370
|
+
{ encoding: "utf8", maxBuffer: 32 * 1024 * 1024, timeout: 6e4 }
|
|
371
|
+
);
|
|
372
|
+
if (res.status !== 0 || !res.stdout) return null;
|
|
373
|
+
try {
|
|
374
|
+
return JSON.parse(res.stdout);
|
|
375
|
+
} catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function collectCursorViaTokscale(lookbackDays = LOOKBACK_DAYS) {
|
|
380
|
+
const bin = resolveTokscaleBin();
|
|
381
|
+
if (!bin) return [];
|
|
382
|
+
const today = /* @__PURE__ */ new Date();
|
|
383
|
+
const day = (offset) => new Date(today.getTime() - offset * 864e5).toISOString().slice(0, 10);
|
|
384
|
+
if (mapTokscaleDay(day(0), runTokscaleDay(bin, day(0))).length === 0) {
|
|
385
|
+
if (mapTokscaleDay(day(1), runTokscaleDay(bin, day(1))).length === 0) {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const out = [];
|
|
390
|
+
for (let i = 0; i < lookbackDays; i++) {
|
|
391
|
+
const d = day(i);
|
|
392
|
+
out.push(...mapTokscaleDay(d, runTokscaleDay(bin, d)));
|
|
393
|
+
}
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/cursor.ts
|
|
398
|
+
var EVENTS_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
|
|
399
|
+
function cursorDbPath() {
|
|
400
|
+
const home = homedir3();
|
|
401
|
+
const os = platform2();
|
|
402
|
+
const p = os === "darwin" ? join4(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join4(process.env.APPDATA ?? join4(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join4(process.env.XDG_CONFIG_HOME ?? join4(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
|
|
403
|
+
return existsSync3(p) ? p : null;
|
|
404
|
+
}
|
|
405
|
+
function readCursorToken(db) {
|
|
406
|
+
const require3 = createRequire2(import.meta.url);
|
|
407
|
+
try {
|
|
408
|
+
const { DatabaseSync } = require3("node:sqlite");
|
|
409
|
+
const d = new DatabaseSync(db, { readOnly: true });
|
|
410
|
+
const row = d.prepare("SELECT value FROM ItemTable WHERE key = ?").get("cursorAuth/accessToken");
|
|
411
|
+
d.close();
|
|
412
|
+
if (row?.value) return String(row.value);
|
|
413
|
+
} catch {
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const res = spawnSync3(
|
|
417
|
+
"sqlite3",
|
|
418
|
+
[db, "SELECT value FROM ItemTable WHERE key='cursorAuth/accessToken';"],
|
|
419
|
+
{ encoding: "utf8", timeout: 1e4 }
|
|
420
|
+
);
|
|
421
|
+
const out = res.stdout?.trim();
|
|
422
|
+
if (res.status === 0 && out) return out;
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
function cursorCookie(token) {
|
|
428
|
+
try {
|
|
429
|
+
const part = token.split(".")[1];
|
|
430
|
+
if (!part) return null;
|
|
431
|
+
const json = JSON.parse(
|
|
432
|
+
Buffer.from(part, "base64url").toString("utf8")
|
|
433
|
+
);
|
|
434
|
+
if (!json.sub) return null;
|
|
435
|
+
return `WorkosCursorSessionToken=${json.sub}%3A%3A${token}`;
|
|
436
|
+
} catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function num2(n) {
|
|
441
|
+
const v = Math.round(Number(n));
|
|
442
|
+
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
443
|
+
}
|
|
444
|
+
function mapCursorEvents(events) {
|
|
445
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
446
|
+
const byHour = /* @__PURE__ */ new Map();
|
|
447
|
+
for (const e of events) {
|
|
448
|
+
const tu = e.tokenUsage;
|
|
449
|
+
const ms = Number(e.timestamp);
|
|
450
|
+
if (!tu || !Number.isFinite(ms)) continue;
|
|
451
|
+
const d = new Date(ms);
|
|
452
|
+
const date = d.toISOString().slice(0, 10);
|
|
453
|
+
const model = e.model || "cursor";
|
|
454
|
+
const input = num2(tu.inputTokens);
|
|
455
|
+
const output = num2(tu.outputTokens);
|
|
456
|
+
const cacheWrite = num2(tu.cacheWriteTokens);
|
|
457
|
+
const cacheRead = num2(tu.cacheReadTokens);
|
|
458
|
+
const cost = Math.max(0, (Number(tu.totalCents) || 0) / 100);
|
|
459
|
+
const total = input + output + cacheWrite + cacheRead;
|
|
460
|
+
if (total === 0 && cost === 0) continue;
|
|
461
|
+
const key = `${date}|${model}`;
|
|
462
|
+
const day = byDay.get(key) ?? {
|
|
463
|
+
date,
|
|
464
|
+
tool: "cursor",
|
|
465
|
+
model,
|
|
466
|
+
inputTokens: 0,
|
|
467
|
+
outputTokens: 0,
|
|
468
|
+
cacheCreationTokens: 0,
|
|
469
|
+
cacheReadTokens: 0,
|
|
470
|
+
costUSD: 0,
|
|
471
|
+
origin: "cli",
|
|
472
|
+
verified: false
|
|
473
|
+
};
|
|
474
|
+
day.inputTokens += input;
|
|
475
|
+
day.outputTokens += output;
|
|
476
|
+
day.cacheCreationTokens += cacheWrite;
|
|
477
|
+
day.cacheReadTokens += cacheRead;
|
|
478
|
+
day.costUSD += cost;
|
|
479
|
+
byDay.set(key, day);
|
|
480
|
+
const hour = new Date(
|
|
481
|
+
Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours())
|
|
482
|
+
).toISOString();
|
|
483
|
+
const blk = byHour.get(hour) ?? { startTime: hour, totalTokens: 0, costUSD: 0 };
|
|
484
|
+
blk.totalTokens += total;
|
|
485
|
+
blk.costUSD += cost;
|
|
486
|
+
byHour.set(hour, blk);
|
|
487
|
+
}
|
|
488
|
+
const entries = [...byDay.values()].map((e) => ({
|
|
489
|
+
...e,
|
|
490
|
+
costUSD: Number(e.costUSD.toFixed(4))
|
|
491
|
+
}));
|
|
492
|
+
const blocks = [...byHour.values()].map((b) => ({
|
|
493
|
+
...b,
|
|
494
|
+
costUSD: Number(b.costUSD.toFixed(4))
|
|
495
|
+
}));
|
|
496
|
+
return { entries, blocks };
|
|
497
|
+
}
|
|
498
|
+
async function fetchCursorEvents(cookie, maxPages = 30, pageSize = 500) {
|
|
499
|
+
const all = [];
|
|
500
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
501
|
+
const res = await fetch(EVENTS_URL, {
|
|
502
|
+
method: "POST",
|
|
503
|
+
headers: {
|
|
504
|
+
"Content-Type": "application/json",
|
|
505
|
+
Origin: "https://cursor.com",
|
|
506
|
+
Cookie: cookie
|
|
507
|
+
},
|
|
508
|
+
body: JSON.stringify({ page, pageSize }),
|
|
509
|
+
signal: AbortSignal.timeout(2e4)
|
|
510
|
+
});
|
|
511
|
+
if (!res.ok) break;
|
|
512
|
+
const body = await res.json();
|
|
513
|
+
const batch = body.usageEventsDisplay ?? [];
|
|
514
|
+
all.push(...batch);
|
|
515
|
+
if (batch.length < pageSize) break;
|
|
516
|
+
}
|
|
517
|
+
return all;
|
|
518
|
+
}
|
|
519
|
+
async function collectCursor() {
|
|
520
|
+
try {
|
|
521
|
+
const db = cursorDbPath();
|
|
522
|
+
const token = db ? readCursorToken(db) : null;
|
|
523
|
+
const cookie = token ? cursorCookie(token) : null;
|
|
524
|
+
if (cookie) {
|
|
525
|
+
const events = await fetchCursorEvents(cookie);
|
|
526
|
+
const { entries, blocks } = mapCursorEvents(events);
|
|
527
|
+
if (entries.length > 0) return { entries, blocks, found: true };
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const entries = collectCursorViaTokscale();
|
|
533
|
+
if (entries.length > 0) return { entries, blocks: [], found: true };
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
536
|
+
return { entries: [], blocks: [], found: false };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/collect.ts
|
|
272
540
|
var SOURCES = [
|
|
273
541
|
"claude",
|
|
274
542
|
"codex",
|
|
@@ -400,18 +668,58 @@ function mapCcusageBlocks(json) {
|
|
|
400
668
|
return out;
|
|
401
669
|
}
|
|
402
670
|
function resolveCcusageBin() {
|
|
403
|
-
const require3 =
|
|
671
|
+
const require3 = createRequire3(import.meta.url);
|
|
404
672
|
const pkgPath = require3.resolve("ccusage/package.json");
|
|
405
673
|
const pkg = require3("ccusage/package.json");
|
|
406
674
|
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.ccusage ?? "ccusage";
|
|
407
|
-
const binPath =
|
|
675
|
+
const binPath = join5(dirname2(pkgPath), rel);
|
|
408
676
|
if (/\.(c|m)?js$/.test(binPath)) {
|
|
409
677
|
return { cmd: process.execPath, prefixArgs: [binPath] };
|
|
410
678
|
}
|
|
411
679
|
return { cmd: binPath, prefixArgs: [] };
|
|
412
680
|
}
|
|
681
|
+
function dedupeDaily(entries) {
|
|
682
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
683
|
+
for (const e of entries) {
|
|
684
|
+
const key = `${e.date}|${e.tool}|${e.model}|${e.origin}`;
|
|
685
|
+
const prev = byKey.get(key);
|
|
686
|
+
if (!prev) {
|
|
687
|
+
byKey.set(key, { ...e });
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
prev.inputTokens += e.inputTokens;
|
|
691
|
+
prev.outputTokens += e.outputTokens;
|
|
692
|
+
prev.cacheCreationTokens += e.cacheCreationTokens;
|
|
693
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
694
|
+
prev.costUSD = Number((prev.costUSD + e.costUSD).toFixed(6));
|
|
695
|
+
prev.verified = prev.verified && e.verified;
|
|
696
|
+
}
|
|
697
|
+
return [...byKey.values()];
|
|
698
|
+
}
|
|
699
|
+
function dedupeSessions(sessions) {
|
|
700
|
+
const byId = /* @__PURE__ */ new Map();
|
|
701
|
+
const total = (s) => s.inputTokens + s.outputTokens + s.cacheCreationTokens + s.cacheReadTokens;
|
|
702
|
+
for (const s of sessions) {
|
|
703
|
+
const prev = byId.get(s.sessionId);
|
|
704
|
+
if (!prev || total(s) >= total(prev)) byId.set(s.sessionId, s);
|
|
705
|
+
}
|
|
706
|
+
return [...byId.values()];
|
|
707
|
+
}
|
|
708
|
+
function dedupeBlocks(blocks) {
|
|
709
|
+
const byStart = /* @__PURE__ */ new Map();
|
|
710
|
+
for (const b of blocks) {
|
|
711
|
+
const prev = byStart.get(b.startTime);
|
|
712
|
+
if (!prev) {
|
|
713
|
+
byStart.set(b.startTime, { ...b });
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
prev.totalTokens += b.totalTokens;
|
|
717
|
+
prev.costUSD = Number((prev.costUSD + b.costUSD).toFixed(6));
|
|
718
|
+
}
|
|
719
|
+
return [...byStart.values()];
|
|
720
|
+
}
|
|
413
721
|
function runCcusage(cmd, args) {
|
|
414
|
-
const res =
|
|
722
|
+
const res = spawnSync4(cmd, args, {
|
|
415
723
|
encoding: "utf8",
|
|
416
724
|
maxBuffer: 64 * 1024 * 1024,
|
|
417
725
|
timeout: 12e4
|
|
@@ -423,7 +731,7 @@ function runCcusage(cmd, args) {
|
|
|
423
731
|
return null;
|
|
424
732
|
}
|
|
425
733
|
}
|
|
426
|
-
function collectAll() {
|
|
734
|
+
async function collectAll() {
|
|
427
735
|
const { cmd, prefixArgs } = resolveCcusageBin();
|
|
428
736
|
const entries = [];
|
|
429
737
|
const toolsFound = [];
|
|
@@ -446,7 +754,18 @@ function collectAll() {
|
|
|
446
754
|
const blockJson = runCcusage(cmd, [...prefixArgs, "blocks", "--json", "--offline"]);
|
|
447
755
|
const sessions = sessionJson ? mapCcusageSessions(sessionJson) : [];
|
|
448
756
|
const blocks = blockJson ? mapCcusageBlocks(blockJson) : [];
|
|
449
|
-
|
|
757
|
+
const cursor = await collectCursor();
|
|
758
|
+
if (cursor.found) {
|
|
759
|
+
entries.push(...cursor.entries);
|
|
760
|
+
blocks.push(...cursor.blocks);
|
|
761
|
+
toolsFound.push("cursor");
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
entries: dedupeDaily(entries),
|
|
765
|
+
sessions: dedupeSessions(sessions),
|
|
766
|
+
blocks: dedupeBlocks(blocks),
|
|
767
|
+
toolsFound
|
|
768
|
+
};
|
|
450
769
|
}
|
|
451
770
|
|
|
452
771
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
@@ -4778,10 +5097,29 @@ async function publishLocal(payload, deps) {
|
|
|
4778
5097
|
}
|
|
4779
5098
|
|
|
4780
5099
|
// src/index.ts
|
|
4781
|
-
var require2 =
|
|
5100
|
+
var require2 = createRequire4(import.meta.url);
|
|
4782
5101
|
var VERSION = require2("../package.json").version;
|
|
5102
|
+
function startSpinner(label) {
|
|
5103
|
+
if (!process.stdout.isTTY) {
|
|
5104
|
+
console.log(pc2.dim(` ${label}`));
|
|
5105
|
+
return () => {
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
5109
|
+
let i = 0;
|
|
5110
|
+
process.stdout.write("\x1B[?25l");
|
|
5111
|
+
const timer = setInterval(() => {
|
|
5112
|
+
i = (i + 1) % frames.length;
|
|
5113
|
+
process.stdout.write(`\r ${pc2.yellow(frames[i])} ${pc2.dim(label)}`);
|
|
5114
|
+
}, 80);
|
|
5115
|
+
return () => {
|
|
5116
|
+
clearInterval(timer);
|
|
5117
|
+
process.stdout.write("\r\x1B[2K");
|
|
5118
|
+
process.stdout.write("\x1B[?25h");
|
|
5119
|
+
};
|
|
5120
|
+
}
|
|
4783
5121
|
function openBrowser(url) {
|
|
4784
|
-
const os =
|
|
5122
|
+
const os = platform3();
|
|
4785
5123
|
const [cmd, args] = os === "darwin" ? ["open", [url]] : os === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
4786
5124
|
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
4787
5125
|
}
|
|
@@ -4816,7 +5154,7 @@ async function confirm(question) {
|
|
|
4816
5154
|
function showLocalDashboard(entries) {
|
|
4817
5155
|
const dir = defaultConfigDir();
|
|
4818
5156
|
mkdirSync3(dir, { recursive: true });
|
|
4819
|
-
const file =
|
|
5157
|
+
const file = join6(dir, "dashboard.html");
|
|
4820
5158
|
writeFileSync3(file, renderDashboardHtml(entries));
|
|
4821
5159
|
console.log();
|
|
4822
5160
|
console.log(` Local dashboard: ${pc2.cyan(`file://${file}`)}`);
|
|
@@ -4827,7 +5165,15 @@ async function run(flags) {
|
|
|
4827
5165
|
if (!flags.quiet) {
|
|
4828
5166
|
console.log(pc2.dim(`whoburnedmore v${VERSION} \xB7 ${flags.local ? "local mode" : apiBase()}`));
|
|
4829
5167
|
}
|
|
4830
|
-
const
|
|
5168
|
+
const stop = flags.quiet ? () => {
|
|
5169
|
+
} : startSpinner("Calculating your burn from local usage\u2026");
|
|
5170
|
+
let collected;
|
|
5171
|
+
try {
|
|
5172
|
+
collected = await collectAll();
|
|
5173
|
+
} finally {
|
|
5174
|
+
stop();
|
|
5175
|
+
}
|
|
5176
|
+
const { entries, sessions, blocks, toolsFound } = collected;
|
|
4831
5177
|
if (entries.length === 0) {
|
|
4832
5178
|
console.log();
|
|
4833
5179
|
console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
|
|
@@ -4864,6 +5210,10 @@ async function run(flags) {
|
|
|
4864
5210
|
const config = loadConfig();
|
|
4865
5211
|
if (config?.token) {
|
|
4866
5212
|
const result = await submitUsage(config.token, payload);
|
|
5213
|
+
if (!flags.quiet) {
|
|
5214
|
+
console.log(pc2.dim(" Opening your dashboard in your browser\u2026"));
|
|
5215
|
+
openBrowser(result.boardUrl ?? result.profileUrl);
|
|
5216
|
+
}
|
|
4867
5217
|
console.log(
|
|
4868
5218
|
` Submitted ${pc2.bold(String(result.upserted))} day-entries from ${toolsFound.join(", ")}.`
|
|
4869
5219
|
);
|
|
@@ -4876,10 +5226,14 @@ async function run(flags) {
|
|
|
4876
5226
|
if (result.boardUrl) {
|
|
4877
5227
|
console.log(` \u{1F91D} You're on the friends board: ${pc2.cyan(result.boardUrl)}`);
|
|
4878
5228
|
}
|
|
4879
|
-
if (!flags.quiet) openBrowser(result.boardUrl ?? result.profileUrl);
|
|
4880
5229
|
} else {
|
|
4881
5230
|
const anonKey = ensureAnonKey();
|
|
4882
5231
|
const result = await anonSubmit(anonKey, payload);
|
|
5232
|
+
const target = result.boardUrl ?? claimUrl(result.dashboardUrl, anonKey);
|
|
5233
|
+
if (!flags.quiet) {
|
|
5234
|
+
console.log(pc2.dim(" Opening your dashboard in your browser\u2026"));
|
|
5235
|
+
openBrowser(target);
|
|
5236
|
+
}
|
|
4883
5237
|
console.log(
|
|
4884
5238
|
` Submitted ${pc2.bold(String(result.upserted))} day-entries from ${toolsFound.join(", ")}.`
|
|
4885
5239
|
);
|
|
@@ -4889,19 +5243,14 @@ async function run(flags) {
|
|
|
4889
5243
|
);
|
|
4890
5244
|
console.log(` ${pc2.cyan(result.boardUrl)}`);
|
|
4891
5245
|
console.log(pc2.dim(` Your dashboard: ${result.dashboardUrl}`));
|
|
4892
|
-
if (!flags.quiet) {
|
|
4893
|
-
openBrowser(result.boardUrl);
|
|
4894
|
-
console.log(pc2.dim(" Opened the board \u2014 see how you stack up. Re-run anytime to update."));
|
|
4895
|
-
}
|
|
4896
5246
|
} else {
|
|
4897
5247
|
console.log(
|
|
4898
5248
|
` You burned ${pc2.bold(formatTokens(result.totalTokens))} tokens \u2014 you're on the public leaderboard:`
|
|
4899
5249
|
);
|
|
4900
5250
|
console.log(` ${pc2.cyan(result.dashboardUrl)}`);
|
|
4901
5251
|
if (!flags.quiet) {
|
|
4902
|
-
openBrowser(claimUrl(result.dashboardUrl, anonKey));
|
|
4903
5252
|
console.log(
|
|
4904
|
-
pc2.dim("
|
|
5253
|
+
pc2.dim(" Claim it (name + X) to own your rank, or make it private / remove it.")
|
|
4905
5254
|
);
|
|
4906
5255
|
console.log(
|
|
4907
5256
|
pc2.dim(" Manage anytime: `npx whoburnedmore private` \xB7 `npx whoburnedmore public` \xB7 `npx whoburnedmore remove`.")
|
|
@@ -4910,15 +5259,10 @@ async function run(flags) {
|
|
|
4910
5259
|
}
|
|
4911
5260
|
}
|
|
4912
5261
|
console.log();
|
|
4913
|
-
if (!flags.quiet
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
} else {
|
|
4918
|
-
console.log(pc2.dim(" OK \u2014 re-run `npx whoburnedmore` anytime, or `npx whoburnedmore install-sync` later."));
|
|
4919
|
-
}
|
|
4920
|
-
} else if (!flags.quiet && autoSyncInstalled()) {
|
|
4921
|
-
console.log(pc2.dim(" Background sync is on \u2014 your page keeps updating automatically. Stop with `npx whoburnedmore uninstall-sync`."));
|
|
5262
|
+
if (!flags.quiet) {
|
|
5263
|
+
console.log(
|
|
5264
|
+
autoSyncInstalled() ? pc2.dim(" Background sync is on \u2014 your page keeps updating automatically (`npx whoburnedmore uninstall-sync` to stop).") : pc2.dim(" Re-run anytime to update \xB7 `npx whoburnedmore install-sync` to keep it live in the background.")
|
|
5265
|
+
);
|
|
4922
5266
|
}
|
|
4923
5267
|
}
|
|
4924
5268
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whoburnedmore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Find out who burned more — submit your AI coding-agent token usage to the public leaderboard at whoburnedmore.com",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dist/index.js"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "rm -rf dist && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:ccusage --external:picocolors",
|
|
13
|
+
"build": "rm -rf dist && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:ccusage --external:picocolors --external:tokscale",
|
|
14
14
|
"test": "vitest run",
|
|
15
15
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
16
16
|
"prepublishOnly": "pnpm run build",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"ccusage": "20.0.9",
|
|
23
|
-
"picocolors": "^1.1.1"
|
|
23
|
+
"picocolors": "^1.1.1",
|
|
24
|
+
"tokscale": "^1.2.7"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/node": "^22.10.0",
|