whoburnedmore 0.9.17 → 0.9.19
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/dist/index.js +98 -46
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -238,7 +238,7 @@ import {
|
|
|
238
238
|
writeFileSync as writeFileSync2
|
|
239
239
|
} from "node:fs";
|
|
240
240
|
import { homedir as homedir2, platform } from "node:os";
|
|
241
|
-
import { dirname, join as join2, win32 } from "node:path";
|
|
241
|
+
import { dirname, join as join2, posix, win32 } from "node:path";
|
|
242
242
|
|
|
243
243
|
// src/config.ts
|
|
244
244
|
import { randomBytes } from "node:crypto";
|
|
@@ -417,9 +417,19 @@ ${envEntries}
|
|
|
417
417
|
interval. Submits are idempotent server-side, so an extra run is safe. -->
|
|
418
418
|
<key>RunAtLoad</key>
|
|
419
419
|
<true/>
|
|
420
|
-
<!--
|
|
420
|
+
<!-- Standard, NOT Background. ProcessType=Background opts the job into
|
|
421
|
+
macOS I/O throttling, which is disastrous for a job whose work is almost
|
|
422
|
+
entirely reading thousands of transcript files: a full collect measured
|
|
423
|
+
~51s unthrottled vs ~197s throttled on the same machine. That blows past
|
|
424
|
+
the collector's own budgets (25s per ccusage child, 45s for the native
|
|
425
|
+
reader), so every source times out at once, entries comes back empty and
|
|
426
|
+
the tick submits nothing \u2014 it logged "Nothing to burn yet" on 206 of 473
|
|
427
|
+
runs (~44%) until this was changed. Nice=0 keeps CPU priority neighbourly
|
|
428
|
+
without throttling the reads. -->
|
|
421
429
|
<key>ProcessType</key>
|
|
422
|
-
<string>
|
|
430
|
+
<string>Standard</string>
|
|
431
|
+
<key>Nice</key>
|
|
432
|
+
<integer>0</integer>
|
|
423
433
|
<key>StandardOutPath</key>
|
|
424
434
|
<string>${xmlEscape(logPath)}</string>
|
|
425
435
|
<key>StandardErrorPath</key>
|
|
@@ -460,7 +470,7 @@ function resolveNpmPath(opts) {
|
|
|
460
470
|
for (const c of candidates) {
|
|
461
471
|
if (check(c)) return c;
|
|
462
472
|
}
|
|
463
|
-
const sibling = os === "win32" ? win32.join(win32.dirname(execPath), "npm.cmd") :
|
|
473
|
+
const sibling = os === "win32" ? win32.join(win32.dirname(execPath), "npm.cmd") : posix.join(posix.dirname(execPath), "npm");
|
|
464
474
|
if (check(sibling)) return sibling;
|
|
465
475
|
return os === "win32" ? "npm.cmd" : "npm";
|
|
466
476
|
}
|
|
@@ -7595,7 +7605,7 @@ async function collectCursor() {
|
|
|
7595
7605
|
// src/native/codex.ts
|
|
7596
7606
|
import { readdir as readdir3 } from "node:fs/promises";
|
|
7597
7607
|
import { homedir as homedir7 } from "node:os";
|
|
7598
|
-
import { join as join9 } from "node:path";
|
|
7608
|
+
import { join as join9, resolve } from "node:path";
|
|
7599
7609
|
function num5(n) {
|
|
7600
7610
|
const v = Math.round(Number(n));
|
|
7601
7611
|
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
@@ -7715,9 +7725,12 @@ function finalizeCodexEntries(acc) {
|
|
|
7715
7725
|
}
|
|
7716
7726
|
return entries;
|
|
7717
7727
|
}
|
|
7718
|
-
function
|
|
7719
|
-
|
|
7720
|
-
|
|
7728
|
+
function resolveCodexHome(env = process.env) {
|
|
7729
|
+
return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join9(homedir7(), ".codex");
|
|
7730
|
+
}
|
|
7731
|
+
function resolveCodexSessionsDirs(env = process.env) {
|
|
7732
|
+
const home = resolveCodexHome(env);
|
|
7733
|
+
return [join9(home, "sessions"), join9(home, "archived_sessions")];
|
|
7721
7734
|
}
|
|
7722
7735
|
async function listJsonl3(dir) {
|
|
7723
7736
|
let dirents;
|
|
@@ -7769,8 +7782,8 @@ function fromCachedSession(t) {
|
|
|
7769
7782
|
};
|
|
7770
7783
|
}
|
|
7771
7784
|
async function collectCodexNative(env = process.env, opts = {}) {
|
|
7772
|
-
const
|
|
7773
|
-
const files = await listJsonl3(
|
|
7785
|
+
const dirs = resolveCodexSessionsDirs(env);
|
|
7786
|
+
const files = (await Promise.all(dirs.map(listJsonl3))).flat();
|
|
7774
7787
|
if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
|
|
7775
7788
|
const now = opts.now ?? Date.now;
|
|
7776
7789
|
const res = await readFilesWithCache({
|
|
@@ -8147,6 +8160,10 @@ function reconcileProvenance(entries, agent, complete, store) {
|
|
|
8147
8160
|
|
|
8148
8161
|
// src/collect.ts
|
|
8149
8162
|
var execFileAsync = promisify(execFile);
|
|
8163
|
+
var NATIVE_COVERED_SOURCES = /* @__PURE__ */ new Set(["claude", "codex"]);
|
|
8164
|
+
var CCUSAGE_TIMEOUT_MS = 25e3;
|
|
8165
|
+
var CCUSAGE_FALLBACK_TIMEOUT_MS = NATIVE_READ_BUDGET_MS;
|
|
8166
|
+
var CCUSAGE_AGGREGATE_TIMEOUT_MS = 18e4;
|
|
8150
8167
|
var SOURCES = [
|
|
8151
8168
|
"claude",
|
|
8152
8169
|
"codex",
|
|
@@ -8342,25 +8359,39 @@ function dedupeBlocks(blocks) {
|
|
|
8342
8359
|
return [...byStart.values()];
|
|
8343
8360
|
}
|
|
8344
8361
|
function selectSourceEntries(source, ccusageEntries, native) {
|
|
8345
|
-
if (source === "claude" && native.claude
|
|
8362
|
+
if (source === "claude" && nativeReaderWon(native.claude))
|
|
8346
8363
|
return native.claude.entries;
|
|
8347
|
-
if (source === "codex" && native.codex
|
|
8364
|
+
if (source === "codex" && nativeReaderWon(native.codex))
|
|
8348
8365
|
return native.codex.entries;
|
|
8349
8366
|
return ccusageEntries;
|
|
8350
8367
|
}
|
|
8368
|
+
function nativeReaderWon(result) {
|
|
8369
|
+
return result.found && result.entries.length > 0;
|
|
8370
|
+
}
|
|
8371
|
+
function ccusageFallbackSources(native) {
|
|
8372
|
+
return SOURCES.filter(
|
|
8373
|
+
(s) => NATIVE_COVERED_SOURCES.has(s) && !nativeReaderWon(s === "claude" ? native.claude : native.codex)
|
|
8374
|
+
);
|
|
8375
|
+
}
|
|
8351
8376
|
function ccusageClaudeEnv(env = process.env) {
|
|
8352
8377
|
if (env.CLAUDE_CONFIG_DIR && env.CLAUDE_CONFIG_DIR.trim()) return env;
|
|
8353
8378
|
return { ...env, CLAUDE_CONFIG_DIR: resolveClaudeConfigRoots(env).join(",") };
|
|
8354
8379
|
}
|
|
8355
|
-
|
|
8380
|
+
function isRetryableCcusageFailure(err) {
|
|
8381
|
+
const e = err;
|
|
8382
|
+
if (e.killed === true) return false;
|
|
8383
|
+
return e.signal != null || typeof e.code === "string";
|
|
8384
|
+
}
|
|
8385
|
+
async function runCcusageOnce(cmd, args, env, timeoutMs = CCUSAGE_TIMEOUT_MS) {
|
|
8356
8386
|
try {
|
|
8357
8387
|
const { stdout } = await execFileAsync(cmd, args, {
|
|
8358
8388
|
encoding: "utf8",
|
|
8359
8389
|
maxBuffer: 64 * 1024 * 1024,
|
|
8360
|
-
// A single source shouldn't be able to hang the whole run
|
|
8361
|
-
//
|
|
8362
|
-
//
|
|
8363
|
-
|
|
8390
|
+
// A single source shouldn't be able to hang the whole run: a hung source
|
|
8391
|
+
// gets killed and (if transient) retried once below rather than stalling
|
|
8392
|
+
// everything for minutes. The claude/codex fallback passes a longer cap —
|
|
8393
|
+
// see CCUSAGE_FALLBACK_TIMEOUT_MS.
|
|
8394
|
+
timeout: timeoutMs,
|
|
8364
8395
|
...env ? { env } : {}
|
|
8365
8396
|
});
|
|
8366
8397
|
if (!stdout) return { json: null, transient: false };
|
|
@@ -8370,15 +8401,13 @@ async function runCcusageOnce(cmd, args, env) {
|
|
|
8370
8401
|
return { json: null, transient: false };
|
|
8371
8402
|
}
|
|
8372
8403
|
} catch (err) {
|
|
8373
|
-
|
|
8374
|
-
const transient = e.killed === true || e.signal != null || typeof e.code === "string";
|
|
8375
|
-
return { json: null, transient };
|
|
8404
|
+
return { json: null, transient: isRetryableCcusageFailure(err) };
|
|
8376
8405
|
}
|
|
8377
8406
|
}
|
|
8378
|
-
async function runCcusage(cmd, args, env) {
|
|
8379
|
-
const first = await runCcusageOnce(cmd, args, env);
|
|
8407
|
+
async function runCcusage(cmd, args, env, timeoutMs = CCUSAGE_TIMEOUT_MS) {
|
|
8408
|
+
const first = await runCcusageOnce(cmd, args, env, timeoutMs);
|
|
8380
8409
|
if (first.json !== null || !first.transient) return first.json;
|
|
8381
|
-
return (await runCcusageOnce(cmd, args, env)).json;
|
|
8410
|
+
return (await runCcusageOnce(cmd, args, env, timeoutMs)).json;
|
|
8382
8411
|
}
|
|
8383
8412
|
var COLLECT_STAGES = SOURCES.length + 4 + VSCODE_AGENTS.length + 1;
|
|
8384
8413
|
function isAuthoritativeScan(attributionComplete, ...fingerprintReaders) {
|
|
@@ -8397,27 +8426,39 @@ async function collectAll(onProgress) {
|
|
|
8397
8426
|
() => ({ entries: [], found: false, filesScanned: 0, timedOut: true })
|
|
8398
8427
|
);
|
|
8399
8428
|
const sourceTasks = SOURCES.map(async (source) => {
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8429
|
+
if (NATIVE_COVERED_SOURCES.has(source)) {
|
|
8430
|
+
await (source === "claude" ? nativeClaudeTask : nativeCodexTask);
|
|
8431
|
+
tick();
|
|
8432
|
+
return { source, mapped: [] };
|
|
8433
|
+
}
|
|
8434
|
+
const json = await runCcusage(cmd, [
|
|
8435
|
+
...prefixArgs,
|
|
8436
|
+
source,
|
|
8437
|
+
"daily",
|
|
8438
|
+
"--json",
|
|
8439
|
+
"--offline"
|
|
8440
|
+
]);
|
|
8406
8441
|
tick();
|
|
8407
8442
|
return { source, mapped: json ? mapCcusageDaily(source, json) : [] };
|
|
8408
8443
|
});
|
|
8409
|
-
const sessionTask = runCcusage(
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
)
|
|
8415
|
-
|
|
8416
|
-
(json)
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8444
|
+
const sessionTask = runCcusage(
|
|
8445
|
+
cmd,
|
|
8446
|
+
[...prefixArgs, "session", "--json", "--offline"],
|
|
8447
|
+
void 0,
|
|
8448
|
+
CCUSAGE_AGGREGATE_TIMEOUT_MS
|
|
8449
|
+
).then((json) => {
|
|
8450
|
+
tick();
|
|
8451
|
+
return json ? mapCcusageSessions(json) : [];
|
|
8452
|
+
});
|
|
8453
|
+
const blockTask = runCcusage(
|
|
8454
|
+
cmd,
|
|
8455
|
+
[...prefixArgs, "blocks", "--json", "--offline"],
|
|
8456
|
+
void 0,
|
|
8457
|
+
CCUSAGE_AGGREGATE_TIMEOUT_MS
|
|
8458
|
+
).then((json) => {
|
|
8459
|
+
tick();
|
|
8460
|
+
return json ? mapCcusageBlocks(json) : [];
|
|
8461
|
+
});
|
|
8421
8462
|
const cursorTask = collectCursor().then((c) => {
|
|
8422
8463
|
tick();
|
|
8423
8464
|
return c;
|
|
@@ -8458,10 +8499,21 @@ async function collectAll(onProgress) {
|
|
|
8458
8499
|
continueTask
|
|
8459
8500
|
]);
|
|
8460
8501
|
const native = { claude: nativeClaude, codex: nativeCodex };
|
|
8502
|
+
const fallbacks = /* @__PURE__ */ new Map();
|
|
8503
|
+
for (const source of ccusageFallbackSources(native)) {
|
|
8504
|
+
const json = await runCcusage(
|
|
8505
|
+
cmd,
|
|
8506
|
+
[...prefixArgs, source, "daily", "--json", "--offline"],
|
|
8507
|
+
// For Claude, force ccusage to scan both config roots (dual-dir hardening).
|
|
8508
|
+
source === "claude" ? ccusageClaudeEnv() : void 0,
|
|
8509
|
+
CCUSAGE_FALLBACK_TIMEOUT_MS
|
|
8510
|
+
);
|
|
8511
|
+
fallbacks.set(source, json ? mapCcusageDaily(source, json) : []);
|
|
8512
|
+
}
|
|
8461
8513
|
const entries = [];
|
|
8462
8514
|
const toolsFound = [];
|
|
8463
8515
|
for (const { source, mapped } of sourceResults) {
|
|
8464
|
-
const chosen = selectSourceEntries(source, mapped, native);
|
|
8516
|
+
const chosen = selectSourceEntries(source, fallbacks.get(source) ?? mapped, native);
|
|
8465
8517
|
if (chosen.length > 0) {
|
|
8466
8518
|
entries.push(...chosen);
|
|
8467
8519
|
toolsFound.push(source);
|
|
@@ -9079,7 +9131,7 @@ async function run(flags) {
|
|
|
9079
9131
|
}
|
|
9080
9132
|
}
|
|
9081
9133
|
function sleep(ms) {
|
|
9082
|
-
return new Promise((
|
|
9134
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
9083
9135
|
}
|
|
9084
9136
|
async function refreshCliTokenFromConfig() {
|
|
9085
9137
|
const cfg = loadConfig();
|
|
@@ -9273,14 +9325,14 @@ async function linkServerInstall(token) {
|
|
|
9273
9325
|
}
|
|
9274
9326
|
function waitOrAbort(ms, signal) {
|
|
9275
9327
|
if (signal.aborted) return Promise.resolve();
|
|
9276
|
-
return new Promise((
|
|
9328
|
+
return new Promise((resolve2) => {
|
|
9277
9329
|
const onAbort = () => {
|
|
9278
9330
|
clearTimeout(timer);
|
|
9279
|
-
|
|
9331
|
+
resolve2();
|
|
9280
9332
|
};
|
|
9281
9333
|
const timer = setTimeout(() => {
|
|
9282
9334
|
signal.removeEventListener("abort", onAbort);
|
|
9283
|
-
|
|
9335
|
+
resolve2();
|
|
9284
9336
|
}, ms);
|
|
9285
9337
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
9286
9338
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whoburnedmore",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.19",
|
|
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": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dist/index.js"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "
|
|
13
|
+
"build": "node scripts/clean.mjs && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:ccusage --external:picocolors --external:tokscale",
|
|
14
14
|
"test": "vitest run",
|
|
15
15
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
16
16
|
"smoke:package": "node scripts/smoke-package.mjs",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"release:major": "npm version major && npm publish"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"ccusage": "20.0.
|
|
23
|
+
"ccusage": "20.0.19",
|
|
24
24
|
"picocolors": "^1.1.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|