whoburnedmore 0.9.15 → 0.9.16

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 (3) hide show
  1. package/README.md +7 -1
  2. package/dist/index.js +472 -46
  3. package/package.json +5 -1
package/README.md CHANGED
@@ -80,7 +80,13 @@ Usage is read via [`ccusage`](https://www.npmjs.com/package/ccusage) from any of
80
80
 
81
81
  `claude` · `codex` · `gemini` · `copilot` · `opencode` · `amp` · `droid` · `goose` · `kimi` · `qwen` · `kilo` · `openclaw` · `hermes` · `pi` · `codebuff`
82
82
 
83
- Whatever you have logs for is picked up automatically you don't pass any flags.
83
+ On top of those, the CLI reads a few agents that `ccusage` can't see, straight from their own local logs:
84
+
85
+ `cursor` · `cline` · `roo` · `continue`
86
+
87
+ (Claude Code and Codex are also read natively for a more accurate count.) Whatever you have logs for is picked up automatically — you don't pass any flags.
88
+
89
+ **Google Antigravity** is detected but **can't be counted**: it stores no readable local usage (its conversation logs are encrypted and it bills flat-rate, so there are no per-request token counts on disk to tally). `npx whoburnedmore doctor` will tell you when it's detected.
84
90
 
85
91
  ## Compete with friends and teams
86
92
 
package/dist/index.js CHANGED
@@ -10,8 +10,8 @@ import { spawn } from "node:child_process";
10
10
  import { createHash } from "node:crypto";
11
11
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
12
12
  import { createRequire as createRequire4 } from "node:module";
13
- import { platform as platform3 } from "node:os";
14
- import { join as join12 } from "node:path";
13
+ import { platform as platform4 } from "node:os";
14
+ import { join as join15 } from "node:path";
15
15
  import { createInterface } from "node:readline/promises";
16
16
  import pc2 from "picocolors";
17
17
 
@@ -789,7 +789,7 @@ async function daemonLoop(deps) {
789
789
  // src/collect.ts
790
790
  import { execFile } from "node:child_process";
791
791
  import { createRequire as createRequire3 } from "node:module";
792
- import { dirname as dirname6, join as join11 } from "node:path";
792
+ import { dirname as dirname6, join as join13 } from "node:path";
793
793
  import { promisify } from "node:util";
794
794
 
795
795
  // src/attribution.ts
@@ -7171,19 +7171,177 @@ async function collectAttribution(env = process.env) {
7171
7171
  return { ...accumulatorToResult(acc), complete };
7172
7172
  }
7173
7173
 
7174
+ // src/continue.ts
7175
+ import { readdir as readdir2 } from "node:fs/promises";
7176
+ import { homedir as homedir5 } from "node:os";
7177
+ import { join as join6 } from "node:path";
7178
+ function num2(n) {
7179
+ const v = Math.round(Number(n));
7180
+ return Number.isFinite(v) && v > 0 ? v : 0;
7181
+ }
7182
+ function toEpochMs(ts) {
7183
+ if (typeof ts === "number" && Number.isFinite(ts)) {
7184
+ if (ts <= 0) return null;
7185
+ return ts >= 1e12 ? ts : ts * 1e3;
7186
+ }
7187
+ if (typeof ts === "string" && ts) {
7188
+ const t = Date.parse(ts);
7189
+ return Number.isFinite(t) ? t : null;
7190
+ }
7191
+ return null;
7192
+ }
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
+ function mapContinueRecords(records) {
7201
+ const byKey = /* @__PURE__ */ new Map();
7202
+ for (const raw of records) {
7203
+ if (!raw || typeof raw !== "object") continue;
7204
+ const rec = raw;
7205
+ if (typeof rec.eventName === "string" && rec.eventName !== "tokensGenerated")
7206
+ continue;
7207
+ const model = typeof rec.model === "string" && rec.model ? rec.model : null;
7208
+ if (!model) continue;
7209
+ const ms = toEpochMs(rec.timestamp ?? rec.eventTimestamp);
7210
+ if (ms === null) continue;
7211
+ const inputTokens = num2(rec.promptTokens ?? rec.prompt_tokens);
7212
+ const outputTokens = num2(rec.generatedTokens ?? rec.generated_tokens);
7213
+ if (inputTokens + outputTokens === 0) continue;
7214
+ const date = localDate2(ms);
7215
+ const key = `${date}|${model}`;
7216
+ let b = byKey.get(key);
7217
+ if (!b) {
7218
+ b = { date, model, inputTokens: 0, outputTokens: 0, costUSD: 0, requestCount: 0 };
7219
+ byKey.set(key, b);
7220
+ }
7221
+ b.inputTokens += inputTokens;
7222
+ b.outputTokens += outputTokens;
7223
+ b.costUSD += estimateCostUSD(model, {
7224
+ inputTokens,
7225
+ outputTokens,
7226
+ cacheCreationTokens: 0,
7227
+ cacheReadTokens: 0
7228
+ });
7229
+ b.requestCount += 1;
7230
+ }
7231
+ const entries = [];
7232
+ for (const b of byKey.values()) {
7233
+ entries.push({
7234
+ date: b.date,
7235
+ tool: "continue",
7236
+ model: b.model,
7237
+ inputTokens: b.inputTokens,
7238
+ outputTokens: b.outputTokens,
7239
+ cacheCreationTokens: 0,
7240
+ cacheReadTokens: 0,
7241
+ costUSD: Number(b.costUSD.toFixed(6)),
7242
+ origin: "cli",
7243
+ verified: false,
7244
+ requestCount: b.requestCount
7245
+ });
7246
+ }
7247
+ return entries;
7248
+ }
7249
+ function parseContinueJsonl(content) {
7250
+ const records = [];
7251
+ for (const line of content.split("\n")) {
7252
+ const trimmed = line.trim();
7253
+ if (!trimmed) continue;
7254
+ try {
7255
+ records.push(JSON.parse(trimmed));
7256
+ } catch {
7257
+ }
7258
+ }
7259
+ return mapContinueRecords(records);
7260
+ }
7261
+ async function listJsonl2(dir) {
7262
+ let dirents;
7263
+ try {
7264
+ dirents = await readdir2(dir, { withFileTypes: true });
7265
+ } catch {
7266
+ return [];
7267
+ }
7268
+ const out = [];
7269
+ for (const d of dirents) {
7270
+ const full = join6(dir, d.name);
7271
+ if (d.isDirectory()) out.push(...await listJsonl2(full));
7272
+ else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
7273
+ }
7274
+ return out;
7275
+ }
7276
+ var CONTINUE_CACHE_VERSION = 1;
7277
+ async function collectContinue(opts = {}) {
7278
+ const env = opts.env ?? process.env;
7279
+ const home = opts.continueDir ?? join6(env.HOME || homedir5(), ".continue");
7280
+ const files = await listJsonl2(join6(home, "dev_data"));
7281
+ if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7282
+ const now = opts.now ?? Date.now;
7283
+ const res = await readFilesWithCache({
7284
+ files,
7285
+ cachePath: opts.cachePath ?? nativeCachePath("continue", env),
7286
+ version: CONTINUE_CACHE_VERSION,
7287
+ parseFile: (content) => parseContinueJsonl(content).map((e) => [
7288
+ e.date,
7289
+ e.model,
7290
+ e.inputTokens,
7291
+ e.outputTokens,
7292
+ e.costUSD,
7293
+ e.requestCount ?? 0
7294
+ ]),
7295
+ deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS),
7296
+ now
7297
+ });
7298
+ if (!res.itemsByFile) {
7299
+ return { entries: [], found: false, filesScanned: res.filesRead, timedOut: true };
7300
+ }
7301
+ const byKey = /* @__PURE__ */ new Map();
7302
+ for (const rows of res.itemsByFile) {
7303
+ for (const r of rows) {
7304
+ const key = `${r[0]}|${r[1]}`;
7305
+ let b = byKey.get(key);
7306
+ if (!b) {
7307
+ b = { date: r[0], model: r[1], inputTokens: 0, outputTokens: 0, costUSD: 0, requestCount: 0 };
7308
+ byKey.set(key, b);
7309
+ }
7310
+ b.inputTokens += r[2];
7311
+ b.outputTokens += r[3];
7312
+ b.costUSD += r[4];
7313
+ b.requestCount += r[5];
7314
+ }
7315
+ }
7316
+ const entries = [...byKey.values()].map((b) => ({
7317
+ date: b.date,
7318
+ tool: "continue",
7319
+ model: b.model,
7320
+ inputTokens: b.inputTokens,
7321
+ outputTokens: b.outputTokens,
7322
+ cacheCreationTokens: 0,
7323
+ cacheReadTokens: 0,
7324
+ costUSD: Number(b.costUSD.toFixed(6)),
7325
+ origin: "cli",
7326
+ verified: false,
7327
+ requestCount: b.requestCount
7328
+ }));
7329
+ return { entries, found: true, filesScanned: res.filesRead };
7330
+ }
7331
+
7174
7332
  // src/cursor.ts
7175
7333
  import { spawnSync as spawnSync3 } from "node:child_process";
7176
7334
  import { existsSync as existsSync3 } from "node:fs";
7177
7335
  import { createRequire as createRequire2 } from "node:module";
7178
- import { homedir as homedir5, platform as platform2 } from "node:os";
7179
- import { join as join7 } from "node:path";
7336
+ import { homedir as homedir6, platform as platform2 } from "node:os";
7337
+ import { join as join8 } from "node:path";
7180
7338
 
7181
7339
  // src/tokscale.ts
7182
7340
  import { spawnSync as spawnSync2 } from "node:child_process";
7183
7341
  import { createRequire } from "node:module";
7184
- import { dirname as dirname3, join as join6 } from "node:path";
7342
+ import { dirname as dirname3, join as join7 } from "node:path";
7185
7343
  var LOOKBACK_DAYS = 30;
7186
- function num2(n) {
7344
+ function num3(n) {
7187
7345
  const v = Math.round(Number(n));
7188
7346
  return Number.isFinite(v) && v > 0 ? v : 0;
7189
7347
  }
@@ -7196,10 +7354,10 @@ function mapTokscaleDay(date, json) {
7196
7354
  if (!Array.isArray(entries)) return [];
7197
7355
  const out = [];
7198
7356
  for (const e of entries) {
7199
- const inputTokens = num2(e.input);
7200
- const outputTokens = num2(e.output) + num2(e.reasoning);
7201
- const cacheCreationTokens = num2(e.cacheWrite);
7202
- const cacheReadTokens = num2(e.cacheRead);
7357
+ const inputTokens = num3(e.input);
7358
+ const outputTokens = num3(e.output) + num3(e.reasoning);
7359
+ const cacheCreationTokens = num3(e.cacheWrite);
7360
+ const cacheReadTokens = num3(e.cacheRead);
7203
7361
  const costUSD = numCost(e.cost);
7204
7362
  const total = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
7205
7363
  if (total === 0 && costUSD === 0) continue;
@@ -7225,7 +7383,7 @@ function resolveTokscaleBin() {
7225
7383
  const pkg = require3("tokscale/package.json");
7226
7384
  const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
7227
7385
  if (!rel) return null;
7228
- const binPath = join6(dirname3(pkgPath), rel);
7386
+ const binPath = join7(dirname3(pkgPath), rel);
7229
7387
  if (/\.(c|m)?js$/.test(binPath)) {
7230
7388
  return { cmd: process.execPath, prefixArgs: [binPath] };
7231
7389
  }
@@ -7283,9 +7441,9 @@ function collectCursorViaTokscale(lookbackDays = LOOKBACK_DAYS) {
7283
7441
  // src/cursor.ts
7284
7442
  var EVENTS_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
7285
7443
  function cursorDbPath() {
7286
- const home = homedir5();
7444
+ const home = homedir6();
7287
7445
  const os = platform2();
7288
- const p = os === "darwin" ? join7(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join7(process.env.APPDATA ?? join7(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join7(process.env.XDG_CONFIG_HOME ?? join7(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
7446
+ const p = os === "darwin" ? join8(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join8(process.env.APPDATA ?? join8(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join8(process.env.XDG_CONFIG_HOME ?? join8(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
7289
7447
  return existsSync3(p) ? p : null;
7290
7448
  }
7291
7449
  function readCursorToken(db) {
@@ -7323,7 +7481,7 @@ function cursorCookie(token) {
7323
7481
  return null;
7324
7482
  }
7325
7483
  }
7326
- function num3(n) {
7484
+ function num4(n) {
7327
7485
  const v = Math.round(Number(n));
7328
7486
  return Number.isFinite(v) && v > 0 ? v : 0;
7329
7487
  }
@@ -7337,10 +7495,10 @@ function mapCursorEvents(events) {
7337
7495
  const d = new Date(ms);
7338
7496
  const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
7339
7497
  const model = e.model || "cursor";
7340
- const input = num3(tu.inputTokens);
7341
- const output = num3(tu.outputTokens);
7342
- const cacheWrite = num3(tu.cacheWriteTokens);
7343
- const cacheRead = num3(tu.cacheReadTokens);
7498
+ const input = num4(tu.inputTokens);
7499
+ const output = num4(tu.outputTokens);
7500
+ const cacheWrite = num4(tu.cacheWriteTokens);
7501
+ const cacheRead = num4(tu.cacheReadTokens);
7344
7502
  const cost = Math.max(0, (Number(tu.totalCents) || 0) / 100);
7345
7503
  const total = input + output + cacheWrite + cacheRead;
7346
7504
  if (total === 0 && cost === 0) continue;
@@ -7425,14 +7583,14 @@ async function collectCursor() {
7425
7583
  }
7426
7584
 
7427
7585
  // src/native/codex.ts
7428
- import { readdir as readdir2 } from "node:fs/promises";
7429
- import { homedir as homedir6 } from "node:os";
7430
- import { join as join8 } from "node:path";
7431
- function num4(n) {
7586
+ import { readdir as readdir3 } from "node:fs/promises";
7587
+ import { homedir as homedir7 } from "node:os";
7588
+ import { join as join9 } from "node:path";
7589
+ function num5(n) {
7432
7590
  const v = Math.round(Number(n));
7433
7591
  return Number.isFinite(v) && v > 0 ? v : 0;
7434
7592
  }
7435
- function localDate2(iso) {
7593
+ function localDate3(iso) {
7436
7594
  const t = Date.parse(iso);
7437
7595
  if (!Number.isFinite(t)) return null;
7438
7596
  const d = new Date(t);
@@ -7448,10 +7606,10 @@ function readTokenFields(payload) {
7448
7606
  const output = src.output_tokens;
7449
7607
  if (input === void 0 && output === void 0) return null;
7450
7608
  return {
7451
- input: num4(input),
7452
- cached: num4(src.cached_input_tokens),
7453
- output: num4(output),
7454
- reasoning: num4(src.reasoning_output_tokens)
7609
+ input: num5(input),
7610
+ cached: num5(src.cached_input_tokens),
7611
+ output: num5(output),
7612
+ reasoning: num5(src.reasoning_output_tokens)
7455
7613
  };
7456
7614
  }
7457
7615
  function parseCodexRollout(lines) {
@@ -7476,7 +7634,7 @@ function parseCodexRollout(lines) {
7476
7634
  if (payload.type === "token_count") {
7477
7635
  const fields = readTokenFields(payload);
7478
7636
  if (!fields) continue;
7479
- const parsed = localDate2(String(obj.timestamp ?? ""));
7637
+ const parsed = localDate3(String(obj.timestamp ?? ""));
7480
7638
  const day = parsed ?? lastSeenDate;
7481
7639
  if (!day) continue;
7482
7640
  lastSeenDate = day;
@@ -7559,20 +7717,20 @@ function finalizeCodexEntries(acc) {
7559
7717
  return entries;
7560
7718
  }
7561
7719
  function resolveCodexSessionsDir(env = process.env) {
7562
- const home = env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join8(homedir6(), ".codex");
7563
- return join8(home, "sessions");
7720
+ const home = env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join9(homedir7(), ".codex");
7721
+ return join9(home, "sessions");
7564
7722
  }
7565
- async function listJsonl2(dir) {
7723
+ async function listJsonl3(dir) {
7566
7724
  let dirents;
7567
7725
  try {
7568
- dirents = await readdir2(dir, { withFileTypes: true });
7726
+ dirents = await readdir3(dir, { withFileTypes: true });
7569
7727
  } catch {
7570
7728
  return [];
7571
7729
  }
7572
7730
  const out = [];
7573
7731
  for (const d of dirents) {
7574
- const full = join8(dir, d.name);
7575
- if (d.isDirectory()) out.push(...await listJsonl2(full));
7732
+ const full = join9(dir, d.name);
7733
+ if (d.isDirectory()) out.push(...await listJsonl3(full));
7576
7734
  else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
7577
7735
  }
7578
7736
  return out;
@@ -7613,7 +7771,7 @@ function fromCachedSession(t) {
7613
7771
  }
7614
7772
  async function collectCodexNative(env = process.env, opts = {}) {
7615
7773
  const dir = resolveCodexSessionsDir(env);
7616
- const files = await listJsonl2(dir);
7774
+ const files = await listJsonl3(dir);
7617
7775
  if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7618
7776
  const now = opts.now ?? Date.now;
7619
7777
  const res = await readFilesWithCache({
@@ -7643,13 +7801,222 @@ async function collectCodexNative(env = process.env, opts = {}) {
7643
7801
  };
7644
7802
  }
7645
7803
 
7804
+ // src/native/vscode-agents.ts
7805
+ import { readdir as readdir4 } from "node:fs/promises";
7806
+ import { homedir as homedir8, platform as platform3 } from "node:os";
7807
+ import { join as join10 } from "node:path";
7808
+ var VSCODE_AGENTS = [
7809
+ { tool: "cline", extIds: ["saoudrizwan.claude-dev"] },
7810
+ { tool: "roo", extIds: ["rooveterinaryinc.roo-cline"] }
7811
+ ];
7812
+ function num6(n) {
7813
+ const v = Math.round(Number(n));
7814
+ return Number.isFinite(v) && v > 0 ? v : 0;
7815
+ }
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
+ function parseApiReqMessage(tool, msg) {
7825
+ if (msg.say !== "api_req_started") return null;
7826
+ const date = localDate4(Number(msg.ts));
7827
+ if (!date) return null;
7828
+ let payload = null;
7829
+ const text = msg.text;
7830
+ if (typeof text === "string") {
7831
+ try {
7832
+ payload = JSON.parse(text);
7833
+ } catch {
7834
+ return null;
7835
+ }
7836
+ } else if (text && typeof text === "object") {
7837
+ payload = text;
7838
+ }
7839
+ if (!payload) return null;
7840
+ const inputTokens = num6(payload.tokensIn);
7841
+ const outputTokens = num6(payload.tokensOut);
7842
+ const cacheCreationTokens = num6(payload.cacheWrites);
7843
+ const cacheReadTokens = num6(payload.cacheReads);
7844
+ if (inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens === 0)
7845
+ return null;
7846
+ const modelInfo = msg.modelInfo;
7847
+ const modelFromRow = modelInfo && typeof modelInfo === "object" && typeof modelInfo.modelId === "string" && modelInfo.modelId ? modelInfo.modelId : null;
7848
+ const modelFromText = typeof payload.model === "string" && payload.model ? payload.model : null;
7849
+ const storedCostRaw = Number(payload.cost);
7850
+ return {
7851
+ date,
7852
+ model: modelFromRow ?? modelFromText ?? tool,
7853
+ inputTokens,
7854
+ outputTokens,
7855
+ cacheCreationTokens,
7856
+ cacheReadTokens,
7857
+ storedCost: Number.isFinite(storedCostRaw) && storedCostRaw > 0 ? storedCostRaw : null
7858
+ };
7859
+ }
7860
+ function bucketsToEntries(tool, buckets) {
7861
+ const entries = [];
7862
+ for (const b of buckets) {
7863
+ const tokens = b.inputTokens + b.outputTokens + b.cacheCreationTokens + b.cacheReadTokens;
7864
+ if (tokens === 0) continue;
7865
+ entries.push({
7866
+ date: b.date,
7867
+ tool,
7868
+ model: b.model,
7869
+ inputTokens: b.inputTokens,
7870
+ outputTokens: b.outputTokens,
7871
+ cacheCreationTokens: b.cacheCreationTokens,
7872
+ cacheReadTokens: b.cacheReadTokens,
7873
+ costUSD: Number(b.costUSD.toFixed(6)),
7874
+ origin: "cli",
7875
+ verified: false,
7876
+ requestCount: b.requestCount
7877
+ });
7878
+ }
7879
+ return entries;
7880
+ }
7881
+ function aggregateVscodeAgentEntries(tool, messages) {
7882
+ const byKey = /* @__PURE__ */ new Map();
7883
+ for (const raw of messages) {
7884
+ if (!raw || typeof raw !== "object") continue;
7885
+ const req = parseApiReqMessage(tool, raw);
7886
+ if (!req) continue;
7887
+ const key = `${req.date}|${req.model}`;
7888
+ let b = byKey.get(key);
7889
+ if (!b) {
7890
+ b = {
7891
+ date: req.date,
7892
+ model: req.model,
7893
+ inputTokens: 0,
7894
+ outputTokens: 0,
7895
+ cacheCreationTokens: 0,
7896
+ cacheReadTokens: 0,
7897
+ costUSD: 0,
7898
+ requestCount: 0
7899
+ };
7900
+ byKey.set(key, b);
7901
+ }
7902
+ b.inputTokens += req.inputTokens;
7903
+ b.outputTokens += req.outputTokens;
7904
+ b.cacheCreationTokens += req.cacheCreationTokens;
7905
+ b.cacheReadTokens += req.cacheReadTokens;
7906
+ b.costUSD += req.storedCost ?? estimateCostUSD(req.model, {
7907
+ inputTokens: req.inputTokens,
7908
+ outputTokens: req.outputTokens,
7909
+ cacheCreationTokens: req.cacheCreationTokens,
7910
+ cacheReadTokens: req.cacheReadTokens
7911
+ });
7912
+ b.requestCount += 1;
7913
+ }
7914
+ return bucketsToEntries(tool, byKey.values());
7915
+ }
7916
+ function parseVscodeAgentMessages(tool, raw) {
7917
+ let arr;
7918
+ try {
7919
+ arr = JSON.parse(raw);
7920
+ } catch {
7921
+ return [];
7922
+ }
7923
+ if (!Array.isArray(arr)) return [];
7924
+ return aggregateVscodeAgentEntries(tool, arr);
7925
+ }
7926
+ function vscodeGlobalStorageRoots(env = process.env) {
7927
+ const apps = ["Code", "Code - Insiders", "Cursor", "VSCodium", "Windsurf"];
7928
+ const home = env.HOME || homedir8();
7929
+ const os = platform3();
7930
+ const base = (app) => os === "darwin" ? join10(home, "Library", "Application Support", app, "User", "globalStorage") : os === "win32" ? join10(env.APPDATA ?? join10(home, "AppData", "Roaming"), app, "User", "globalStorage") : join10(env.XDG_CONFIG_HOME ?? join10(home, ".config"), app, "User", "globalStorage");
7931
+ return apps.map(base);
7932
+ }
7933
+ async function listTaskFiles(roots, extIds) {
7934
+ const files = [];
7935
+ for (const root of roots) {
7936
+ for (const extId of extIds) {
7937
+ const tasksDir = join10(root, extId, "tasks");
7938
+ let taskDirs;
7939
+ try {
7940
+ const dirents = await readdir4(tasksDir, { withFileTypes: true });
7941
+ taskDirs = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
7942
+ } catch {
7943
+ continue;
7944
+ }
7945
+ for (const t of taskDirs) files.push(join10(tasksDir, t, "ui_messages.json"));
7946
+ }
7947
+ }
7948
+ return files;
7949
+ }
7950
+ var VSCODE_CACHE_VERSION = 1;
7951
+ function entryToRow(e) {
7952
+ return [
7953
+ e.date,
7954
+ e.model,
7955
+ e.inputTokens,
7956
+ e.outputTokens,
7957
+ e.cacheCreationTokens,
7958
+ e.cacheReadTokens,
7959
+ e.costUSD,
7960
+ e.requestCount ?? 0
7961
+ ];
7962
+ }
7963
+ async function collectVscodeAgent(opts) {
7964
+ const env = opts.env ?? process.env;
7965
+ const roots = opts.roots ?? vscodeGlobalStorageRoots(env);
7966
+ const files = await listTaskFiles(roots, opts.extIds);
7967
+ if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7968
+ const now = opts.now ?? Date.now;
7969
+ const res = await readFilesWithCache({
7970
+ files,
7971
+ cachePath: opts.cachePath ?? nativeCachePath(`vscode-${opts.tool}`, env),
7972
+ version: VSCODE_CACHE_VERSION,
7973
+ parseFile: (content) => parseVscodeAgentMessages(opts.tool, content).map(entryToRow),
7974
+ deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS),
7975
+ now
7976
+ });
7977
+ if (!res.itemsByFile) {
7978
+ return { entries: [], found: false, filesScanned: res.filesRead, timedOut: true };
7979
+ }
7980
+ const byKey = /* @__PURE__ */ new Map();
7981
+ for (const rows of res.itemsByFile) {
7982
+ for (const r of rows) {
7983
+ const key = `${r[0]}|${r[1]}`;
7984
+ let b = byKey.get(key);
7985
+ if (!b) {
7986
+ b = {
7987
+ date: r[0],
7988
+ model: r[1],
7989
+ inputTokens: 0,
7990
+ outputTokens: 0,
7991
+ cacheCreationTokens: 0,
7992
+ cacheReadTokens: 0,
7993
+ costUSD: 0,
7994
+ requestCount: 0
7995
+ };
7996
+ byKey.set(key, b);
7997
+ }
7998
+ b.inputTokens += r[2];
7999
+ b.outputTokens += r[3];
8000
+ b.cacheCreationTokens += r[4];
8001
+ b.cacheReadTokens += r[5];
8002
+ b.costUSD += r[6];
8003
+ b.requestCount += r[7];
8004
+ }
8005
+ }
8006
+ return {
8007
+ entries: bucketsToEntries(opts.tool, byKey.values()),
8008
+ found: true,
8009
+ filesScanned: res.filesRead
8010
+ };
8011
+ }
8012
+
7646
8013
  // src/pricing-live.ts
7647
8014
  import { mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
7648
- import { dirname as dirname4, join as join9 } from "node:path";
8015
+ import { dirname as dirname4, join as join11 } from "node:path";
7649
8016
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
7650
8017
  var FETCH_TIMEOUT_MS = 5e3;
7651
8018
  function pricingCachePath(dir = defaultConfigDir()) {
7652
- return join9(dir, "pricing-cache.json");
8019
+ return join11(dir, "pricing-cache.json");
7653
8020
  }
7654
8021
  async function readCache(path) {
7655
8022
  try {
@@ -7702,7 +8069,7 @@ async function loadLivePricing(env = process.env, now = Date.now, cachePath = pr
7702
8069
 
7703
8070
  // src/provenance-store.ts
7704
8071
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
7705
- import { dirname as dirname5, join as join10 } from "node:path";
8072
+ import { dirname as dirname5, join as join12 } from "node:path";
7706
8073
  var PROVENANCE_STORE_VERSION = 1;
7707
8074
  var KEY_SEP = "|";
7708
8075
  var keyOf = (date, tool) => `${date}${KEY_SEP}${tool}`;
@@ -7712,7 +8079,7 @@ function entryTokens(e) {
7712
8079
  function provenanceStorePath(env = process.env) {
7713
8080
  const override = env.WHOBURNEDMORE_CONFIG_DIR?.trim();
7714
8081
  const dir = override || defaultConfigDir();
7715
- return join10(dir, "native-cache-provenance.json");
8082
+ return join12(dir, "native-cache-provenance.json");
7716
8083
  }
7717
8084
  function loadProvenanceStore(path) {
7718
8085
  try {
@@ -7927,7 +8294,7 @@ function resolveCcusageBin() {
7927
8294
  const pkgPath = require3.resolve("ccusage/package.json");
7928
8295
  const pkg = require3("ccusage/package.json");
7929
8296
  const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.ccusage ?? "ccusage";
7930
- const binPath = join11(dirname6(pkgPath), rel);
8297
+ const binPath = join13(dirname6(pkgPath), rel);
7931
8298
  if (/\.(c|m)?js$/.test(binPath)) {
7932
8299
  return { cmd: process.execPath, prefixArgs: [binPath] };
7933
8300
  }
@@ -8022,7 +8389,7 @@ async function runCcusage(cmd, args, env) {
8022
8389
  if (first.json !== null || !first.transient) return first.json;
8023
8390
  return (await runCcusageOnce(cmd, args, env)).json;
8024
8391
  }
8025
- var COLLECT_STAGES = SOURCES.length + 4;
8392
+ var COLLECT_STAGES = SOURCES.length + 4 + VSCODE_AGENTS.length + 1;
8026
8393
  function isAuthoritativeScan(attributionComplete, nativeClaude, nativeCodex) {
8027
8394
  return attributionComplete && nativeClaude.timedOut !== true && nativeCodex.timedOut !== true;
8028
8395
  }
@@ -8068,14 +8435,36 @@ async function collectAll(onProgress) {
8068
8435
  tick();
8069
8436
  return a;
8070
8437
  });
8071
- const [sourceResults, sessions, blocks, cursor, attribution, nativeClaude, nativeCodex] = await Promise.all([
8438
+ const vscodeTasks = VSCODE_AGENTS.map(
8439
+ (a) => collectVscodeAgent({ tool: a.tool, extIds: a.extIds }).then((r) => {
8440
+ tick();
8441
+ return { tool: a.tool, result: r };
8442
+ })
8443
+ );
8444
+ const continueTask = collectContinue().then((r) => {
8445
+ tick();
8446
+ return r;
8447
+ });
8448
+ const [
8449
+ sourceResults,
8450
+ sessions,
8451
+ blocks,
8452
+ cursor,
8453
+ attribution,
8454
+ nativeClaude,
8455
+ nativeCodex,
8456
+ vscodeResults,
8457
+ continueResult
8458
+ ] = await Promise.all([
8072
8459
  Promise.all(sourceTasks),
8073
8460
  sessionTask,
8074
8461
  blockTask,
8075
8462
  cursorTask,
8076
8463
  attributionTask,
8077
8464
  nativeClaudeTask,
8078
- nativeCodexTask
8465
+ nativeCodexTask,
8466
+ Promise.all(vscodeTasks),
8467
+ continueTask
8079
8468
  ]);
8080
8469
  const native = { claude: nativeClaude, codex: nativeCodex };
8081
8470
  const entries = [];
@@ -8092,6 +8481,16 @@ async function collectAll(onProgress) {
8092
8481
  blocks.push(...cursor.blocks);
8093
8482
  toolsFound.push("cursor");
8094
8483
  }
8484
+ for (const { tool, result } of vscodeResults) {
8485
+ if (result.found && result.entries.length > 0) {
8486
+ entries.push(...result.entries);
8487
+ toolsFound.push(tool);
8488
+ }
8489
+ }
8490
+ if (continueResult.found && continueResult.entries.length > 0) {
8491
+ entries.push(...continueResult.entries);
8492
+ toolsFound.push("continue");
8493
+ }
8095
8494
  const { tools, skills, agent, sessionMessages, complete } = attribution;
8096
8495
  onProgress?.(COLLECT_STAGES, COLLECT_STAGES, "");
8097
8496
  const dedupedSessions = dedupeSessions(sessions).map((s) => {
@@ -8127,6 +8526,25 @@ async function collectAll(onProgress) {
8127
8526
  };
8128
8527
  }
8129
8528
 
8529
+ // src/antigravity.ts
8530
+ import { existsSync as existsSync4 } from "node:fs";
8531
+ import { homedir as homedir9 } from "node:os";
8532
+ import { join as join14 } from "node:path";
8533
+ function antigravityDataDir(env = process.env) {
8534
+ return join14(env.HOME || homedir9(), ".gemini", "antigravity");
8535
+ }
8536
+ function detectAntigravity(env = process.env) {
8537
+ return existsSync4(antigravityDataDir(env));
8538
+ }
8539
+ function antigravityNoticeLines() {
8540
+ return [
8541
+ " \u2022 Google Antigravity detected \u2014 but its usage can't be counted.",
8542
+ " Antigravity stores no readable local usage: its conversation logs are",
8543
+ " encrypted and it bills flat-rate, so there are no per-request token",
8544
+ " counts on disk to tally. It's the one major agent we can't put on the board."
8545
+ ];
8546
+ }
8547
+
8130
8548
  // src/status.ts
8131
8549
  function ago(ms) {
8132
8550
  const mins = Math.round(ms / 6e4);
@@ -8514,7 +8932,7 @@ function startProgress() {
8514
8932
  }
8515
8933
  function openBrowser(url) {
8516
8934
  if (!isOpenableUrl(url)) return;
8517
- const os = platform3();
8935
+ const os = platform4();
8518
8936
  const [cmd, args] = os === "darwin" ? ["open", [url]] : os === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
8519
8937
  spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
8520
8938
  }
@@ -8528,7 +8946,7 @@ async function confirm(question) {
8528
8946
  function showLocalDashboard(payload) {
8529
8947
  const dir = defaultConfigDir();
8530
8948
  mkdirSync5(dir, { recursive: true });
8531
- const file = join12(dir, "dashboard.html");
8949
+ const file = join15(dir, "dashboard.html");
8532
8950
  writeFileSync5(
8533
8951
  file,
8534
8952
  renderDashboardHtml(payload.entries, /* @__PURE__ */ new Date(), { webBaseUrl: webBase() })
@@ -8565,6 +8983,10 @@ async function run(flags) {
8565
8983
  console.log();
8566
8984
  console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
8567
8985
  console.log(pc2.dim(" Use Claude Code, Codex, Gemini CLI (or friends) and come back."));
8986
+ if (detectAntigravity()) {
8987
+ console.log();
8988
+ for (const line of antigravityNoticeLines()) console.log(pc2.dim(line));
8989
+ }
8568
8990
  return;
8569
8991
  }
8570
8992
  const payload = { cliVersion: VERSION, entries };
@@ -9117,6 +9539,10 @@ async function main() {
9117
9539
  case "status":
9118
9540
  case "doctor": {
9119
9541
  for (const line of agentStatusReport()) console.log(line);
9542
+ if (detectAntigravity()) {
9543
+ console.log("");
9544
+ for (const line of antigravityNoticeLines()) console.log(line);
9545
+ }
9120
9546
  break;
9121
9547
  }
9122
9548
  case "private":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whoburnedmore",
3
- "version": "0.9.15",
3
+ "version": "0.9.16",
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": {
@@ -51,6 +51,10 @@
51
51
  "codex",
52
52
  "gemini-cli",
53
53
  "copilot",
54
+ "cursor",
55
+ "cline",
56
+ "roo",
57
+ "continue",
54
58
  "ccusage",
55
59
  "usage",
56
60
  "cli"