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.
- package/README.md +7 -1
- package/dist/index.js +472 -46
- 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
|
-
|
|
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
|
|
14
|
-
import { join as
|
|
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
|
|
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
|
|
7179
|
-
import { join as
|
|
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
|
|
7342
|
+
import { dirname as dirname3, join as join7 } from "node:path";
|
|
7185
7343
|
var LOOKBACK_DAYS = 30;
|
|
7186
|
-
function
|
|
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 =
|
|
7200
|
-
const outputTokens =
|
|
7201
|
-
const cacheCreationTokens =
|
|
7202
|
-
const cacheReadTokens =
|
|
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 =
|
|
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 =
|
|
7444
|
+
const home = homedir6();
|
|
7287
7445
|
const os = platform2();
|
|
7288
|
-
const p = os === "darwin" ?
|
|
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
|
|
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 =
|
|
7341
|
-
const output =
|
|
7342
|
-
const cacheWrite =
|
|
7343
|
-
const cacheRead =
|
|
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
|
|
7429
|
-
import { homedir as
|
|
7430
|
-
import { join as
|
|
7431
|
-
function
|
|
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
|
|
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:
|
|
7452
|
-
cached:
|
|
7453
|
-
output:
|
|
7454
|
-
reasoning:
|
|
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 =
|
|
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() :
|
|
7563
|
-
return
|
|
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
|
|
7723
|
+
async function listJsonl3(dir) {
|
|
7566
7724
|
let dirents;
|
|
7567
7725
|
try {
|
|
7568
|
-
dirents = await
|
|
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 =
|
|
7575
|
-
if (d.isDirectory()) out.push(...await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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.
|
|
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"
|