whoburnedmore 0.5.0 → 0.6.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 +109 -140
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29,6 +29,9 @@ function parseBoard(args) {
|
|
|
29
29
|
function apiBase() {
|
|
30
30
|
return process.env.WHOBURNEDMORE_API ?? "https://api.whoburnedmore.com";
|
|
31
31
|
}
|
|
32
|
+
function webBase() {
|
|
33
|
+
return process.env.WHOBURNEDMORE_WEB ?? "https://whoburnedmore.com";
|
|
34
|
+
}
|
|
32
35
|
async function readJson(res) {
|
|
33
36
|
const text = await res.text();
|
|
34
37
|
if (!text) return {};
|
|
@@ -40,15 +43,12 @@ async function readJson(res) {
|
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
|
-
async function post(path, body
|
|
46
|
+
async function post(path, body) {
|
|
44
47
|
let res;
|
|
45
48
|
try {
|
|
46
49
|
res = await fetch(`${apiBase()}${path}`, {
|
|
47
50
|
method: "POST",
|
|
48
|
-
headers: {
|
|
49
|
-
"Content-Type": "application/json",
|
|
50
|
-
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
51
|
-
},
|
|
51
|
+
headers: { "Content-Type": "application/json" },
|
|
52
52
|
body: JSON.stringify(body)
|
|
53
53
|
});
|
|
54
54
|
} catch {
|
|
@@ -58,34 +58,6 @@ async function post(path, body, token) {
|
|
|
58
58
|
}
|
|
59
59
|
return { status: res.status, body: await readJson(res) };
|
|
60
60
|
}
|
|
61
|
-
async function deviceStart() {
|
|
62
|
-
const { status, body } = await post("/v1/auth/device", {});
|
|
63
|
-
if (status !== 200) throw new Error(`device auth failed (HTTP ${status})`);
|
|
64
|
-
return body;
|
|
65
|
-
}
|
|
66
|
-
async function devicePoll(deviceCode) {
|
|
67
|
-
const { body } = await post("/v1/auth/device/token", {
|
|
68
|
-
deviceCode
|
|
69
|
-
});
|
|
70
|
-
return body;
|
|
71
|
-
}
|
|
72
|
-
async function submitUsage(token, payload) {
|
|
73
|
-
const { status, body } = await post(
|
|
74
|
-
"/v1/submit",
|
|
75
|
-
payload,
|
|
76
|
-
token
|
|
77
|
-
);
|
|
78
|
-
if (status === 401) {
|
|
79
|
-
throw new Error("session expired \u2014 run `npx whoburnedmore login` again");
|
|
80
|
-
}
|
|
81
|
-
if (status !== 200) {
|
|
82
|
-
const err = body;
|
|
83
|
-
const details = err.details?.length ? `
|
|
84
|
-
- ${err.details.join("\n - ")}` : "";
|
|
85
|
-
throw new Error(`${err.error ?? `submit failed (HTTP ${status})`}${details}`);
|
|
86
|
-
}
|
|
87
|
-
return body;
|
|
88
|
-
}
|
|
89
61
|
async function anonSubmit(anonKey, payload) {
|
|
90
62
|
const { status, body } = await post("/v1/anon/submit", { ...payload, anonKey });
|
|
91
63
|
if (status !== 200) {
|
|
@@ -122,14 +94,14 @@ async function anonRemove(anonKey) {
|
|
|
122
94
|
|
|
123
95
|
// src/autosync.ts
|
|
124
96
|
import { spawnSync } from "node:child_process";
|
|
125
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, rmSync
|
|
97
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
126
98
|
import { homedir as homedir2, platform } from "node:os";
|
|
127
99
|
import { join as join2 } from "node:path";
|
|
128
100
|
import { fileURLToPath } from "node:url";
|
|
129
101
|
|
|
130
102
|
// src/config.ts
|
|
131
103
|
import { randomBytes } from "node:crypto";
|
|
132
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
104
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
133
105
|
import { homedir } from "node:os";
|
|
134
106
|
import { join } from "node:path";
|
|
135
107
|
function defaultConfigDir() {
|
|
@@ -141,8 +113,6 @@ function loadConfig(dir = defaultConfigDir()) {
|
|
|
141
113
|
try {
|
|
142
114
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
143
115
|
const config = {};
|
|
144
|
-
if (typeof parsed.token === "string") config.token = parsed.token;
|
|
145
|
-
if (typeof parsed.handle === "string") config.handle = parsed.handle;
|
|
146
116
|
if (typeof parsed.anonKey === "string") config.anonKey = parsed.anonKey;
|
|
147
117
|
return Object.keys(config).length > 0 ? config : null;
|
|
148
118
|
} catch {
|
|
@@ -154,9 +124,6 @@ function saveConfig(dir = defaultConfigDir(), config = {}) {
|
|
|
154
124
|
const file = join(dir, "config.json");
|
|
155
125
|
writeFileSync(file, JSON.stringify(config, null, 2), { mode: 384 });
|
|
156
126
|
}
|
|
157
|
-
function clearConfig(dir = defaultConfigDir()) {
|
|
158
|
-
rmSync(join(dir, "config.json"), { force: true });
|
|
159
|
-
}
|
|
160
127
|
function ensureAnonKey(dir = defaultConfigDir()) {
|
|
161
128
|
const config = loadConfig(dir) ?? {};
|
|
162
129
|
if (config.anonKey) return config.anonKey;
|
|
@@ -250,7 +217,7 @@ function uninstallAutoSync() {
|
|
|
250
217
|
const plistPath = launchAgentPath();
|
|
251
218
|
if (existsSync2(plistPath)) {
|
|
252
219
|
spawnSync("launchctl", ["unload", plistPath], { stdio: "ignore" });
|
|
253
|
-
|
|
220
|
+
rmSync(plistPath, { force: true });
|
|
254
221
|
}
|
|
255
222
|
return "launchd agent removed";
|
|
256
223
|
}
|
|
@@ -349,9 +316,13 @@ function recordTokens(usage) {
|
|
|
349
316
|
function processRecord(rec, acc, ctx) {
|
|
350
317
|
if (!rec || typeof rec !== "object") return;
|
|
351
318
|
const r = rec;
|
|
319
|
+
const recTokens = recordTokens(r.message?.usage);
|
|
352
320
|
if (typeof r.attributionSkill === "string" && r.attributionSkill) {
|
|
353
321
|
const s = r.attributionSkill.slice(0, 128);
|
|
354
|
-
|
|
322
|
+
const sk = acc.skills.get(s) ?? { count: 0, tokens: 0 };
|
|
323
|
+
sk.count += 1;
|
|
324
|
+
sk.tokens += recTokens;
|
|
325
|
+
acc.skills.set(s, sk);
|
|
355
326
|
}
|
|
356
327
|
if (r.type === "ai-title" && typeof r.aiTitle === "string" && r.aiTitle && typeof r.sessionId === "string" && r.sessionId) {
|
|
357
328
|
acc.titles.set(r.sessionId, r.aiTitle.slice(0, 200));
|
|
@@ -361,18 +332,24 @@ function processRecord(rec, acc, ctx) {
|
|
|
361
332
|
const isAssistant = r.type === "assistant" || r.message?.role === "assistant";
|
|
362
333
|
const isUser = r.type === "user" || r.message?.role === "user";
|
|
363
334
|
if (isAssistant) {
|
|
335
|
+
const toolUses = [];
|
|
364
336
|
for (const block of content) {
|
|
365
337
|
if (block && typeof block === "object" && block.type === "tool_use" && typeof block.name === "string") {
|
|
366
338
|
const name = block.name.slice(0, 128);
|
|
367
339
|
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
340
|
const id = block.id;
|
|
372
|
-
|
|
341
|
+
toolUses.push({ name, id: typeof id === "string" ? id : void 0 });
|
|
373
342
|
}
|
|
374
343
|
}
|
|
375
|
-
const
|
|
344
|
+
const perToolTokens = toolUses.length > 0 ? Math.floor(recTokens / toolUses.length) : 0;
|
|
345
|
+
for (const tu of toolUses) {
|
|
346
|
+
const t = acc.tools.get(tu.name) ?? { count: 0, errors: 0, tokens: 0 };
|
|
347
|
+
t.count += 1;
|
|
348
|
+
t.tokens += perToolTokens;
|
|
349
|
+
acc.tools.set(tu.name, t);
|
|
350
|
+
if (tu.id) ctx.toolNames.set(tu.id, tu.name);
|
|
351
|
+
}
|
|
352
|
+
const tokens = recTokens;
|
|
376
353
|
acc.agent.messageCount += 1;
|
|
377
354
|
acc.agent.totalTokens += tokens;
|
|
378
355
|
const sidechain = r.isSidechain === true;
|
|
@@ -411,7 +388,7 @@ function processRecord(rec, acc, ctx) {
|
|
|
411
388
|
const id = block.tool_use_id;
|
|
412
389
|
const name = typeof id === "string" ? ctx.toolNames.get(id) : void 0;
|
|
413
390
|
if (name) {
|
|
414
|
-
const t = acc.tools.get(name) ?? { count: 0, errors: 0 };
|
|
391
|
+
const t = acc.tools.get(name) ?? { count: 0, errors: 0, tokens: 0 };
|
|
415
392
|
t.errors += 1;
|
|
416
393
|
acc.tools.set(name, t);
|
|
417
394
|
}
|
|
@@ -420,10 +397,15 @@ function processRecord(rec, acc, ctx) {
|
|
|
420
397
|
}
|
|
421
398
|
}
|
|
422
399
|
function toSkillStats(map) {
|
|
423
|
-
return [...map.entries()].map(([name,
|
|
400
|
+
return [...map.entries()].map(([name, v]) => ({ name, count: v.count, tokens: v.tokens })).filter((s) => s.count > 0).sort((a, b) => b.tokens - a.tokens || b.count - a.count).slice(0, MAX_STATS).map((s) => s.tokens > 0 ? s : { name: s.name, count: s.count });
|
|
424
401
|
}
|
|
425
402
|
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) =>
|
|
403
|
+
return [...map.entries()].map(([name, v]) => ({ name, count: v.count, errors: v.errors, tokens: v.tokens })).filter((s) => s.count > 0).sort((a, b) => b.tokens - a.tokens || b.count - a.count).slice(0, MAX_STATS).map((s) => {
|
|
404
|
+
const base = { name: s.name, count: s.count };
|
|
405
|
+
if (s.errors > 0) base.errors = s.errors;
|
|
406
|
+
if (s.tokens > 0) base.tokens = s.tokens;
|
|
407
|
+
return base;
|
|
408
|
+
});
|
|
427
409
|
}
|
|
428
410
|
function toProjectStats(map) {
|
|
429
411
|
return [...map.entries()].map(([name, v]) => ({
|
|
@@ -5096,7 +5078,9 @@ var ToolStat = external_exports.object({
|
|
|
5096
5078
|
name: external_exports.string().min(1).max(128),
|
|
5097
5079
|
count: external_exports.number().int().nonnegative(),
|
|
5098
5080
|
/** How many of those calls returned an error/interrupt (tool reliability). Optional. */
|
|
5099
|
-
errors: external_exports.number().int().nonnegative().optional()
|
|
5081
|
+
errors: external_exports.number().int().nonnegative().optional(),
|
|
5082
|
+
/** Tokens burned on turns that used this tool (turn tokens split across its tool calls). Optional. */
|
|
5083
|
+
tokens: external_exports.number().int().nonnegative().optional()
|
|
5100
5084
|
});
|
|
5101
5085
|
var ProjectStat = external_exports.object({
|
|
5102
5086
|
name: external_exports.string().min(1).max(128),
|
|
@@ -5115,7 +5099,9 @@ var AgentStat = external_exports.object({
|
|
|
5115
5099
|
});
|
|
5116
5100
|
var SkillStat = external_exports.object({
|
|
5117
5101
|
name: external_exports.string().min(1).max(128),
|
|
5118
|
-
count: external_exports.number().int().nonnegative()
|
|
5102
|
+
count: external_exports.number().int().nonnegative(),
|
|
5103
|
+
/** Tokens burned in records produced while this skill was active. Optional. */
|
|
5104
|
+
tokens: external_exports.number().int().nonnegative().optional()
|
|
5119
5105
|
});
|
|
5120
5106
|
var SubmitPayload = external_exports.object({
|
|
5121
5107
|
cliVersion: external_exports.string().min(1).max(32),
|
|
@@ -5196,7 +5182,7 @@ function printSummary(entries) {
|
|
|
5196
5182
|
function esc(s) {
|
|
5197
5183
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5198
5184
|
}
|
|
5199
|
-
function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date()) {
|
|
5185
|
+
function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date(), connect) {
|
|
5200
5186
|
const today = generatedAt.toISOString().slice(0, 10);
|
|
5201
5187
|
const totals = {
|
|
5202
5188
|
tokens: 0,
|
|
@@ -5248,6 +5234,18 @@ function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date())
|
|
|
5248
5234
|
([model, a]) => `<tr><td class="mono">${esc(model)}</td><td class="num">${esc(formatTokens(a.tokens))}</td><td class="num">${esc(formatUSD(a.cost))}</td></tr>`
|
|
5249
5235
|
).join("");
|
|
5250
5236
|
const stat = (label, value, accent = false) => `<div class="card"><div class="label">${label}</div><div class="value${accent ? " accent" : ""}">${esc(value)}</div></div>`;
|
|
5237
|
+
const connectCta = connect ? `
|
|
5238
|
+
<form class="connect" method="POST" action="${esc(connect.webBaseUrl)}/connect">
|
|
5239
|
+
<input type="hidden" name="payload" value="${Buffer.from(JSON.stringify(connect.payload)).toString("base64")}">
|
|
5240
|
+
<div class="connect-row">
|
|
5241
|
+
<div>
|
|
5242
|
+
<div class="connect-title">Connect your account</div>
|
|
5243
|
+
<div class="connect-sub">Save this dashboard to your account and claim your spot on the public leaderboard. The local numbers above become your starting point \u2014 nothing has left your machine yet.</div>
|
|
5244
|
+
</div>
|
|
5245
|
+
<button type="submit">Connect your account \u2192</button>
|
|
5246
|
+
</div>
|
|
5247
|
+
<div class="connect-note">After connecting, run <code>npx whoburnedmore</code> (no flag) once so it keeps syncing automatically in the background.</div>
|
|
5248
|
+
</form>` : "";
|
|
5251
5249
|
return `<!doctype html>
|
|
5252
5250
|
<html lang="en">
|
|
5253
5251
|
<head>
|
|
@@ -5285,13 +5283,22 @@ function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date())
|
|
|
5285
5283
|
td.mono { font-family: ui-monospace, monospace; font-size: 12px; }
|
|
5286
5284
|
.foot { color: #78716c; font-size: 12px; margin-top: 32px; }
|
|
5287
5285
|
.foot code { color: #d6d3d1; }
|
|
5286
|
+
.connect { display: block; margin: 24px 0 4px; background: linear-gradient(135deg, rgba(234,88,12,.16), rgba(249,115,22,.06)); border: 1px solid rgba(234,88,12,.5); border-radius: 14px; padding: 18px 20px; }
|
|
5287
|
+
.connect-row { display: flex; flex-direction: column; gap: 14px; align-items: flex-start; }
|
|
5288
|
+
@media (min-width: 640px) { .connect-row { flex-direction: row; align-items: center; justify-content: space-between; } }
|
|
5289
|
+
.connect-title { font-size: 16px; font-weight: 700; }
|
|
5290
|
+
.connect-sub { color: #d6d3d1; font-size: 13px; margin-top: 4px; max-width: 60ch; }
|
|
5291
|
+
.connect button { flex-shrink: 0; cursor: pointer; border: 0; border-radius: 10px; background: #ea580c; color: #fff; font-size: 14px; font-weight: 600; padding: 11px 18px; font-family: inherit; transition: background .15s; }
|
|
5292
|
+
.connect button:hover { background: #f97316; }
|
|
5293
|
+
.connect-note { color: #a8a29e; font-size: 12px; margin-top: 14px; padding-top: 12px; border-top: 1px solid rgba(234,88,12,.25); }
|
|
5294
|
+
.connect-note code { color: #fed7aa; background: rgba(0,0,0,.25); padding: 1px 6px; border-radius: 5px; }
|
|
5288
5295
|
</style>
|
|
5289
5296
|
</head>
|
|
5290
5297
|
<body>
|
|
5291
5298
|
<div class="wrap">
|
|
5292
5299
|
<h1>who burned more<span class="q">?</span></h1>
|
|
5293
5300
|
<div class="sub">your local burn report \xB7 generated ${esc(generatedAt.toISOString().slice(0, 16).replace("T", " "))} \xB7 nothing left your machine</div>
|
|
5294
|
-
|
|
5301
|
+
${connectCta}
|
|
5295
5302
|
<div class="grid">
|
|
5296
5303
|
${stat("total tokens", formatTokens(totals.tokens), true)}
|
|
5297
5304
|
${stat("est. cost", formatUSD(totals.cost))}
|
|
@@ -5335,7 +5342,7 @@ function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date())
|
|
|
5335
5342
|
|
|
5336
5343
|
<div class="foot">
|
|
5337
5344
|
Re-run <code>npx whoburnedmore --local</code> to refresh this page.<br>
|
|
5338
|
-
Run <code>npx whoburnedmore</code> (no flag) to get a shareable dashboard at whoburnedmore.com \u2014 no sign-in.
|
|
5345
|
+
${connect ? "Use \u201CConnect your account\u201D above to save it to whoburnedmore.com and join the leaderboard." : "Run <code>npx whoburnedmore</code> (no flag) to get a shareable dashboard at whoburnedmore.com \u2014 no sign-in."}
|
|
5339
5346
|
</div>
|
|
5340
5347
|
</div>
|
|
5341
5348
|
</body>
|
|
@@ -5386,27 +5393,6 @@ function openBrowser(url) {
|
|
|
5386
5393
|
const [cmd, args] = os === "darwin" ? ["open", [url]] : os === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
5387
5394
|
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
5388
5395
|
}
|
|
5389
|
-
async function login() {
|
|
5390
|
-
const device = await deviceStart();
|
|
5391
|
-
console.log();
|
|
5392
|
-
console.log(` Opening ${pc2.cyan(device.verifyUrl)}`);
|
|
5393
|
-
console.log(` Your code: ${pc2.bold(pc2.yellow(device.userCode))}`);
|
|
5394
|
-
console.log(pc2.dim(" Sign in with Google or GitHub and approve this device."));
|
|
5395
|
-
openBrowser(device.verifyUrl);
|
|
5396
|
-
const deadline = Date.now() + device.expiresInSeconds * 1e3;
|
|
5397
|
-
while (Date.now() < deadline) {
|
|
5398
|
-
await new Promise((r) => setTimeout(r, device.pollIntervalSeconds * 1e3));
|
|
5399
|
-
const poll = await devicePoll(device.deviceCode);
|
|
5400
|
-
if (poll.status === "ok") {
|
|
5401
|
-
const config = { token: poll.token, handle: poll.handle };
|
|
5402
|
-
saveConfig(void 0, config);
|
|
5403
|
-
console.log(` Signed in as ${pc2.bold(poll.handle)} \u2713`);
|
|
5404
|
-
return config;
|
|
5405
|
-
}
|
|
5406
|
-
if (poll.status === "expired") break;
|
|
5407
|
-
}
|
|
5408
|
-
throw new Error("login timed out \u2014 run `npx whoburnedmore` to try again");
|
|
5409
|
-
}
|
|
5410
5396
|
async function confirm(question) {
|
|
5411
5397
|
if (!process.stdin.isTTY) return false;
|
|
5412
5398
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -5414,11 +5400,17 @@ async function confirm(question) {
|
|
|
5414
5400
|
rl.close();
|
|
5415
5401
|
return answer === "" || /^y(es)?$/i.test(answer);
|
|
5416
5402
|
}
|
|
5417
|
-
function showLocalDashboard(
|
|
5403
|
+
function showLocalDashboard(payload) {
|
|
5418
5404
|
const dir = defaultConfigDir();
|
|
5419
5405
|
mkdirSync3(dir, { recursive: true });
|
|
5420
5406
|
const file = join7(dir, "dashboard.html");
|
|
5421
|
-
writeFileSync3(
|
|
5407
|
+
writeFileSync3(
|
|
5408
|
+
file,
|
|
5409
|
+
renderDashboardHtml(payload.entries, /* @__PURE__ */ new Date(), {
|
|
5410
|
+
payload,
|
|
5411
|
+
webBaseUrl: webBase()
|
|
5412
|
+
})
|
|
5413
|
+
);
|
|
5422
5414
|
console.log();
|
|
5423
5415
|
console.log(` Local dashboard: ${pc2.cyan(`file://${file}`)}`);
|
|
5424
5416
|
console.log(pc2.dim(" Re-run `npx whoburnedmore --local` to refresh it. Nothing left your machine."));
|
|
@@ -5458,7 +5450,7 @@ async function run(flags) {
|
|
|
5458
5450
|
}
|
|
5459
5451
|
if (!flags.quiet) printSummary(entries);
|
|
5460
5452
|
if (flags.local) {
|
|
5461
|
-
showLocalDashboard(
|
|
5453
|
+
showLocalDashboard(payload);
|
|
5462
5454
|
if (!flags.quiet && process.stdin.isTTY) {
|
|
5463
5455
|
await publishLocal(payload, {
|
|
5464
5456
|
confirm,
|
|
@@ -5474,61 +5466,46 @@ async function run(flags) {
|
|
|
5474
5466
|
console.log(pc2.dim(" --no-submit: skipped the dashboard."));
|
|
5475
5467
|
return;
|
|
5476
5468
|
}
|
|
5477
|
-
const
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5469
|
+
const anonKey = ensureAnonKey();
|
|
5470
|
+
const result = await anonSubmit(anonKey, payload);
|
|
5471
|
+
const target = result.boardUrl ?? claimUrl(result.dashboardUrl, anonKey);
|
|
5472
|
+
if (!flags.quiet) {
|
|
5473
|
+
console.log(pc2.dim(" Opening your dashboard in your browser\u2026"));
|
|
5474
|
+
openBrowser(target);
|
|
5475
|
+
}
|
|
5476
|
+
console.log(
|
|
5477
|
+
` Submitted ${pc2.bold(String(result.upserted))} day-entries from ${toolsFound.join(", ")}.`
|
|
5478
|
+
);
|
|
5479
|
+
if (result.boardUrl) {
|
|
5484
5480
|
console.log(
|
|
5485
|
-
`
|
|
5481
|
+
` You burned ${pc2.bold(formatTokens(result.totalTokens))} tokens \u2014 \u{1F91D} you're on the friends board:`
|
|
5486
5482
|
);
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
` You are ${pc2.bold(pc2.yellow(`#${result.rank}`))} with ${pc2.bold(formatTokens(result.totalTokens))} tokens burned.`
|
|
5490
|
-
);
|
|
5491
|
-
}
|
|
5492
|
-
console.log(` ${pc2.cyan(result.profileUrl)}`);
|
|
5493
|
-
if (result.boardUrl) {
|
|
5494
|
-
console.log(` \u{1F91D} You're on the friends board: ${pc2.cyan(result.boardUrl)}`);
|
|
5495
|
-
}
|
|
5483
|
+
console.log(` ${pc2.cyan(result.boardUrl)}`);
|
|
5484
|
+
console.log(pc2.dim(` Your dashboard: ${result.dashboardUrl}`));
|
|
5496
5485
|
} else {
|
|
5497
|
-
const anonKey = ensureAnonKey();
|
|
5498
|
-
const result = await anonSubmit(anonKey, payload);
|
|
5499
|
-
const target = result.boardUrl ?? claimUrl(result.dashboardUrl, anonKey);
|
|
5500
|
-
if (!flags.quiet) {
|
|
5501
|
-
console.log(pc2.dim(" Opening your dashboard in your browser\u2026"));
|
|
5502
|
-
openBrowser(target);
|
|
5503
|
-
}
|
|
5504
5486
|
console.log(
|
|
5505
|
-
`
|
|
5487
|
+
` You burned ${pc2.bold(formatTokens(result.totalTokens))} tokens \u2014 you're on the public leaderboard:`
|
|
5506
5488
|
);
|
|
5507
|
-
|
|
5489
|
+
console.log(` ${pc2.cyan(result.dashboardUrl)}`);
|
|
5490
|
+
if (!flags.quiet) {
|
|
5508
5491
|
console.log(
|
|
5509
|
-
|
|
5492
|
+
pc2.dim(" Claim it (name + X) on the web to own your rank, or make it private / remove it.")
|
|
5510
5493
|
);
|
|
5511
|
-
console.log(` ${pc2.cyan(result.boardUrl)}`);
|
|
5512
|
-
console.log(pc2.dim(` Your dashboard: ${result.dashboardUrl}`));
|
|
5513
|
-
} else {
|
|
5514
5494
|
console.log(
|
|
5515
|
-
|
|
5495
|
+
pc2.dim(" Manage anytime: `npx whoburnedmore private` \xB7 `npx whoburnedmore public` \xB7 `npx whoburnedmore remove`.")
|
|
5516
5496
|
);
|
|
5517
|
-
console.log(` ${pc2.cyan(result.dashboardUrl)}`);
|
|
5518
|
-
if (!flags.quiet) {
|
|
5519
|
-
console.log(
|
|
5520
|
-
pc2.dim(" Claim it (name + X) to own your rank, or make it private / remove it.")
|
|
5521
|
-
);
|
|
5522
|
-
console.log(
|
|
5523
|
-
pc2.dim(" Manage anytime: `npx whoburnedmore private` \xB7 `npx whoburnedmore public` \xB7 `npx whoburnedmore remove`.")
|
|
5524
|
-
);
|
|
5525
|
-
}
|
|
5526
5497
|
}
|
|
5527
5498
|
}
|
|
5528
|
-
|
|
5499
|
+
if (!flags.quiet && !autoSyncInstalled()) {
|
|
5500
|
+
try {
|
|
5501
|
+
installAutoSync();
|
|
5502
|
+
} catch {
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5529
5505
|
if (!flags.quiet) {
|
|
5506
|
+
console.log();
|
|
5530
5507
|
console.log(
|
|
5531
|
-
autoSyncInstalled() ? pc2.dim(" Background sync is on \u2014 your page
|
|
5508
|
+
autoSyncInstalled() ? pc2.dim(" Background sync is on \u2014 your page updates automatically every 3h (`npx whoburnedmore uninstall-sync` to stop).") : pc2.dim(" Re-run anytime to update your page.")
|
|
5532
5509
|
);
|
|
5533
5510
|
}
|
|
5534
5511
|
}
|
|
@@ -5557,14 +5534,6 @@ async function main() {
|
|
|
5557
5534
|
await run({ ...flags, noSubmit: false, dryRun: false, local: false });
|
|
5558
5535
|
break;
|
|
5559
5536
|
}
|
|
5560
|
-
case "login":
|
|
5561
|
-
await login();
|
|
5562
|
-
break;
|
|
5563
|
-
case "logout":
|
|
5564
|
-
clearConfig();
|
|
5565
|
-
console.log(" Logged out. Your leaderboard data is untouched.");
|
|
5566
|
-
console.log(pc2.dim(" Delete your data anytime from your profile page."));
|
|
5567
|
-
break;
|
|
5568
5537
|
case "private":
|
|
5569
5538
|
case "public": {
|
|
5570
5539
|
const cfg = loadConfig();
|
|
@@ -5619,19 +5588,19 @@ function printHelp() {
|
|
|
5619
5588
|
npx whoburnedmore --local build the dashboard on your machine and open it (offline)
|
|
5620
5589
|
npx whoburnedmore --dry-run print exactly what would be sent, send nothing
|
|
5621
5590
|
npx whoburnedmore --no-submit print local stats only, send nothing
|
|
5622
|
-
npx whoburnedmore private hide your
|
|
5591
|
+
npx whoburnedmore private hide your dashboard from the leaderboard
|
|
5623
5592
|
npx whoburnedmore public put it back on the leaderboard
|
|
5624
|
-
npx whoburnedmore remove delete your
|
|
5625
|
-
npx whoburnedmore
|
|
5626
|
-
npx whoburnedmore
|
|
5627
|
-
npx whoburnedmore install-sync keep your dashboard live (background sync, 3h)
|
|
5628
|
-
npx whoburnedmore uninstall-sync remove background sync
|
|
5593
|
+
npx whoburnedmore remove delete your dashboard and its data
|
|
5594
|
+
npx whoburnedmore uninstall-sync turn off the background sync
|
|
5595
|
+
npx whoburnedmore install-sync turn it back on after uninstalling
|
|
5629
5596
|
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5597
|
+
Background sync is on by default: after your first run, your page refreshes
|
|
5598
|
+
automatically every 3h (\`uninstall-sync\` to stop). Your dashboard is public on
|
|
5599
|
+
the leaderboard as an anonymous burner \u2014 sign in on whoburnedmore.com to claim
|
|
5600
|
+
it (handle + X) and own your rank, or run \`private\`/\`remove\` to pull it. Only
|
|
5601
|
+
daily aggregate numbers (date, tool, model, token counts, est. cost) ever leave
|
|
5602
|
+
your machine \u2014 never prompts, code, or file names. With --local, nothing leaves
|
|
5603
|
+
your machine at all.
|
|
5635
5604
|
`);
|
|
5636
5605
|
}
|
|
5637
5606
|
main().catch((err) => {
|