whoburnedmore 0.9.25 → 0.9.26
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 +137 -59
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,54 @@ function applyScope(payload, flags) {
|
|
|
33
33
|
payload.orgCode = flags.orgCode;
|
|
34
34
|
return payload;
|
|
35
35
|
}
|
|
36
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["--board", "--org", "--token", "--pass", "--code"]);
|
|
37
|
+
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
38
|
+
...VALUE_FLAGS,
|
|
39
|
+
"--dry-run",
|
|
40
|
+
"--no-submit",
|
|
41
|
+
"--local",
|
|
42
|
+
"--watch",
|
|
43
|
+
"--help",
|
|
44
|
+
"-h",
|
|
45
|
+
"--version",
|
|
46
|
+
"-v"
|
|
47
|
+
]);
|
|
48
|
+
var NO_UPLOAD_FLAGS = ["--dry-run", "--no-submit", "--local"];
|
|
49
|
+
function argumentProblem(args, command) {
|
|
50
|
+
if (command === "help" || command === "version") return null;
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
const a = args[i];
|
|
53
|
+
if (!a.startsWith("-")) continue;
|
|
54
|
+
const name = a.includes("=") ? a.slice(0, a.indexOf("=")) : a;
|
|
55
|
+
if (!KNOWN_FLAGS.has(name)) return `Unknown option ${name}.`;
|
|
56
|
+
if (!VALUE_FLAGS.has(name) && a.includes("=")) {
|
|
57
|
+
return `${name} doesn't take a value; use ${name} on its own.`;
|
|
58
|
+
}
|
|
59
|
+
if (VALUE_FLAGS.has(name)) {
|
|
60
|
+
if (parseValueFlag([a, args[i + 1] ?? ""], name) === void 0) {
|
|
61
|
+
const example = { "--board": "CODE", "--org": "SLUG", "--token": "TOKEN" }[name] ?? "PASSWORD";
|
|
62
|
+
return `${name} needs a value, for example ${name}=${example}.`;
|
|
63
|
+
}
|
|
64
|
+
if (!a.includes("=")) i++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const noUpload = NO_UPLOAD_FLAGS.filter((f) => args.includes(f));
|
|
68
|
+
if (args.includes("--watch")) {
|
|
69
|
+
if (command !== "run" && command !== "sync" && command !== "daemon") {
|
|
70
|
+
return `--watch only works with sync (it keeps uploading every 15 minutes).`;
|
|
71
|
+
}
|
|
72
|
+
if (noUpload.length > 0) {
|
|
73
|
+
return `--watch keeps uploading every 15 minutes, so it can't be combined with ${noUpload.join(", ")}.`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if ((command === "sync" || command === "daemon") && noUpload.length > 0) {
|
|
77
|
+
return `${command} always uploads, so it can't be combined with ${noUpload.join(", ")}. To preview without sending, run \`npx whoburnedmore --dry-run\`.`;
|
|
78
|
+
}
|
|
79
|
+
if (command !== "run" && noUpload.length > 0) {
|
|
80
|
+
return `${noUpload.join(", ")} only works with the default command, not \`${command}\`.`;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
36
84
|
function parseValueFlag(args, name) {
|
|
37
85
|
const prefix = `${name}=`;
|
|
38
86
|
const eq = args.find((a) => a.startsWith(prefix));
|
|
@@ -44,14 +92,13 @@ function parseValueFlag(args, name) {
|
|
|
44
92
|
return void 0;
|
|
45
93
|
}
|
|
46
94
|
function resolveCommand(args) {
|
|
47
|
-
const valueFlags = /* @__PURE__ */ new Set(["--board", "--org", "--token", "--pass", "--code"]);
|
|
48
95
|
const flags = [];
|
|
49
96
|
const words = [];
|
|
50
97
|
for (let i = 0; i < args.length; i++) {
|
|
51
98
|
const a = args[i];
|
|
52
99
|
if (a.startsWith("-")) {
|
|
53
100
|
flags.push(a);
|
|
54
|
-
if (
|
|
101
|
+
if (VALUE_FLAGS.has(a) && args[i + 1] !== void 0 && !args[i + 1].startsWith("-")) i++;
|
|
55
102
|
continue;
|
|
56
103
|
}
|
|
57
104
|
words.push(a);
|
|
@@ -64,6 +111,10 @@ function resolveCommand(args) {
|
|
|
64
111
|
}
|
|
65
112
|
return words[0] ?? "run";
|
|
66
113
|
}
|
|
114
|
+
function confirmAnswer(answer, defaultYes) {
|
|
115
|
+
const a = answer.trim();
|
|
116
|
+
return a === "" ? defaultYes : /^y(es)?$/i.test(a);
|
|
117
|
+
}
|
|
67
118
|
|
|
68
119
|
// src/http-bounds.ts
|
|
69
120
|
async function readResponseBytesCapped(response, maxBytes) {
|
|
@@ -716,8 +767,13 @@ var FORWARDED_ENV_VARS = [
|
|
|
716
767
|
// dashboard URL shown to the user
|
|
717
768
|
"XDG_CONFIG_HOME",
|
|
718
769
|
// base for the default config dir
|
|
719
|
-
"CLAUDE_CONFIG_DIR"
|
|
770
|
+
"CLAUDE_CONFIG_DIR",
|
|
720
771
|
// a primary usage-collection source
|
|
772
|
+
// Usage is bucketed by local calendar day and the native parse caches record
|
|
773
|
+
// the zone: a shell TZ that differs from the system zone must reach the
|
|
774
|
+
// background job too, or the two runs bucket differently and keep
|
|
775
|
+
// invalidating each other's caches.
|
|
776
|
+
"TZ"
|
|
721
777
|
];
|
|
722
778
|
function syncEnv(opts) {
|
|
723
779
|
const env = opts?.env ?? process.env;
|
|
@@ -6549,6 +6605,7 @@ function normalizeModelKey(model) {
|
|
|
6549
6605
|
/^(?:us|eu|au|jp|apac|global|anthropic|amazon|meta|openai|google|vertex|azure|mistral)\./.test(prefix) ? "" : prefix
|
|
6550
6606
|
));
|
|
6551
6607
|
k = k.replace(/:\d+$/, "").replace(/:latest$/, "");
|
|
6608
|
+
k = k.replace(/\[[^\]]*\]$/, "");
|
|
6552
6609
|
return k;
|
|
6553
6610
|
}
|
|
6554
6611
|
function lookup(key) {
|
|
@@ -6564,7 +6621,7 @@ function resolveModelPrice(model) {
|
|
|
6564
6621
|
resolveCache.set(model, resolved);
|
|
6565
6622
|
return resolved;
|
|
6566
6623
|
}
|
|
6567
|
-
function
|
|
6624
|
+
function exactCandidates(model) {
|
|
6568
6625
|
const norm2 = normalizeModelKey(model);
|
|
6569
6626
|
const candidates = [norm2];
|
|
6570
6627
|
const dateless = norm2.replace(/[-@]20\d{6}$/, "").replace(/-20\d{2}-\d{2}-\d{2}$/, "");
|
|
@@ -6572,12 +6629,19 @@ function resolveModelPriceUncached(model) {
|
|
|
6572
6629
|
candidates.push(dateless);
|
|
6573
6630
|
if (norm2.endsWith("-latest"))
|
|
6574
6631
|
candidates.push(norm2.slice(0, -"-latest".length));
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6632
|
+
return { candidates, dateless };
|
|
6633
|
+
}
|
|
6634
|
+
function lookupExact(model) {
|
|
6635
|
+
for (const c of exactCandidates(model).candidates) {
|
|
6636
|
+
const row = lookup(c);
|
|
6578
6637
|
if (row)
|
|
6579
|
-
|
|
6638
|
+
return row;
|
|
6580
6639
|
}
|
|
6640
|
+
return void 0;
|
|
6641
|
+
}
|
|
6642
|
+
function resolveModelPriceUncached(model) {
|
|
6643
|
+
const { dateless } = exactCandidates(model);
|
|
6644
|
+
let row = lookupExact(model);
|
|
6581
6645
|
if (!row) {
|
|
6582
6646
|
let base = dateless;
|
|
6583
6647
|
for (let i = 0; i < 3 && !row; i++) {
|
|
@@ -7225,6 +7289,7 @@ function parseClaudeLine(raw) {
|
|
|
7225
7289
|
} catch {
|
|
7226
7290
|
return null;
|
|
7227
7291
|
}
|
|
7292
|
+
if (!obj || typeof obj !== "object") return null;
|
|
7228
7293
|
const message = obj.message;
|
|
7229
7294
|
if (!message || typeof message !== "object") return null;
|
|
7230
7295
|
const usage = message.usage;
|
|
@@ -7506,6 +7571,7 @@ function inspectCodexRollout(lines) {
|
|
|
7506
7571
|
} catch {
|
|
7507
7572
|
continue;
|
|
7508
7573
|
}
|
|
7574
|
+
if (!obj || typeof obj !== "object") continue;
|
|
7509
7575
|
const payload = obj.payload;
|
|
7510
7576
|
if (!payload || typeof payload !== "object") continue;
|
|
7511
7577
|
const kind = obj.type;
|
|
@@ -8453,6 +8519,7 @@ function mapCursorEvents(events) {
|
|
|
8453
8519
|
}
|
|
8454
8520
|
async function fetchCursorEvents(cookie, maxPages = 30, pageSize = 500) {
|
|
8455
8521
|
const all = [];
|
|
8522
|
+
let reachedEnd = false;
|
|
8456
8523
|
for (let page = 1; page <= maxPages; page++) {
|
|
8457
8524
|
const res = await fetch(EVENTS_URL, {
|
|
8458
8525
|
method: "POST",
|
|
@@ -8474,9 +8541,18 @@ async function fetchCursorEvents(cookie, maxPages = 30, pageSize = 500) {
|
|
|
8474
8541
|
);
|
|
8475
8542
|
const batch = body.usageEventsDisplay ?? [];
|
|
8476
8543
|
all.push(...batch);
|
|
8477
|
-
if (batch.length < pageSize)
|
|
8544
|
+
if (batch.length < pageSize) {
|
|
8545
|
+
reachedEnd = true;
|
|
8546
|
+
break;
|
|
8547
|
+
}
|
|
8478
8548
|
}
|
|
8479
|
-
return all;
|
|
8549
|
+
return reachedEnd ? all : dropBoundaryDay(all);
|
|
8550
|
+
}
|
|
8551
|
+
function dropBoundaryDay(events) {
|
|
8552
|
+
const dayOf = (e) => localUsageDate(Number(e.timestamp));
|
|
8553
|
+
const boundary = events.length > 0 ? dayOf(events[events.length - 1]) : null;
|
|
8554
|
+
if (!boundary) return events;
|
|
8555
|
+
return events.filter((e) => dayOf(e) !== boundary);
|
|
8480
8556
|
}
|
|
8481
8557
|
async function collectCursor(opts) {
|
|
8482
8558
|
try {
|
|
@@ -9013,6 +9089,14 @@ function normCost(n) {
|
|
|
9013
9089
|
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
9014
9090
|
}
|
|
9015
9091
|
var PRICE_TABLE_COST_SOURCES = /* @__PURE__ */ new Set(["claude", "codex"]);
|
|
9092
|
+
function repriceCcusageCost(tool, model, tokens, ccusageCostUSD) {
|
|
9093
|
+
if (ccusageCostUSD > 0) return ccusageCostUSD;
|
|
9094
|
+
const known = model.length > 0 && model !== "unknown";
|
|
9095
|
+
if (known && PRICE_TABLE_COST_SOURCES.has(tool) && resolveModelPrice(model)) {
|
|
9096
|
+
return estimateCostUSD(model, tokens);
|
|
9097
|
+
}
|
|
9098
|
+
return 0;
|
|
9099
|
+
}
|
|
9016
9100
|
function mapCcusageDaily(tool, json, now = Date.now()) {
|
|
9017
9101
|
const daily = json?.daily;
|
|
9018
9102
|
if (!Array.isArray(daily)) return [];
|
|
@@ -9070,43 +9154,13 @@ function mapCcusageDaily(tool, json, now = Date.now()) {
|
|
|
9070
9154
|
}
|
|
9071
9155
|
for (const c of candidates) {
|
|
9072
9156
|
const tokens = c.inputTokens + c.outputTokens + c.cacheCreationTokens + c.cacheReadTokens;
|
|
9073
|
-
|
|
9074
|
-
|
|
9157
|
+
const costUSD2 = repriceCcusageCost(tool, c.model, c, c.costUSD);
|
|
9158
|
+
if (tokens === 0 && costUSD2 === 0) continue;
|
|
9075
9159
|
entries.push({ date, tool, ...c, costUSD: costUSD2, origin: "cli", verified: false });
|
|
9076
9160
|
}
|
|
9077
9161
|
}
|
|
9078
9162
|
return entries;
|
|
9079
9163
|
}
|
|
9080
|
-
function mapCcusageSessions(json) {
|
|
9081
|
-
const sessions = json?.session;
|
|
9082
|
-
if (!Array.isArray(sessions)) return [];
|
|
9083
|
-
const out = [];
|
|
9084
|
-
for (const raw of sessions) {
|
|
9085
|
-
const s = raw;
|
|
9086
|
-
const sessionId = typeof s.period === "string" ? s.period : typeof s.sessionId === "string" ? s.sessionId : null;
|
|
9087
|
-
if (!sessionId) continue;
|
|
9088
|
-
const inputTokens = norm(s.inputTokens);
|
|
9089
|
-
const outputTokens = norm(s.outputTokens);
|
|
9090
|
-
const cacheCreationTokens = norm(s.cacheCreationTokens);
|
|
9091
|
-
const cacheReadTokens = norm(s.cacheReadTokens);
|
|
9092
|
-
if (inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens === 0)
|
|
9093
|
-
continue;
|
|
9094
|
-
const models = Array.isArray(s.modelsUsed) ? s.modelsUsed.filter((m) => typeof m === "string") : [];
|
|
9095
|
-
const meta = s.metadata ?? {};
|
|
9096
|
-
out.push({
|
|
9097
|
-
sessionId: sessionId.slice(0, 128),
|
|
9098
|
-
tool: typeof s.agent === "string" ? s.agent : "unknown",
|
|
9099
|
-
model: models[0] ?? "unknown",
|
|
9100
|
-
inputTokens,
|
|
9101
|
-
outputTokens,
|
|
9102
|
-
cacheCreationTokens,
|
|
9103
|
-
cacheReadTokens,
|
|
9104
|
-
costUSD: normCost(s.totalCost ?? s.costUSD),
|
|
9105
|
-
lastActivity: typeof meta.lastActivity === "string" ? meta.lastActivity : (/* @__PURE__ */ new Date()).toISOString()
|
|
9106
|
-
});
|
|
9107
|
-
}
|
|
9108
|
-
return out;
|
|
9109
|
-
}
|
|
9110
9164
|
function mapCcusageBlocks(json) {
|
|
9111
9165
|
const blocks = json?.blocks;
|
|
9112
9166
|
if (!Array.isArray(blocks)) return [];
|
|
@@ -9334,14 +9388,9 @@ async function collectAll(onProgress, opts) {
|
|
|
9334
9388
|
return { source, mapped: json ? mapCcusageDaily(source, json) : [] };
|
|
9335
9389
|
}
|
|
9336
9390
|
);
|
|
9337
|
-
const sessionTask =
|
|
9338
|
-
cmd,
|
|
9339
|
-
[...prefixArgs, "session", "--json", "--offline"],
|
|
9340
|
-
void 0,
|
|
9341
|
-
CCUSAGE_AGGREGATE_TIMEOUT_MS
|
|
9342
|
-
).then((json) => {
|
|
9391
|
+
const sessionTask = Promise.resolve().then(() => {
|
|
9343
9392
|
tick();
|
|
9344
|
-
return
|
|
9393
|
+
return [];
|
|
9345
9394
|
});
|
|
9346
9395
|
const blockTask = runCcusage(
|
|
9347
9396
|
cmd,
|
|
@@ -9662,8 +9711,12 @@ function writeLocalDashboard(dir, html) {
|
|
|
9662
9711
|
function esc(s) {
|
|
9663
9712
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
9664
9713
|
}
|
|
9714
|
+
function localDay(d) {
|
|
9715
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
9716
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
9717
|
+
}
|
|
9665
9718
|
function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date(), connect) {
|
|
9666
|
-
const today = generatedAt
|
|
9719
|
+
const today = localDay(generatedAt);
|
|
9667
9720
|
const totals = {
|
|
9668
9721
|
tokens: 0,
|
|
9669
9722
|
cost: 0,
|
|
@@ -9699,7 +9752,9 @@ function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date(),
|
|
|
9699
9752
|
const avgPerDay = activeDays > 0 ? totals.tokens / activeDays : 0;
|
|
9700
9753
|
const days = [];
|
|
9701
9754
|
for (let i = 59; i >= 0; i--) {
|
|
9702
|
-
const
|
|
9755
|
+
const day = new Date(generatedAt);
|
|
9756
|
+
day.setDate(day.getDate() - i);
|
|
9757
|
+
const d = localDay(day);
|
|
9703
9758
|
days.push({ date: d, tokens: byDate.get(d) ?? 0 });
|
|
9704
9759
|
}
|
|
9705
9760
|
const maxDay = Math.max(1, ...days.map((d) => d.tokens));
|
|
@@ -9807,7 +9862,7 @@ function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date(),
|
|
|
9807
9862
|
<div class="flame">\u{1F525}</div>
|
|
9808
9863
|
<div>
|
|
9809
9864
|
<h1>your local burn</h1>
|
|
9810
|
-
<div class="sub">generated ${esc(generatedAt.
|
|
9865
|
+
<div class="sub">generated ${esc(`${localDay(generatedAt)} ${String(generatedAt.getHours()).padStart(2, "0")}:${String(generatedAt.getMinutes()).padStart(2, "0")}`)} \xB7 nothing left your machine</div>
|
|
9811
9866
|
</div>
|
|
9812
9867
|
</header>
|
|
9813
9868
|
${connectCta}
|
|
@@ -9960,11 +10015,11 @@ function openBrowser(url) {
|
|
|
9960
10015
|
async function confirm(question, defaultYes = true) {
|
|
9961
10016
|
if (!process.stdin.isTTY) return false;
|
|
9962
10017
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9963
|
-
const
|
|
9964
|
-
const answer = (await rl.question(`${question} ${pc2.dim(hint)} `)).trim();
|
|
10018
|
+
const answer = (await rl.question(`${question} ${pc2.dim(defaultYes ? "[Y/n]" : "[y/N]")} `)).trim();
|
|
9965
10019
|
rl.close();
|
|
9966
|
-
return answer
|
|
10020
|
+
return confirmAnswer(answer, defaultYes);
|
|
9967
10021
|
}
|
|
10022
|
+
var confirmDefaultNo = (question) => confirm(question, false);
|
|
9968
10023
|
function showLocalDashboard(payload) {
|
|
9969
10024
|
const dir = defaultConfigDir();
|
|
9970
10025
|
const file = writeLocalDashboard(
|
|
@@ -10056,7 +10111,7 @@ async function run(flags) {
|
|
|
10056
10111
|
showLocalDashboard(payload);
|
|
10057
10112
|
if (!flags.quiet && process.stdin.isTTY) {
|
|
10058
10113
|
await publishLocal(payload, {
|
|
10059
|
-
confirm,
|
|
10114
|
+
confirm: confirmDefaultNo,
|
|
10060
10115
|
// Stored token, then a silent refresh, then device sign-in — the same
|
|
10061
10116
|
// order as every other signed-in command.
|
|
10062
10117
|
signIn: () => requireSignedIn("--local"),
|
|
@@ -10410,8 +10465,23 @@ async function runVerify() {
|
|
|
10410
10465
|
return;
|
|
10411
10466
|
}
|
|
10412
10467
|
}
|
|
10413
|
-
console.log(pc2.dim(" Reading your local Claude Code logs\u2026"));
|
|
10414
|
-
const { requests, found, timedOut } = await collectClaudeRequests(
|
|
10468
|
+
console.log(pc2.dim(" Reading your local Claude Code logs (large histories can take a few minutes)\u2026"));
|
|
10469
|
+
const { requests, found, timedOut } = await collectClaudeRequests(process.env, {
|
|
10470
|
+
budgetMs: VERIFY_READ_BUDGET_MS
|
|
10471
|
+
});
|
|
10472
|
+
if (timedOut === true && requests.length === 0) {
|
|
10473
|
+
console.log(
|
|
10474
|
+
pc2.yellow(
|
|
10475
|
+
" Your Claude Code logs are too large to finish reading in time, so nothing was sent."
|
|
10476
|
+
)
|
|
10477
|
+
);
|
|
10478
|
+
console.log(
|
|
10479
|
+
pc2.dim(
|
|
10480
|
+
" Try again when the machine is idle, or file a written appeal at whoburnedmore.com/appeal."
|
|
10481
|
+
)
|
|
10482
|
+
);
|
|
10483
|
+
return;
|
|
10484
|
+
}
|
|
10415
10485
|
if (!found || requests.length === 0) {
|
|
10416
10486
|
console.log(
|
|
10417
10487
|
pc2.yellow(" No local Claude Code logs found on this machine to verify.")
|
|
@@ -10545,7 +10615,7 @@ async function runRemove() {
|
|
|
10545
10615
|
console.log(
|
|
10546
10616
|
pc2.dim(" Your local logs are untouched, and the account itself survives (delete it from your dashboard settings on the web).")
|
|
10547
10617
|
);
|
|
10548
|
-
if (!await
|
|
10618
|
+
if (!await confirmDefaultNo(" Delete your usage data?")) {
|
|
10549
10619
|
console.log(pc2.dim(" Cancelled \u2014 nothing was deleted."));
|
|
10550
10620
|
return;
|
|
10551
10621
|
}
|
|
@@ -10572,6 +10642,7 @@ async function runRemove() {
|
|
|
10572
10642
|
);
|
|
10573
10643
|
console.log(pc2.dim(" Run `npx whoburnedmore` anytime to start fresh."));
|
|
10574
10644
|
}
|
|
10645
|
+
var VERIFY_READ_BUDGET_MS = 18e4;
|
|
10575
10646
|
async function main() {
|
|
10576
10647
|
const major = Number(process.versions.node.split(".")[0]);
|
|
10577
10648
|
if (major < 20) {
|
|
@@ -10581,6 +10652,13 @@ async function main() {
|
|
|
10581
10652
|
}
|
|
10582
10653
|
const args = process.argv.slice(2);
|
|
10583
10654
|
const baseCommand = resolveCommand(args);
|
|
10655
|
+
const problem = argumentProblem(args, baseCommand);
|
|
10656
|
+
if (problem) {
|
|
10657
|
+
console.error(` ${problem}`);
|
|
10658
|
+
console.error(" Nothing was collected or sent. Run `npx whoburnedmore --help` for the options.");
|
|
10659
|
+
process.exitCode = 2;
|
|
10660
|
+
return;
|
|
10661
|
+
}
|
|
10584
10662
|
const command = args.includes("--watch") && (baseCommand === "run" || baseCommand === "sync") ? "daemon" : baseCommand;
|
|
10585
10663
|
const flags = {
|
|
10586
10664
|
dryRun: args.includes("--dry-run"),
|
package/package.json
CHANGED