whoburnedmore 0.9.16 → 0.9.18

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 +171 -113
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,7 +7,6 @@ var __export = (target, all) => {
7
7
 
8
8
  // src/index.ts
9
9
  import { spawn } from "node:child_process";
10
- import { createHash } from "node:crypto";
11
10
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
12
11
  import { createRequire as createRequire4 } from "node:module";
13
12
  import { platform as platform4 } from "node:os";
@@ -418,9 +417,19 @@ ${envEntries}
418
417
  interval. Submits are idempotent server-side, so an extra run is safe. -->
419
418
  <key>RunAtLoad</key>
420
419
  <true/>
421
- <!-- Be a good citizen: macOS schedules this with background priority. -->
420
+ <!-- Standard, NOT Background. ProcessType=Background opts the job into
421
+ macOS I/O throttling, which is disastrous for a job whose work is almost
422
+ entirely reading thousands of transcript files: a full collect measured
423
+ ~51s unthrottled vs ~197s throttled on the same machine. That blows past
424
+ the collector's own budgets (25s per ccusage child, 45s for the native
425
+ reader), so every source times out at once, entries comes back empty and
426
+ the tick submits nothing \u2014 it logged "Nothing to burn yet" on 206 of 473
427
+ runs (~44%) until this was changed. Nice=0 keeps CPU priority neighbourly
428
+ without throttling the reads. -->
422
429
  <key>ProcessType</key>
423
- <string>Background</string>
430
+ <string>Standard</string>
431
+ <key>Nice</key>
432
+ <integer>0</integer>
424
433
  <key>StandardOutPath</key>
425
434
  <string>${xmlEscape(logPath)}</string>
426
435
  <key>StandardErrorPath</key>
@@ -6618,6 +6627,31 @@ async function readFilesWithCache(opts) {
6618
6627
  return { itemsByFile, filesRead, timedOut: false };
6619
6628
  }
6620
6629
 
6630
+ // src/native/usage-date.ts
6631
+ var MIN_USAGE_TIMESTAMP_MS = Date.UTC(2020, 0, 1);
6632
+ var MAX_CLOCK_SKEW_MS = 2 * 864e5;
6633
+ function localUsageDate(ms, now = Date.now()) {
6634
+ if (!Number.isFinite(ms) || ms < MIN_USAGE_TIMESTAMP_MS || ms > now + MAX_CLOCK_SKEW_MS) {
6635
+ return null;
6636
+ }
6637
+ const d = new Date(ms);
6638
+ if (!Number.isFinite(d.getTime())) return null;
6639
+ const y = d.getFullYear();
6640
+ const m = String(d.getMonth() + 1).padStart(2, "0");
6641
+ const day = String(d.getDate()).padStart(2, "0");
6642
+ return `${y}-${m}-${day}`;
6643
+ }
6644
+ function plausibleUsageDate(value, now = Date.now()) {
6645
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
6646
+ if (!match) return false;
6647
+ const year = Number(match[1]);
6648
+ const month = Number(match[2]);
6649
+ const day = Number(match[3]);
6650
+ const ms = Date.UTC(year, month - 1, day);
6651
+ const parsed = new Date(ms);
6652
+ return parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month - 1 && parsed.getUTCDate() === day && ms >= MIN_USAGE_TIMESTAMP_MS && ms <= now + MAX_CLOCK_SKEW_MS;
6653
+ }
6654
+
6621
6655
  // src/native/claude.ts
6622
6656
  function num(n) {
6623
6657
  const v = Math.round(Number(n));
@@ -6626,15 +6660,6 @@ function num(n) {
6626
6660
  function reqTokens(r) {
6627
6661
  return r.inputTokens + r.outputTokens + r.cacheCreationTokens + r.cacheReadTokens;
6628
6662
  }
6629
- function localDate(iso) {
6630
- const t = Date.parse(iso);
6631
- if (!Number.isFinite(t)) return null;
6632
- const d = new Date(t);
6633
- const y = d.getFullYear();
6634
- const m = String(d.getMonth() + 1).padStart(2, "0");
6635
- const day = String(d.getDate()).padStart(2, "0");
6636
- return `${y}-${m}-${day}`;
6637
- }
6638
6663
  var syntheticCounter = 0;
6639
6664
  function parseClaudeLine(raw) {
6640
6665
  const trimmed = raw.trim();
@@ -6650,10 +6675,10 @@ function parseClaudeLine(raw) {
6650
6675
  const usage = message.usage;
6651
6676
  if (!usage || typeof usage !== "object") return null;
6652
6677
  if (message.role !== void 0 && message.role !== "assistant") return null;
6653
- const date = localDate(String(obj.timestamp ?? ""));
6654
- if (!date) return null;
6655
6678
  const tsParsed = Date.parse(String(obj.timestamp ?? ""));
6656
- const ts = Number.isFinite(tsParsed) ? tsParsed : 0;
6679
+ const date = localUsageDate(tsParsed);
6680
+ if (!date) return null;
6681
+ const ts = tsParsed;
6657
6682
  const messageId = typeof message.id === "string" ? message.id : "";
6658
6683
  const requestId = typeof obj.requestId === "string" ? obj.requestId : "";
6659
6684
  const hasRealId = messageId !== "" || requestId !== "";
@@ -6747,7 +6772,7 @@ async function listJsonl(dir) {
6747
6772
  return out;
6748
6773
  }
6749
6774
  var NATIVE_READ_BUDGET_MS = 45e3;
6750
- var CLAUDE_CACHE_VERSION = 1;
6775
+ var CLAUDE_CACHE_VERSION = 2;
6751
6776
  function toCached(r) {
6752
6777
  return [
6753
6778
  r.key,
@@ -7190,13 +7215,6 @@ function toEpochMs(ts) {
7190
7215
  }
7191
7216
  return null;
7192
7217
  }
7193
- function localDate2(ms) {
7194
- const d = new Date(ms);
7195
- const y = d.getFullYear();
7196
- const m = String(d.getMonth() + 1).padStart(2, "0");
7197
- const day = String(d.getDate()).padStart(2, "0");
7198
- return `${y}-${m}-${day}`;
7199
- }
7200
7218
  function mapContinueRecords(records) {
7201
7219
  const byKey = /* @__PURE__ */ new Map();
7202
7220
  for (const raw of records) {
@@ -7208,10 +7226,11 @@ function mapContinueRecords(records) {
7208
7226
  if (!model) continue;
7209
7227
  const ms = toEpochMs(rec.timestamp ?? rec.eventTimestamp);
7210
7228
  if (ms === null) continue;
7229
+ const date = localUsageDate(ms);
7230
+ if (!date) continue;
7211
7231
  const inputTokens = num2(rec.promptTokens ?? rec.prompt_tokens);
7212
7232
  const outputTokens = num2(rec.generatedTokens ?? rec.generated_tokens);
7213
7233
  if (inputTokens + outputTokens === 0) continue;
7214
- const date = localDate2(ms);
7215
7234
  const key = `${date}|${model}`;
7216
7235
  let b = byKey.get(key);
7217
7236
  if (!b) {
@@ -7269,11 +7288,11 @@ async function listJsonl2(dir) {
7269
7288
  for (const d of dirents) {
7270
7289
  const full = join6(dir, d.name);
7271
7290
  if (d.isDirectory()) out.push(...await listJsonl2(full));
7272
- else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
7291
+ else if (d.isFile() && d.name === "tokensGenerated.jsonl") out.push(full);
7273
7292
  }
7274
7293
  return out;
7275
7294
  }
7276
- var CONTINUE_CACHE_VERSION = 1;
7295
+ var CONTINUE_CACHE_VERSION = 2;
7277
7296
  async function collectContinue(opts = {}) {
7278
7297
  const env = opts.env ?? process.env;
7279
7298
  const home = opts.continueDir ?? join6(env.HOME || homedir5(), ".continue");
@@ -7493,7 +7512,8 @@ function mapCursorEvents(events) {
7493
7512
  const ms = Number(e.timestamp);
7494
7513
  if (!tu || !Number.isFinite(ms)) continue;
7495
7514
  const d = new Date(ms);
7496
- const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
7515
+ const date = localUsageDate(ms);
7516
+ if (!date) continue;
7497
7517
  const model = e.model || "cursor";
7498
7518
  const input = num4(tu.inputTokens);
7499
7519
  const output = num4(tu.outputTokens);
@@ -7590,15 +7610,6 @@ function num5(n) {
7590
7610
  const v = Math.round(Number(n));
7591
7611
  return Number.isFinite(v) && v > 0 ? v : 0;
7592
7612
  }
7593
- function localDate3(iso) {
7594
- const t = Date.parse(iso);
7595
- if (!Number.isFinite(t)) return null;
7596
- const d = new Date(t);
7597
- const y = d.getFullYear();
7598
- const m = String(d.getMonth() + 1).padStart(2, "0");
7599
- const day = String(d.getDate()).padStart(2, "0");
7600
- return `${y}-${m}-${day}`;
7601
- }
7602
7613
  function readTokenFields(payload) {
7603
7614
  const info = payload.info;
7604
7615
  const src = info?.total_token_usage ?? payload;
@@ -7608,8 +7619,7 @@ function readTokenFields(payload) {
7608
7619
  return {
7609
7620
  input: num5(input),
7610
7621
  cached: num5(src.cached_input_tokens),
7611
- output: num5(output),
7612
- reasoning: num5(src.reasoning_output_tokens)
7622
+ output: num5(output)
7613
7623
  };
7614
7624
  }
7615
7625
  function parseCodexRollout(lines) {
@@ -7634,7 +7644,7 @@ function parseCodexRollout(lines) {
7634
7644
  if (payload.type === "token_count") {
7635
7645
  const fields = readTokenFields(payload);
7636
7646
  if (!fields) continue;
7637
- const parsed = localDate3(String(obj.timestamp ?? ""));
7647
+ const parsed = localUsageDate(Date.parse(String(obj.timestamp ?? "")));
7638
7648
  const day = parsed ?? lastSeenDate;
7639
7649
  if (!day) continue;
7640
7650
  lastSeenDate = day;
@@ -7650,17 +7660,16 @@ function parseCodexRollout(lines) {
7650
7660
  if (perDay.size === 0) return [];
7651
7661
  const dates = [...perDay.keys()].sort();
7652
7662
  const out = [];
7653
- let prev = { input: 0, cached: 0, output: 0, reasoning: 0 };
7663
+ let prev = { input: 0, cached: 0, output: 0 };
7654
7664
  for (const date of dates) {
7655
7665
  const { cum, turns } = perDay.get(date);
7656
7666
  const dInput = Math.max(0, cum.input - prev.input);
7657
7667
  const dCached = Math.max(0, cum.cached - prev.cached);
7658
7668
  const dOutput = Math.max(0, cum.output - prev.output);
7659
- const dReasoning = Math.max(0, cum.reasoning - prev.reasoning);
7660
7669
  prev = cum;
7661
7670
  const cacheReadTokens = dCached;
7662
7671
  const inputTokens = Math.max(0, dInput - dCached);
7663
- const outputTokens = dOutput + dReasoning;
7672
+ const outputTokens = dOutput;
7664
7673
  if (inputTokens + outputTokens + cacheReadTokens === 0) continue;
7665
7674
  out.push({
7666
7675
  date,
@@ -7716,9 +7725,12 @@ function finalizeCodexEntries(acc) {
7716
7725
  }
7717
7726
  return entries;
7718
7727
  }
7719
- function resolveCodexSessionsDir(env = process.env) {
7720
- const home = env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join9(homedir7(), ".codex");
7721
- return join9(home, "sessions");
7728
+ function resolveCodexHome(env = process.env) {
7729
+ return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join9(homedir7(), ".codex");
7730
+ }
7731
+ function resolveCodexSessionsDirs(env = process.env) {
7732
+ const home = resolveCodexHome(env);
7733
+ return [join9(home, "sessions"), join9(home, "archived_sessions")];
7722
7734
  }
7723
7735
  async function listJsonl3(dir) {
7724
7736
  let dirents;
@@ -7746,7 +7758,7 @@ function* splitLines3(content) {
7746
7758
  if (start < content.length) yield content.slice(start);
7747
7759
  }
7748
7760
  var NATIVE_READ_BUDGET_MS2 = 45e3;
7749
- var CODEX_CACHE_VERSION = 1;
7761
+ var CODEX_CACHE_VERSION = 2;
7750
7762
  function toCachedSession(s) {
7751
7763
  return [
7752
7764
  s.date,
@@ -7770,8 +7782,8 @@ function fromCachedSession(t) {
7770
7782
  };
7771
7783
  }
7772
7784
  async function collectCodexNative(env = process.env, opts = {}) {
7773
- const dir = resolveCodexSessionsDir(env);
7774
- const files = await listJsonl3(dir);
7785
+ const dirs = resolveCodexSessionsDirs(env);
7786
+ const files = (await Promise.all(dirs.map(listJsonl3))).flat();
7775
7787
  if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7776
7788
  const now = opts.now ?? Date.now;
7777
7789
  const res = await readFilesWithCache({
@@ -7813,17 +7825,9 @@ function num6(n) {
7813
7825
  const v = Math.round(Number(n));
7814
7826
  return Number.isFinite(v) && v > 0 ? v : 0;
7815
7827
  }
7816
- function localDate4(ms) {
7817
- if (!Number.isFinite(ms) || ms <= 0) return null;
7818
- const d = new Date(ms);
7819
- const y = d.getFullYear();
7820
- const m = String(d.getMonth() + 1).padStart(2, "0");
7821
- const day = String(d.getDate()).padStart(2, "0");
7822
- return `${y}-${m}-${day}`;
7823
- }
7824
7828
  function parseApiReqMessage(tool, msg) {
7825
7829
  if (msg.say !== "api_req_started") return null;
7826
- const date = localDate4(Number(msg.ts));
7830
+ const date = localUsageDate(Number(msg.ts));
7827
7831
  if (!date) return null;
7828
7832
  let payload = null;
7829
7833
  const text = msg.text;
@@ -7947,7 +7951,7 @@ async function listTaskFiles(roots, extIds) {
7947
7951
  }
7948
7952
  return files;
7949
7953
  }
7950
- var VSCODE_CACHE_VERSION = 1;
7954
+ var VSCODE_CACHE_VERSION = 2;
7951
7955
  function entryToRow(e) {
7952
7956
  return [
7953
7957
  e.date,
@@ -8156,6 +8160,10 @@ function reconcileProvenance(entries, agent, complete, store) {
8156
8160
 
8157
8161
  // src/collect.ts
8158
8162
  var execFileAsync = promisify(execFile);
8163
+ var NATIVE_COVERED_SOURCES = /* @__PURE__ */ new Set(["claude", "codex"]);
8164
+ var CCUSAGE_TIMEOUT_MS = 25e3;
8165
+ var CCUSAGE_FALLBACK_TIMEOUT_MS = NATIVE_READ_BUDGET_MS;
8166
+ var CCUSAGE_AGGREGATE_TIMEOUT_MS = 18e4;
8159
8167
  var SOURCES = [
8160
8168
  "claude",
8161
8169
  "codex",
@@ -8181,14 +8189,14 @@ function normCost(n) {
8181
8189
  const v = Number(n);
8182
8190
  return Number.isFinite(v) && v > 0 ? v : 0;
8183
8191
  }
8184
- function mapCcusageDaily(tool, json) {
8192
+ function mapCcusageDaily(tool, json, now = Date.now()) {
8185
8193
  const daily = json?.daily;
8186
8194
  if (!Array.isArray(daily)) return [];
8187
8195
  const entries = [];
8188
8196
  for (const rawDay of daily) {
8189
8197
  const day = rawDay;
8190
8198
  const date = day.date ?? day.period;
8191
- if (typeof date !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(date)) continue;
8199
+ if (typeof date !== "string" || !plausibleUsageDate(date, now)) continue;
8192
8200
  const breakdowns = Array.isArray(day.modelBreakdowns) ? day.modelBreakdowns : [];
8193
8201
  const modelsMap = day.models && typeof day.models === "object" && !Array.isArray(day.models) ? day.models : null;
8194
8202
  const dayCost = normCost(day.totalCost ?? day.costUSD);
@@ -8351,25 +8359,39 @@ function dedupeBlocks(blocks) {
8351
8359
  return [...byStart.values()];
8352
8360
  }
8353
8361
  function selectSourceEntries(source, ccusageEntries, native) {
8354
- if (source === "claude" && native.claude.found && native.claude.entries.length > 0)
8362
+ if (source === "claude" && nativeReaderWon(native.claude))
8355
8363
  return native.claude.entries;
8356
- if (source === "codex" && native.codex.found && native.codex.entries.length > 0)
8364
+ if (source === "codex" && nativeReaderWon(native.codex))
8357
8365
  return native.codex.entries;
8358
8366
  return ccusageEntries;
8359
8367
  }
8368
+ function nativeReaderWon(result) {
8369
+ return result.found && result.entries.length > 0;
8370
+ }
8371
+ function ccusageFallbackSources(native) {
8372
+ return SOURCES.filter(
8373
+ (s) => NATIVE_COVERED_SOURCES.has(s) && !nativeReaderWon(s === "claude" ? native.claude : native.codex)
8374
+ );
8375
+ }
8360
8376
  function ccusageClaudeEnv(env = process.env) {
8361
8377
  if (env.CLAUDE_CONFIG_DIR && env.CLAUDE_CONFIG_DIR.trim()) return env;
8362
8378
  return { ...env, CLAUDE_CONFIG_DIR: resolveClaudeConfigRoots(env).join(",") };
8363
8379
  }
8364
- async function runCcusageOnce(cmd, args, env) {
8380
+ function isRetryableCcusageFailure(err) {
8381
+ const e = err;
8382
+ if (e.killed === true) return false;
8383
+ return e.signal != null || typeof e.code === "string";
8384
+ }
8385
+ async function runCcusageOnce(cmd, args, env, timeoutMs = CCUSAGE_TIMEOUT_MS) {
8365
8386
  try {
8366
8387
  const { stdout } = await execFileAsync(cmd, args, {
8367
8388
  encoding: "utf8",
8368
8389
  maxBuffer: 64 * 1024 * 1024,
8369
- // A single source shouldn't be able to hang the whole run. 25s is plenty
8370
- // for a healthy local read; a hung source gets killed and (if transient)
8371
- // retried once below rather than stalling everything for minutes.
8372
- timeout: 25e3,
8390
+ // A single source shouldn't be able to hang the whole run: a hung source
8391
+ // gets killed and (if transient) retried once below rather than stalling
8392
+ // everything for minutes. The claude/codex fallback passes a longer cap —
8393
+ // see CCUSAGE_FALLBACK_TIMEOUT_MS.
8394
+ timeout: timeoutMs,
8373
8395
  ...env ? { env } : {}
8374
8396
  });
8375
8397
  if (!stdout) return { json: null, transient: false };
@@ -8379,19 +8401,17 @@ async function runCcusageOnce(cmd, args, env) {
8379
8401
  return { json: null, transient: false };
8380
8402
  }
8381
8403
  } catch (err) {
8382
- const e = err;
8383
- const transient = e.killed === true || e.signal != null || typeof e.code === "string";
8384
- return { json: null, transient };
8404
+ return { json: null, transient: isRetryableCcusageFailure(err) };
8385
8405
  }
8386
8406
  }
8387
- async function runCcusage(cmd, args, env) {
8388
- const first = await runCcusageOnce(cmd, args, env);
8407
+ async function runCcusage(cmd, args, env, timeoutMs = CCUSAGE_TIMEOUT_MS) {
8408
+ const first = await runCcusageOnce(cmd, args, env, timeoutMs);
8389
8409
  if (first.json !== null || !first.transient) return first.json;
8390
- return (await runCcusageOnce(cmd, args, env)).json;
8410
+ return (await runCcusageOnce(cmd, args, env, timeoutMs)).json;
8391
8411
  }
8392
8412
  var COLLECT_STAGES = SOURCES.length + 4 + VSCODE_AGENTS.length + 1;
8393
- function isAuthoritativeScan(attributionComplete, nativeClaude, nativeCodex) {
8394
- return attributionComplete && nativeClaude.timedOut !== true && nativeCodex.timedOut !== true;
8413
+ function isAuthoritativeScan(attributionComplete, ...fingerprintReaders) {
8414
+ return attributionComplete && fingerprintReaders.every((reader) => reader.timedOut !== true);
8395
8415
  }
8396
8416
  async function collectAll(onProgress) {
8397
8417
  await loadLivePricing().catch(() => {
@@ -8406,27 +8426,39 @@ async function collectAll(onProgress) {
8406
8426
  () => ({ entries: [], found: false, filesScanned: 0, timedOut: true })
8407
8427
  );
8408
8428
  const sourceTasks = SOURCES.map(async (source) => {
8409
- const env = source === "claude" ? ccusageClaudeEnv() : void 0;
8410
- const json = await runCcusage(
8411
- cmd,
8412
- [...prefixArgs, source, "daily", "--json", "--offline"],
8413
- env
8414
- );
8429
+ if (NATIVE_COVERED_SOURCES.has(source)) {
8430
+ await (source === "claude" ? nativeClaudeTask : nativeCodexTask);
8431
+ tick();
8432
+ return { source, mapped: [] };
8433
+ }
8434
+ const json = await runCcusage(cmd, [
8435
+ ...prefixArgs,
8436
+ source,
8437
+ "daily",
8438
+ "--json",
8439
+ "--offline"
8440
+ ]);
8415
8441
  tick();
8416
8442
  return { source, mapped: json ? mapCcusageDaily(source, json) : [] };
8417
8443
  });
8418
- const sessionTask = runCcusage(cmd, [...prefixArgs, "session", "--json", "--offline"]).then(
8419
- (json) => {
8420
- tick();
8421
- return json ? mapCcusageSessions(json) : [];
8422
- }
8423
- );
8424
- const blockTask = runCcusage(cmd, [...prefixArgs, "blocks", "--json", "--offline"]).then(
8425
- (json) => {
8426
- tick();
8427
- return json ? mapCcusageBlocks(json) : [];
8428
- }
8429
- );
8444
+ const sessionTask = runCcusage(
8445
+ cmd,
8446
+ [...prefixArgs, "session", "--json", "--offline"],
8447
+ void 0,
8448
+ CCUSAGE_AGGREGATE_TIMEOUT_MS
8449
+ ).then((json) => {
8450
+ tick();
8451
+ return json ? mapCcusageSessions(json) : [];
8452
+ });
8453
+ const blockTask = runCcusage(
8454
+ cmd,
8455
+ [...prefixArgs, "blocks", "--json", "--offline"],
8456
+ void 0,
8457
+ CCUSAGE_AGGREGATE_TIMEOUT_MS
8458
+ ).then((json) => {
8459
+ tick();
8460
+ return json ? mapCcusageBlocks(json) : [];
8461
+ });
8430
8462
  const cursorTask = collectCursor().then((c) => {
8431
8463
  tick();
8432
8464
  return c;
@@ -8467,10 +8499,21 @@ async function collectAll(onProgress) {
8467
8499
  continueTask
8468
8500
  ]);
8469
8501
  const native = { claude: nativeClaude, codex: nativeCodex };
8502
+ const fallbacks = /* @__PURE__ */ new Map();
8503
+ for (const source of ccusageFallbackSources(native)) {
8504
+ const json = await runCcusage(
8505
+ cmd,
8506
+ [...prefixArgs, source, "daily", "--json", "--offline"],
8507
+ // For Claude, force ccusage to scan both config roots (dual-dir hardening).
8508
+ source === "claude" ? ccusageClaudeEnv() : void 0,
8509
+ CCUSAGE_FALLBACK_TIMEOUT_MS
8510
+ );
8511
+ fallbacks.set(source, json ? mapCcusageDaily(source, json) : []);
8512
+ }
8470
8513
  const entries = [];
8471
8514
  const toolsFound = [];
8472
8515
  for (const { source, mapped } of sourceResults) {
8473
- const chosen = selectSourceEntries(source, mapped, native);
8516
+ const chosen = selectSourceEntries(source, fallbacks.get(source) ?? mapped, native);
8474
8517
  if (chosen.length > 0) {
8475
8518
  entries.push(...chosen);
8476
8519
  toolsFound.push(source);
@@ -8500,7 +8543,13 @@ async function collectAll(onProgress) {
8500
8543
  ...messageCount ? { messageCount } : {}
8501
8544
  };
8502
8545
  });
8503
- const scanComplete = isAuthoritativeScan(complete, nativeClaude, nativeCodex);
8546
+ const scanComplete = isAuthoritativeScan(
8547
+ complete,
8548
+ nativeClaude,
8549
+ nativeCodex,
8550
+ ...vscodeResults.map(({ result }) => result),
8551
+ continueResult
8552
+ );
8504
8553
  const storePath = provenanceStorePath();
8505
8554
  const reconciled = reconcileProvenance(
8506
8555
  dedupeDaily(entries),
@@ -8545,6 +8594,26 @@ function antigravityNoticeLines() {
8545
8594
  ];
8546
8595
  }
8547
8596
 
8597
+ // src/verify-upload.ts
8598
+ import { createHash } from "node:crypto";
8599
+ function prepareVerifyUpload(requests, scanTimedOut, cap = 5e4) {
8600
+ const sorted = requests.slice().sort((a, b) => b.ts - a.ts);
8601
+ const truncated = scanTimedOut || sorted.length > cap;
8602
+ const capped = sorted.length > cap ? sorted.slice(0, cap) : sorted;
8603
+ const records = capped.map((r) => ({
8604
+ date: r.date,
8605
+ ts: r.ts,
8606
+ tool: "claude",
8607
+ model: r.model.trim().slice(0, 128) || "unknown",
8608
+ inputTokens: r.inputTokens,
8609
+ outputTokens: r.outputTokens,
8610
+ cacheCreationTokens: r.cacheCreationTokens,
8611
+ cacheReadTokens: r.cacheReadTokens,
8612
+ reqHash: createHash("sha256").update(r.key).digest("hex").slice(0, 32)
8613
+ }));
8614
+ return { records, truncated };
8615
+ }
8616
+
8548
8617
  // src/status.ts
8549
8618
  function ago(ms) {
8550
8619
  const mins = Math.round(ms / 6e4);
@@ -9338,7 +9407,7 @@ async function runVerify() {
9338
9407
  }
9339
9408
  }
9340
9409
  console.log(pc2.dim(" Reading your local Claude Code logs\u2026"));
9341
- const { requests, found } = await collectClaudeRequests();
9410
+ const { requests, found, timedOut } = await collectClaudeRequests();
9342
9411
  if (!found || requests.length === 0) {
9343
9412
  console.log(
9344
9413
  pc2.yellow(" No local Claude Code logs found on this machine to verify.")
@@ -9350,21 +9419,10 @@ async function runVerify() {
9350
9419
  );
9351
9420
  return;
9352
9421
  }
9353
- const CAP = 5e4;
9354
- const sorted = requests.slice().sort((a, b) => b.ts - a.ts);
9355
- const truncated = sorted.length > CAP;
9356
- const capped = truncated ? sorted.slice(0, CAP) : sorted;
9357
- const records = capped.map((r) => ({
9358
- date: r.date,
9359
- ts: r.ts,
9360
- tool: "claude",
9361
- model: r.model,
9362
- inputTokens: r.inputTokens,
9363
- outputTokens: r.outputTokens,
9364
- cacheCreationTokens: r.cacheCreationTokens,
9365
- cacheReadTokens: r.cacheReadTokens,
9366
- reqHash: createHash("sha256").update(r.key).digest("hex").slice(0, 32)
9367
- }));
9422
+ const { records, truncated } = prepareVerifyUpload(
9423
+ requests,
9424
+ timedOut === true
9425
+ );
9368
9426
  console.log(
9369
9427
  pc2.dim(
9370
9428
  ` Sending ${records.length} request records for analysis${truncated ? " (most recent, sampled)" : ""}\u2026`
@@ -9397,7 +9455,7 @@ async function runVerify() {
9397
9455
  console.log(result.verdict === "fail" ? pc2.yellow(line) : line);
9398
9456
  console.log(
9399
9457
  pc2.dim(
9400
- " We'll relist you automatically if it checks out \u2014 re-run `npx whoburnedmore` anytime to check."
9458
+ " A reviewer can clear the moderation after checking this evidence \u2014 re-run `npx whoburnedmore` anytime to check."
9401
9459
  )
9402
9460
  );
9403
9461
  }
@@ -9586,7 +9644,7 @@ function printHelp() {
9586
9644
  npx whoburnedmore private take yourself off the public leaderboard
9587
9645
  npx whoburnedmore public put yourself back on it
9588
9646
  npx whoburnedmore remove delete your usage data and stop background sync
9589
- npx whoburnedmore verify delisted? re-verify your usage to get back on (sends a detailed breakdown)
9647
+ npx whoburnedmore verify submit detailed usage evidence for review (never prompts or code)
9590
9648
  npx whoburnedmore status check background-sync health (last sync, staleness)
9591
9649
  npx whoburnedmore uninstall-sync turn off the background sync
9592
9650
  npx whoburnedmore install-sync turn it back on after uninstalling
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whoburnedmore",
3
- "version": "0.9.16",
3
+ "version": "0.9.18",
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": {