whoburnedmore 0.9.15 → 0.9.17

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 +528 -96
  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
@@ -7,11 +7,10 @@ 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
- import { platform as platform3 } from "node:os";
14
- import { join as join12 } from "node:path";
12
+ import { platform as platform4 } from "node:os";
13
+ import { join as join15 } from "node:path";
15
14
  import { createInterface } from "node:readline/promises";
16
15
  import pc2 from "picocolors";
17
16
 
@@ -789,7 +788,7 @@ async function daemonLoop(deps) {
789
788
  // src/collect.ts
790
789
  import { execFile } from "node:child_process";
791
790
  import { createRequire as createRequire3 } from "node:module";
792
- import { dirname as dirname6, join as join11 } from "node:path";
791
+ import { dirname as dirname6, join as join13 } from "node:path";
793
792
  import { promisify } from "node:util";
794
793
 
795
794
  // src/attribution.ts
@@ -6618,6 +6617,31 @@ async function readFilesWithCache(opts) {
6618
6617
  return { itemsByFile, filesRead, timedOut: false };
6619
6618
  }
6620
6619
 
6620
+ // src/native/usage-date.ts
6621
+ var MIN_USAGE_TIMESTAMP_MS = Date.UTC(2020, 0, 1);
6622
+ var MAX_CLOCK_SKEW_MS = 2 * 864e5;
6623
+ function localUsageDate(ms, now = Date.now()) {
6624
+ if (!Number.isFinite(ms) || ms < MIN_USAGE_TIMESTAMP_MS || ms > now + MAX_CLOCK_SKEW_MS) {
6625
+ return null;
6626
+ }
6627
+ const d = new Date(ms);
6628
+ if (!Number.isFinite(d.getTime())) return null;
6629
+ const y = d.getFullYear();
6630
+ const m = String(d.getMonth() + 1).padStart(2, "0");
6631
+ const day = String(d.getDate()).padStart(2, "0");
6632
+ return `${y}-${m}-${day}`;
6633
+ }
6634
+ function plausibleUsageDate(value, now = Date.now()) {
6635
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
6636
+ if (!match) return false;
6637
+ const year = Number(match[1]);
6638
+ const month = Number(match[2]);
6639
+ const day = Number(match[3]);
6640
+ const ms = Date.UTC(year, month - 1, day);
6641
+ const parsed = new Date(ms);
6642
+ return parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month - 1 && parsed.getUTCDate() === day && ms >= MIN_USAGE_TIMESTAMP_MS && ms <= now + MAX_CLOCK_SKEW_MS;
6643
+ }
6644
+
6621
6645
  // src/native/claude.ts
6622
6646
  function num(n) {
6623
6647
  const v = Math.round(Number(n));
@@ -6626,15 +6650,6 @@ function num(n) {
6626
6650
  function reqTokens(r) {
6627
6651
  return r.inputTokens + r.outputTokens + r.cacheCreationTokens + r.cacheReadTokens;
6628
6652
  }
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
6653
  var syntheticCounter = 0;
6639
6654
  function parseClaudeLine(raw) {
6640
6655
  const trimmed = raw.trim();
@@ -6650,10 +6665,10 @@ function parseClaudeLine(raw) {
6650
6665
  const usage = message.usage;
6651
6666
  if (!usage || typeof usage !== "object") return null;
6652
6667
  if (message.role !== void 0 && message.role !== "assistant") return null;
6653
- const date = localDate(String(obj.timestamp ?? ""));
6654
- if (!date) return null;
6655
6668
  const tsParsed = Date.parse(String(obj.timestamp ?? ""));
6656
- const ts = Number.isFinite(tsParsed) ? tsParsed : 0;
6669
+ const date = localUsageDate(tsParsed);
6670
+ if (!date) return null;
6671
+ const ts = tsParsed;
6657
6672
  const messageId = typeof message.id === "string" ? message.id : "";
6658
6673
  const requestId = typeof obj.requestId === "string" ? obj.requestId : "";
6659
6674
  const hasRealId = messageId !== "" || requestId !== "";
@@ -6747,7 +6762,7 @@ async function listJsonl(dir) {
6747
6762
  return out;
6748
6763
  }
6749
6764
  var NATIVE_READ_BUDGET_MS = 45e3;
6750
- var CLAUDE_CACHE_VERSION = 1;
6765
+ var CLAUDE_CACHE_VERSION = 2;
6751
6766
  function toCached(r) {
6752
6767
  return [
6753
6768
  r.key,
@@ -7171,19 +7186,171 @@ async function collectAttribution(env = process.env) {
7171
7186
  return { ...accumulatorToResult(acc), complete };
7172
7187
  }
7173
7188
 
7189
+ // src/continue.ts
7190
+ import { readdir as readdir2 } from "node:fs/promises";
7191
+ import { homedir as homedir5 } from "node:os";
7192
+ import { join as join6 } from "node:path";
7193
+ function num2(n) {
7194
+ const v = Math.round(Number(n));
7195
+ return Number.isFinite(v) && v > 0 ? v : 0;
7196
+ }
7197
+ function toEpochMs(ts) {
7198
+ if (typeof ts === "number" && Number.isFinite(ts)) {
7199
+ if (ts <= 0) return null;
7200
+ return ts >= 1e12 ? ts : ts * 1e3;
7201
+ }
7202
+ if (typeof ts === "string" && ts) {
7203
+ const t = Date.parse(ts);
7204
+ return Number.isFinite(t) ? t : null;
7205
+ }
7206
+ return null;
7207
+ }
7208
+ function mapContinueRecords(records) {
7209
+ const byKey = /* @__PURE__ */ new Map();
7210
+ for (const raw of records) {
7211
+ if (!raw || typeof raw !== "object") continue;
7212
+ const rec = raw;
7213
+ if (typeof rec.eventName === "string" && rec.eventName !== "tokensGenerated")
7214
+ continue;
7215
+ const model = typeof rec.model === "string" && rec.model ? rec.model : null;
7216
+ if (!model) continue;
7217
+ const ms = toEpochMs(rec.timestamp ?? rec.eventTimestamp);
7218
+ if (ms === null) continue;
7219
+ const date = localUsageDate(ms);
7220
+ if (!date) continue;
7221
+ const inputTokens = num2(rec.promptTokens ?? rec.prompt_tokens);
7222
+ const outputTokens = num2(rec.generatedTokens ?? rec.generated_tokens);
7223
+ if (inputTokens + outputTokens === 0) continue;
7224
+ const key = `${date}|${model}`;
7225
+ let b = byKey.get(key);
7226
+ if (!b) {
7227
+ b = { date, model, inputTokens: 0, outputTokens: 0, costUSD: 0, requestCount: 0 };
7228
+ byKey.set(key, b);
7229
+ }
7230
+ b.inputTokens += inputTokens;
7231
+ b.outputTokens += outputTokens;
7232
+ b.costUSD += estimateCostUSD(model, {
7233
+ inputTokens,
7234
+ outputTokens,
7235
+ cacheCreationTokens: 0,
7236
+ cacheReadTokens: 0
7237
+ });
7238
+ b.requestCount += 1;
7239
+ }
7240
+ const entries = [];
7241
+ for (const b of byKey.values()) {
7242
+ entries.push({
7243
+ date: b.date,
7244
+ tool: "continue",
7245
+ model: b.model,
7246
+ inputTokens: b.inputTokens,
7247
+ outputTokens: b.outputTokens,
7248
+ cacheCreationTokens: 0,
7249
+ cacheReadTokens: 0,
7250
+ costUSD: Number(b.costUSD.toFixed(6)),
7251
+ origin: "cli",
7252
+ verified: false,
7253
+ requestCount: b.requestCount
7254
+ });
7255
+ }
7256
+ return entries;
7257
+ }
7258
+ function parseContinueJsonl(content) {
7259
+ const records = [];
7260
+ for (const line of content.split("\n")) {
7261
+ const trimmed = line.trim();
7262
+ if (!trimmed) continue;
7263
+ try {
7264
+ records.push(JSON.parse(trimmed));
7265
+ } catch {
7266
+ }
7267
+ }
7268
+ return mapContinueRecords(records);
7269
+ }
7270
+ async function listJsonl2(dir) {
7271
+ let dirents;
7272
+ try {
7273
+ dirents = await readdir2(dir, { withFileTypes: true });
7274
+ } catch {
7275
+ return [];
7276
+ }
7277
+ const out = [];
7278
+ for (const d of dirents) {
7279
+ const full = join6(dir, d.name);
7280
+ if (d.isDirectory()) out.push(...await listJsonl2(full));
7281
+ else if (d.isFile() && d.name === "tokensGenerated.jsonl") out.push(full);
7282
+ }
7283
+ return out;
7284
+ }
7285
+ var CONTINUE_CACHE_VERSION = 2;
7286
+ async function collectContinue(opts = {}) {
7287
+ const env = opts.env ?? process.env;
7288
+ const home = opts.continueDir ?? join6(env.HOME || homedir5(), ".continue");
7289
+ const files = await listJsonl2(join6(home, "dev_data"));
7290
+ if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7291
+ const now = opts.now ?? Date.now;
7292
+ const res = await readFilesWithCache({
7293
+ files,
7294
+ cachePath: opts.cachePath ?? nativeCachePath("continue", env),
7295
+ version: CONTINUE_CACHE_VERSION,
7296
+ parseFile: (content) => parseContinueJsonl(content).map((e) => [
7297
+ e.date,
7298
+ e.model,
7299
+ e.inputTokens,
7300
+ e.outputTokens,
7301
+ e.costUSD,
7302
+ e.requestCount ?? 0
7303
+ ]),
7304
+ deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS),
7305
+ now
7306
+ });
7307
+ if (!res.itemsByFile) {
7308
+ return { entries: [], found: false, filesScanned: res.filesRead, timedOut: true };
7309
+ }
7310
+ const byKey = /* @__PURE__ */ new Map();
7311
+ for (const rows of res.itemsByFile) {
7312
+ for (const r of rows) {
7313
+ const key = `${r[0]}|${r[1]}`;
7314
+ let b = byKey.get(key);
7315
+ if (!b) {
7316
+ b = { date: r[0], model: r[1], inputTokens: 0, outputTokens: 0, costUSD: 0, requestCount: 0 };
7317
+ byKey.set(key, b);
7318
+ }
7319
+ b.inputTokens += r[2];
7320
+ b.outputTokens += r[3];
7321
+ b.costUSD += r[4];
7322
+ b.requestCount += r[5];
7323
+ }
7324
+ }
7325
+ const entries = [...byKey.values()].map((b) => ({
7326
+ date: b.date,
7327
+ tool: "continue",
7328
+ model: b.model,
7329
+ inputTokens: b.inputTokens,
7330
+ outputTokens: b.outputTokens,
7331
+ cacheCreationTokens: 0,
7332
+ cacheReadTokens: 0,
7333
+ costUSD: Number(b.costUSD.toFixed(6)),
7334
+ origin: "cli",
7335
+ verified: false,
7336
+ requestCount: b.requestCount
7337
+ }));
7338
+ return { entries, found: true, filesScanned: res.filesRead };
7339
+ }
7340
+
7174
7341
  // src/cursor.ts
7175
7342
  import { spawnSync as spawnSync3 } from "node:child_process";
7176
7343
  import { existsSync as existsSync3 } from "node:fs";
7177
7344
  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";
7345
+ import { homedir as homedir6, platform as platform2 } from "node:os";
7346
+ import { join as join8 } from "node:path";
7180
7347
 
7181
7348
  // src/tokscale.ts
7182
7349
  import { spawnSync as spawnSync2 } from "node:child_process";
7183
7350
  import { createRequire } from "node:module";
7184
- import { dirname as dirname3, join as join6 } from "node:path";
7351
+ import { dirname as dirname3, join as join7 } from "node:path";
7185
7352
  var LOOKBACK_DAYS = 30;
7186
- function num2(n) {
7353
+ function num3(n) {
7187
7354
  const v = Math.round(Number(n));
7188
7355
  return Number.isFinite(v) && v > 0 ? v : 0;
7189
7356
  }
@@ -7196,10 +7363,10 @@ function mapTokscaleDay(date, json) {
7196
7363
  if (!Array.isArray(entries)) return [];
7197
7364
  const out = [];
7198
7365
  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);
7366
+ const inputTokens = num3(e.input);
7367
+ const outputTokens = num3(e.output) + num3(e.reasoning);
7368
+ const cacheCreationTokens = num3(e.cacheWrite);
7369
+ const cacheReadTokens = num3(e.cacheRead);
7203
7370
  const costUSD = numCost(e.cost);
7204
7371
  const total = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
7205
7372
  if (total === 0 && costUSD === 0) continue;
@@ -7225,7 +7392,7 @@ function resolveTokscaleBin() {
7225
7392
  const pkg = require3("tokscale/package.json");
7226
7393
  const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
7227
7394
  if (!rel) return null;
7228
- const binPath = join6(dirname3(pkgPath), rel);
7395
+ const binPath = join7(dirname3(pkgPath), rel);
7229
7396
  if (/\.(c|m)?js$/.test(binPath)) {
7230
7397
  return { cmd: process.execPath, prefixArgs: [binPath] };
7231
7398
  }
@@ -7283,9 +7450,9 @@ function collectCursorViaTokscale(lookbackDays = LOOKBACK_DAYS) {
7283
7450
  // src/cursor.ts
7284
7451
  var EVENTS_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
7285
7452
  function cursorDbPath() {
7286
- const home = homedir5();
7453
+ const home = homedir6();
7287
7454
  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");
7455
+ 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
7456
  return existsSync3(p) ? p : null;
7290
7457
  }
7291
7458
  function readCursorToken(db) {
@@ -7323,7 +7490,7 @@ function cursorCookie(token) {
7323
7490
  return null;
7324
7491
  }
7325
7492
  }
7326
- function num3(n) {
7493
+ function num4(n) {
7327
7494
  const v = Math.round(Number(n));
7328
7495
  return Number.isFinite(v) && v > 0 ? v : 0;
7329
7496
  }
@@ -7335,12 +7502,13 @@ function mapCursorEvents(events) {
7335
7502
  const ms = Number(e.timestamp);
7336
7503
  if (!tu || !Number.isFinite(ms)) continue;
7337
7504
  const d = new Date(ms);
7338
- const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
7505
+ const date = localUsageDate(ms);
7506
+ if (!date) continue;
7339
7507
  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);
7508
+ const input = num4(tu.inputTokens);
7509
+ const output = num4(tu.outputTokens);
7510
+ const cacheWrite = num4(tu.cacheWriteTokens);
7511
+ const cacheRead = num4(tu.cacheReadTokens);
7344
7512
  const cost = Math.max(0, (Number(tu.totalCents) || 0) / 100);
7345
7513
  const total = input + output + cacheWrite + cacheRead;
7346
7514
  if (total === 0 && cost === 0) continue;
@@ -7425,22 +7593,13 @@ async function collectCursor() {
7425
7593
  }
7426
7594
 
7427
7595
  // 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) {
7596
+ import { readdir as readdir3 } from "node:fs/promises";
7597
+ import { homedir as homedir7 } from "node:os";
7598
+ import { join as join9 } from "node:path";
7599
+ function num5(n) {
7432
7600
  const v = Math.round(Number(n));
7433
7601
  return Number.isFinite(v) && v > 0 ? v : 0;
7434
7602
  }
7435
- function localDate2(iso) {
7436
- const t = Date.parse(iso);
7437
- if (!Number.isFinite(t)) return null;
7438
- const d = new Date(t);
7439
- const y = d.getFullYear();
7440
- const m = String(d.getMonth() + 1).padStart(2, "0");
7441
- const day = String(d.getDate()).padStart(2, "0");
7442
- return `${y}-${m}-${day}`;
7443
- }
7444
7603
  function readTokenFields(payload) {
7445
7604
  const info = payload.info;
7446
7605
  const src = info?.total_token_usage ?? payload;
@@ -7448,10 +7607,9 @@ function readTokenFields(payload) {
7448
7607
  const output = src.output_tokens;
7449
7608
  if (input === void 0 && output === void 0) return null;
7450
7609
  return {
7451
- input: num4(input),
7452
- cached: num4(src.cached_input_tokens),
7453
- output: num4(output),
7454
- reasoning: num4(src.reasoning_output_tokens)
7610
+ input: num5(input),
7611
+ cached: num5(src.cached_input_tokens),
7612
+ output: num5(output)
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 = localUsageDate(Date.parse(String(obj.timestamp ?? "")));
7480
7638
  const day = parsed ?? lastSeenDate;
7481
7639
  if (!day) continue;
7482
7640
  lastSeenDate = day;
@@ -7492,17 +7650,16 @@ function parseCodexRollout(lines) {
7492
7650
  if (perDay.size === 0) return [];
7493
7651
  const dates = [...perDay.keys()].sort();
7494
7652
  const out = [];
7495
- let prev = { input: 0, cached: 0, output: 0, reasoning: 0 };
7653
+ let prev = { input: 0, cached: 0, output: 0 };
7496
7654
  for (const date of dates) {
7497
7655
  const { cum, turns } = perDay.get(date);
7498
7656
  const dInput = Math.max(0, cum.input - prev.input);
7499
7657
  const dCached = Math.max(0, cum.cached - prev.cached);
7500
7658
  const dOutput = Math.max(0, cum.output - prev.output);
7501
- const dReasoning = Math.max(0, cum.reasoning - prev.reasoning);
7502
7659
  prev = cum;
7503
7660
  const cacheReadTokens = dCached;
7504
7661
  const inputTokens = Math.max(0, dInput - dCached);
7505
- const outputTokens = dOutput + dReasoning;
7662
+ const outputTokens = dOutput;
7506
7663
  if (inputTokens + outputTokens + cacheReadTokens === 0) continue;
7507
7664
  out.push({
7508
7665
  date,
@@ -7559,20 +7716,20 @@ function finalizeCodexEntries(acc) {
7559
7716
  return entries;
7560
7717
  }
7561
7718
  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");
7719
+ const home = env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join9(homedir7(), ".codex");
7720
+ return join9(home, "sessions");
7564
7721
  }
7565
- async function listJsonl2(dir) {
7722
+ async function listJsonl3(dir) {
7566
7723
  let dirents;
7567
7724
  try {
7568
- dirents = await readdir2(dir, { withFileTypes: true });
7725
+ dirents = await readdir3(dir, { withFileTypes: true });
7569
7726
  } catch {
7570
7727
  return [];
7571
7728
  }
7572
7729
  const out = [];
7573
7730
  for (const d of dirents) {
7574
- const full = join8(dir, d.name);
7575
- if (d.isDirectory()) out.push(...await listJsonl2(full));
7731
+ const full = join9(dir, d.name);
7732
+ if (d.isDirectory()) out.push(...await listJsonl3(full));
7576
7733
  else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
7577
7734
  }
7578
7735
  return out;
@@ -7588,7 +7745,7 @@ function* splitLines3(content) {
7588
7745
  if (start < content.length) yield content.slice(start);
7589
7746
  }
7590
7747
  var NATIVE_READ_BUDGET_MS2 = 45e3;
7591
- var CODEX_CACHE_VERSION = 1;
7748
+ var CODEX_CACHE_VERSION = 2;
7592
7749
  function toCachedSession(s) {
7593
7750
  return [
7594
7751
  s.date,
@@ -7613,7 +7770,7 @@ function fromCachedSession(t) {
7613
7770
  }
7614
7771
  async function collectCodexNative(env = process.env, opts = {}) {
7615
7772
  const dir = resolveCodexSessionsDir(env);
7616
- const files = await listJsonl2(dir);
7773
+ const files = await listJsonl3(dir);
7617
7774
  if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7618
7775
  const now = opts.now ?? Date.now;
7619
7776
  const res = await readFilesWithCache({
@@ -7643,13 +7800,214 @@ async function collectCodexNative(env = process.env, opts = {}) {
7643
7800
  };
7644
7801
  }
7645
7802
 
7803
+ // src/native/vscode-agents.ts
7804
+ import { readdir as readdir4 } from "node:fs/promises";
7805
+ import { homedir as homedir8, platform as platform3 } from "node:os";
7806
+ import { join as join10 } from "node:path";
7807
+ var VSCODE_AGENTS = [
7808
+ { tool: "cline", extIds: ["saoudrizwan.claude-dev"] },
7809
+ { tool: "roo", extIds: ["rooveterinaryinc.roo-cline"] }
7810
+ ];
7811
+ function num6(n) {
7812
+ const v = Math.round(Number(n));
7813
+ return Number.isFinite(v) && v > 0 ? v : 0;
7814
+ }
7815
+ function parseApiReqMessage(tool, msg) {
7816
+ if (msg.say !== "api_req_started") return null;
7817
+ const date = localUsageDate(Number(msg.ts));
7818
+ if (!date) return null;
7819
+ let payload = null;
7820
+ const text = msg.text;
7821
+ if (typeof text === "string") {
7822
+ try {
7823
+ payload = JSON.parse(text);
7824
+ } catch {
7825
+ return null;
7826
+ }
7827
+ } else if (text && typeof text === "object") {
7828
+ payload = text;
7829
+ }
7830
+ if (!payload) return null;
7831
+ const inputTokens = num6(payload.tokensIn);
7832
+ const outputTokens = num6(payload.tokensOut);
7833
+ const cacheCreationTokens = num6(payload.cacheWrites);
7834
+ const cacheReadTokens = num6(payload.cacheReads);
7835
+ if (inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens === 0)
7836
+ return null;
7837
+ const modelInfo = msg.modelInfo;
7838
+ const modelFromRow = modelInfo && typeof modelInfo === "object" && typeof modelInfo.modelId === "string" && modelInfo.modelId ? modelInfo.modelId : null;
7839
+ const modelFromText = typeof payload.model === "string" && payload.model ? payload.model : null;
7840
+ const storedCostRaw = Number(payload.cost);
7841
+ return {
7842
+ date,
7843
+ model: modelFromRow ?? modelFromText ?? tool,
7844
+ inputTokens,
7845
+ outputTokens,
7846
+ cacheCreationTokens,
7847
+ cacheReadTokens,
7848
+ storedCost: Number.isFinite(storedCostRaw) && storedCostRaw > 0 ? storedCostRaw : null
7849
+ };
7850
+ }
7851
+ function bucketsToEntries(tool, buckets) {
7852
+ const entries = [];
7853
+ for (const b of buckets) {
7854
+ const tokens = b.inputTokens + b.outputTokens + b.cacheCreationTokens + b.cacheReadTokens;
7855
+ if (tokens === 0) continue;
7856
+ entries.push({
7857
+ date: b.date,
7858
+ tool,
7859
+ model: b.model,
7860
+ inputTokens: b.inputTokens,
7861
+ outputTokens: b.outputTokens,
7862
+ cacheCreationTokens: b.cacheCreationTokens,
7863
+ cacheReadTokens: b.cacheReadTokens,
7864
+ costUSD: Number(b.costUSD.toFixed(6)),
7865
+ origin: "cli",
7866
+ verified: false,
7867
+ requestCount: b.requestCount
7868
+ });
7869
+ }
7870
+ return entries;
7871
+ }
7872
+ function aggregateVscodeAgentEntries(tool, messages) {
7873
+ const byKey = /* @__PURE__ */ new Map();
7874
+ for (const raw of messages) {
7875
+ if (!raw || typeof raw !== "object") continue;
7876
+ const req = parseApiReqMessage(tool, raw);
7877
+ if (!req) continue;
7878
+ const key = `${req.date}|${req.model}`;
7879
+ let b = byKey.get(key);
7880
+ if (!b) {
7881
+ b = {
7882
+ date: req.date,
7883
+ model: req.model,
7884
+ inputTokens: 0,
7885
+ outputTokens: 0,
7886
+ cacheCreationTokens: 0,
7887
+ cacheReadTokens: 0,
7888
+ costUSD: 0,
7889
+ requestCount: 0
7890
+ };
7891
+ byKey.set(key, b);
7892
+ }
7893
+ b.inputTokens += req.inputTokens;
7894
+ b.outputTokens += req.outputTokens;
7895
+ b.cacheCreationTokens += req.cacheCreationTokens;
7896
+ b.cacheReadTokens += req.cacheReadTokens;
7897
+ b.costUSD += req.storedCost ?? estimateCostUSD(req.model, {
7898
+ inputTokens: req.inputTokens,
7899
+ outputTokens: req.outputTokens,
7900
+ cacheCreationTokens: req.cacheCreationTokens,
7901
+ cacheReadTokens: req.cacheReadTokens
7902
+ });
7903
+ b.requestCount += 1;
7904
+ }
7905
+ return bucketsToEntries(tool, byKey.values());
7906
+ }
7907
+ function parseVscodeAgentMessages(tool, raw) {
7908
+ let arr;
7909
+ try {
7910
+ arr = JSON.parse(raw);
7911
+ } catch {
7912
+ return [];
7913
+ }
7914
+ if (!Array.isArray(arr)) return [];
7915
+ return aggregateVscodeAgentEntries(tool, arr);
7916
+ }
7917
+ function vscodeGlobalStorageRoots(env = process.env) {
7918
+ const apps = ["Code", "Code - Insiders", "Cursor", "VSCodium", "Windsurf"];
7919
+ const home = env.HOME || homedir8();
7920
+ const os = platform3();
7921
+ 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");
7922
+ return apps.map(base);
7923
+ }
7924
+ async function listTaskFiles(roots, extIds) {
7925
+ const files = [];
7926
+ for (const root of roots) {
7927
+ for (const extId of extIds) {
7928
+ const tasksDir = join10(root, extId, "tasks");
7929
+ let taskDirs;
7930
+ try {
7931
+ const dirents = await readdir4(tasksDir, { withFileTypes: true });
7932
+ taskDirs = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
7933
+ } catch {
7934
+ continue;
7935
+ }
7936
+ for (const t of taskDirs) files.push(join10(tasksDir, t, "ui_messages.json"));
7937
+ }
7938
+ }
7939
+ return files;
7940
+ }
7941
+ var VSCODE_CACHE_VERSION = 2;
7942
+ function entryToRow(e) {
7943
+ return [
7944
+ e.date,
7945
+ e.model,
7946
+ e.inputTokens,
7947
+ e.outputTokens,
7948
+ e.cacheCreationTokens,
7949
+ e.cacheReadTokens,
7950
+ e.costUSD,
7951
+ e.requestCount ?? 0
7952
+ ];
7953
+ }
7954
+ async function collectVscodeAgent(opts) {
7955
+ const env = opts.env ?? process.env;
7956
+ const roots = opts.roots ?? vscodeGlobalStorageRoots(env);
7957
+ const files = await listTaskFiles(roots, opts.extIds);
7958
+ if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7959
+ const now = opts.now ?? Date.now;
7960
+ const res = await readFilesWithCache({
7961
+ files,
7962
+ cachePath: opts.cachePath ?? nativeCachePath(`vscode-${opts.tool}`, env),
7963
+ version: VSCODE_CACHE_VERSION,
7964
+ parseFile: (content) => parseVscodeAgentMessages(opts.tool, content).map(entryToRow),
7965
+ deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS),
7966
+ now
7967
+ });
7968
+ if (!res.itemsByFile) {
7969
+ return { entries: [], found: false, filesScanned: res.filesRead, timedOut: true };
7970
+ }
7971
+ const byKey = /* @__PURE__ */ new Map();
7972
+ for (const rows of res.itemsByFile) {
7973
+ for (const r of rows) {
7974
+ const key = `${r[0]}|${r[1]}`;
7975
+ let b = byKey.get(key);
7976
+ if (!b) {
7977
+ b = {
7978
+ date: r[0],
7979
+ model: r[1],
7980
+ inputTokens: 0,
7981
+ outputTokens: 0,
7982
+ cacheCreationTokens: 0,
7983
+ cacheReadTokens: 0,
7984
+ costUSD: 0,
7985
+ requestCount: 0
7986
+ };
7987
+ byKey.set(key, b);
7988
+ }
7989
+ b.inputTokens += r[2];
7990
+ b.outputTokens += r[3];
7991
+ b.cacheCreationTokens += r[4];
7992
+ b.cacheReadTokens += r[5];
7993
+ b.costUSD += r[6];
7994
+ b.requestCount += r[7];
7995
+ }
7996
+ }
7997
+ return {
7998
+ entries: bucketsToEntries(opts.tool, byKey.values()),
7999
+ found: true,
8000
+ filesScanned: res.filesRead
8001
+ };
8002
+ }
8003
+
7646
8004
  // src/pricing-live.ts
7647
8005
  import { mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
7648
- import { dirname as dirname4, join as join9 } from "node:path";
8006
+ import { dirname as dirname4, join as join11 } from "node:path";
7649
8007
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
7650
8008
  var FETCH_TIMEOUT_MS = 5e3;
7651
8009
  function pricingCachePath(dir = defaultConfigDir()) {
7652
- return join9(dir, "pricing-cache.json");
8010
+ return join11(dir, "pricing-cache.json");
7653
8011
  }
7654
8012
  async function readCache(path) {
7655
8013
  try {
@@ -7702,7 +8060,7 @@ async function loadLivePricing(env = process.env, now = Date.now, cachePath = pr
7702
8060
 
7703
8061
  // src/provenance-store.ts
7704
8062
  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";
8063
+ import { dirname as dirname5, join as join12 } from "node:path";
7706
8064
  var PROVENANCE_STORE_VERSION = 1;
7707
8065
  var KEY_SEP = "|";
7708
8066
  var keyOf = (date, tool) => `${date}${KEY_SEP}${tool}`;
@@ -7712,7 +8070,7 @@ function entryTokens(e) {
7712
8070
  function provenanceStorePath(env = process.env) {
7713
8071
  const override = env.WHOBURNEDMORE_CONFIG_DIR?.trim();
7714
8072
  const dir = override || defaultConfigDir();
7715
- return join10(dir, "native-cache-provenance.json");
8073
+ return join12(dir, "native-cache-provenance.json");
7716
8074
  }
7717
8075
  function loadProvenanceStore(path) {
7718
8076
  try {
@@ -7814,14 +8172,14 @@ function normCost(n) {
7814
8172
  const v = Number(n);
7815
8173
  return Number.isFinite(v) && v > 0 ? v : 0;
7816
8174
  }
7817
- function mapCcusageDaily(tool, json) {
8175
+ function mapCcusageDaily(tool, json, now = Date.now()) {
7818
8176
  const daily = json?.daily;
7819
8177
  if (!Array.isArray(daily)) return [];
7820
8178
  const entries = [];
7821
8179
  for (const rawDay of daily) {
7822
8180
  const day = rawDay;
7823
8181
  const date = day.date ?? day.period;
7824
- if (typeof date !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(date)) continue;
8182
+ if (typeof date !== "string" || !plausibleUsageDate(date, now)) continue;
7825
8183
  const breakdowns = Array.isArray(day.modelBreakdowns) ? day.modelBreakdowns : [];
7826
8184
  const modelsMap = day.models && typeof day.models === "object" && !Array.isArray(day.models) ? day.models : null;
7827
8185
  const dayCost = normCost(day.totalCost ?? day.costUSD);
@@ -7927,7 +8285,7 @@ function resolveCcusageBin() {
7927
8285
  const pkgPath = require3.resolve("ccusage/package.json");
7928
8286
  const pkg = require3("ccusage/package.json");
7929
8287
  const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.ccusage ?? "ccusage";
7930
- const binPath = join11(dirname6(pkgPath), rel);
8288
+ const binPath = join13(dirname6(pkgPath), rel);
7931
8289
  if (/\.(c|m)?js$/.test(binPath)) {
7932
8290
  return { cmd: process.execPath, prefixArgs: [binPath] };
7933
8291
  }
@@ -8022,9 +8380,9 @@ async function runCcusage(cmd, args, env) {
8022
8380
  if (first.json !== null || !first.transient) return first.json;
8023
8381
  return (await runCcusageOnce(cmd, args, env)).json;
8024
8382
  }
8025
- var COLLECT_STAGES = SOURCES.length + 4;
8026
- function isAuthoritativeScan(attributionComplete, nativeClaude, nativeCodex) {
8027
- return attributionComplete && nativeClaude.timedOut !== true && nativeCodex.timedOut !== true;
8383
+ var COLLECT_STAGES = SOURCES.length + 4 + VSCODE_AGENTS.length + 1;
8384
+ function isAuthoritativeScan(attributionComplete, ...fingerprintReaders) {
8385
+ return attributionComplete && fingerprintReaders.every((reader) => reader.timedOut !== true);
8028
8386
  }
8029
8387
  async function collectAll(onProgress) {
8030
8388
  await loadLivePricing().catch(() => {
@@ -8068,14 +8426,36 @@ async function collectAll(onProgress) {
8068
8426
  tick();
8069
8427
  return a;
8070
8428
  });
8071
- const [sourceResults, sessions, blocks, cursor, attribution, nativeClaude, nativeCodex] = await Promise.all([
8429
+ const vscodeTasks = VSCODE_AGENTS.map(
8430
+ (a) => collectVscodeAgent({ tool: a.tool, extIds: a.extIds }).then((r) => {
8431
+ tick();
8432
+ return { tool: a.tool, result: r };
8433
+ })
8434
+ );
8435
+ const continueTask = collectContinue().then((r) => {
8436
+ tick();
8437
+ return r;
8438
+ });
8439
+ const [
8440
+ sourceResults,
8441
+ sessions,
8442
+ blocks,
8443
+ cursor,
8444
+ attribution,
8445
+ nativeClaude,
8446
+ nativeCodex,
8447
+ vscodeResults,
8448
+ continueResult
8449
+ ] = await Promise.all([
8072
8450
  Promise.all(sourceTasks),
8073
8451
  sessionTask,
8074
8452
  blockTask,
8075
8453
  cursorTask,
8076
8454
  attributionTask,
8077
8455
  nativeClaudeTask,
8078
- nativeCodexTask
8456
+ nativeCodexTask,
8457
+ Promise.all(vscodeTasks),
8458
+ continueTask
8079
8459
  ]);
8080
8460
  const native = { claude: nativeClaude, codex: nativeCodex };
8081
8461
  const entries = [];
@@ -8092,6 +8472,16 @@ async function collectAll(onProgress) {
8092
8472
  blocks.push(...cursor.blocks);
8093
8473
  toolsFound.push("cursor");
8094
8474
  }
8475
+ for (const { tool, result } of vscodeResults) {
8476
+ if (result.found && result.entries.length > 0) {
8477
+ entries.push(...result.entries);
8478
+ toolsFound.push(tool);
8479
+ }
8480
+ }
8481
+ if (continueResult.found && continueResult.entries.length > 0) {
8482
+ entries.push(...continueResult.entries);
8483
+ toolsFound.push("continue");
8484
+ }
8095
8485
  const { tools, skills, agent, sessionMessages, complete } = attribution;
8096
8486
  onProgress?.(COLLECT_STAGES, COLLECT_STAGES, "");
8097
8487
  const dedupedSessions = dedupeSessions(sessions).map((s) => {
@@ -8101,7 +8491,13 @@ async function collectAll(onProgress) {
8101
8491
  ...messageCount ? { messageCount } : {}
8102
8492
  };
8103
8493
  });
8104
- const scanComplete = isAuthoritativeScan(complete, nativeClaude, nativeCodex);
8494
+ const scanComplete = isAuthoritativeScan(
8495
+ complete,
8496
+ nativeClaude,
8497
+ nativeCodex,
8498
+ ...vscodeResults.map(({ result }) => result),
8499
+ continueResult
8500
+ );
8105
8501
  const storePath = provenanceStorePath();
8106
8502
  const reconciled = reconcileProvenance(
8107
8503
  dedupeDaily(entries),
@@ -8127,6 +8523,45 @@ async function collectAll(onProgress) {
8127
8523
  };
8128
8524
  }
8129
8525
 
8526
+ // src/antigravity.ts
8527
+ import { existsSync as existsSync4 } from "node:fs";
8528
+ import { homedir as homedir9 } from "node:os";
8529
+ import { join as join14 } from "node:path";
8530
+ function antigravityDataDir(env = process.env) {
8531
+ return join14(env.HOME || homedir9(), ".gemini", "antigravity");
8532
+ }
8533
+ function detectAntigravity(env = process.env) {
8534
+ return existsSync4(antigravityDataDir(env));
8535
+ }
8536
+ function antigravityNoticeLines() {
8537
+ return [
8538
+ " \u2022 Google Antigravity detected \u2014 but its usage can't be counted.",
8539
+ " Antigravity stores no readable local usage: its conversation logs are",
8540
+ " encrypted and it bills flat-rate, so there are no per-request token",
8541
+ " counts on disk to tally. It's the one major agent we can't put on the board."
8542
+ ];
8543
+ }
8544
+
8545
+ // src/verify-upload.ts
8546
+ import { createHash } from "node:crypto";
8547
+ function prepareVerifyUpload(requests, scanTimedOut, cap = 5e4) {
8548
+ const sorted = requests.slice().sort((a, b) => b.ts - a.ts);
8549
+ const truncated = scanTimedOut || sorted.length > cap;
8550
+ const capped = sorted.length > cap ? sorted.slice(0, cap) : sorted;
8551
+ const records = capped.map((r) => ({
8552
+ date: r.date,
8553
+ ts: r.ts,
8554
+ tool: "claude",
8555
+ model: r.model.trim().slice(0, 128) || "unknown",
8556
+ inputTokens: r.inputTokens,
8557
+ outputTokens: r.outputTokens,
8558
+ cacheCreationTokens: r.cacheCreationTokens,
8559
+ cacheReadTokens: r.cacheReadTokens,
8560
+ reqHash: createHash("sha256").update(r.key).digest("hex").slice(0, 32)
8561
+ }));
8562
+ return { records, truncated };
8563
+ }
8564
+
8130
8565
  // src/status.ts
8131
8566
  function ago(ms) {
8132
8567
  const mins = Math.round(ms / 6e4);
@@ -8514,7 +8949,7 @@ function startProgress() {
8514
8949
  }
8515
8950
  function openBrowser(url) {
8516
8951
  if (!isOpenableUrl(url)) return;
8517
- const os = platform3();
8952
+ const os = platform4();
8518
8953
  const [cmd, args] = os === "darwin" ? ["open", [url]] : os === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
8519
8954
  spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
8520
8955
  }
@@ -8528,7 +8963,7 @@ async function confirm(question) {
8528
8963
  function showLocalDashboard(payload) {
8529
8964
  const dir = defaultConfigDir();
8530
8965
  mkdirSync5(dir, { recursive: true });
8531
- const file = join12(dir, "dashboard.html");
8966
+ const file = join15(dir, "dashboard.html");
8532
8967
  writeFileSync5(
8533
8968
  file,
8534
8969
  renderDashboardHtml(payload.entries, /* @__PURE__ */ new Date(), { webBaseUrl: webBase() })
@@ -8565,6 +9000,10 @@ async function run(flags) {
8565
9000
  console.log();
8566
9001
  console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
8567
9002
  console.log(pc2.dim(" Use Claude Code, Codex, Gemini CLI (or friends) and come back."));
9003
+ if (detectAntigravity()) {
9004
+ console.log();
9005
+ for (const line of antigravityNoticeLines()) console.log(pc2.dim(line));
9006
+ }
8568
9007
  return;
8569
9008
  }
8570
9009
  const payload = { cliVersion: VERSION, entries };
@@ -8916,7 +9355,7 @@ async function runVerify() {
8916
9355
  }
8917
9356
  }
8918
9357
  console.log(pc2.dim(" Reading your local Claude Code logs\u2026"));
8919
- const { requests, found } = await collectClaudeRequests();
9358
+ const { requests, found, timedOut } = await collectClaudeRequests();
8920
9359
  if (!found || requests.length === 0) {
8921
9360
  console.log(
8922
9361
  pc2.yellow(" No local Claude Code logs found on this machine to verify.")
@@ -8928,21 +9367,10 @@ async function runVerify() {
8928
9367
  );
8929
9368
  return;
8930
9369
  }
8931
- const CAP = 5e4;
8932
- const sorted = requests.slice().sort((a, b) => b.ts - a.ts);
8933
- const truncated = sorted.length > CAP;
8934
- const capped = truncated ? sorted.slice(0, CAP) : sorted;
8935
- const records = capped.map((r) => ({
8936
- date: r.date,
8937
- ts: r.ts,
8938
- tool: "claude",
8939
- model: r.model,
8940
- inputTokens: r.inputTokens,
8941
- outputTokens: r.outputTokens,
8942
- cacheCreationTokens: r.cacheCreationTokens,
8943
- cacheReadTokens: r.cacheReadTokens,
8944
- reqHash: createHash("sha256").update(r.key).digest("hex").slice(0, 32)
8945
- }));
9370
+ const { records, truncated } = prepareVerifyUpload(
9371
+ requests,
9372
+ timedOut === true
9373
+ );
8946
9374
  console.log(
8947
9375
  pc2.dim(
8948
9376
  ` Sending ${records.length} request records for analysis${truncated ? " (most recent, sampled)" : ""}\u2026`
@@ -8975,7 +9403,7 @@ async function runVerify() {
8975
9403
  console.log(result.verdict === "fail" ? pc2.yellow(line) : line);
8976
9404
  console.log(
8977
9405
  pc2.dim(
8978
- " We'll relist you automatically if it checks out \u2014 re-run `npx whoburnedmore` anytime to check."
9406
+ " A reviewer can clear the moderation after checking this evidence \u2014 re-run `npx whoburnedmore` anytime to check."
8979
9407
  )
8980
9408
  );
8981
9409
  }
@@ -9117,6 +9545,10 @@ async function main() {
9117
9545
  case "status":
9118
9546
  case "doctor": {
9119
9547
  for (const line of agentStatusReport()) console.log(line);
9548
+ if (detectAntigravity()) {
9549
+ console.log("");
9550
+ for (const line of antigravityNoticeLines()) console.log(line);
9551
+ }
9120
9552
  break;
9121
9553
  }
9122
9554
  case "private":
@@ -9160,7 +9592,7 @@ function printHelp() {
9160
9592
  npx whoburnedmore private take yourself off the public leaderboard
9161
9593
  npx whoburnedmore public put yourself back on it
9162
9594
  npx whoburnedmore remove delete your usage data and stop background sync
9163
- npx whoburnedmore verify delisted? re-verify your usage to get back on (sends a detailed breakdown)
9595
+ npx whoburnedmore verify submit detailed usage evidence for review (never prompts or code)
9164
9596
  npx whoburnedmore status check background-sync health (last sync, staleness)
9165
9597
  npx whoburnedmore uninstall-sync turn off the background sync
9166
9598
  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.15",
3
+ "version": "0.9.17",
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"