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.
- package/README.md +7 -1
- package/dist/index.js +528 -96
- 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
|
@@ -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
|
|
14
|
-
import { join as
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
7179
|
-
import { join as
|
|
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
|
|
7351
|
+
import { dirname as dirname3, join as join7 } from "node:path";
|
|
7185
7352
|
var LOOKBACK_DAYS = 30;
|
|
7186
|
-
function
|
|
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 =
|
|
7200
|
-
const outputTokens =
|
|
7201
|
-
const cacheCreationTokens =
|
|
7202
|
-
const cacheReadTokens =
|
|
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 =
|
|
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 =
|
|
7453
|
+
const home = homedir6();
|
|
7287
7454
|
const os = platform2();
|
|
7288
|
-
const p = os === "darwin" ?
|
|
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
|
|
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 =
|
|
7505
|
+
const date = localUsageDate(ms);
|
|
7506
|
+
if (!date) continue;
|
|
7339
7507
|
const model = e.model || "cursor";
|
|
7340
|
-
const input =
|
|
7341
|
-
const output =
|
|
7342
|
-
const cacheWrite =
|
|
7343
|
-
const cacheRead =
|
|
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
|
|
7429
|
-
import { homedir as
|
|
7430
|
-
import { join as
|
|
7431
|
-
function
|
|
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:
|
|
7452
|
-
cached:
|
|
7453
|
-
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 =
|
|
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
|
|
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
|
|
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() :
|
|
7563
|
-
return
|
|
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
|
|
7722
|
+
async function listJsonl3(dir) {
|
|
7566
7723
|
let dirents;
|
|
7567
7724
|
try {
|
|
7568
|
-
dirents = await
|
|
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 =
|
|
7575
|
-
if (d.isDirectory()) out.push(...await
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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" ||
|
|
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 =
|
|
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,
|
|
8027
|
-
return attributionComplete &&
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
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
|
-
"
|
|
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
|
|
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.
|
|
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"
|