whoburnedmore 0.9.24 → 0.9.25
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 +800 -728
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -422,6 +422,7 @@ function loadConfig(dir = defaultConfigDir()) {
|
|
|
422
422
|
if (typeof parsed.deviceBoundAt === "number" && Number.isFinite(parsed.deviceBoundAt)) {
|
|
423
423
|
config.deviceBoundAt = parsed.deviceBoundAt;
|
|
424
424
|
}
|
|
425
|
+
if (parsed.autoSyncOptOut === true) config.autoSyncOptOut = true;
|
|
425
426
|
return Object.keys(config).length > 0 ? config : null;
|
|
426
427
|
} catch {
|
|
427
428
|
return null;
|
|
@@ -448,6 +449,15 @@ function recordDeviceBound(dir = defaultConfigDir(), when = Date.now()) {
|
|
|
448
449
|
const config = loadConfig(dir) ?? {};
|
|
449
450
|
saveConfig(dir, { ...config, deviceBoundAt: when });
|
|
450
451
|
}
|
|
452
|
+
function setAutoSyncOptOut(optOut, dir = defaultConfigDir()) {
|
|
453
|
+
const config = loadConfig(dir);
|
|
454
|
+
if (!optOut && !config?.autoSyncOptOut) return;
|
|
455
|
+
const { autoSyncOptOut: _o, ...rest } = config ?? {};
|
|
456
|
+
saveConfig(dir, optOut ? { ...rest, autoSyncOptOut: true } : rest);
|
|
457
|
+
}
|
|
458
|
+
function autoSyncOptedOut(dir = defaultConfigDir()) {
|
|
459
|
+
return loadConfig(dir)?.autoSyncOptOut === true;
|
|
460
|
+
}
|
|
451
461
|
function saveAuth(dir = defaultConfigDir(), auth = { cliToken: "" }) {
|
|
452
462
|
const config = loadConfig(dir) ?? {};
|
|
453
463
|
saveConfig(dir, {
|
|
@@ -1153,8 +1163,7 @@ import { promisify } from "node:util";
|
|
|
1153
1163
|
|
|
1154
1164
|
// src/attribution.ts
|
|
1155
1165
|
import { readdirSync, statSync as statSync2 } from "node:fs";
|
|
1156
|
-
import {
|
|
1157
|
-
import { join as join6 } from "node:path";
|
|
1166
|
+
import { join as join7 } from "node:path";
|
|
1158
1167
|
|
|
1159
1168
|
// src/native/claude.ts
|
|
1160
1169
|
import { readdir } from "node:fs/promises";
|
|
@@ -6995,12 +7004,20 @@ function nativeCachePath(reader, env = process.env) {
|
|
|
6995
7004
|
const dir = override || defaultConfigDir();
|
|
6996
7005
|
return join4(dir, `native-cache-${reader}.json`);
|
|
6997
7006
|
}
|
|
7007
|
+
function cacheTimeZone() {
|
|
7008
|
+
try {
|
|
7009
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "unknown";
|
|
7010
|
+
} catch {
|
|
7011
|
+
return "unknown";
|
|
7012
|
+
}
|
|
7013
|
+
}
|
|
6998
7014
|
async function loadCache(path, version, maxBytes) {
|
|
6999
7015
|
try {
|
|
7000
7016
|
const read = await readTextFileCapped(path, { maxBytes });
|
|
7001
7017
|
if (!read.ok) return {};
|
|
7002
7018
|
const parsed = JSON.parse(read.content);
|
|
7003
|
-
|
|
7019
|
+
const sameZone = parsed?.tz === void 0 || parsed.tz === cacheTimeZone();
|
|
7020
|
+
if (parsed && parsed.v === version && sameZone && parsed.files && typeof parsed.files === "object") {
|
|
7004
7021
|
return parsed.files;
|
|
7005
7022
|
}
|
|
7006
7023
|
} catch {
|
|
@@ -7014,7 +7031,7 @@ function saveCache(path, version, files, maxBytes) {
|
|
|
7014
7031
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
7015
7032
|
chmodSync2(dir, 448);
|
|
7016
7033
|
tmp = `${path}.tmp-${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
7017
|
-
const serialized = JSON.stringify({ v: version, files });
|
|
7034
|
+
const serialized = JSON.stringify({ v: version, tz: cacheTimeZone(), files });
|
|
7018
7035
|
if (Buffer.byteLength(serialized, "utf8") > maxBytes) return;
|
|
7019
7036
|
writeFileSync4(tmp, serialized, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
7020
7037
|
chmodSync2(tmp, 384);
|
|
@@ -7037,6 +7054,7 @@ async function readFilesWithCache(opts) {
|
|
|
7037
7054
|
const cached = await loadCache(opts.cachePath, opts.version, maxCacheBytes);
|
|
7038
7055
|
const fresh = {};
|
|
7039
7056
|
const itemsByFile = [];
|
|
7057
|
+
const permissionDenied = [];
|
|
7040
7058
|
let filesRead = 0;
|
|
7041
7059
|
for (const f of opts.files) {
|
|
7042
7060
|
let size;
|
|
@@ -7070,6 +7088,10 @@ async function readFilesWithCache(opts) {
|
|
|
7070
7088
|
}
|
|
7071
7089
|
if (opts.streamFile) {
|
|
7072
7090
|
const streamed = await opts.streamFile(f).catch(() => ({ ok: false, reason: "unreadable" }));
|
|
7091
|
+
if (!streamed.ok && streamed.reason === "permission-denied") {
|
|
7092
|
+
permissionDenied.push(f);
|
|
7093
|
+
continue;
|
|
7094
|
+
}
|
|
7073
7095
|
if (!streamed.ok) {
|
|
7074
7096
|
const progress = { ...cached, ...fresh };
|
|
7075
7097
|
delete progress[f];
|
|
@@ -7099,7 +7121,12 @@ async function readFilesWithCache(opts) {
|
|
|
7099
7121
|
filesRead += 1;
|
|
7100
7122
|
}
|
|
7101
7123
|
saveCache(opts.cachePath, opts.version, fresh, maxCacheBytes);
|
|
7102
|
-
return {
|
|
7124
|
+
return {
|
|
7125
|
+
itemsByFile,
|
|
7126
|
+
filesRead,
|
|
7127
|
+
timedOut: false,
|
|
7128
|
+
...permissionDenied.length > 0 ? { permissionDenied } : {}
|
|
7129
|
+
};
|
|
7103
7130
|
}
|
|
7104
7131
|
|
|
7105
7132
|
// src/native/jsonl-stream.ts
|
|
@@ -7146,7 +7173,9 @@ async function readJsonlStream(path, onLine, opts) {
|
|
|
7146
7173
|
if (pendingBytes > 0) onLine(Buffer.concat(parts, pendingBytes).toString("utf8"));
|
|
7147
7174
|
if (now() > opts.deadline) return { ok: false, reason: "timed-out" };
|
|
7148
7175
|
return { ok: true };
|
|
7149
|
-
} catch {
|
|
7176
|
+
} catch (err) {
|
|
7177
|
+
const code = err?.code;
|
|
7178
|
+
if (code === "EACCES" || code === "EPERM") return { ok: false, reason: "permission-denied" };
|
|
7150
7179
|
return { ok: false, reason: "unreadable" };
|
|
7151
7180
|
} finally {
|
|
7152
7181
|
await handle?.close().catch(() => void 0);
|
|
@@ -7406,7 +7435,10 @@ async function collectClaudeNative(env = process.env, opts = {}) {
|
|
|
7406
7435
|
return {
|
|
7407
7436
|
entries: finalizeClaudeEntries(acc),
|
|
7408
7437
|
found: true,
|
|
7409
|
-
filesScanned: res.filesRead
|
|
7438
|
+
filesScanned: res.filesRead,
|
|
7439
|
+
// Skipped files mean this scan is partial: keep its rows, but flag it the
|
|
7440
|
+
// way a timeout is flagged so request fingerprints are never lowered.
|
|
7441
|
+
...res.permissionDenied ? { timedOut: true, permissionDenied: res.permissionDenied } : {}
|
|
7410
7442
|
};
|
|
7411
7443
|
}
|
|
7412
7444
|
async function collectClaudeRequests(env = process.env, opts = {}) {
|
|
@@ -7418,6 +7450,7 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
|
|
|
7418
7450
|
const deadline = now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS);
|
|
7419
7451
|
const acc = /* @__PURE__ */ new Map();
|
|
7420
7452
|
const onLine = boundedLineAccumulator(acc, opts.maxRequestBytes);
|
|
7453
|
+
let skipped = false;
|
|
7421
7454
|
for (const f of files) {
|
|
7422
7455
|
if (now() > deadline) {
|
|
7423
7456
|
return { requests: [], found: false, timedOut: true };
|
|
@@ -7427,187 +7460,175 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
|
|
|
7427
7460
|
deadline,
|
|
7428
7461
|
now
|
|
7429
7462
|
});
|
|
7463
|
+
if (!read.ok && read.reason === "permission-denied") {
|
|
7464
|
+
skipped = true;
|
|
7465
|
+
continue;
|
|
7466
|
+
}
|
|
7430
7467
|
if (!read.ok) {
|
|
7431
7468
|
return { requests: [], found: false, timedOut: true };
|
|
7432
7469
|
}
|
|
7433
7470
|
}
|
|
7434
|
-
return { requests: [...acc.values()], found: true };
|
|
7471
|
+
return { requests: [...acc.values()], found: true, ...skipped ? { timedOut: true } : {} };
|
|
7435
7472
|
}
|
|
7436
7473
|
|
|
7437
|
-
// src/
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
function createAccumulator() {
|
|
7445
|
-
return {
|
|
7446
|
-
tools: /* @__PURE__ */ new Map(),
|
|
7447
|
-
skills: /* @__PURE__ */ new Map(),
|
|
7448
|
-
agent: {
|
|
7449
|
-
messageCount: 0,
|
|
7450
|
-
subagentMessages: 0,
|
|
7451
|
-
subagentTokens: 0,
|
|
7452
|
-
totalTokens: 0,
|
|
7453
|
-
userMessageCount: 0
|
|
7454
|
-
},
|
|
7455
|
-
sessionMessages: /* @__PURE__ */ new Map()
|
|
7456
|
-
};
|
|
7457
|
-
}
|
|
7458
|
-
function createFileContext() {
|
|
7459
|
-
return { toolNames: /* @__PURE__ */ new Map() };
|
|
7460
|
-
}
|
|
7461
|
-
function recordTokens(usage) {
|
|
7462
|
-
if (!usage || typeof usage !== "object") return 0;
|
|
7463
|
-
const u = usage;
|
|
7464
|
-
const n = (v) => {
|
|
7465
|
-
const x = Math.round(Number(v));
|
|
7466
|
-
return Number.isFinite(x) && x > 0 ? x : 0;
|
|
7467
|
-
};
|
|
7468
|
-
return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens) + n(u.cache_read_input_tokens);
|
|
7474
|
+
// src/native/codex.ts
|
|
7475
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
7476
|
+
import { homedir as homedir4 } from "node:os";
|
|
7477
|
+
import { basename, join as join6, resolve as resolve2 } from "node:path";
|
|
7478
|
+
function num2(n) {
|
|
7479
|
+
const v = Math.round(Number(n));
|
|
7480
|
+
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
7469
7481
|
}
|
|
7470
|
-
function
|
|
7471
|
-
const
|
|
7472
|
-
|
|
7473
|
-
|
|
7482
|
+
function readTokenFields(payload) {
|
|
7483
|
+
const info = payload.info;
|
|
7484
|
+
const src = info?.total_token_usage ?? payload;
|
|
7485
|
+
const input = src.input_tokens;
|
|
7486
|
+
const output = src.output_tokens;
|
|
7487
|
+
if (input === void 0 && output === void 0) return null;
|
|
7488
|
+
return {
|
|
7489
|
+
input: num2(input),
|
|
7490
|
+
cached: num2(src.cached_input_tokens),
|
|
7491
|
+
output: num2(output)
|
|
7474
7492
|
};
|
|
7475
|
-
if (typeof content === "string") return isHuman(content);
|
|
7476
|
-
if (!Array.isArray(content)) return false;
|
|
7477
|
-
return content.some(
|
|
7478
|
-
(b) => b !== null && typeof b === "object" && b.type === "text" && typeof b.text === "string" && isHuman(b.text)
|
|
7479
|
-
);
|
|
7480
7493
|
}
|
|
7481
|
-
function
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
const content = r.message?.content;
|
|
7496
|
-
if (!Array.isArray(content)) return;
|
|
7497
|
-
const isAssistant = r.type === "assistant" || r.message?.role === "assistant";
|
|
7498
|
-
const isUser = r.type === "user" || r.message?.role === "user";
|
|
7499
|
-
if (isAssistant) {
|
|
7500
|
-
const toolUses = [];
|
|
7501
|
-
for (const block of content) {
|
|
7502
|
-
if (block && typeof block === "object" && block.type === "tool_use" && typeof block.name === "string") {
|
|
7503
|
-
const name = block.name.slice(0, 128);
|
|
7504
|
-
if (!name) continue;
|
|
7505
|
-
const id = block.id;
|
|
7506
|
-
toolUses.push({ name, id: typeof id === "string" ? id : void 0 });
|
|
7507
|
-
}
|
|
7508
|
-
}
|
|
7509
|
-
const perToolTokens = toolUses.length > 0 ? Math.floor(recTokens / toolUses.length) : 0;
|
|
7510
|
-
for (const tu of toolUses) {
|
|
7511
|
-
const t = acc.tools.get(tu.name) ?? { count: 0, errors: 0, tokens: 0 };
|
|
7512
|
-
t.count += 1;
|
|
7513
|
-
t.tokens += perToolTokens;
|
|
7514
|
-
acc.tools.set(tu.name, t);
|
|
7515
|
-
if (tu.id) ctx.toolNames.set(tu.id, tu.name);
|
|
7494
|
+
function inspectCodexRollout(lines) {
|
|
7495
|
+
let model = "unknown";
|
|
7496
|
+
let sawSessionMeta = false;
|
|
7497
|
+
let replayedRollout = false;
|
|
7498
|
+
const perDay = /* @__PURE__ */ new Map();
|
|
7499
|
+
let lastSeenDate = null;
|
|
7500
|
+
for (const raw of lines) {
|
|
7501
|
+
const trimmed = raw.trim();
|
|
7502
|
+
if (!trimmed) continue;
|
|
7503
|
+
let obj;
|
|
7504
|
+
try {
|
|
7505
|
+
obj = JSON.parse(trimmed);
|
|
7506
|
+
} catch {
|
|
7507
|
+
continue;
|
|
7516
7508
|
}
|
|
7517
|
-
const
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7509
|
+
const payload = obj.payload;
|
|
7510
|
+
if (!payload || typeof payload !== "object") continue;
|
|
7511
|
+
const kind = obj.type;
|
|
7512
|
+
if (kind === "session_meta" && !sawSessionMeta) {
|
|
7513
|
+
sawSessionMeta = true;
|
|
7514
|
+
const source = payload.source;
|
|
7515
|
+
if (typeof payload.forked_from_id === "string" || typeof payload.parent_thread_id === "string" || source && typeof source === "object" && source.subagent !== void 0) {
|
|
7516
|
+
replayedRollout = true;
|
|
7517
|
+
}
|
|
7524
7518
|
}
|
|
7525
|
-
if (
|
|
7526
|
-
|
|
7527
|
-
r.sessionId,
|
|
7528
|
-
(acc.sessionMessages.get(r.sessionId) ?? 0) + 1
|
|
7529
|
-
);
|
|
7519
|
+
if (kind === "session_meta" || kind === "turn_context") {
|
|
7520
|
+
if (typeof payload.model === "string" && payload.model) model = payload.model;
|
|
7530
7521
|
}
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
if (
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7522
|
+
if (payload.type === "token_count") {
|
|
7523
|
+
const fields = readTokenFields(payload);
|
|
7524
|
+
if (!fields) continue;
|
|
7525
|
+
const parsed = localUsageDate(Date.parse(String(obj.timestamp ?? "")));
|
|
7526
|
+
const day = parsed ?? lastSeenDate;
|
|
7527
|
+
if (!day) continue;
|
|
7528
|
+
lastSeenDate = day;
|
|
7529
|
+
const e = perDay.get(day);
|
|
7530
|
+
if (e) {
|
|
7531
|
+
e.cum = fields;
|
|
7532
|
+
e.turns += 1;
|
|
7533
|
+
} else {
|
|
7534
|
+
perDay.set(day, { cum: fields, turns: 1 });
|
|
7541
7535
|
}
|
|
7542
7536
|
}
|
|
7543
7537
|
}
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
const
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7538
|
+
if (perDay.size === 0)
|
|
7539
|
+
return { sessions: [], replaySessions: [], replayCandidateDates: [] };
|
|
7540
|
+
const dates = [...perDay.keys()].sort();
|
|
7541
|
+
const out = [];
|
|
7542
|
+
let prev = { input: 0, cached: 0, output: 0 };
|
|
7543
|
+
for (const date of dates) {
|
|
7544
|
+
const { cum, turns } = perDay.get(date);
|
|
7545
|
+
const dInput = Math.max(0, cum.input - prev.input);
|
|
7546
|
+
const dCached = Math.max(0, cum.cached - prev.cached);
|
|
7547
|
+
const dOutput = Math.max(0, cum.output - prev.output);
|
|
7548
|
+
prev = cum;
|
|
7549
|
+
const cacheReadTokens = Math.min(dCached, dInput);
|
|
7550
|
+
const inputTokens = dInput - cacheReadTokens;
|
|
7551
|
+
const outputTokens = dOutput;
|
|
7552
|
+
if (inputTokens + outputTokens + cacheReadTokens === 0) continue;
|
|
7553
|
+
out.push({
|
|
7554
|
+
date,
|
|
7555
|
+
model,
|
|
7556
|
+
inputTokens,
|
|
7557
|
+
outputTokens,
|
|
7558
|
+
cacheCreationTokens: 0,
|
|
7559
|
+
cacheReadTokens,
|
|
7560
|
+
turnCount: turns
|
|
7561
|
+
});
|
|
7562
|
+
}
|
|
7557
7563
|
return {
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
sessionMessages: acc.sessionMessages,
|
|
7562
|
-
complete: true
|
|
7564
|
+
sessions: replayedRollout ? [] : out,
|
|
7565
|
+
replaySessions: replayedRollout ? out : [],
|
|
7566
|
+
replayCandidateDates: replayedRollout ? dates : []
|
|
7563
7567
|
};
|
|
7564
7568
|
}
|
|
7565
|
-
function
|
|
7566
|
-
const
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7569
|
+
function foldCodexSessions(acc, sessions) {
|
|
7570
|
+
for (const s of sessions) {
|
|
7571
|
+
const k = `${s.date}|${s.model}`;
|
|
7572
|
+
let b = acc.get(k);
|
|
7573
|
+
if (!b) {
|
|
7574
|
+
b = {
|
|
7575
|
+
date: s.date,
|
|
7576
|
+
model: s.model,
|
|
7577
|
+
inputTokens: 0,
|
|
7578
|
+
outputTokens: 0,
|
|
7579
|
+
cacheCreationTokens: 0,
|
|
7580
|
+
cacheReadTokens: 0,
|
|
7581
|
+
requestCount: 0
|
|
7582
|
+
};
|
|
7583
|
+
acc.set(k, b);
|
|
7584
|
+
}
|
|
7585
|
+
b.inputTokens += s.inputTokens;
|
|
7586
|
+
b.outputTokens += s.outputTokens;
|
|
7587
|
+
b.cacheCreationTokens += s.cacheCreationTokens;
|
|
7588
|
+
b.cacheReadTokens += s.cacheReadTokens;
|
|
7589
|
+
b.requestCount += s.turnCount;
|
|
7590
|
+
}
|
|
7571
7591
|
}
|
|
7572
|
-
function
|
|
7573
|
-
|
|
7574
|
-
const
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
return;
|
|
7592
|
+
function finalizeCodexEntries(acc) {
|
|
7593
|
+
const entries = [];
|
|
7594
|
+
for (const b of acc.values()) {
|
|
7595
|
+
entries.push({
|
|
7596
|
+
date: b.date,
|
|
7597
|
+
tool: "codex",
|
|
7598
|
+
model: b.model,
|
|
7599
|
+
inputTokens: b.inputTokens,
|
|
7600
|
+
outputTokens: b.outputTokens,
|
|
7601
|
+
cacheCreationTokens: b.cacheCreationTokens,
|
|
7602
|
+
cacheReadTokens: b.cacheReadTokens,
|
|
7603
|
+
costUSD: estimateCostUSD(b.model, b),
|
|
7604
|
+
origin: "cli",
|
|
7605
|
+
verified: false,
|
|
7606
|
+
requestCount: b.requestCount
|
|
7607
|
+
});
|
|
7589
7608
|
}
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
for (const tu of ctx.pending) {
|
|
7606
|
-
const t = acc.tools.get(tu.name);
|
|
7607
|
-
if (t) t.tokens += per;
|
|
7608
|
-
}
|
|
7609
|
-
ctx.pending = [];
|
|
7609
|
+
return entries;
|
|
7610
|
+
}
|
|
7611
|
+
function resolveCodexHome(env = process.env) {
|
|
7612
|
+
return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join6(homedir4(), ".codex");
|
|
7613
|
+
}
|
|
7614
|
+
function resolveCodexSessionsDirs(env = process.env) {
|
|
7615
|
+
const home = resolve2(resolveCodexHome(env));
|
|
7616
|
+
return [join6(home, "sessions"), join6(home, "archived_sessions")];
|
|
7617
|
+
}
|
|
7618
|
+
async function listJsonl2(dir) {
|
|
7619
|
+
let dirents;
|
|
7620
|
+
try {
|
|
7621
|
+
dirents = await readdir2(dir, { withFileTypes: true });
|
|
7622
|
+
} catch {
|
|
7623
|
+
return [];
|
|
7610
7624
|
}
|
|
7625
|
+
const out = [];
|
|
7626
|
+
for (const d of dirents) {
|
|
7627
|
+
const full = join6(dir, d.name);
|
|
7628
|
+
if (d.isDirectory()) out.push(...await listJsonl2(full));
|
|
7629
|
+
else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
|
|
7630
|
+
}
|
|
7631
|
+
return out;
|
|
7611
7632
|
}
|
|
7612
7633
|
function* splitLines2(content) {
|
|
7613
7634
|
let start = 0;
|
|
@@ -7619,113 +7640,385 @@ function* splitLines2(content) {
|
|
|
7619
7640
|
}
|
|
7620
7641
|
if (start < content.length) yield content.slice(start);
|
|
7621
7642
|
}
|
|
7622
|
-
|
|
7643
|
+
var NATIVE_READ_BUDGET_MS2 = 45e3;
|
|
7644
|
+
var CODEX_CACHE_VERSION = 7;
|
|
7645
|
+
function toCachedSession(s, kind = "usage") {
|
|
7646
|
+
return [
|
|
7647
|
+
kind,
|
|
7648
|
+
s.date,
|
|
7649
|
+
s.model,
|
|
7650
|
+
s.inputTokens,
|
|
7651
|
+
s.outputTokens,
|
|
7652
|
+
s.cacheCreationTokens,
|
|
7653
|
+
s.cacheReadTokens,
|
|
7654
|
+
s.turnCount
|
|
7655
|
+
];
|
|
7656
|
+
}
|
|
7657
|
+
function fromCachedSession(t) {
|
|
7623
7658
|
return {
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
acc.agent.userMessageCount
|
|
7632
|
-
],
|
|
7633
|
-
sessions: [...acc.sessionMessages].map(([s, c]) => [s, c])
|
|
7659
|
+
date: t[1],
|
|
7660
|
+
model: t[2],
|
|
7661
|
+
inputTokens: t[3],
|
|
7662
|
+
outputTokens: t[4],
|
|
7663
|
+
cacheCreationTokens: t[5],
|
|
7664
|
+
cacheReadTokens: t[6],
|
|
7665
|
+
turnCount: t[7]
|
|
7634
7666
|
};
|
|
7635
7667
|
}
|
|
7636
|
-
function
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7668
|
+
async function collectCodexNative(env = process.env, opts = {}) {
|
|
7669
|
+
const dirs = resolveCodexSessionsDirs(env);
|
|
7670
|
+
const files = [];
|
|
7671
|
+
const primaryFiles = /* @__PURE__ */ new Set();
|
|
7672
|
+
const seenIdentities = /* @__PURE__ */ new Set();
|
|
7673
|
+
for (const dir of dirs) {
|
|
7674
|
+
for (const file of await listJsonl2(dir)) {
|
|
7675
|
+
const key = basename(file);
|
|
7676
|
+
files.push(file);
|
|
7677
|
+
if (!seenIdentities.has(key)) {
|
|
7678
|
+
seenIdentities.add(key);
|
|
7679
|
+
primaryFiles.add(file);
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
7649
7682
|
}
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7683
|
+
if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
|
|
7684
|
+
const now = opts.now ?? Date.now;
|
|
7685
|
+
const res = await readFilesWithCache({
|
|
7686
|
+
files,
|
|
7687
|
+
cachePath: opts.cachePath ?? nativeCachePath("codex", env),
|
|
7688
|
+
version: CODEX_CACHE_VERSION,
|
|
7689
|
+
parseFile: (content, path) => {
|
|
7690
|
+
const inspected = inspectCodexRollout(splitLines2(content));
|
|
7691
|
+
const primary = primaryFiles.has(path);
|
|
7692
|
+
return [
|
|
7693
|
+
// Do not pass toCachedSession directly to map: Array.map's numeric
|
|
7694
|
+
// index would be supplied as the optional `kind` argument.
|
|
7695
|
+
...inspected.sessions.map(
|
|
7696
|
+
(session) => toCachedSession(session, primary ? "usage" : "duplicate")
|
|
7697
|
+
),
|
|
7698
|
+
...inspected.replaySessions.map(
|
|
7699
|
+
(session) => toCachedSession(
|
|
7700
|
+
session,
|
|
7701
|
+
primary ? "replay" : "replay-duplicate"
|
|
7702
|
+
)
|
|
7703
|
+
)
|
|
7704
|
+
];
|
|
7705
|
+
},
|
|
7706
|
+
deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS2),
|
|
7707
|
+
now
|
|
7708
|
+
});
|
|
7709
|
+
if (!res.itemsByFile) {
|
|
7710
|
+
return {
|
|
7711
|
+
entries: [],
|
|
7712
|
+
found: false,
|
|
7713
|
+
filesScanned: res.filesRead,
|
|
7714
|
+
timedOut: true
|
|
7715
|
+
};
|
|
7657
7716
|
}
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
const
|
|
7661
|
-
const
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7717
|
+
const acc = /* @__PURE__ */ new Map();
|
|
7718
|
+
const legacyAcc = /* @__PURE__ */ new Map();
|
|
7719
|
+
const replayCandidateDates = /* @__PURE__ */ new Set();
|
|
7720
|
+
for (const items of res.itemsByFile) {
|
|
7721
|
+
const usage = items.filter((item) => item[0] === "usage").map(fromCachedSession);
|
|
7722
|
+
const legacy = items.map(fromCachedSession);
|
|
7723
|
+
const replay = items.filter(
|
|
7724
|
+
(item) => item[0] === "replay" || item[0] === "replay-duplicate"
|
|
7725
|
+
).map(fromCachedSession);
|
|
7726
|
+
foldCodexSessions(acc, usage);
|
|
7727
|
+
foldCodexSessions(legacyAcc, legacy);
|
|
7728
|
+
for (const session of replay) replayCandidateDates.add(session.date);
|
|
7668
7729
|
}
|
|
7669
|
-
return
|
|
7730
|
+
return {
|
|
7731
|
+
entries: finalizeCodexEntries(acc),
|
|
7732
|
+
found: true,
|
|
7733
|
+
filesScanned: res.filesRead,
|
|
7734
|
+
replayCandidateDates: [...replayCandidateDates].sort(),
|
|
7735
|
+
legacyEntries: finalizeCodexEntries(legacyAcc)
|
|
7736
|
+
};
|
|
7670
7737
|
}
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7738
|
+
|
|
7739
|
+
// src/attribution.ts
|
|
7740
|
+
var MAX_FILES = 5e3;
|
|
7741
|
+
var TIME_BUDGET_MS = NATIVE_READ_BUDGET_MS;
|
|
7742
|
+
var ATTRIBUTION_CACHE_VERSION = 1;
|
|
7743
|
+
var MAX_STATS = 300;
|
|
7744
|
+
function createAccumulator() {
|
|
7745
|
+
return {
|
|
7746
|
+
tools: /* @__PURE__ */ new Map(),
|
|
7747
|
+
skills: /* @__PURE__ */ new Map(),
|
|
7748
|
+
agent: {
|
|
7749
|
+
messageCount: 0,
|
|
7750
|
+
subagentMessages: 0,
|
|
7751
|
+
subagentTokens: 0,
|
|
7752
|
+
totalTokens: 0,
|
|
7753
|
+
userMessageCount: 0
|
|
7754
|
+
},
|
|
7755
|
+
sessionMessages: /* @__PURE__ */ new Map()
|
|
7756
|
+
};
|
|
7682
7757
|
}
|
|
7683
|
-
function
|
|
7684
|
-
|
|
7685
|
-
const cfg = process.env.CLAUDE_CONFIG_DIR;
|
|
7686
|
-
if (cfg) dirs.push(join6(cfg, "projects"));
|
|
7687
|
-
dirs.push(join6(homedir4(), ".config", "claude", "projects"));
|
|
7688
|
-
return [...new Set(dirs)];
|
|
7758
|
+
function createFileContext() {
|
|
7759
|
+
return { toolNames: /* @__PURE__ */ new Map() };
|
|
7689
7760
|
}
|
|
7690
|
-
function
|
|
7691
|
-
|
|
7692
|
-
const
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
entries = readdirSync(d, { withFileTypes: true });
|
|
7697
|
-
} catch {
|
|
7698
|
-
return;
|
|
7699
|
-
}
|
|
7700
|
-
for (const e of entries) {
|
|
7701
|
-
if (out.length >= MAX_FILES) return;
|
|
7702
|
-
const p = join6(d, e.name);
|
|
7703
|
-
if (e.isDirectory()) walk(p);
|
|
7704
|
-
else if (e.isFile() && e.name.endsWith(".jsonl")) {
|
|
7705
|
-
try {
|
|
7706
|
-
out.push({ path: p, mtime: statSync2(p).mtimeMs });
|
|
7707
|
-
} catch {
|
|
7708
|
-
}
|
|
7709
|
-
}
|
|
7710
|
-
}
|
|
7761
|
+
function recordTokens(usage) {
|
|
7762
|
+
if (!usage || typeof usage !== "object") return 0;
|
|
7763
|
+
const u = usage;
|
|
7764
|
+
const n = (v) => {
|
|
7765
|
+
const x = Math.round(Number(v));
|
|
7766
|
+
return Number.isFinite(x) && x > 0 ? x : 0;
|
|
7711
7767
|
};
|
|
7712
|
-
|
|
7713
|
-
return out.sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES).map((f) => f.path);
|
|
7768
|
+
return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens) + n(u.cache_read_input_tokens);
|
|
7714
7769
|
}
|
|
7715
|
-
|
|
7716
|
-
const
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
const mergeInto = (items) => {
|
|
7720
|
-
if (!items) {
|
|
7721
|
-
complete = false;
|
|
7722
|
-
return;
|
|
7723
|
-
}
|
|
7724
|
-
for (const perFile of items) for (const agg of perFile) mergeAgg(acc, agg);
|
|
7770
|
+
function hasHumanText(content) {
|
|
7771
|
+
const isHuman = (t) => {
|
|
7772
|
+
const s = t.trim();
|
|
7773
|
+
return s.length > 0 && !s.startsWith("<system-reminder") && !s.startsWith("<command-") && !s.startsWith("Caveat:");
|
|
7725
7774
|
};
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7775
|
+
if (typeof content === "string") return isHuman(content);
|
|
7776
|
+
if (!Array.isArray(content)) return false;
|
|
7777
|
+
return content.some(
|
|
7778
|
+
(b) => b !== null && typeof b === "object" && b.type === "text" && typeof b.text === "string" && isHuman(b.text)
|
|
7779
|
+
);
|
|
7780
|
+
}
|
|
7781
|
+
function processRecord(rec, acc, ctx) {
|
|
7782
|
+
if (!rec || typeof rec !== "object") return;
|
|
7783
|
+
const r = rec;
|
|
7784
|
+
const recTokens = recordTokens(r.message?.usage);
|
|
7785
|
+
if ((r.type === "user" || r.message?.role === "user") && r.isSidechain !== true && r.isMeta !== true && hasHumanText(r.message?.content)) {
|
|
7786
|
+
acc.agent.userMessageCount += 1;
|
|
7787
|
+
}
|
|
7788
|
+
if (typeof r.attributionSkill === "string" && r.attributionSkill) {
|
|
7789
|
+
const s = r.attributionSkill.slice(0, 128);
|
|
7790
|
+
const sk = acc.skills.get(s) ?? { count: 0, tokens: 0 };
|
|
7791
|
+
sk.count += 1;
|
|
7792
|
+
sk.tokens += recTokens;
|
|
7793
|
+
acc.skills.set(s, sk);
|
|
7794
|
+
}
|
|
7795
|
+
const content = r.message?.content;
|
|
7796
|
+
if (!Array.isArray(content)) return;
|
|
7797
|
+
const isAssistant = r.type === "assistant" || r.message?.role === "assistant";
|
|
7798
|
+
const isUser = r.type === "user" || r.message?.role === "user";
|
|
7799
|
+
if (isAssistant) {
|
|
7800
|
+
const toolUses = [];
|
|
7801
|
+
for (const block of content) {
|
|
7802
|
+
if (block && typeof block === "object" && block.type === "tool_use" && typeof block.name === "string") {
|
|
7803
|
+
const name = block.name.slice(0, 128);
|
|
7804
|
+
if (!name) continue;
|
|
7805
|
+
const id = block.id;
|
|
7806
|
+
toolUses.push({ name, id: typeof id === "string" ? id : void 0 });
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
const perToolTokens = toolUses.length > 0 ? Math.floor(recTokens / toolUses.length) : 0;
|
|
7810
|
+
for (const tu of toolUses) {
|
|
7811
|
+
const t = acc.tools.get(tu.name) ?? { count: 0, errors: 0, tokens: 0 };
|
|
7812
|
+
t.count += 1;
|
|
7813
|
+
t.tokens += perToolTokens;
|
|
7814
|
+
acc.tools.set(tu.name, t);
|
|
7815
|
+
if (tu.id) ctx.toolNames.set(tu.id, tu.name);
|
|
7816
|
+
}
|
|
7817
|
+
const tokens = recTokens;
|
|
7818
|
+
acc.agent.messageCount += 1;
|
|
7819
|
+
acc.agent.totalTokens += tokens;
|
|
7820
|
+
const sidechain = r.isSidechain === true;
|
|
7821
|
+
if (sidechain) {
|
|
7822
|
+
acc.agent.subagentMessages += 1;
|
|
7823
|
+
acc.agent.subagentTokens += tokens;
|
|
7824
|
+
}
|
|
7825
|
+
if (typeof r.sessionId === "string" && r.sessionId) {
|
|
7826
|
+
acc.sessionMessages.set(
|
|
7827
|
+
r.sessionId,
|
|
7828
|
+
(acc.sessionMessages.get(r.sessionId) ?? 0) + 1
|
|
7829
|
+
);
|
|
7830
|
+
}
|
|
7831
|
+
} else if (isUser) {
|
|
7832
|
+
for (const block of content) {
|
|
7833
|
+
if (block && typeof block === "object" && block.type === "tool_result" && block.is_error === true) {
|
|
7834
|
+
const id = block.tool_use_id;
|
|
7835
|
+
const name = typeof id === "string" ? ctx.toolNames.get(id) : void 0;
|
|
7836
|
+
if (name) {
|
|
7837
|
+
const t = acc.tools.get(name) ?? { count: 0, errors: 0, tokens: 0 };
|
|
7838
|
+
t.errors += 1;
|
|
7839
|
+
acc.tools.set(name, t);
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
}
|
|
7845
|
+
function toSkillStats(map) {
|
|
7846
|
+
return [...map.entries()].map(([name, v]) => ({ name, count: v.count, tokens: v.tokens })).filter((s) => s.count > 0).sort((a, b) => b.tokens - a.tokens || b.count - a.count).slice(0, MAX_STATS).map((s) => s.tokens > 0 ? s : { name: s.name, count: s.count });
|
|
7847
|
+
}
|
|
7848
|
+
function toToolStats(map) {
|
|
7849
|
+
return [...map.entries()].map(([name, v]) => ({ name, count: v.count, errors: v.errors, tokens: v.tokens })).filter((s) => s.count > 0).sort((a, b) => b.tokens - a.tokens || b.count - a.count).slice(0, MAX_STATS).map((s) => {
|
|
7850
|
+
const base = { name: s.name, count: s.count };
|
|
7851
|
+
if (s.errors > 0) base.errors = s.errors;
|
|
7852
|
+
if (s.tokens > 0) base.tokens = s.tokens;
|
|
7853
|
+
return base;
|
|
7854
|
+
});
|
|
7855
|
+
}
|
|
7856
|
+
function accumulatorToResult(acc) {
|
|
7857
|
+
return {
|
|
7858
|
+
tools: toToolStats(acc.tools),
|
|
7859
|
+
skills: toSkillStats(acc.skills),
|
|
7860
|
+
agent: { ...acc.agent },
|
|
7861
|
+
sessionMessages: acc.sessionMessages,
|
|
7862
|
+
complete: true
|
|
7863
|
+
};
|
|
7864
|
+
}
|
|
7865
|
+
function numTok(v) {
|
|
7866
|
+
const x = Math.round(Number(v));
|
|
7867
|
+
return Number.isFinite(x) && x > 0 ? x : 0;
|
|
7868
|
+
}
|
|
7869
|
+
function createCodexContext() {
|
|
7870
|
+
return { pending: [] };
|
|
7871
|
+
}
|
|
7872
|
+
function processCodexRecord(rec, acc, ctx) {
|
|
7873
|
+
if (!rec || typeof rec !== "object") return;
|
|
7874
|
+
const r = rec;
|
|
7875
|
+
if (!r.payload || typeof r.payload !== "object") return;
|
|
7876
|
+
const pl = r.payload;
|
|
7877
|
+
const ptype = pl.type;
|
|
7878
|
+
if (r.type === "session_meta" || r.type === "turn_context") return;
|
|
7879
|
+
if (ptype === "function_call" || ptype === "custom_tool_call" || ptype === "local_shell_call") {
|
|
7880
|
+
const raw = typeof pl.name === "string" ? pl.name : ptype === "local_shell_call" ? "local_shell" : "";
|
|
7881
|
+
const name = raw.slice(0, 128);
|
|
7882
|
+
if (!name) return;
|
|
7883
|
+
const id = typeof pl.call_id === "string" ? pl.call_id : void 0;
|
|
7884
|
+
ctx.pending.push({ name, id });
|
|
7885
|
+
const t = acc.tools.get(name) ?? { count: 0, errors: 0, tokens: 0 };
|
|
7886
|
+
t.count += 1;
|
|
7887
|
+
acc.tools.set(name, t);
|
|
7888
|
+
return;
|
|
7889
|
+
}
|
|
7890
|
+
if (ptype === "token_count") {
|
|
7891
|
+
const info = pl.info;
|
|
7892
|
+
const last = info?.last_token_usage;
|
|
7893
|
+
if (!last) return;
|
|
7894
|
+
const inputTokens = numTok(last.input_tokens);
|
|
7895
|
+
const cacheReadTokens = numTok(last.cached_input_tokens);
|
|
7896
|
+
const outputTokens = numTok(last.output_tokens) + numTok(last.reasoning_output_tokens);
|
|
7897
|
+
const tokens = numTok(last.total_tokens) || inputTokens + cacheReadTokens + outputTokens;
|
|
7898
|
+
if (tokens <= 0) {
|
|
7899
|
+
ctx.pending = [];
|
|
7900
|
+
return;
|
|
7901
|
+
}
|
|
7902
|
+
acc.agent.messageCount += 1;
|
|
7903
|
+
acc.agent.totalTokens += tokens;
|
|
7904
|
+
const per = ctx.pending.length > 0 ? Math.floor(tokens / ctx.pending.length) : 0;
|
|
7905
|
+
for (const tu of ctx.pending) {
|
|
7906
|
+
const t = acc.tools.get(tu.name);
|
|
7907
|
+
if (t) t.tokens += per;
|
|
7908
|
+
}
|
|
7909
|
+
ctx.pending = [];
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
function* splitLines3(content) {
|
|
7913
|
+
let start = 0;
|
|
7914
|
+
for (let i = 0; i < content.length; i++) {
|
|
7915
|
+
if (content[i] === "\n") {
|
|
7916
|
+
yield content.slice(start, i);
|
|
7917
|
+
start = i + 1;
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
if (start < content.length) yield content.slice(start);
|
|
7921
|
+
}
|
|
7922
|
+
function serializeAcc(acc) {
|
|
7923
|
+
return {
|
|
7924
|
+
tools: [...acc.tools].map(([n, v]) => [n, v.count, v.errors, v.tokens]),
|
|
7925
|
+
skills: [...acc.skills].map(([n, v]) => [n, v.count, v.tokens]),
|
|
7926
|
+
agent: [
|
|
7927
|
+
acc.agent.messageCount,
|
|
7928
|
+
acc.agent.subagentMessages,
|
|
7929
|
+
acc.agent.subagentTokens,
|
|
7930
|
+
acc.agent.totalTokens,
|
|
7931
|
+
acc.agent.userMessageCount
|
|
7932
|
+
],
|
|
7933
|
+
sessions: [...acc.sessionMessages].map(([s, c]) => [s, c])
|
|
7934
|
+
};
|
|
7935
|
+
}
|
|
7936
|
+
function mergeAgg(acc, agg) {
|
|
7937
|
+
for (const [n, count, errors, tokens] of agg.tools) {
|
|
7938
|
+
const cur = acc.tools.get(n) ?? { count: 0, errors: 0, tokens: 0 };
|
|
7939
|
+
cur.count += count;
|
|
7940
|
+
cur.errors += errors;
|
|
7941
|
+
cur.tokens += tokens;
|
|
7942
|
+
acc.tools.set(n, cur);
|
|
7943
|
+
}
|
|
7944
|
+
for (const [n, count, tokens] of agg.skills) {
|
|
7945
|
+
const cur = acc.skills.get(n) ?? { count: 0, tokens: 0 };
|
|
7946
|
+
cur.count += count;
|
|
7947
|
+
cur.tokens += tokens;
|
|
7948
|
+
acc.skills.set(n, cur);
|
|
7949
|
+
}
|
|
7950
|
+
acc.agent.messageCount += agg.agent[0];
|
|
7951
|
+
acc.agent.subagentMessages += agg.agent[1];
|
|
7952
|
+
acc.agent.subagentTokens += agg.agent[2];
|
|
7953
|
+
acc.agent.totalTokens += agg.agent[3];
|
|
7954
|
+
acc.agent.userMessageCount += agg.agent[4];
|
|
7955
|
+
for (const [s, c] of agg.sessions) {
|
|
7956
|
+
acc.sessionMessages.set(s, (acc.sessionMessages.get(s) ?? 0) + c);
|
|
7957
|
+
}
|
|
7958
|
+
}
|
|
7959
|
+
function parseClaudeAttrFile(content) {
|
|
7960
|
+
const acc = createAccumulator();
|
|
7961
|
+
const ctx = createFileContext();
|
|
7962
|
+
for (const line of splitLines3(content)) {
|
|
7963
|
+
if (!line) continue;
|
|
7964
|
+
try {
|
|
7965
|
+
processRecord(JSON.parse(line), acc, ctx);
|
|
7966
|
+
} catch {
|
|
7967
|
+
}
|
|
7968
|
+
}
|
|
7969
|
+
return [serializeAcc(acc)];
|
|
7970
|
+
}
|
|
7971
|
+
function parseCodexAttrFile(content) {
|
|
7972
|
+
const acc = createAccumulator();
|
|
7973
|
+
const ctx = createCodexContext();
|
|
7974
|
+
for (const line of splitLines3(content)) {
|
|
7975
|
+
if (!line) continue;
|
|
7976
|
+
try {
|
|
7977
|
+
processCodexRecord(JSON.parse(line), acc, ctx);
|
|
7978
|
+
} catch {
|
|
7979
|
+
}
|
|
7980
|
+
}
|
|
7981
|
+
return [serializeAcc(acc)];
|
|
7982
|
+
}
|
|
7983
|
+
function listTranscripts(dir) {
|
|
7984
|
+
const out = [];
|
|
7985
|
+
const walk = (d) => {
|
|
7986
|
+
if (out.length >= MAX_FILES) return;
|
|
7987
|
+
let entries;
|
|
7988
|
+
try {
|
|
7989
|
+
entries = readdirSync(d, { withFileTypes: true });
|
|
7990
|
+
} catch {
|
|
7991
|
+
return;
|
|
7992
|
+
}
|
|
7993
|
+
for (const e of entries) {
|
|
7994
|
+
if (out.length >= MAX_FILES) return;
|
|
7995
|
+
const p = join7(d, e.name);
|
|
7996
|
+
if (e.isDirectory()) walk(p);
|
|
7997
|
+
else if (e.isFile() && e.name.endsWith(".jsonl")) {
|
|
7998
|
+
try {
|
|
7999
|
+
out.push({ path: p, mtime: statSync2(p).mtimeMs });
|
|
8000
|
+
} catch {
|
|
8001
|
+
}
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
8004
|
+
};
|
|
8005
|
+
walk(dir);
|
|
8006
|
+
return out.sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES).map((f) => f.path);
|
|
8007
|
+
}
|
|
8008
|
+
async function collectAttribution(env = process.env) {
|
|
8009
|
+
const acc = createAccumulator();
|
|
8010
|
+
const deadline = Date.now() + TIME_BUDGET_MS;
|
|
8011
|
+
let complete = true;
|
|
8012
|
+
const mergeInto = (items) => {
|
|
8013
|
+
if (!items) {
|
|
8014
|
+
complete = false;
|
|
8015
|
+
return;
|
|
8016
|
+
}
|
|
8017
|
+
for (const perFile of items) for (const agg of perFile) mergeAgg(acc, agg);
|
|
8018
|
+
};
|
|
8019
|
+
try {
|
|
8020
|
+
const claudeFiles = [];
|
|
8021
|
+
for (const dir of resolveClaudeProjectDirs(env)) claudeFiles.push(...listTranscripts(dir));
|
|
7729
8022
|
if (claudeFiles.length > 0) {
|
|
7730
8023
|
const res = await readFilesWithCache({
|
|
7731
8024
|
files: claudeFiles,
|
|
@@ -7736,7 +8029,7 @@ async function collectAttribution(env = process.env) {
|
|
|
7736
8029
|
});
|
|
7737
8030
|
mergeInto(res.itemsByFile);
|
|
7738
8031
|
}
|
|
7739
|
-
const codexFiles = listTranscripts(
|
|
8032
|
+
const codexFiles = resolveCodexSessionsDirs(env).flatMap((dir) => listTranscripts(dir));
|
|
7740
8033
|
if (codexFiles.length > 0) {
|
|
7741
8034
|
const res = await readFilesWithCache({
|
|
7742
8035
|
files: codexFiles,
|
|
@@ -7754,10 +8047,10 @@ async function collectAttribution(env = process.env) {
|
|
|
7754
8047
|
}
|
|
7755
8048
|
|
|
7756
8049
|
// src/continue.ts
|
|
7757
|
-
import { readdir as
|
|
8050
|
+
import { readdir as readdir3 } from "node:fs/promises";
|
|
7758
8051
|
import { homedir as homedir5 } from "node:os";
|
|
7759
|
-
import { join as
|
|
7760
|
-
function
|
|
8052
|
+
import { join as join8 } from "node:path";
|
|
8053
|
+
function num3(n) {
|
|
7761
8054
|
const v = Math.round(Number(n));
|
|
7762
8055
|
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
7763
8056
|
}
|
|
@@ -7785,8 +8078,8 @@ function mapContinueRecords(records) {
|
|
|
7785
8078
|
if (ms === null) continue;
|
|
7786
8079
|
const date = localUsageDate(ms);
|
|
7787
8080
|
if (!date) continue;
|
|
7788
|
-
const inputTokens =
|
|
7789
|
-
const outputTokens =
|
|
8081
|
+
const inputTokens = num3(rec.promptTokens ?? rec.prompt_tokens);
|
|
8082
|
+
const outputTokens = num3(rec.generatedTokens ?? rec.generated_tokens);
|
|
7790
8083
|
if (inputTokens + outputTokens === 0) continue;
|
|
7791
8084
|
const key = `${date}|${model}`;
|
|
7792
8085
|
let b = byKey.get(key);
|
|
@@ -7834,7 +8127,7 @@ function parseContinueJsonl(content) {
|
|
|
7834
8127
|
}
|
|
7835
8128
|
return mapContinueRecords(records);
|
|
7836
8129
|
}
|
|
7837
|
-
async function
|
|
8130
|
+
async function listJsonl3(root, opts) {
|
|
7838
8131
|
const queue = [{ dir: root, depth: 0 }];
|
|
7839
8132
|
let queueIndex = 0;
|
|
7840
8133
|
const files = [];
|
|
@@ -7845,7 +8138,7 @@ async function listJsonl2(root, opts) {
|
|
|
7845
8138
|
const current = queue[queueIndex++];
|
|
7846
8139
|
let dirents;
|
|
7847
8140
|
try {
|
|
7848
|
-
dirents = await
|
|
8141
|
+
dirents = await readdir3(current.dir, { withFileTypes: true });
|
|
7849
8142
|
} catch {
|
|
7850
8143
|
continue;
|
|
7851
8144
|
}
|
|
@@ -7854,7 +8147,7 @@ async function listJsonl2(root, opts) {
|
|
|
7854
8147
|
if (visitedEntries > maxEntries || opts.now() > opts.deadline) {
|
|
7855
8148
|
return { files: [], aborted: true };
|
|
7856
8149
|
}
|
|
7857
|
-
const full =
|
|
8150
|
+
const full = join8(current.dir, d.name);
|
|
7858
8151
|
if (d.isDirectory()) {
|
|
7859
8152
|
if (current.depth >= 32) return { files: [], aborted: true };
|
|
7860
8153
|
queue.push({ dir: full, depth: current.depth + 1 });
|
|
@@ -7869,10 +8162,10 @@ async function listJsonl2(root, opts) {
|
|
|
7869
8162
|
var CONTINUE_CACHE_VERSION = 2;
|
|
7870
8163
|
async function collectContinue(opts = {}) {
|
|
7871
8164
|
const env = opts.env ?? process.env;
|
|
7872
|
-
const home = opts.continueDir ??
|
|
8165
|
+
const home = opts.continueDir ?? join8(env.HOME || homedir5(), ".continue");
|
|
7873
8166
|
const now = opts.now ?? Date.now;
|
|
7874
8167
|
const deadline = now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS);
|
|
7875
|
-
const discovery = await
|
|
8168
|
+
const discovery = await listJsonl3(join8(home, "dev_data"), {
|
|
7876
8169
|
deadline,
|
|
7877
8170
|
now,
|
|
7878
8171
|
maxFiles: Math.max(1, opts.maxFiles ?? 1e4)
|
|
@@ -7936,14 +8229,14 @@ import { spawnSync as spawnSync4 } from "node:child_process";
|
|
|
7936
8229
|
import { existsSync as existsSync4, realpathSync, statSync as statSync3 } from "node:fs";
|
|
7937
8230
|
import { createRequire as createRequire3 } from "node:module";
|
|
7938
8231
|
import { homedir as homedir6, platform as platform2 } from "node:os";
|
|
7939
|
-
import { join as
|
|
8232
|
+
import { join as join10 } from "node:path";
|
|
7940
8233
|
|
|
7941
8234
|
// src/tokscale.ts
|
|
7942
8235
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
7943
8236
|
import { createRequire as createRequire2 } from "node:module";
|
|
7944
|
-
import { dirname as dirname4, join as
|
|
8237
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
7945
8238
|
var LOOKBACK_DAYS = 30;
|
|
7946
|
-
function
|
|
8239
|
+
function num4(n) {
|
|
7947
8240
|
const v = Math.round(Number(n));
|
|
7948
8241
|
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
7949
8242
|
}
|
|
@@ -7956,10 +8249,10 @@ function mapTokscaleDay(date, json) {
|
|
|
7956
8249
|
if (!Array.isArray(entries)) return [];
|
|
7957
8250
|
const out = [];
|
|
7958
8251
|
for (const e of entries) {
|
|
7959
|
-
const inputTokens =
|
|
7960
|
-
const outputTokens =
|
|
7961
|
-
const cacheCreationTokens =
|
|
7962
|
-
const cacheReadTokens =
|
|
8252
|
+
const inputTokens = num4(e.input);
|
|
8253
|
+
const outputTokens = num4(e.output) + num4(e.reasoning);
|
|
8254
|
+
const cacheCreationTokens = num4(e.cacheWrite);
|
|
8255
|
+
const cacheReadTokens = num4(e.cacheRead);
|
|
7963
8256
|
const costUSD2 = numCost(e.cost);
|
|
7964
8257
|
const total = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
|
|
7965
8258
|
if (total === 0 && costUSD2 === 0) continue;
|
|
@@ -7985,7 +8278,7 @@ function resolveTokscaleBin() {
|
|
|
7985
8278
|
const pkg = require4("tokscale/package.json");
|
|
7986
8279
|
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
|
|
7987
8280
|
if (!rel) return null;
|
|
7988
|
-
const binPath =
|
|
8281
|
+
const binPath = join9(dirname4(pkgPath), rel);
|
|
7989
8282
|
if (/\.(c|m)?js$/.test(binPath)) {
|
|
7990
8283
|
return { cmd: process.execPath, prefixArgs: [binPath] };
|
|
7991
8284
|
}
|
|
@@ -8046,7 +8339,7 @@ var MAX_CURSOR_PAGE_BYTES = 4 * 1024 * 1024;
|
|
|
8046
8339
|
function cursorDbPath() {
|
|
8047
8340
|
const home = homedir6();
|
|
8048
8341
|
const os = platform2();
|
|
8049
|
-
const p = os === "darwin" ?
|
|
8342
|
+
const p = os === "darwin" ? join10(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join10(process.env.APPDATA ?? join10(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join10(process.env.XDG_CONFIG_HOME ?? join10(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
|
|
8050
8343
|
return existsSync4(p) ? p : null;
|
|
8051
8344
|
}
|
|
8052
8345
|
function trustedSqliteBinaries() {
|
|
@@ -8079,400 +8372,135 @@ function readCursorToken(db) {
|
|
|
8079
8372
|
[db, "SELECT value FROM ItemTable WHERE key='cursorAuth/accessToken';"],
|
|
8080
8373
|
{ encoding: "utf8", windowsHide: true, timeout: 1e4 }
|
|
8081
8374
|
);
|
|
8082
|
-
const out = res.stdout?.trim();
|
|
8083
|
-
if (res.status === 0 && out) return out;
|
|
8084
|
-
} catch {
|
|
8085
|
-
}
|
|
8086
|
-
}
|
|
8087
|
-
return null;
|
|
8088
|
-
}
|
|
8089
|
-
function cursorCookie(token) {
|
|
8090
|
-
try {
|
|
8091
|
-
const part = token.split(".")[1];
|
|
8092
|
-
if (!part) return null;
|
|
8093
|
-
const json = JSON.parse(
|
|
8094
|
-
Buffer.from(part, "base64url").toString("utf8")
|
|
8095
|
-
);
|
|
8096
|
-
if (!json.sub) return null;
|
|
8097
|
-
return `WorkosCursorSessionToken=${json.sub}%3A%3A${token}`;
|
|
8098
|
-
} catch {
|
|
8099
|
-
return null;
|
|
8100
|
-
}
|
|
8101
|
-
}
|
|
8102
|
-
function num4(n) {
|
|
8103
|
-
const v = Math.round(Number(n));
|
|
8104
|
-
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
8105
|
-
}
|
|
8106
|
-
function mapCursorEvents(events) {
|
|
8107
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
8108
|
-
const byHour = /* @__PURE__ */ new Map();
|
|
8109
|
-
for (const e of events) {
|
|
8110
|
-
const tu = e.tokenUsage;
|
|
8111
|
-
const ms = Number(e.timestamp);
|
|
8112
|
-
if (!tu || !Number.isFinite(ms)) continue;
|
|
8113
|
-
const d = new Date(ms);
|
|
8114
|
-
const date = localUsageDate(ms);
|
|
8115
|
-
if (!date) continue;
|
|
8116
|
-
const model = e.model || "cursor";
|
|
8117
|
-
const input = num4(tu.inputTokens);
|
|
8118
|
-
const output = num4(tu.outputTokens);
|
|
8119
|
-
const cacheWrite = num4(tu.cacheWriteTokens);
|
|
8120
|
-
const cacheRead = num4(tu.cacheReadTokens);
|
|
8121
|
-
const cost = Math.max(0, (Number(tu.totalCents) || 0) / 100);
|
|
8122
|
-
const total = input + output + cacheWrite + cacheRead;
|
|
8123
|
-
if (total === 0 && cost === 0) continue;
|
|
8124
|
-
const key = `${date}|${model}`;
|
|
8125
|
-
const day = byDay.get(key) ?? {
|
|
8126
|
-
date,
|
|
8127
|
-
tool: "cursor",
|
|
8128
|
-
model,
|
|
8129
|
-
inputTokens: 0,
|
|
8130
|
-
outputTokens: 0,
|
|
8131
|
-
cacheCreationTokens: 0,
|
|
8132
|
-
cacheReadTokens: 0,
|
|
8133
|
-
costUSD: 0,
|
|
8134
|
-
origin: "cli",
|
|
8135
|
-
verified: false
|
|
8136
|
-
};
|
|
8137
|
-
day.inputTokens += input;
|
|
8138
|
-
day.outputTokens += output;
|
|
8139
|
-
day.cacheCreationTokens += cacheWrite;
|
|
8140
|
-
day.cacheReadTokens += cacheRead;
|
|
8141
|
-
day.costUSD += cost;
|
|
8142
|
-
byDay.set(key, day);
|
|
8143
|
-
const hour = new Date(
|
|
8144
|
-
Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours())
|
|
8145
|
-
).toISOString();
|
|
8146
|
-
const blk = byHour.get(hour) ?? { startTime: hour, totalTokens: 0, costUSD: 0 };
|
|
8147
|
-
blk.totalTokens += total;
|
|
8148
|
-
blk.costUSD += cost;
|
|
8149
|
-
byHour.set(hour, blk);
|
|
8150
|
-
}
|
|
8151
|
-
const entries = [...byDay.values()].map((e) => ({
|
|
8152
|
-
...e,
|
|
8153
|
-
costUSD: Number(e.costUSD.toFixed(4))
|
|
8154
|
-
}));
|
|
8155
|
-
const blocks = [...byHour.values()].map((b) => ({
|
|
8156
|
-
...b,
|
|
8157
|
-
costUSD: Number(b.costUSD.toFixed(4))
|
|
8158
|
-
}));
|
|
8159
|
-
return { entries, blocks };
|
|
8160
|
-
}
|
|
8161
|
-
async function fetchCursorEvents(cookie, maxPages = 30, pageSize = 500) {
|
|
8162
|
-
const all = [];
|
|
8163
|
-
for (let page = 1; page <= maxPages; page++) {
|
|
8164
|
-
const res = await fetch(EVENTS_URL, {
|
|
8165
|
-
method: "POST",
|
|
8166
|
-
headers: {
|
|
8167
|
-
"Content-Type": "application/json",
|
|
8168
|
-
Origin: "https://cursor.com",
|
|
8169
|
-
Cookie: cookie
|
|
8170
|
-
},
|
|
8171
|
-
body: JSON.stringify({ page, pageSize }),
|
|
8172
|
-
signal: AbortSignal.timeout(2e4),
|
|
8173
|
-
redirect: "error"
|
|
8174
|
-
});
|
|
8175
|
-
if (!res.ok) {
|
|
8176
|
-
throw new Error(`cursor usage page ${page} failed (HTTP ${res.status})`);
|
|
8177
|
-
}
|
|
8178
|
-
const body = await readJsonResponseCapped(
|
|
8179
|
-
res,
|
|
8180
|
-
MAX_CURSOR_PAGE_BYTES
|
|
8181
|
-
);
|
|
8182
|
-
const batch = body.usageEventsDisplay ?? [];
|
|
8183
|
-
all.push(...batch);
|
|
8184
|
-
if (batch.length < pageSize) break;
|
|
8185
|
-
}
|
|
8186
|
-
return all;
|
|
8187
|
-
}
|
|
8188
|
-
async function collectCursor(opts) {
|
|
8189
|
-
try {
|
|
8190
|
-
const db = cursorDbPath();
|
|
8191
|
-
const token = db ? readCursorToken(db) : null;
|
|
8192
|
-
const cookie = token ? cursorCookie(token) : null;
|
|
8193
|
-
if (cookie && !opts?.offline) {
|
|
8194
|
-
const events = await fetchCursorEvents(cookie);
|
|
8195
|
-
const { entries, blocks } = mapCursorEvents(events);
|
|
8196
|
-
if (entries.length > 0) return { entries, blocks, found: true };
|
|
8197
|
-
}
|
|
8198
|
-
} catch {
|
|
8199
|
-
}
|
|
8200
|
-
try {
|
|
8201
|
-
const entries = collectCursorViaTokscale();
|
|
8202
|
-
if (entries.length > 0) return { entries, blocks: [], found: true };
|
|
8203
|
-
} catch {
|
|
8204
|
-
}
|
|
8205
|
-
return { entries: [], blocks: [], found: false };
|
|
8206
|
-
}
|
|
8207
|
-
|
|
8208
|
-
// src/native/codex.ts
|
|
8209
|
-
import { readdir as readdir3 } from "node:fs/promises";
|
|
8210
|
-
import { homedir as homedir7 } from "node:os";
|
|
8211
|
-
import { basename, join as join10, resolve as resolve2 } from "node:path";
|
|
8212
|
-
function num5(n) {
|
|
8213
|
-
const v = Math.round(Number(n));
|
|
8214
|
-
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
8215
|
-
}
|
|
8216
|
-
function readTokenFields(payload) {
|
|
8217
|
-
const info = payload.info;
|
|
8218
|
-
const src = info?.total_token_usage ?? payload;
|
|
8219
|
-
const input = src.input_tokens;
|
|
8220
|
-
const output = src.output_tokens;
|
|
8221
|
-
if (input === void 0 && output === void 0) return null;
|
|
8222
|
-
return {
|
|
8223
|
-
input: num5(input),
|
|
8224
|
-
cached: num5(src.cached_input_tokens),
|
|
8225
|
-
output: num5(output)
|
|
8226
|
-
};
|
|
8227
|
-
}
|
|
8228
|
-
function inspectCodexRollout(lines) {
|
|
8229
|
-
let model = "unknown";
|
|
8230
|
-
let sawSessionMeta = false;
|
|
8231
|
-
let replayedRollout = false;
|
|
8232
|
-
const perDay = /* @__PURE__ */ new Map();
|
|
8233
|
-
let lastSeenDate = null;
|
|
8234
|
-
for (const raw of lines) {
|
|
8235
|
-
const trimmed = raw.trim();
|
|
8236
|
-
if (!trimmed) continue;
|
|
8237
|
-
let obj;
|
|
8238
|
-
try {
|
|
8239
|
-
obj = JSON.parse(trimmed);
|
|
8240
|
-
} catch {
|
|
8241
|
-
continue;
|
|
8242
|
-
}
|
|
8243
|
-
const payload = obj.payload;
|
|
8244
|
-
if (!payload || typeof payload !== "object") continue;
|
|
8245
|
-
const kind = obj.type;
|
|
8246
|
-
if (kind === "session_meta" && !sawSessionMeta) {
|
|
8247
|
-
sawSessionMeta = true;
|
|
8248
|
-
const source = payload.source;
|
|
8249
|
-
if (typeof payload.forked_from_id === "string" || typeof payload.parent_thread_id === "string" || source && typeof source === "object" && source.subagent !== void 0) {
|
|
8250
|
-
replayedRollout = true;
|
|
8251
|
-
}
|
|
8252
|
-
}
|
|
8253
|
-
if (kind === "session_meta" || kind === "turn_context") {
|
|
8254
|
-
if (typeof payload.model === "string" && payload.model) model = payload.model;
|
|
8255
|
-
}
|
|
8256
|
-
if (payload.type === "token_count") {
|
|
8257
|
-
const fields = readTokenFields(payload);
|
|
8258
|
-
if (!fields) continue;
|
|
8259
|
-
const parsed = localUsageDate(Date.parse(String(obj.timestamp ?? "")));
|
|
8260
|
-
const day = parsed ?? lastSeenDate;
|
|
8261
|
-
if (!day) continue;
|
|
8262
|
-
lastSeenDate = day;
|
|
8263
|
-
const e = perDay.get(day);
|
|
8264
|
-
if (e) {
|
|
8265
|
-
e.cum = fields;
|
|
8266
|
-
e.turns += 1;
|
|
8267
|
-
} else {
|
|
8268
|
-
perDay.set(day, { cum: fields, turns: 1 });
|
|
8269
|
-
}
|
|
8270
|
-
}
|
|
8271
|
-
}
|
|
8272
|
-
if (perDay.size === 0)
|
|
8273
|
-
return { sessions: [], replaySessions: [], replayCandidateDates: [] };
|
|
8274
|
-
const dates = [...perDay.keys()].sort();
|
|
8275
|
-
const out = [];
|
|
8276
|
-
let prev = { input: 0, cached: 0, output: 0 };
|
|
8277
|
-
for (const date of dates) {
|
|
8278
|
-
const { cum, turns } = perDay.get(date);
|
|
8279
|
-
const dInput = Math.max(0, cum.input - prev.input);
|
|
8280
|
-
const dCached = Math.max(0, cum.cached - prev.cached);
|
|
8281
|
-
const dOutput = Math.max(0, cum.output - prev.output);
|
|
8282
|
-
prev = cum;
|
|
8283
|
-
const cacheReadTokens = Math.min(dCached, dInput);
|
|
8284
|
-
const inputTokens = dInput - cacheReadTokens;
|
|
8285
|
-
const outputTokens = dOutput;
|
|
8286
|
-
if (inputTokens + outputTokens + cacheReadTokens === 0) continue;
|
|
8287
|
-
out.push({
|
|
8288
|
-
date,
|
|
8289
|
-
model,
|
|
8290
|
-
inputTokens,
|
|
8291
|
-
outputTokens,
|
|
8292
|
-
cacheCreationTokens: 0,
|
|
8293
|
-
cacheReadTokens,
|
|
8294
|
-
turnCount: turns
|
|
8295
|
-
});
|
|
8296
|
-
}
|
|
8297
|
-
return {
|
|
8298
|
-
sessions: replayedRollout ? [] : out,
|
|
8299
|
-
replaySessions: replayedRollout ? out : [],
|
|
8300
|
-
replayCandidateDates: replayedRollout ? dates : []
|
|
8301
|
-
};
|
|
8302
|
-
}
|
|
8303
|
-
function foldCodexSessions(acc, sessions) {
|
|
8304
|
-
for (const s of sessions) {
|
|
8305
|
-
const k = `${s.date}|${s.model}`;
|
|
8306
|
-
let b = acc.get(k);
|
|
8307
|
-
if (!b) {
|
|
8308
|
-
b = {
|
|
8309
|
-
date: s.date,
|
|
8310
|
-
model: s.model,
|
|
8311
|
-
inputTokens: 0,
|
|
8312
|
-
outputTokens: 0,
|
|
8313
|
-
cacheCreationTokens: 0,
|
|
8314
|
-
cacheReadTokens: 0,
|
|
8315
|
-
requestCount: 0
|
|
8316
|
-
};
|
|
8317
|
-
acc.set(k, b);
|
|
8318
|
-
}
|
|
8319
|
-
b.inputTokens += s.inputTokens;
|
|
8320
|
-
b.outputTokens += s.outputTokens;
|
|
8321
|
-
b.cacheCreationTokens += s.cacheCreationTokens;
|
|
8322
|
-
b.cacheReadTokens += s.cacheReadTokens;
|
|
8323
|
-
b.requestCount += s.turnCount;
|
|
8324
|
-
}
|
|
8325
|
-
}
|
|
8326
|
-
function finalizeCodexEntries(acc) {
|
|
8327
|
-
const entries = [];
|
|
8328
|
-
for (const b of acc.values()) {
|
|
8329
|
-
entries.push({
|
|
8330
|
-
date: b.date,
|
|
8331
|
-
tool: "codex",
|
|
8332
|
-
model: b.model,
|
|
8333
|
-
inputTokens: b.inputTokens,
|
|
8334
|
-
outputTokens: b.outputTokens,
|
|
8335
|
-
cacheCreationTokens: b.cacheCreationTokens,
|
|
8336
|
-
cacheReadTokens: b.cacheReadTokens,
|
|
8337
|
-
costUSD: estimateCostUSD(b.model, b),
|
|
8338
|
-
origin: "cli",
|
|
8339
|
-
verified: false,
|
|
8340
|
-
requestCount: b.requestCount
|
|
8341
|
-
});
|
|
8342
|
-
}
|
|
8343
|
-
return entries;
|
|
8344
|
-
}
|
|
8345
|
-
function resolveCodexHome(env = process.env) {
|
|
8346
|
-
return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join10(homedir7(), ".codex");
|
|
8347
|
-
}
|
|
8348
|
-
function resolveCodexSessionsDirs(env = process.env) {
|
|
8349
|
-
const home = resolve2(resolveCodexHome(env));
|
|
8350
|
-
return [join10(home, "sessions"), join10(home, "archived_sessions")];
|
|
8375
|
+
const out = res.stdout?.trim();
|
|
8376
|
+
if (res.status === 0 && out) return out;
|
|
8377
|
+
} catch {
|
|
8378
|
+
}
|
|
8379
|
+
}
|
|
8380
|
+
return null;
|
|
8351
8381
|
}
|
|
8352
|
-
|
|
8353
|
-
let dirents;
|
|
8382
|
+
function cursorCookie(token) {
|
|
8354
8383
|
try {
|
|
8355
|
-
|
|
8384
|
+
const part = token.split(".")[1];
|
|
8385
|
+
if (!part) return null;
|
|
8386
|
+
const json = JSON.parse(
|
|
8387
|
+
Buffer.from(part, "base64url").toString("utf8")
|
|
8388
|
+
);
|
|
8389
|
+
if (!json.sub) return null;
|
|
8390
|
+
return `WorkosCursorSessionToken=${json.sub}%3A%3A${token}`;
|
|
8356
8391
|
} catch {
|
|
8357
|
-
return
|
|
8392
|
+
return null;
|
|
8358
8393
|
}
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8394
|
+
}
|
|
8395
|
+
function num5(n) {
|
|
8396
|
+
const v = Math.round(Number(n));
|
|
8397
|
+
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
8398
|
+
}
|
|
8399
|
+
function mapCursorEvents(events) {
|
|
8400
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
8401
|
+
const byHour = /* @__PURE__ */ new Map();
|
|
8402
|
+
for (const e of events) {
|
|
8403
|
+
const tu = e.tokenUsage;
|
|
8404
|
+
const ms = Number(e.timestamp);
|
|
8405
|
+
if (!tu || !Number.isFinite(ms)) continue;
|
|
8406
|
+
const d = new Date(ms);
|
|
8407
|
+
const date = localUsageDate(ms);
|
|
8408
|
+
if (!date) continue;
|
|
8409
|
+
const model = e.model || "cursor";
|
|
8410
|
+
const input = num5(tu.inputTokens);
|
|
8411
|
+
const output = num5(tu.outputTokens);
|
|
8412
|
+
const cacheWrite = num5(tu.cacheWriteTokens);
|
|
8413
|
+
const cacheRead = num5(tu.cacheReadTokens);
|
|
8414
|
+
const cost = Math.max(0, (Number(tu.totalCents) || 0) / 100);
|
|
8415
|
+
const total = input + output + cacheWrite + cacheRead;
|
|
8416
|
+
if (total === 0 && cost === 0) continue;
|
|
8417
|
+
const key = `${date}|${model}`;
|
|
8418
|
+
const day = byDay.get(key) ?? {
|
|
8419
|
+
date,
|
|
8420
|
+
tool: "cursor",
|
|
8421
|
+
model,
|
|
8422
|
+
inputTokens: 0,
|
|
8423
|
+
outputTokens: 0,
|
|
8424
|
+
cacheCreationTokens: 0,
|
|
8425
|
+
cacheReadTokens: 0,
|
|
8426
|
+
costUSD: 0,
|
|
8427
|
+
origin: "cli",
|
|
8428
|
+
verified: false
|
|
8429
|
+
};
|
|
8430
|
+
day.inputTokens += input;
|
|
8431
|
+
day.outputTokens += output;
|
|
8432
|
+
day.cacheCreationTokens += cacheWrite;
|
|
8433
|
+
day.cacheReadTokens += cacheRead;
|
|
8434
|
+
day.costUSD += cost;
|
|
8435
|
+
byDay.set(key, day);
|
|
8436
|
+
const hour = new Date(
|
|
8437
|
+
Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours())
|
|
8438
|
+
).toISOString();
|
|
8439
|
+
const blk = byHour.get(hour) ?? { startTime: hour, totalTokens: 0, costUSD: 0 };
|
|
8440
|
+
blk.totalTokens += total;
|
|
8441
|
+
blk.costUSD += cost;
|
|
8442
|
+
byHour.set(hour, blk);
|
|
8364
8443
|
}
|
|
8365
|
-
|
|
8444
|
+
const entries = [...byDay.values()].map((e) => ({
|
|
8445
|
+
...e,
|
|
8446
|
+
costUSD: Number(e.costUSD.toFixed(4))
|
|
8447
|
+
}));
|
|
8448
|
+
const blocks = [...byHour.values()].map((b) => ({
|
|
8449
|
+
...b,
|
|
8450
|
+
costUSD: Number(b.costUSD.toFixed(4))
|
|
8451
|
+
}));
|
|
8452
|
+
return { entries, blocks };
|
|
8366
8453
|
}
|
|
8367
|
-
function
|
|
8368
|
-
|
|
8369
|
-
for (let
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8454
|
+
async function fetchCursorEvents(cookie, maxPages = 30, pageSize = 500) {
|
|
8455
|
+
const all = [];
|
|
8456
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
8457
|
+
const res = await fetch(EVENTS_URL, {
|
|
8458
|
+
method: "POST",
|
|
8459
|
+
headers: {
|
|
8460
|
+
"Content-Type": "application/json",
|
|
8461
|
+
Origin: "https://cursor.com",
|
|
8462
|
+
Cookie: cookie
|
|
8463
|
+
},
|
|
8464
|
+
body: JSON.stringify({ page, pageSize }),
|
|
8465
|
+
signal: AbortSignal.timeout(2e4),
|
|
8466
|
+
redirect: "error"
|
|
8467
|
+
});
|
|
8468
|
+
if (!res.ok) {
|
|
8469
|
+
throw new Error(`cursor usage page ${page} failed (HTTP ${res.status})`);
|
|
8373
8470
|
}
|
|
8471
|
+
const body = await readJsonResponseCapped(
|
|
8472
|
+
res,
|
|
8473
|
+
MAX_CURSOR_PAGE_BYTES
|
|
8474
|
+
);
|
|
8475
|
+
const batch = body.usageEventsDisplay ?? [];
|
|
8476
|
+
all.push(...batch);
|
|
8477
|
+
if (batch.length < pageSize) break;
|
|
8374
8478
|
}
|
|
8375
|
-
|
|
8376
|
-
}
|
|
8377
|
-
var NATIVE_READ_BUDGET_MS2 = 45e3;
|
|
8378
|
-
var CODEX_CACHE_VERSION = 7;
|
|
8379
|
-
function toCachedSession(s, kind = "usage") {
|
|
8380
|
-
return [
|
|
8381
|
-
kind,
|
|
8382
|
-
s.date,
|
|
8383
|
-
s.model,
|
|
8384
|
-
s.inputTokens,
|
|
8385
|
-
s.outputTokens,
|
|
8386
|
-
s.cacheCreationTokens,
|
|
8387
|
-
s.cacheReadTokens,
|
|
8388
|
-
s.turnCount
|
|
8389
|
-
];
|
|
8390
|
-
}
|
|
8391
|
-
function fromCachedSession(t) {
|
|
8392
|
-
return {
|
|
8393
|
-
date: t[1],
|
|
8394
|
-
model: t[2],
|
|
8395
|
-
inputTokens: t[3],
|
|
8396
|
-
outputTokens: t[4],
|
|
8397
|
-
cacheCreationTokens: t[5],
|
|
8398
|
-
cacheReadTokens: t[6],
|
|
8399
|
-
turnCount: t[7]
|
|
8400
|
-
};
|
|
8479
|
+
return all;
|
|
8401
8480
|
}
|
|
8402
|
-
async function
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
const
|
|
8410
|
-
|
|
8411
|
-
if (!seenIdentities.has(key)) {
|
|
8412
|
-
seenIdentities.add(key);
|
|
8413
|
-
primaryFiles.add(file);
|
|
8414
|
-
}
|
|
8481
|
+
async function collectCursor(opts) {
|
|
8482
|
+
try {
|
|
8483
|
+
const db = cursorDbPath();
|
|
8484
|
+
const token = db ? readCursorToken(db) : null;
|
|
8485
|
+
const cookie = token ? cursorCookie(token) : null;
|
|
8486
|
+
if (cookie && !opts?.offline) {
|
|
8487
|
+
const events = await fetchCursorEvents(cookie);
|
|
8488
|
+
const { entries, blocks } = mapCursorEvents(events);
|
|
8489
|
+
if (entries.length > 0) return { entries, blocks, found: true };
|
|
8415
8490
|
}
|
|
8491
|
+
} catch {
|
|
8416
8492
|
}
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
cachePath: opts.cachePath ?? nativeCachePath("codex", env),
|
|
8422
|
-
version: CODEX_CACHE_VERSION,
|
|
8423
|
-
parseFile: (content, path) => {
|
|
8424
|
-
const inspected = inspectCodexRollout(splitLines3(content));
|
|
8425
|
-
const primary = primaryFiles.has(path);
|
|
8426
|
-
return [
|
|
8427
|
-
// Do not pass toCachedSession directly to map: Array.map's numeric
|
|
8428
|
-
// index would be supplied as the optional `kind` argument.
|
|
8429
|
-
...inspected.sessions.map(
|
|
8430
|
-
(session) => toCachedSession(session, primary ? "usage" : "duplicate")
|
|
8431
|
-
),
|
|
8432
|
-
...inspected.replaySessions.map(
|
|
8433
|
-
(session) => toCachedSession(
|
|
8434
|
-
session,
|
|
8435
|
-
primary ? "replay" : "replay-duplicate"
|
|
8436
|
-
)
|
|
8437
|
-
)
|
|
8438
|
-
];
|
|
8439
|
-
},
|
|
8440
|
-
deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS2),
|
|
8441
|
-
now
|
|
8442
|
-
});
|
|
8443
|
-
if (!res.itemsByFile) {
|
|
8444
|
-
return {
|
|
8445
|
-
entries: [],
|
|
8446
|
-
found: false,
|
|
8447
|
-
filesScanned: res.filesRead,
|
|
8448
|
-
timedOut: true
|
|
8449
|
-
};
|
|
8450
|
-
}
|
|
8451
|
-
const acc = /* @__PURE__ */ new Map();
|
|
8452
|
-
const legacyAcc = /* @__PURE__ */ new Map();
|
|
8453
|
-
const replayCandidateDates = /* @__PURE__ */ new Set();
|
|
8454
|
-
for (const items of res.itemsByFile) {
|
|
8455
|
-
const usage = items.filter((item) => item[0] === "usage").map(fromCachedSession);
|
|
8456
|
-
const legacy = items.map(fromCachedSession);
|
|
8457
|
-
const replay = items.filter(
|
|
8458
|
-
(item) => item[0] === "replay" || item[0] === "replay-duplicate"
|
|
8459
|
-
).map(fromCachedSession);
|
|
8460
|
-
foldCodexSessions(acc, usage);
|
|
8461
|
-
foldCodexSessions(legacyAcc, legacy);
|
|
8462
|
-
for (const session of replay) replayCandidateDates.add(session.date);
|
|
8493
|
+
try {
|
|
8494
|
+
const entries = collectCursorViaTokscale();
|
|
8495
|
+
if (entries.length > 0) return { entries, blocks: [], found: true };
|
|
8496
|
+
} catch {
|
|
8463
8497
|
}
|
|
8464
|
-
return {
|
|
8465
|
-
entries: finalizeCodexEntries(acc),
|
|
8466
|
-
found: true,
|
|
8467
|
-
filesScanned: res.filesRead,
|
|
8468
|
-
replayCandidateDates: [...replayCandidateDates].sort(),
|
|
8469
|
-
legacyEntries: finalizeCodexEntries(legacyAcc)
|
|
8470
|
-
};
|
|
8498
|
+
return { entries: [], blocks: [], found: false };
|
|
8471
8499
|
}
|
|
8472
8500
|
|
|
8473
8501
|
// src/native/vscode-agents.ts
|
|
8474
8502
|
import { readdir as readdir4 } from "node:fs/promises";
|
|
8475
|
-
import { homedir as
|
|
8503
|
+
import { homedir as homedir7, platform as platform3 } from "node:os";
|
|
8476
8504
|
import { join as join11 } from "node:path";
|
|
8477
8505
|
var VSCODE_AGENTS = [
|
|
8478
8506
|
{ tool: "cline", extIds: ["saoudrizwan.claude-dev"] },
|
|
@@ -8586,7 +8614,7 @@ function parseVscodeAgentMessages(tool, raw) {
|
|
|
8586
8614
|
}
|
|
8587
8615
|
function vscodeGlobalStorageRoots(env = process.env) {
|
|
8588
8616
|
const apps = ["Code", "Code - Insiders", "Cursor", "VSCodium", "Windsurf"];
|
|
8589
|
-
const home = env.HOME ||
|
|
8617
|
+
const home = env.HOME || homedir7();
|
|
8590
8618
|
const os = platform3();
|
|
8591
8619
|
const base = (app) => os === "darwin" ? join11(home, "Library", "Application Support", app, "User", "globalStorage") : os === "win32" ? join11(env.APPDATA ?? join11(home, "AppData", "Roaming"), app, "User", "globalStorage") : join11(env.XDG_CONFIG_HOME ?? join11(home, ".config"), app, "User", "globalStorage");
|
|
8592
8620
|
return apps.map(base);
|
|
@@ -8969,7 +8997,12 @@ var SOURCES = [
|
|
|
8969
8997
|
"openclaw",
|
|
8970
8998
|
"hermes",
|
|
8971
8999
|
"pi",
|
|
8972
|
-
"codebuff"
|
|
9000
|
+
"codebuff",
|
|
9001
|
+
// Read by ccusage >= 20.0.29 (Antigravity's conversations/*.db ledgers,
|
|
9002
|
+
// Grok Build CLI's sessions/**/updates.jsonl, ZCode's cli/db/db.sqlite).
|
|
9003
|
+
"antigravity",
|
|
9004
|
+
"grok",
|
|
9005
|
+
"zcode"
|
|
8973
9006
|
];
|
|
8974
9007
|
function norm(n) {
|
|
8975
9008
|
const v = Math.round(Number(n));
|
|
@@ -8979,6 +9012,7 @@ function normCost(n) {
|
|
|
8979
9012
|
const v = Number(n);
|
|
8980
9013
|
return Number.isFinite(v) && v > 0 ? v : 0;
|
|
8981
9014
|
}
|
|
9015
|
+
var PRICE_TABLE_COST_SOURCES = /* @__PURE__ */ new Set(["claude", "codex"]);
|
|
8982
9016
|
function mapCcusageDaily(tool, json, now = Date.now()) {
|
|
8983
9017
|
const daily = json?.daily;
|
|
8984
9018
|
if (!Array.isArray(daily)) return [];
|
|
@@ -9037,7 +9071,8 @@ function mapCcusageDaily(tool, json, now = Date.now()) {
|
|
|
9037
9071
|
for (const c of candidates) {
|
|
9038
9072
|
const tokens = c.inputTokens + c.outputTokens + c.cacheCreationTokens + c.cacheReadTokens;
|
|
9039
9073
|
if (tokens === 0 && c.costUSD === 0) continue;
|
|
9040
|
-
|
|
9074
|
+
const costUSD2 = c.costUSD === 0 && PRICE_TABLE_COST_SOURCES.has(tool) ? estimateCostUSD(c.model, c) : c.costUSD;
|
|
9075
|
+
entries.push({ date, tool, ...c, costUSD: costUSD2, origin: "cli", verified: false });
|
|
9041
9076
|
}
|
|
9042
9077
|
}
|
|
9043
9078
|
return entries;
|
|
@@ -9325,13 +9360,19 @@ async function collectAll(onProgress, opts) {
|
|
|
9325
9360
|
tick();
|
|
9326
9361
|
return a;
|
|
9327
9362
|
});
|
|
9363
|
+
const bailed = () => ({
|
|
9364
|
+
entries: [],
|
|
9365
|
+
found: false,
|
|
9366
|
+
filesScanned: 0,
|
|
9367
|
+
timedOut: true
|
|
9368
|
+
});
|
|
9328
9369
|
const vscodeTasks = VSCODE_AGENTS.map(
|
|
9329
|
-
(a) => collectVscodeAgent({ tool: a.tool, extIds: a.extIds }).then((r) => {
|
|
9370
|
+
(a) => collectVscodeAgent({ tool: a.tool, extIds: a.extIds }).catch(bailed).then((r) => {
|
|
9330
9371
|
tick();
|
|
9331
9372
|
return { tool: a.tool, result: r };
|
|
9332
9373
|
})
|
|
9333
9374
|
);
|
|
9334
|
-
const continueTask = collectContinue().then((r) => {
|
|
9375
|
+
const continueTask = collectContinue().catch(bailed).then((r) => {
|
|
9335
9376
|
tick();
|
|
9336
9377
|
return r;
|
|
9337
9378
|
});
|
|
@@ -9455,26 +9496,30 @@ async function collectAll(onProgress, opts) {
|
|
|
9455
9496
|
tools: sanitizeToolStats(tools),
|
|
9456
9497
|
skills: sanitizeSkillStats(skills),
|
|
9457
9498
|
agent: sanitizeAgentStat(reconciled.agent),
|
|
9458
|
-
attributionComplete: complete
|
|
9499
|
+
attributionComplete: complete,
|
|
9500
|
+
...nativeClaude.permissionDenied ? { unreadableClaudeFiles: nativeClaude.permissionDenied } : {}
|
|
9459
9501
|
};
|
|
9460
9502
|
}
|
|
9461
9503
|
|
|
9462
9504
|
// src/antigravity.ts
|
|
9463
9505
|
import { existsSync as existsSync5 } from "node:fs";
|
|
9464
|
-
import { homedir as
|
|
9506
|
+
import { homedir as homedir8 } from "node:os";
|
|
9465
9507
|
import { join as join15 } from "node:path";
|
|
9466
|
-
function
|
|
9467
|
-
|
|
9508
|
+
function antigravityDataDirs(env = process.env) {
|
|
9509
|
+
const gemini = join15(env.HOME || homedir8(), ".gemini");
|
|
9510
|
+
return ["antigravity", "antigravity-cli", "antigravity-ide"].map(
|
|
9511
|
+
(name) => join15(gemini, name)
|
|
9512
|
+
);
|
|
9468
9513
|
}
|
|
9469
9514
|
function detectAntigravity(env = process.env) {
|
|
9470
|
-
return
|
|
9515
|
+
return antigravityDataDirs(env).some((dir) => existsSync5(dir));
|
|
9471
9516
|
}
|
|
9472
9517
|
function antigravityNoticeLines() {
|
|
9473
9518
|
return [
|
|
9474
|
-
" \u2022 Google Antigravity detected
|
|
9475
|
-
"
|
|
9476
|
-
"
|
|
9477
|
-
" counts
|
|
9519
|
+
" \u2022 Google Antigravity detected. Its usage is read from the local",
|
|
9520
|
+
" conversation databases (~/.gemini/antigravity*/conversations/*.db).",
|
|
9521
|
+
" Older Antigravity versions kept only encrypted logs with no token",
|
|
9522
|
+
" counts; that history can't be counted. Update Antigravity if none shows."
|
|
9478
9523
|
];
|
|
9479
9524
|
}
|
|
9480
9525
|
|
|
@@ -9512,7 +9557,7 @@ function buildStatusReport(s) {
|
|
|
9512
9557
|
lines.push(" whoburnedmore \u2014 background sync status");
|
|
9513
9558
|
lines.push("");
|
|
9514
9559
|
lines.push(
|
|
9515
|
-
s.installed ? ` \u2022 Background agent: installed${s.loaded ? " and loaded" : " but NOT loaded with the scheduler"}` : " \u2022 Background agent: NOT installed \u2014 run `npx whoburnedmore` to set it up"
|
|
9560
|
+
s.installed ? ` \u2022 Background agent: installed${s.loaded ? " and loaded" : " but NOT loaded with the scheduler"}` : s.optedOut ? " \u2022 Background agent: off (turned off with `uninstall-sync`) \u2014 run `npx whoburnedmore install-sync` to turn it back on" : " \u2022 Background agent: NOT installed \u2014 run `npx whoburnedmore` to set it up"
|
|
9516
9561
|
);
|
|
9517
9562
|
if (s.installed && s.drift !== "ok") {
|
|
9518
9563
|
lines.push(
|
|
@@ -9556,7 +9601,8 @@ function agentStatusReport(now = Date.now()) {
|
|
|
9556
9601
|
now,
|
|
9557
9602
|
nodePath,
|
|
9558
9603
|
nodePathStable: !nodePath.includes("/Cellar/"),
|
|
9559
|
-
logPath: syncLogPath()
|
|
9604
|
+
logPath: syncLogPath(),
|
|
9605
|
+
optedOut: cfg?.autoSyncOptOut === true
|
|
9560
9606
|
});
|
|
9561
9607
|
}
|
|
9562
9608
|
|
|
@@ -9810,7 +9856,7 @@ ${connectCta}
|
|
|
9810
9856
|
|
|
9811
9857
|
<div class="foot">
|
|
9812
9858
|
Re-run <code>npx whoburnedmore --local</code> to refresh this page.<br>
|
|
9813
|
-
${connect ? "
|
|
9859
|
+
${connect ? "Run the command above to sign in and put this burn on whoburnedmore.com." : "Run <code>npx whoburnedmore</code> to sign in and publish a shareable dashboard."}
|
|
9814
9860
|
</div>
|
|
9815
9861
|
</div>
|
|
9816
9862
|
</body>
|
|
@@ -9830,6 +9876,10 @@ async function publishLocal(payload, deps) {
|
|
|
9830
9876
|
return false;
|
|
9831
9877
|
}
|
|
9832
9878
|
const res = await deps.submit(auth.token, payload);
|
|
9879
|
+
if (!res) {
|
|
9880
|
+
deps.log(" Your sign-in expired and wasn't renewed \u2014 kept local. Run `npx whoburnedmore` to sign in again.");
|
|
9881
|
+
return false;
|
|
9882
|
+
}
|
|
9833
9883
|
deps.log(` Published \u2014 you're on the board: ${sanitizeServerText(res.profileUrl)}`);
|
|
9834
9884
|
if (isTrustedWebUrl(res.profileUrl)) {
|
|
9835
9885
|
deps.openBrowser(res.profileUrl);
|
|
@@ -9907,12 +9957,13 @@ function openBrowser(url) {
|
|
|
9907
9957
|
const [cmd, args] = os === "darwin" ? ["open", [url]] : os === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
9908
9958
|
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
9909
9959
|
}
|
|
9910
|
-
async function confirm(question) {
|
|
9960
|
+
async function confirm(question, defaultYes = true) {
|
|
9911
9961
|
if (!process.stdin.isTTY) return false;
|
|
9912
9962
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9913
|
-
const
|
|
9963
|
+
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
9964
|
+
const answer = (await rl.question(`${question} ${pc2.dim(hint)} `)).trim();
|
|
9914
9965
|
rl.close();
|
|
9915
|
-
return answer === ""
|
|
9966
|
+
return answer === "" ? defaultYes : /^y(es)?$/i.test(answer);
|
|
9916
9967
|
}
|
|
9917
9968
|
function showLocalDashboard(payload) {
|
|
9918
9969
|
const dir = defaultConfigDir();
|
|
@@ -9924,7 +9975,7 @@ function showLocalDashboard(payload) {
|
|
|
9924
9975
|
console.log();
|
|
9925
9976
|
console.log(` Local dashboard: ${pc2.cyan(fileUrl)}`);
|
|
9926
9977
|
console.log(pc2.dim(" Re-run `npx whoburnedmore --local` to refresh it. Nothing left your machine."));
|
|
9927
|
-
openBrowser(fileUrl);
|
|
9978
|
+
if (process.stdout.isTTY) openBrowser(fileUrl);
|
|
9928
9979
|
}
|
|
9929
9980
|
async function run(flags) {
|
|
9930
9981
|
if (!flags.quiet) {
|
|
@@ -9948,6 +9999,18 @@ async function run(flags) {
|
|
|
9948
9999
|
} finally {
|
|
9949
10000
|
progress.stop();
|
|
9950
10001
|
}
|
|
10002
|
+
const unreadable = collected.unreadableClaudeFiles ?? [];
|
|
10003
|
+
if (unreadable.length > 0 && !flags.quiet) {
|
|
10004
|
+
console.log(
|
|
10005
|
+
pc2.yellow(
|
|
10006
|
+
` \u26A0 Skipped ${unreadable.length} Claude Code transcript${unreadable.length === 1 ? "" : "s"} this account can't read (permission denied), e.g.`
|
|
10007
|
+
)
|
|
10008
|
+
);
|
|
10009
|
+
console.log(pc2.dim(` ${sanitizeServerText(unreadable[0])}`));
|
|
10010
|
+
console.log(
|
|
10011
|
+
pc2.dim(" Their usage isn't counted until you fix the file's owner or permissions (often left by `sudo claude`).")
|
|
10012
|
+
);
|
|
10013
|
+
}
|
|
9951
10014
|
const {
|
|
9952
10015
|
entries,
|
|
9953
10016
|
codexReplayTombstoneDates: codexReplayTombstoneDates2,
|
|
@@ -9994,9 +10057,12 @@ async function run(flags) {
|
|
|
9994
10057
|
if (!flags.quiet && process.stdin.isTTY) {
|
|
9995
10058
|
await publishLocal(payload, {
|
|
9996
10059
|
confirm,
|
|
9997
|
-
|
|
10060
|
+
// Stored token, then a silent refresh, then device sign-in — the same
|
|
10061
|
+
// order as every other signed-in command.
|
|
10062
|
+
signIn: () => requireSignedIn("--local"),
|
|
9998
10063
|
submit: async (token, p) => {
|
|
9999
|
-
const result = await
|
|
10064
|
+
const result = await submitWithAuthRecovery(token, p, true);
|
|
10065
|
+
if (!result) return null;
|
|
10000
10066
|
try {
|
|
10001
10067
|
recordSync();
|
|
10002
10068
|
} catch {
|
|
@@ -10088,27 +10154,26 @@ async function ensureSignedIn() {
|
|
|
10088
10154
|
);
|
|
10089
10155
|
return null;
|
|
10090
10156
|
}
|
|
10091
|
-
async function
|
|
10092
|
-
let result;
|
|
10157
|
+
async function submitWithAuthRecovery(token, payload, interactive) {
|
|
10093
10158
|
try {
|
|
10094
|
-
|
|
10159
|
+
return await submitFromBoundDevice(token, payload);
|
|
10095
10160
|
} catch (err) {
|
|
10096
|
-
if (err instanceof UnauthorizedError)
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
}
|
|
10108
|
-
} else {
|
|
10109
|
-
throw err;
|
|
10110
|
-
}
|
|
10161
|
+
if (!(err instanceof UnauthorizedError)) throw err;
|
|
10162
|
+
const healed = await refreshCliTokenFromConfig();
|
|
10163
|
+
if (healed) {
|
|
10164
|
+
saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
|
|
10165
|
+
return await submitFromBoundDevice(healed.token, payload);
|
|
10166
|
+
}
|
|
10167
|
+
clearAuth();
|
|
10168
|
+
if (!interactive) return null;
|
|
10169
|
+
const auth = await ensureSignedIn();
|
|
10170
|
+
if (!auth) return null;
|
|
10171
|
+
return await submitFromBoundDevice(auth.token, payload);
|
|
10111
10172
|
}
|
|
10173
|
+
}
|
|
10174
|
+
async function submitSignedIn(token, payload, flags, interactive) {
|
|
10175
|
+
const result = await submitWithAuthRecovery(token, payload, interactive);
|
|
10176
|
+
if (!result) return;
|
|
10112
10177
|
try {
|
|
10113
10178
|
recordSync();
|
|
10114
10179
|
} catch {
|
|
@@ -10130,7 +10195,7 @@ async function submitSignedIn(token, payload, flags, interactive) {
|
|
|
10130
10195
|
" Verify your usage to get back on \u2014 this sends a detailed per-request breakdown (timestamps + token counts, never your prompts or code)."
|
|
10131
10196
|
)
|
|
10132
10197
|
);
|
|
10133
|
-
if (process.stdin.isTTY && await confirm(" Verify now?")) {
|
|
10198
|
+
if (process.stdin.isTTY && await confirm(" Verify now?", false)) {
|
|
10134
10199
|
await runVerify();
|
|
10135
10200
|
afterSubmitChores(flags);
|
|
10136
10201
|
return;
|
|
@@ -10156,8 +10221,10 @@ async function submitSignedIn(token, payload, flags, interactive) {
|
|
|
10156
10221
|
);
|
|
10157
10222
|
}
|
|
10158
10223
|
if (isTrustedWebUrl(baseUrl)) {
|
|
10159
|
-
|
|
10160
|
-
|
|
10224
|
+
if (process.stdout.isTTY) {
|
|
10225
|
+
console.log(pc2.dim(" Opening your dashboard in your browser\u2026"));
|
|
10226
|
+
openBrowser(baseUrl);
|
|
10227
|
+
}
|
|
10161
10228
|
} else {
|
|
10162
10229
|
console.log(
|
|
10163
10230
|
pc2.dim(" The server returned an unexpected dashboard address, so it was NOT auto-opened. Open it yourself only if you trust it:")
|
|
@@ -10202,13 +10269,16 @@ function afterSubmitChores(flags) {
|
|
|
10202
10269
|
}
|
|
10203
10270
|
return;
|
|
10204
10271
|
}
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10272
|
+
const optedOut = autoSyncOptedOut();
|
|
10273
|
+
if (!optedOut) {
|
|
10274
|
+
try {
|
|
10275
|
+
reconcileAutoSync();
|
|
10276
|
+
} catch {
|
|
10277
|
+
}
|
|
10208
10278
|
}
|
|
10209
10279
|
console.log();
|
|
10210
10280
|
console.log(
|
|
10211
|
-
autoSyncInstalled() ? pc2.dim(" Background sync is on \u2014 your page updates automatically every 15 min (`npx whoburnedmore uninstall-sync` to stop).") : pc2.dim(" Re-run anytime to update your page.")
|
|
10281
|
+
optedOut ? pc2.dim(" Background sync is off. Re-run anytime to update your page, or `npx whoburnedmore install-sync` to turn it back on.") : autoSyncInstalled() ? pc2.dim(" Background sync is on \u2014 your page updates automatically every 15 min (`npx whoburnedmore uninstall-sync` to stop).") : pc2.dim(" Re-run anytime to update your page.")
|
|
10212
10282
|
);
|
|
10213
10283
|
}
|
|
10214
10284
|
async function readServerInstallToken() {
|
|
@@ -10256,6 +10326,7 @@ async function linkServerInstall(token) {
|
|
|
10256
10326
|
quiet: true
|
|
10257
10327
|
});
|
|
10258
10328
|
try {
|
|
10329
|
+
setAutoSyncOptOut(false);
|
|
10259
10330
|
const action = reconcileAutoSync();
|
|
10260
10331
|
if (action === "installed") {
|
|
10261
10332
|
console.log(pc2.dim(" Background sync installed; this machine will refresh every 15 min."));
|
|
@@ -10330,21 +10401,8 @@ async function runVerify() {
|
|
|
10330
10401
|
" This sends a DETAILED per-request breakdown \u2014 timestamps and token counts, still never your prompts, code, or file names \u2014 so we can confirm your usage is real. It's deleted after review."
|
|
10331
10402
|
)
|
|
10332
10403
|
);
|
|
10333
|
-
const
|
|
10334
|
-
|
|
10335
|
-
if (!token) {
|
|
10336
|
-
if (!process.stdout.isTTY) {
|
|
10337
|
-
console.log(
|
|
10338
|
-
pc2.yellow(
|
|
10339
|
-
" Sign in first \u2014 run `npx whoburnedmore verify` in an interactive terminal."
|
|
10340
|
-
)
|
|
10341
|
-
);
|
|
10342
|
-
return;
|
|
10343
|
-
}
|
|
10344
|
-
const auth = await ensureSignedIn();
|
|
10345
|
-
if (!auth) return;
|
|
10346
|
-
token = auth.token;
|
|
10347
|
-
}
|
|
10404
|
+
const auth = await requireSignedIn("verify");
|
|
10405
|
+
if (!auth) return;
|
|
10348
10406
|
if (process.stdin.isTTY) {
|
|
10349
10407
|
const ok = await confirm("\n Send this breakdown to verify your usage?");
|
|
10350
10408
|
if (!ok) {
|
|
@@ -10374,13 +10432,25 @@ async function runVerify() {
|
|
|
10374
10432
|
` Sending ${records.length} request records for analysis${truncated ? " (most recent, sampled)" : ""}\u2026`
|
|
10375
10433
|
)
|
|
10376
10434
|
);
|
|
10435
|
+
const body = {
|
|
10436
|
+
cliVersion: VERSION,
|
|
10437
|
+
requests: records,
|
|
10438
|
+
...truncated ? { truncated: true } : {}
|
|
10439
|
+
};
|
|
10440
|
+
const verifyWithRefresh = async () => {
|
|
10441
|
+
try {
|
|
10442
|
+
return await verifyUsage(auth.token, body);
|
|
10443
|
+
} catch (err) {
|
|
10444
|
+
if (!(err instanceof UnauthorizedError)) throw err;
|
|
10445
|
+
const healed = await refreshCliTokenFromConfig();
|
|
10446
|
+
if (!healed) throw err;
|
|
10447
|
+
saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
|
|
10448
|
+
return await verifyUsage(healed.token, body);
|
|
10449
|
+
}
|
|
10450
|
+
};
|
|
10377
10451
|
let result;
|
|
10378
10452
|
try {
|
|
10379
|
-
result = await
|
|
10380
|
-
cliVersion: VERSION,
|
|
10381
|
-
requests: records,
|
|
10382
|
-
...truncated ? { truncated: true } : {}
|
|
10383
|
-
});
|
|
10453
|
+
result = await verifyWithRefresh();
|
|
10384
10454
|
} catch (err) {
|
|
10385
10455
|
if (err instanceof UnauthorizedError) {
|
|
10386
10456
|
clearAuth();
|
|
@@ -10561,9 +10631,11 @@ async function main() {
|
|
|
10561
10631
|
break;
|
|
10562
10632
|
case "install-sync":
|
|
10563
10633
|
console.log(` ${installAutoSync()}`);
|
|
10634
|
+
setAutoSyncOptOut(false);
|
|
10564
10635
|
break;
|
|
10565
10636
|
case "uninstall-sync":
|
|
10566
10637
|
console.log(` ${uninstallAutoSync()}`);
|
|
10638
|
+
setAutoSyncOptOut(true);
|
|
10567
10639
|
break;
|
|
10568
10640
|
case "help":
|
|
10569
10641
|
printHelp();
|