whoburnedmore 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +101 -12
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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
- const res = await fetch(`${apiBase()}${path}`, {
34
- method: "POST",
35
- headers: {
36
- "Content-Type": "application/json",
37
- ...token ? { Authorization: `Bearer ${token}` } : {}
38
- },
39
- body: JSON.stringify(body)
40
- });
41
- return { status: res.status, body: await res.json() };
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", {});
@@ -660,6 +678,46 @@ function resolveCcusageBin() {
660
678
  }
661
679
  return { cmd: binPath, prefixArgs: [] };
662
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
+ }
663
721
  function runCcusage(cmd, args) {
664
722
  const res = spawnSync4(cmd, args, {
665
723
  encoding: "utf8",
@@ -702,7 +760,12 @@ async function collectAll() {
702
760
  blocks.push(...cursor.blocks);
703
761
  toolsFound.push("cursor");
704
762
  }
705
- return { entries, sessions, blocks, toolsFound };
763
+ return {
764
+ entries: dedupeDaily(entries),
765
+ sessions: dedupeSessions(sessions),
766
+ blocks: dedupeBlocks(blocks),
767
+ toolsFound
768
+ };
706
769
  }
707
770
 
708
771
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
@@ -5036,6 +5099,25 @@ async function publishLocal(payload, deps) {
5036
5099
  // src/index.ts
5037
5100
  var require2 = createRequire4(import.meta.url);
5038
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
+ }
5039
5121
  function openBrowser(url) {
5040
5122
  const os = platform3();
5041
5123
  const [cmd, args] = os === "darwin" ? ["open", [url]] : os === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
@@ -5082,9 +5164,16 @@ function showLocalDashboard(entries) {
5082
5164
  async function run(flags) {
5083
5165
  if (!flags.quiet) {
5084
5166
  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
5167
  }
5087
- const { entries, sessions, blocks, toolsFound } = await collectAll();
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;
5088
5177
  if (entries.length === 0) {
5089
5178
  console.log();
5090
5179
  console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whoburnedmore",
3
- "version": "0.3.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": {