whoburnedmore 0.9.12 → 0.9.14

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 +223 -58
  2. package/package.json +3 -4
package/dist/index.js CHANGED
@@ -8,10 +8,10 @@ var __export = (target, all) => {
8
8
  // src/index.ts
9
9
  import { spawn } from "node:child_process";
10
10
  import { createHash } from "node:crypto";
11
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
11
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "node:fs";
12
12
  import { createRequire as createRequire4 } from "node:module";
13
13
  import { platform as platform3 } from "node:os";
14
- import { join as join10 } from "node:path";
14
+ import { join as join11 } from "node:path";
15
15
  import { createInterface } from "node:readline/promises";
16
16
  import pc2 from "picocolors";
17
17
 
@@ -786,7 +786,7 @@ async function daemonLoop(deps) {
786
786
  // src/collect.ts
787
787
  import { execFile } from "node:child_process";
788
788
  import { createRequire as createRequire3 } from "node:module";
789
- import { dirname as dirname4, join as join9 } from "node:path";
789
+ import { dirname as dirname5, join as join10 } from "node:path";
790
790
  import { promisify } from "node:util";
791
791
 
792
792
  // src/attribution.ts
@@ -1155,7 +1155,10 @@ function collectCursorViaTokscale(lookbackDays = LOOKBACK_DAYS) {
1155
1155
  const bin = resolveTokscaleBin();
1156
1156
  if (!bin) return [];
1157
1157
  const today = /* @__PURE__ */ new Date();
1158
- const day = (offset) => new Date(today.getTime() - offset * 864e5).toISOString().slice(0, 10);
1158
+ const day = (offset) => {
1159
+ const d = new Date(today.getTime() - offset * 864e5);
1160
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1161
+ };
1159
1162
  if (mapTokscaleDay(day(0), runTokscaleDay(bin, day(0))).length === 0) {
1160
1163
  if (mapTokscaleDay(day(1), runTokscaleDay(bin, day(1))).length === 0) {
1161
1164
  return [];
@@ -1224,7 +1227,7 @@ function mapCursorEvents(events) {
1224
1227
  const ms = Number(e.timestamp);
1225
1228
  if (!tu || !Number.isFinite(ms)) continue;
1226
1229
  const d = new Date(ms);
1227
- const date = d.toISOString().slice(0, 10);
1230
+ const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1228
1231
  const model = e.model || "cursor";
1229
1232
  const input = num2(tu.inputTokens);
1230
1233
  const output = num2(tu.outputTokens);
@@ -1314,9 +1317,9 @@ async function collectCursor() {
1314
1317
  }
1315
1318
 
1316
1319
  // src/native/claude.ts
1317
- import { readdir, readFile } from "node:fs/promises";
1320
+ import { readdir, readFile as readFile2 } from "node:fs/promises";
1318
1321
  import { homedir as homedir5 } from "node:os";
1319
- import { join as join6 } from "node:path";
1322
+ import { join as join7 } from "node:path";
1320
1323
 
1321
1324
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
1322
1325
  var external_exports = {};
@@ -6761,7 +6764,8 @@ var ConnectorProvider = external_exports.enum([
6761
6764
  "openai-api",
6762
6765
  "openrouter",
6763
6766
  "google-api",
6764
- "cursor"
6767
+ "cursor",
6768
+ "azure-foundry"
6765
6769
  ]);
6766
6770
  var UsageOrigin = external_exports.enum([
6767
6771
  "cli",
@@ -6770,6 +6774,7 @@ var UsageOrigin = external_exports.enum([
6770
6774
  "openrouter",
6771
6775
  "google-api",
6772
6776
  "cursor",
6777
+ "azure-foundry",
6773
6778
  "import"
6774
6779
  ]);
6775
6780
  var DailyUsageEntry = external_exports.object({
@@ -7062,6 +7067,76 @@ var OrgJoinInput = external_exports.object({
7062
7067
  code: external_exports.string().min(1).max(64).optional()
7063
7068
  });
7064
7069
 
7070
+ // src/native/file-cache.ts
7071
+ import { mkdirSync as mkdirSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
7072
+ import { readFile, stat } from "node:fs/promises";
7073
+ import { dirname as dirname3, join as join6 } from "node:path";
7074
+ function nativeCachePath(reader, env = process.env) {
7075
+ const override = env.WHOBURNEDMORE_CONFIG_DIR?.trim();
7076
+ const dir = override || defaultConfigDir();
7077
+ return join6(dir, `native-cache-${reader}.json`);
7078
+ }
7079
+ async function loadCache(path, version) {
7080
+ try {
7081
+ const raw = await readFile(path, "utf8");
7082
+ const parsed = JSON.parse(raw);
7083
+ if (parsed && parsed.v === version && parsed.files && typeof parsed.files === "object") {
7084
+ return parsed.files;
7085
+ }
7086
+ } catch {
7087
+ }
7088
+ return {};
7089
+ }
7090
+ function saveCache(path, version, files) {
7091
+ try {
7092
+ mkdirSync3(dirname3(path), { recursive: true });
7093
+ const tmp = `${path}.tmp-${process.pid}`;
7094
+ writeFileSync3(tmp, JSON.stringify({ v: version, files }));
7095
+ renameSync3(tmp, path);
7096
+ } catch {
7097
+ }
7098
+ }
7099
+ async function readFilesWithCache(opts) {
7100
+ const now = opts.now ?? Date.now;
7101
+ const cached = await loadCache(opts.cachePath, opts.version);
7102
+ const fresh = {};
7103
+ const itemsByFile = [];
7104
+ let filesRead = 0;
7105
+ for (const f of opts.files) {
7106
+ let size;
7107
+ let mtimeMs;
7108
+ try {
7109
+ const s = await stat(f);
7110
+ size = s.size;
7111
+ mtimeMs = s.mtimeMs;
7112
+ } catch {
7113
+ continue;
7114
+ }
7115
+ const hit = cached[f];
7116
+ if (hit && hit.size === size && hit.mtimeMs === mtimeMs) {
7117
+ fresh[f] = hit;
7118
+ itemsByFile.push(hit.items);
7119
+ continue;
7120
+ }
7121
+ if (now() > opts.deadline) {
7122
+ saveCache(opts.cachePath, opts.version, { ...cached, ...fresh });
7123
+ return { itemsByFile: null, filesRead, timedOut: true };
7124
+ }
7125
+ let content;
7126
+ try {
7127
+ content = await readFile(f, "utf8");
7128
+ } catch {
7129
+ continue;
7130
+ }
7131
+ const items = opts.parseFile(content, f);
7132
+ fresh[f] = { size, mtimeMs, items };
7133
+ itemsByFile.push(items);
7134
+ filesRead += 1;
7135
+ }
7136
+ saveCache(opts.cachePath, opts.version, fresh);
7137
+ return { itemsByFile, filesRead, timedOut: false };
7138
+ }
7139
+
7065
7140
  // src/native/claude.ts
7066
7141
  function num3(n) {
7067
7142
  const v = Math.round(Number(n));
@@ -7167,10 +7242,10 @@ function finalizeClaudeEntries(acc) {
7167
7242
  }
7168
7243
  function resolveClaudeConfigRoots(env = process.env) {
7169
7244
  const override = env.CLAUDE_CONFIG_DIR;
7170
- return override ? override.split(",").map((s) => s.trim()).filter(Boolean) : [join6(homedir5(), ".claude"), join6(homedir5(), ".config", "claude")];
7245
+ return override ? override.split(",").map((s) => s.trim()).filter(Boolean) : [join7(homedir5(), ".claude"), join7(homedir5(), ".config", "claude")];
7171
7246
  }
7172
7247
  function resolveClaudeProjectDirs(env = process.env) {
7173
- return resolveClaudeConfigRoots(env).map((r) => join6(r, "projects"));
7248
+ return resolveClaudeConfigRoots(env).map((r) => join7(r, "projects"));
7174
7249
  }
7175
7250
  async function listJsonl(dir) {
7176
7251
  let dirents;
@@ -7181,7 +7256,7 @@ async function listJsonl(dir) {
7181
7256
  }
7182
7257
  const out = [];
7183
7258
  for (const d of dirents) {
7184
- const full = join6(dir, d.name);
7259
+ const full = join7(dir, d.name);
7185
7260
  if (d.isDirectory()) {
7186
7261
  out.push(...await listJsonl(full));
7187
7262
  } else if (d.isFile() && d.name.endsWith(".jsonl")) {
@@ -7190,7 +7265,43 @@ async function listJsonl(dir) {
7190
7265
  }
7191
7266
  return out;
7192
7267
  }
7193
- var NATIVE_READ_BUDGET_MS = 2e4;
7268
+ var NATIVE_READ_BUDGET_MS = 45e3;
7269
+ var CLAUDE_CACHE_VERSION = 1;
7270
+ function toCached(r) {
7271
+ return [
7272
+ r.key,
7273
+ r.hasRealId ? 1 : 0,
7274
+ r.date,
7275
+ r.model,
7276
+ r.inputTokens,
7277
+ r.outputTokens,
7278
+ r.cacheCreationTokens,
7279
+ r.cacheReadTokens
7280
+ ];
7281
+ }
7282
+ function fromCached(t) {
7283
+ return {
7284
+ key: t[0],
7285
+ hasRealId: t[1] === 1,
7286
+ date: t[2],
7287
+ ts: 0,
7288
+ model: t[3],
7289
+ inputTokens: t[4],
7290
+ outputTokens: t[5],
7291
+ cacheCreationTokens: t[6],
7292
+ cacheReadTokens: t[7]
7293
+ };
7294
+ }
7295
+ function parseFileToCache(content, path) {
7296
+ const acc = /* @__PURE__ */ new Map();
7297
+ accumulateClaudeLines(acc, splitLines(content));
7298
+ let synIndex = 0;
7299
+ return [...acc.values()].map(
7300
+ (r) => toCached(
7301
+ r.hasRealId ? r : { ...r, key: `synthetic|${path}|${synIndex += 1}` }
7302
+ )
7303
+ );
7304
+ }
7194
7305
  function* splitLines(content) {
7195
7306
  let start = 0;
7196
7307
  for (let i = 0; i < content.length; i++) {
@@ -7207,23 +7318,35 @@ async function collectClaudeNative(env = process.env, opts = {}) {
7207
7318
  for (const dir of dirs) files.push(...await listJsonl(dir));
7208
7319
  if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7209
7320
  const now = opts.now ?? Date.now;
7210
- const deadline = now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS);
7321
+ const res = await readFilesWithCache({
7322
+ files,
7323
+ cachePath: opts.cachePath ?? nativeCachePath("claude", env),
7324
+ version: CLAUDE_CACHE_VERSION,
7325
+ parseFile: parseFileToCache,
7326
+ deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS),
7327
+ now
7328
+ });
7329
+ if (!res.itemsByFile) {
7330
+ return {
7331
+ entries: [],
7332
+ found: false,
7333
+ filesScanned: res.filesRead,
7334
+ timedOut: true
7335
+ };
7336
+ }
7211
7337
  const acc = /* @__PURE__ */ new Map();
7212
- let scanned = 0;
7213
- for (const f of files) {
7214
- if (now() > deadline) {
7215
- return { entries: [], found: false, filesScanned: scanned, timedOut: true };
7216
- }
7217
- let content;
7218
- try {
7219
- content = await readFile(f, "utf8");
7220
- } catch {
7221
- continue;
7338
+ for (const items of res.itemsByFile) {
7339
+ for (const t of items) {
7340
+ const r = fromCached(t);
7341
+ const prev = acc.get(r.key);
7342
+ if (!prev || reqTokens(r) > reqTokens(prev)) acc.set(r.key, r);
7222
7343
  }
7223
- accumulateClaudeLines(acc, splitLines(content));
7224
- scanned += 1;
7225
7344
  }
7226
- return { entries: finalizeClaudeEntries(acc), found: true, filesScanned: scanned };
7345
+ return {
7346
+ entries: finalizeClaudeEntries(acc),
7347
+ found: true,
7348
+ filesScanned: res.filesRead
7349
+ };
7227
7350
  }
7228
7351
  async function collectClaudeRequests(env = process.env, opts = {}) {
7229
7352
  const dirs = resolveClaudeProjectDirs(env);
@@ -7239,7 +7362,7 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
7239
7362
  }
7240
7363
  let content;
7241
7364
  try {
7242
- content = await readFile(f, "utf8");
7365
+ content = await readFile2(f, "utf8");
7243
7366
  } catch {
7244
7367
  continue;
7245
7368
  }
@@ -7249,9 +7372,9 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
7249
7372
  }
7250
7373
 
7251
7374
  // src/native/codex.ts
7252
- import { readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
7375
+ import { readdir as readdir2 } from "node:fs/promises";
7253
7376
  import { homedir as homedir6 } from "node:os";
7254
- import { join as join7 } from "node:path";
7377
+ import { join as join8 } from "node:path";
7255
7378
  function num4(n) {
7256
7379
  const v = Math.round(Number(n));
7257
7380
  return Number.isFinite(v) && v > 0 ? v : 0;
@@ -7340,8 +7463,8 @@ function parseCodexRollout(lines) {
7340
7463
  }
7341
7464
  return out;
7342
7465
  }
7343
- function accumulateCodexSession(acc, lines) {
7344
- for (const s of parseCodexRollout(lines)) {
7466
+ function foldCodexSessions(acc, sessions) {
7467
+ for (const s of sessions) {
7345
7468
  const k = `${s.date}|${s.model}`;
7346
7469
  let b = acc.get(k);
7347
7470
  if (!b) {
@@ -7383,8 +7506,8 @@ function finalizeCodexEntries(acc) {
7383
7506
  return entries;
7384
7507
  }
7385
7508
  function resolveCodexSessionsDir(env = process.env) {
7386
- const home = env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join7(homedir6(), ".codex");
7387
- return join7(home, "sessions");
7509
+ const home = env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join8(homedir6(), ".codex");
7510
+ return join8(home, "sessions");
7388
7511
  }
7389
7512
  async function listJsonl2(dir) {
7390
7513
  let dirents;
@@ -7395,7 +7518,7 @@ async function listJsonl2(dir) {
7395
7518
  }
7396
7519
  const out = [];
7397
7520
  for (const d of dirents) {
7398
- const full = join7(dir, d.name);
7521
+ const full = join8(dir, d.name);
7399
7522
  if (d.isDirectory()) out.push(...await listJsonl2(full));
7400
7523
  else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
7401
7524
  }
@@ -7411,38 +7534,69 @@ function* splitLines2(content) {
7411
7534
  }
7412
7535
  if (start < content.length) yield content.slice(start);
7413
7536
  }
7414
- var NATIVE_READ_BUDGET_MS2 = 2e4;
7537
+ var NATIVE_READ_BUDGET_MS2 = 45e3;
7538
+ var CODEX_CACHE_VERSION = 1;
7539
+ function toCachedSession(s) {
7540
+ return [
7541
+ s.date,
7542
+ s.model,
7543
+ s.inputTokens,
7544
+ s.outputTokens,
7545
+ s.cacheCreationTokens,
7546
+ s.cacheReadTokens,
7547
+ s.turnCount
7548
+ ];
7549
+ }
7550
+ function fromCachedSession(t) {
7551
+ return {
7552
+ date: t[0],
7553
+ model: t[1],
7554
+ inputTokens: t[2],
7555
+ outputTokens: t[3],
7556
+ cacheCreationTokens: t[4],
7557
+ cacheReadTokens: t[5],
7558
+ turnCount: t[6]
7559
+ };
7560
+ }
7415
7561
  async function collectCodexNative(env = process.env, opts = {}) {
7416
7562
  const dir = resolveCodexSessionsDir(env);
7417
7563
  const files = await listJsonl2(dir);
7418
7564
  if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7419
7565
  const now = opts.now ?? Date.now;
7420
- const deadline = now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS2);
7566
+ const res = await readFilesWithCache({
7567
+ files,
7568
+ cachePath: opts.cachePath ?? nativeCachePath("codex", env),
7569
+ version: CODEX_CACHE_VERSION,
7570
+ parseFile: (content) => parseCodexRollout(splitLines2(content)).map(toCachedSession),
7571
+ deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS2),
7572
+ now
7573
+ });
7574
+ if (!res.itemsByFile) {
7575
+ return {
7576
+ entries: [],
7577
+ found: false,
7578
+ filesScanned: res.filesRead,
7579
+ timedOut: true
7580
+ };
7581
+ }
7421
7582
  const acc = /* @__PURE__ */ new Map();
7422
- let scanned = 0;
7423
- for (const f of files) {
7424
- if (now() > deadline) {
7425
- return { entries: [], found: false, filesScanned: scanned, timedOut: true };
7426
- }
7427
- let content;
7428
- try {
7429
- content = await readFile2(f, "utf8");
7430
- } catch {
7431
- continue;
7432
- }
7433
- accumulateCodexSession(acc, splitLines2(content));
7434
- scanned += 1;
7583
+ for (const items of res.itemsByFile) {
7584
+ foldCodexSessions(acc, items.map(fromCachedSession));
7435
7585
  }
7436
- return { entries: finalizeCodexEntries(acc), found: true, filesScanned: scanned };
7586
+ return {
7587
+ entries: finalizeCodexEntries(acc),
7588
+ found: true,
7589
+ filesScanned: res.filesRead
7590
+ };
7437
7591
  }
7438
7592
 
7439
7593
  // src/pricing-live.ts
7440
7594
  import { mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
7441
- import { dirname as dirname3, join as join8 } from "node:path";
7595
+ import { dirname as dirname4, join as join9 } from "node:path";
7442
7596
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
7443
7597
  var FETCH_TIMEOUT_MS = 5e3;
7444
7598
  function pricingCachePath(dir = defaultConfigDir()) {
7445
- return join8(dir, "pricing-cache.json");
7599
+ return join9(dir, "pricing-cache.json");
7446
7600
  }
7447
7601
  async function readCache(path) {
7448
7602
  try {
@@ -7478,7 +7632,7 @@ async function loadLivePricing(env = process.env, now = Date.now, cachePath = pr
7478
7632
  if (live) {
7479
7633
  setLivePricing(live);
7480
7634
  try {
7481
- await mkdir(dirname3(path), { recursive: true });
7635
+ await mkdir(dirname4(path), { recursive: true });
7482
7636
  const tmp = `${path}.${process.pid}.tmp`;
7483
7637
  await writeFile(tmp, JSON.stringify({ fetchedAt: now(), table: live }));
7484
7638
  await rename(tmp, path);
@@ -7633,7 +7787,7 @@ function resolveCcusageBin() {
7633
7787
  const pkgPath = require3.resolve("ccusage/package.json");
7634
7788
  const pkg = require3("ccusage/package.json");
7635
7789
  const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.ccusage ?? "ccusage";
7636
- const binPath = join9(dirname4(pkgPath), rel);
7790
+ const binPath = join10(dirname5(pkgPath), rel);
7637
7791
  if (/\.(c|m)?js$/.test(binPath)) {
7638
7792
  return { cmd: process.execPath, prefixArgs: [binPath] };
7639
7793
  }
@@ -8221,9 +8375,9 @@ async function confirm(question) {
8221
8375
  }
8222
8376
  function showLocalDashboard(payload) {
8223
8377
  const dir = defaultConfigDir();
8224
- mkdirSync3(dir, { recursive: true });
8225
- const file = join10(dir, "dashboard.html");
8226
- writeFileSync3(
8378
+ mkdirSync4(dir, { recursive: true });
8379
+ const file = join11(dir, "dashboard.html");
8380
+ writeFileSync4(
8227
8381
  file,
8228
8382
  renderDashboardHtml(payload.entries, /* @__PURE__ */ new Date(), { webBaseUrl: webBase() })
8229
8383
  );
@@ -8474,7 +8628,13 @@ async function submitSignedIn(token, payload, flags, interactive) {
8474
8628
  afterSubmitChores(flags);
8475
8629
  }
8476
8630
  function afterSubmitChores(flags) {
8477
- if (flags.quiet) return;
8631
+ if (flags.quiet) {
8632
+ try {
8633
+ if (autoSyncInstalled()) reconcileAutoSync();
8634
+ } catch {
8635
+ }
8636
+ return;
8637
+ }
8478
8638
  try {
8479
8639
  reconcileAutoSync();
8480
8640
  } catch {
@@ -8562,6 +8722,11 @@ async function runDaemon() {
8562
8722
  runOnce: async () => {
8563
8723
  rotateLogIfLarge();
8564
8724
  await run({ dryRun: false, noSubmit: false, local: false, quiet: true });
8725
+ if (!loadConfig()?.cliToken) {
8726
+ throw new Error(
8727
+ "not linked \u2014 nothing submitted (run `npx whoburnedmore link --token=\u2026` from your signed-in profile first)"
8728
+ );
8729
+ }
8565
8730
  }
8566
8731
  });
8567
8732
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whoburnedmore",
3
- "version": "0.9.12",
3
+ "version": "0.9.14",
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": {
@@ -38,11 +38,10 @@
38
38
  "homepage": "https://whoburnedmore.com",
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "git+https://github.com/amiinwani/whoburnedmore.git",
42
- "directory": "packages/cli"
41
+ "url": "git+https://github.com/arhxam/whoburnedmore.git"
43
42
  },
44
43
  "bugs": {
45
- "url": "https://github.com/amiinwani/whoburnedmore/issues"
44
+ "url": "https://github.com/arhxam/whoburnedmore/issues"
46
45
  },
47
46
  "keywords": [
48
47
  "ai",