whoburnedmore 0.1.0 → 0.2.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/README.md +106 -0
- package/dist/index.js +249 -30
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# whoburnedmore
|
|
2
|
+
|
|
3
|
+
**who burned more tokens — you or them?** One command reads your local AI coding-agent usage logs, shows your burn, and gives you a **shareable dashboard with no sign-in required**.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/whoburnedmore)
|
|
6
|
+
[](https://www.npmjs.com/package/whoburnedmore)
|
|
7
|
+
[](https://www.npmjs.com/package/whoburnedmore)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx whoburnedmore
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
That's the whole thing. No install, no account, no config.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What you'll see
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
whoburnedmore · https://whoburnedmore.com
|
|
22
|
+
|
|
23
|
+
🔥 your burn report
|
|
24
|
+
|
|
25
|
+
claude 2.26B tokens $2,640.07 19 days
|
|
26
|
+
codex 58.2M tokens $16.85 5 days
|
|
27
|
+
──────────────────────────────────────────────
|
|
28
|
+
total 2.31B tokens $2,656.91
|
|
29
|
+
today 92.8M tokens
|
|
30
|
+
|
|
31
|
+
You burned 2.31B tokens. Your shareable dashboard:
|
|
32
|
+
https://whoburnedmore.com/d/5twjwi-jtga4r
|
|
33
|
+
|
|
34
|
+
Re-run anytime to update it · `npx whoburnedmore login` to claim a public handle and compete.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Your dashboard is **anonymous and unlisted by default** — only people you send the link to can see it. Run the command again anytime to refresh it.
|
|
38
|
+
|
|
39
|
+
## Privacy first
|
|
40
|
+
|
|
41
|
+
Only **daily aggregate numbers** ever leave your machine — date, tool, model, token counts, and estimated cost. **Never your prompts, your code, or your file names.**
|
|
42
|
+
|
|
43
|
+
See exactly what would be sent, and send nothing:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx whoburnedmore --dry-run
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or stay completely offline — `--local` builds the same dashboard as a self-contained HTML file on your machine and opens it in your browser, sending nothing anywhere:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx whoburnedmore --local
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Commands
|
|
56
|
+
|
|
57
|
+
| Command | What it does |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `npx whoburnedmore` | Show your burn + get a shareable dashboard (no sign-in) |
|
|
60
|
+
| `npx whoburnedmore --local` | Build the dashboard locally and open it (fully offline) |
|
|
61
|
+
| `npx whoburnedmore --dry-run` | Print exactly what would be sent, send nothing |
|
|
62
|
+
| `npx whoburnedmore --no-submit` | Print local stats only, send nothing |
|
|
63
|
+
| `npx whoburnedmore login` | Sign in to claim a public handle + join the leaderboard |
|
|
64
|
+
| `npx whoburnedmore logout` | Forget the local token (your data is untouched) |
|
|
65
|
+
| `npx whoburnedmore install-sync` | Keep your dashboard live with a background sync (every 3h) |
|
|
66
|
+
| `npx whoburnedmore uninstall-sync` | Remove the background sync |
|
|
67
|
+
|
|
68
|
+
## Supported tools
|
|
69
|
+
|
|
70
|
+
Usage is read via [`ccusage`](https://www.npmjs.com/package/ccusage) from any of these agents you use locally:
|
|
71
|
+
|
|
72
|
+
`claude` · `codex` · `gemini` · `copilot` · `opencode` · `amp` · `droid` · `goose` · `kimi` · `qwen` · `kilo` · `openclaw` · `hermes` · `pi` · `codebuff`
|
|
73
|
+
|
|
74
|
+
Whatever you have logs for is picked up automatically — you don't pass any flags.
|
|
75
|
+
|
|
76
|
+
## Claim a public handle (optional)
|
|
77
|
+
|
|
78
|
+
Want on the public leaderboard? Sign in with **GitHub or Google**:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx whoburnedmore login
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
It uses a device flow — a code appears in your terminal, you approve it in the browser, and you're done. Your submissions then link to your handle at `whoburnedmore.com/u/<handle>`. You can delete your account and every row of data anytime from your profile.
|
|
85
|
+
|
|
86
|
+
## Keep it live
|
|
87
|
+
|
|
88
|
+
Want your dashboard to stay fresh without re-running by hand?
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npx whoburnedmore install-sync # background sync every 3h (launchd / cron / scheduled task)
|
|
92
|
+
npx whoburnedmore uninstall-sync # remove it
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Links
|
|
96
|
+
|
|
97
|
+
- 🏆 Leaderboard — **[whoburnedmore.com](https://whoburnedmore.com)**
|
|
98
|
+
- 📊 Your dashboard — printed in the terminal after your first run
|
|
99
|
+
|
|
100
|
+
## Requirements
|
|
101
|
+
|
|
102
|
+
Node.js >= 20.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT © Arham Wani
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,17 @@ import { join as join4 } from "node:path";
|
|
|
14
14
|
import { createInterface } from "node:readline/promises";
|
|
15
15
|
import pc2 from "picocolors";
|
|
16
16
|
|
|
17
|
+
// src/args.ts
|
|
18
|
+
function parseBoard(args) {
|
|
19
|
+
const eq = args.find((a) => a.startsWith("--board="));
|
|
20
|
+
if (eq) return eq.slice("--board=".length).trim() || void 0;
|
|
21
|
+
const i = args.indexOf("--board");
|
|
22
|
+
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
23
|
+
return args[i + 1].trim() || void 0;
|
|
24
|
+
}
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
// src/api.ts
|
|
18
29
|
function apiBase() {
|
|
19
30
|
return process.env.WHOBURNEDMORE_API ?? "https://api.whoburnedmore.com";
|
|
@@ -67,6 +78,29 @@ async function anonSubmit(anonKey, payload) {
|
|
|
67
78
|
}
|
|
68
79
|
return body;
|
|
69
80
|
}
|
|
81
|
+
function claimUrl(dashboardUrl, anonKey) {
|
|
82
|
+
return `${dashboardUrl}#k=${encodeURIComponent(anonKey)}`;
|
|
83
|
+
}
|
|
84
|
+
async function anonVisibility(anonKey, listed) {
|
|
85
|
+
const { status, body } = await post(
|
|
86
|
+
"/v1/anon/visibility",
|
|
87
|
+
{ anonKey, listed }
|
|
88
|
+
);
|
|
89
|
+
if (status !== 200) {
|
|
90
|
+
throw new Error(body.error ?? `failed (HTTP ${status})`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function anonRemove(anonKey) {
|
|
94
|
+
const res = await fetch(`${apiBase()}/v1/anon`, {
|
|
95
|
+
method: "DELETE",
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
body: JSON.stringify({ anonKey })
|
|
98
|
+
});
|
|
99
|
+
if (res.status !== 200) {
|
|
100
|
+
const b = await res.json().catch(() => ({}));
|
|
101
|
+
throw new Error(b.error ?? `failed (HTTP ${res.status})`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
70
104
|
|
|
71
105
|
// src/autosync.ts
|
|
72
106
|
import { spawnSync } from "node:child_process";
|
|
@@ -315,11 +349,56 @@ function mapCcusageDaily(tool, json) {
|
|
|
315
349
|
for (const c of candidates) {
|
|
316
350
|
const tokens = c.inputTokens + c.outputTokens + c.cacheCreationTokens + c.cacheReadTokens;
|
|
317
351
|
if (tokens === 0 && c.costUSD === 0) continue;
|
|
318
|
-
entries.push({ date, tool, ...c });
|
|
352
|
+
entries.push({ date, tool, ...c, origin: "cli", verified: false });
|
|
319
353
|
}
|
|
320
354
|
}
|
|
321
355
|
return entries;
|
|
322
356
|
}
|
|
357
|
+
function mapCcusageSessions(json) {
|
|
358
|
+
const sessions = json?.session;
|
|
359
|
+
if (!Array.isArray(sessions)) return [];
|
|
360
|
+
const out = [];
|
|
361
|
+
for (const raw of sessions) {
|
|
362
|
+
const s = raw;
|
|
363
|
+
const sessionId = typeof s.period === "string" ? s.period : typeof s.sessionId === "string" ? s.sessionId : null;
|
|
364
|
+
if (!sessionId) continue;
|
|
365
|
+
const inputTokens = norm(s.inputTokens);
|
|
366
|
+
const outputTokens = norm(s.outputTokens);
|
|
367
|
+
const cacheCreationTokens = norm(s.cacheCreationTokens);
|
|
368
|
+
const cacheReadTokens = norm(s.cacheReadTokens);
|
|
369
|
+
if (inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens === 0)
|
|
370
|
+
continue;
|
|
371
|
+
const models = Array.isArray(s.modelsUsed) ? s.modelsUsed.filter((m) => typeof m === "string") : [];
|
|
372
|
+
const meta = s.metadata ?? {};
|
|
373
|
+
out.push({
|
|
374
|
+
sessionId: sessionId.slice(0, 128),
|
|
375
|
+
tool: typeof s.agent === "string" ? s.agent : "unknown",
|
|
376
|
+
model: models[0] ?? "unknown",
|
|
377
|
+
inputTokens,
|
|
378
|
+
outputTokens,
|
|
379
|
+
cacheCreationTokens,
|
|
380
|
+
cacheReadTokens,
|
|
381
|
+
costUSD: normCost(s.totalCost ?? s.costUSD),
|
|
382
|
+
lastActivity: typeof meta.lastActivity === "string" ? meta.lastActivity : (/* @__PURE__ */ new Date()).toISOString()
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
387
|
+
function mapCcusageBlocks(json) {
|
|
388
|
+
const blocks = json?.blocks;
|
|
389
|
+
if (!Array.isArray(blocks)) return [];
|
|
390
|
+
const out = [];
|
|
391
|
+
for (const raw of blocks) {
|
|
392
|
+
const b = raw;
|
|
393
|
+
if (b.isGap === true) continue;
|
|
394
|
+
if (typeof b.startTime !== "string") continue;
|
|
395
|
+
const totalTokens = norm(b.totalTokens);
|
|
396
|
+
const costUSD = normCost(b.costUSD);
|
|
397
|
+
if (totalTokens === 0 && costUSD === 0) continue;
|
|
398
|
+
out.push({ startTime: b.startTime, totalTokens, costUSD });
|
|
399
|
+
}
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
323
402
|
function resolveCcusageBin() {
|
|
324
403
|
const require3 = createRequire(import.meta.url);
|
|
325
404
|
const pkgPath = require3.resolve("ccusage/package.json");
|
|
@@ -331,27 +410,43 @@ function resolveCcusageBin() {
|
|
|
331
410
|
}
|
|
332
411
|
return { cmd: binPath, prefixArgs: [] };
|
|
333
412
|
}
|
|
413
|
+
function runCcusage(cmd, args) {
|
|
414
|
+
const res = spawnSync2(cmd, args, {
|
|
415
|
+
encoding: "utf8",
|
|
416
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
417
|
+
timeout: 12e4
|
|
418
|
+
});
|
|
419
|
+
if (res.status !== 0 || !res.stdout) return null;
|
|
420
|
+
try {
|
|
421
|
+
return JSON.parse(res.stdout);
|
|
422
|
+
} catch {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
334
426
|
function collectAll() {
|
|
335
427
|
const { cmd, prefixArgs } = resolveCcusageBin();
|
|
336
428
|
const entries = [];
|
|
337
429
|
const toolsFound = [];
|
|
338
430
|
for (const source of SOURCES) {
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
} catch {
|
|
431
|
+
const json = runCcusage(cmd, [
|
|
432
|
+
...prefixArgs,
|
|
433
|
+
source,
|
|
434
|
+
"daily",
|
|
435
|
+
"--json",
|
|
436
|
+
"--offline"
|
|
437
|
+
]);
|
|
438
|
+
if (!json) continue;
|
|
439
|
+
const mapped = mapCcusageDaily(source, json);
|
|
440
|
+
if (mapped.length > 0) {
|
|
441
|
+
entries.push(...mapped);
|
|
442
|
+
toolsFound.push(source);
|
|
352
443
|
}
|
|
353
444
|
}
|
|
354
|
-
|
|
445
|
+
const sessionJson = runCcusage(cmd, [...prefixArgs, "session", "--json", "--offline"]);
|
|
446
|
+
const blockJson = runCcusage(cmd, [...prefixArgs, "blocks", "--json", "--offline"]);
|
|
447
|
+
const sessions = sessionJson ? mapCcusageSessions(sessionJson) : [];
|
|
448
|
+
const blocks = blockJson ? mapCcusageBlocks(blockJson) : [];
|
|
449
|
+
return { entries, sessions, blocks, toolsFound };
|
|
355
450
|
}
|
|
356
451
|
|
|
357
452
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
@@ -4398,6 +4493,22 @@ var NEVER = INVALID;
|
|
|
4398
4493
|
// ../shared/dist/index.js
|
|
4399
4494
|
var DateString = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD");
|
|
4400
4495
|
var tokenCount = external_exports.number().int().nonnegative();
|
|
4496
|
+
var ConnectorProvider = external_exports.enum([
|
|
4497
|
+
"anthropic-api",
|
|
4498
|
+
"openai-api",
|
|
4499
|
+
"openrouter",
|
|
4500
|
+
"google-api",
|
|
4501
|
+
"cursor"
|
|
4502
|
+
]);
|
|
4503
|
+
var UsageOrigin = external_exports.enum([
|
|
4504
|
+
"cli",
|
|
4505
|
+
"anthropic-api",
|
|
4506
|
+
"openai-api",
|
|
4507
|
+
"openrouter",
|
|
4508
|
+
"google-api",
|
|
4509
|
+
"cursor",
|
|
4510
|
+
"import"
|
|
4511
|
+
]);
|
|
4401
4512
|
var DailyUsageEntry = external_exports.object({
|
|
4402
4513
|
date: DateString,
|
|
4403
4514
|
/** Source agent id, e.g. "claude", "codex", "gemini", "copilot". */
|
|
@@ -4409,11 +4520,38 @@ var DailyUsageEntry = external_exports.object({
|
|
|
4409
4520
|
cacheCreationTokens: tokenCount,
|
|
4410
4521
|
cacheReadTokens: tokenCount,
|
|
4411
4522
|
/** Estimated cost in USD for this entry. */
|
|
4523
|
+
costUSD: external_exports.number().nonnegative(),
|
|
4524
|
+
/** Where this entry came from. Defaults to the local CLI for back-compat. */
|
|
4525
|
+
origin: UsageOrigin.default("cli"),
|
|
4526
|
+
/** True when the numbers come from a provider's authoritative usage API. */
|
|
4527
|
+
verified: external_exports.boolean().default(false)
|
|
4528
|
+
});
|
|
4529
|
+
var Timestamp = external_exports.string().min(1).max(40);
|
|
4530
|
+
var SessionEntry = external_exports.object({
|
|
4531
|
+
sessionId: external_exports.string().min(1).max(128),
|
|
4532
|
+
tool: external_exports.string().min(1).max(64),
|
|
4533
|
+
model: external_exports.string().min(1).max(128),
|
|
4534
|
+
inputTokens: tokenCount,
|
|
4535
|
+
outputTokens: tokenCount,
|
|
4536
|
+
cacheCreationTokens: tokenCount,
|
|
4537
|
+
cacheReadTokens: tokenCount,
|
|
4538
|
+
costUSD: external_exports.number().nonnegative(),
|
|
4539
|
+
lastActivity: Timestamp
|
|
4540
|
+
});
|
|
4541
|
+
var BlockEntry = external_exports.object({
|
|
4542
|
+
startTime: Timestamp,
|
|
4543
|
+
totalTokens: tokenCount,
|
|
4412
4544
|
costUSD: external_exports.number().nonnegative()
|
|
4413
4545
|
});
|
|
4414
4546
|
var SubmitPayload = external_exports.object({
|
|
4415
4547
|
cliVersion: external_exports.string().min(1).max(32),
|
|
4416
|
-
entries: external_exports.array(DailyUsageEntry).min(1).max(2e4)
|
|
4548
|
+
entries: external_exports.array(DailyUsageEntry).min(1).max(2e4),
|
|
4549
|
+
/** Optional per-conversation rollups (ccusage session). Back-compat: omittable. */
|
|
4550
|
+
sessions: external_exports.array(SessionEntry).max(1e4).optional(),
|
|
4551
|
+
/** Optional time-window rollups (ccusage blocks) for peak-hours analysis. */
|
|
4552
|
+
blocks: external_exports.array(BlockEntry).max(1e4).optional(),
|
|
4553
|
+
/** Optional friends-board code (from `--board=<code>`): auto-join this board on submit. */
|
|
4554
|
+
board: external_exports.string().min(1).max(32).optional()
|
|
4417
4555
|
});
|
|
4418
4556
|
var AnonSubmitPayload = SubmitPayload.extend({
|
|
4419
4557
|
/** Client-generated secret (hex). The server stores only its hash. */
|
|
@@ -4623,6 +4761,22 @@ function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date())
|
|
|
4623
4761
|
`;
|
|
4624
4762
|
}
|
|
4625
4763
|
|
|
4764
|
+
// src/publish.ts
|
|
4765
|
+
async function publishLocal(payload, deps) {
|
|
4766
|
+
if (!await deps.confirm(" Publish this to the public leaderboard?")) {
|
|
4767
|
+
deps.log(" Kept local. Nothing left your machine.");
|
|
4768
|
+
return false;
|
|
4769
|
+
}
|
|
4770
|
+
const key = deps.ensureAnonKey();
|
|
4771
|
+
const res = await deps.anonSubmit(key, payload);
|
|
4772
|
+
deps.log(` Published \u2014 you're on the board: ${res.dashboardUrl}`);
|
|
4773
|
+
deps.openBrowser(claimUrl(res.dashboardUrl, key));
|
|
4774
|
+
deps.log(
|
|
4775
|
+
" Sign in there to claim it, or `npx whoburnedmore private` to hide it again."
|
|
4776
|
+
);
|
|
4777
|
+
return true;
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4626
4780
|
// src/index.ts
|
|
4627
4781
|
var require2 = createRequire2(import.meta.url);
|
|
4628
4782
|
var VERSION = require2("../package.json").version;
|
|
@@ -4673,7 +4827,7 @@ async function run(flags) {
|
|
|
4673
4827
|
if (!flags.quiet) {
|
|
4674
4828
|
console.log(pc2.dim(`whoburnedmore v${VERSION} \xB7 ${flags.local ? "local mode" : apiBase()}`));
|
|
4675
4829
|
}
|
|
4676
|
-
const { entries, toolsFound } = collectAll();
|
|
4830
|
+
const { entries, sessions, blocks, toolsFound } = collectAll();
|
|
4677
4831
|
if (entries.length === 0) {
|
|
4678
4832
|
console.log();
|
|
4679
4833
|
console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
|
|
@@ -4681,6 +4835,9 @@ async function run(flags) {
|
|
|
4681
4835
|
return;
|
|
4682
4836
|
}
|
|
4683
4837
|
const payload = { cliVersion: VERSION, entries };
|
|
4838
|
+
if (sessions.length > 0) payload.sessions = sessions;
|
|
4839
|
+
if (blocks.length > 0) payload.blocks = blocks;
|
|
4840
|
+
if (flags.board) payload.board = flags.board;
|
|
4684
4841
|
if (flags.dryRun) {
|
|
4685
4842
|
console.log(pc2.dim("\n --dry-run: this exact payload would be sent, nothing else:\n"));
|
|
4686
4843
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -4689,6 +4846,15 @@ async function run(flags) {
|
|
|
4689
4846
|
if (!flags.quiet) printSummary(entries);
|
|
4690
4847
|
if (flags.local) {
|
|
4691
4848
|
showLocalDashboard(entries);
|
|
4849
|
+
if (!flags.quiet && process.stdin.isTTY) {
|
|
4850
|
+
await publishLocal(payload, {
|
|
4851
|
+
confirm,
|
|
4852
|
+
ensureAnonKey,
|
|
4853
|
+
anonSubmit,
|
|
4854
|
+
openBrowser,
|
|
4855
|
+
log: (line) => console.log(pc2.dim(line))
|
|
4856
|
+
});
|
|
4857
|
+
}
|
|
4692
4858
|
return;
|
|
4693
4859
|
}
|
|
4694
4860
|
if (flags.noSubmit) {
|
|
@@ -4707,28 +4873,52 @@ async function run(flags) {
|
|
|
4707
4873
|
);
|
|
4708
4874
|
}
|
|
4709
4875
|
console.log(` ${pc2.cyan(result.profileUrl)}`);
|
|
4876
|
+
if (result.boardUrl) {
|
|
4877
|
+
console.log(` \u{1F91D} You're on the friends board: ${pc2.cyan(result.boardUrl)}`);
|
|
4878
|
+
}
|
|
4879
|
+
if (!flags.quiet) openBrowser(result.boardUrl ?? result.profileUrl);
|
|
4710
4880
|
} else {
|
|
4711
|
-
const
|
|
4881
|
+
const anonKey = ensureAnonKey();
|
|
4882
|
+
const result = await anonSubmit(anonKey, payload);
|
|
4712
4883
|
console.log(
|
|
4713
4884
|
` Submitted ${pc2.bold(String(result.upserted))} day-entries from ${toolsFound.join(", ")}.`
|
|
4714
4885
|
);
|
|
4715
|
-
|
|
4716
|
-
` You burned ${pc2.bold(formatTokens(result.totalTokens))} tokens. Your dashboard:`
|
|
4717
|
-
);
|
|
4718
|
-
console.log(` ${pc2.cyan(result.dashboardUrl)}`);
|
|
4719
|
-
if (!flags.quiet) {
|
|
4886
|
+
if (result.boardUrl) {
|
|
4720
4887
|
console.log(
|
|
4721
|
-
pc2.
|
|
4888
|
+
` You burned ${pc2.bold(formatTokens(result.totalTokens))} tokens \u2014 \u{1F91D} you're on the friends board:`
|
|
4722
4889
|
);
|
|
4890
|
+
console.log(` ${pc2.cyan(result.boardUrl)}`);
|
|
4891
|
+
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
|
+
} else {
|
|
4897
|
+
console.log(
|
|
4898
|
+
` You burned ${pc2.bold(formatTokens(result.totalTokens))} tokens \u2014 you're on the public leaderboard:`
|
|
4899
|
+
);
|
|
4900
|
+
console.log(` ${pc2.cyan(result.dashboardUrl)}`);
|
|
4901
|
+
if (!flags.quiet) {
|
|
4902
|
+
openBrowser(claimUrl(result.dashboardUrl, anonKey));
|
|
4903
|
+
console.log(
|
|
4904
|
+
pc2.dim(" Opened your dashboard \u2014 claim it (name + X) to own your rank, or make it private / remove it.")
|
|
4905
|
+
);
|
|
4906
|
+
console.log(
|
|
4907
|
+
pc2.dim(" Manage anytime: `npx whoburnedmore private` \xB7 `npx whoburnedmore public` \xB7 `npx whoburnedmore remove`.")
|
|
4908
|
+
);
|
|
4909
|
+
}
|
|
4723
4910
|
}
|
|
4724
4911
|
}
|
|
4725
4912
|
console.log();
|
|
4726
4913
|
if (!flags.quiet && !autoSyncInstalled()) {
|
|
4727
4914
|
if (await confirm(" Keep your stats live? Install background sync (every 3h)?")) {
|
|
4728
4915
|
console.log(` ${installAutoSync()}`);
|
|
4916
|
+
console.log(pc2.dim(" Your page now updates in the background. Stop anytime with `npx whoburnedmore uninstall-sync`."));
|
|
4729
4917
|
} else {
|
|
4730
4918
|
console.log(pc2.dim(" OK \u2014 re-run `npx whoburnedmore` anytime, or `npx whoburnedmore install-sync` later."));
|
|
4731
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`."));
|
|
4732
4922
|
}
|
|
4733
4923
|
}
|
|
4734
4924
|
async function main() {
|
|
@@ -4744,7 +4934,8 @@ async function main() {
|
|
|
4744
4934
|
dryRun: args.includes("--dry-run"),
|
|
4745
4935
|
noSubmit: args.includes("--no-submit"),
|
|
4746
4936
|
local: args.includes("--local"),
|
|
4747
|
-
quiet: command === "sync"
|
|
4937
|
+
quiet: command === "sync",
|
|
4938
|
+
board: parseBoard(args)
|
|
4748
4939
|
};
|
|
4749
4940
|
switch (command) {
|
|
4750
4941
|
case "run":
|
|
@@ -4763,6 +4954,29 @@ async function main() {
|
|
|
4763
4954
|
console.log(" Logged out. Your leaderboard data is untouched.");
|
|
4764
4955
|
console.log(pc2.dim(" Delete your data anytime from your profile page."));
|
|
4765
4956
|
break;
|
|
4957
|
+
case "private":
|
|
4958
|
+
case "public": {
|
|
4959
|
+
const cfg = loadConfig();
|
|
4960
|
+
if (!cfg?.anonKey) {
|
|
4961
|
+
console.log(" No anonymous dashboard on this machine \u2014 run `npx whoburnedmore` first.");
|
|
4962
|
+
break;
|
|
4963
|
+
}
|
|
4964
|
+
await anonVisibility(cfg.anonKey, command === "public");
|
|
4965
|
+
console.log(
|
|
4966
|
+
command === "public" ? " Your dashboard is public on the leaderboard again." : " Your dashboard is now private \u2014 off the public leaderboard."
|
|
4967
|
+
);
|
|
4968
|
+
break;
|
|
4969
|
+
}
|
|
4970
|
+
case "remove": {
|
|
4971
|
+
const cfg = loadConfig();
|
|
4972
|
+
if (!cfg?.anonKey) {
|
|
4973
|
+
console.log(" No anonymous dashboard on this machine \u2014 nothing to remove.");
|
|
4974
|
+
break;
|
|
4975
|
+
}
|
|
4976
|
+
await anonRemove(cfg.anonKey);
|
|
4977
|
+
console.log(" Removed your anonymous dashboard and all its data.");
|
|
4978
|
+
break;
|
|
4979
|
+
}
|
|
4766
4980
|
case "install-sync":
|
|
4767
4981
|
console.log(` ${installAutoSync()}`);
|
|
4768
4982
|
break;
|
|
@@ -4789,19 +5003,24 @@ function printHelp() {
|
|
|
4789
5003
|
${pc2.bold("whoburnedmore")} \u2014 who burned more tokens, you or them?
|
|
4790
5004
|
|
|
4791
5005
|
${pc2.bold("usage")}
|
|
4792
|
-
npx whoburnedmore
|
|
5006
|
+
npx whoburnedmore burn + land on the public leaderboard, open your dashboard
|
|
5007
|
+
npx whoburnedmore --board=CODE compare with friends \u2014 join their board (no sign-in)
|
|
4793
5008
|
npx whoburnedmore --local build the dashboard on your machine and open it (offline)
|
|
4794
5009
|
npx whoburnedmore --dry-run print exactly what would be sent, send nothing
|
|
4795
5010
|
npx whoburnedmore --no-submit print local stats only, send nothing
|
|
5011
|
+
npx whoburnedmore private hide your anonymous dashboard from the leaderboard
|
|
5012
|
+
npx whoburnedmore public put it back on the leaderboard
|
|
5013
|
+
npx whoburnedmore remove delete your anonymous dashboard and its data
|
|
4796
5014
|
npx whoburnedmore login sign in to claim a public handle + join the leaderboard
|
|
4797
5015
|
npx whoburnedmore logout forget the local token
|
|
4798
5016
|
npx whoburnedmore install-sync keep your dashboard live (background sync, 3h)
|
|
4799
5017
|
npx whoburnedmore uninstall-sync remove background sync
|
|
4800
5018
|
|
|
4801
|
-
By default your dashboard is
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
file names. With --local,
|
|
5019
|
+
By default your dashboard is public on the leaderboard as an anonymous burner \u2014
|
|
5020
|
+
claim it (sign in for a handle + X) to own your rank, or run \`private\`/\`remove\`
|
|
5021
|
+
to pull it. Only daily aggregate numbers (date, tool, model, token counts, est.
|
|
5022
|
+
cost) ever leave your machine \u2014 never prompts, code, or file names. With --local,
|
|
5023
|
+
nothing leaves your machine at all.
|
|
4805
5024
|
`);
|
|
4806
5025
|
}
|
|
4807
5026
|
main().catch((err) => {
|