whoburnedmore 0.9.24 → 0.9.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +784 -634
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -33,6 +33,54 @@ function applyScope(payload, flags) {
33
33
  payload.orgCode = flags.orgCode;
34
34
  return payload;
35
35
  }
36
+ var VALUE_FLAGS = /* @__PURE__ */ new Set(["--board", "--org", "--token", "--pass", "--code"]);
37
+ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
38
+ ...VALUE_FLAGS,
39
+ "--dry-run",
40
+ "--no-submit",
41
+ "--local",
42
+ "--watch",
43
+ "--help",
44
+ "-h",
45
+ "--version",
46
+ "-v"
47
+ ]);
48
+ var NO_UPLOAD_FLAGS = ["--dry-run", "--no-submit", "--local"];
49
+ function argumentProblem(args, command) {
50
+ if (command === "help" || command === "version") return null;
51
+ for (let i = 0; i < args.length; i++) {
52
+ const a = args[i];
53
+ if (!a.startsWith("-")) continue;
54
+ const name = a.includes("=") ? a.slice(0, a.indexOf("=")) : a;
55
+ if (!KNOWN_FLAGS.has(name)) return `Unknown option ${name}.`;
56
+ if (!VALUE_FLAGS.has(name) && a.includes("=")) {
57
+ return `${name} doesn't take a value; use ${name} on its own.`;
58
+ }
59
+ if (VALUE_FLAGS.has(name)) {
60
+ if (parseValueFlag([a, args[i + 1] ?? ""], name) === void 0) {
61
+ const example = { "--board": "CODE", "--org": "SLUG", "--token": "TOKEN" }[name] ?? "PASSWORD";
62
+ return `${name} needs a value, for example ${name}=${example}.`;
63
+ }
64
+ if (!a.includes("=")) i++;
65
+ }
66
+ }
67
+ const noUpload = NO_UPLOAD_FLAGS.filter((f) => args.includes(f));
68
+ if (args.includes("--watch")) {
69
+ if (command !== "run" && command !== "sync" && command !== "daemon") {
70
+ return `--watch only works with sync (it keeps uploading every 15 minutes).`;
71
+ }
72
+ if (noUpload.length > 0) {
73
+ return `--watch keeps uploading every 15 minutes, so it can't be combined with ${noUpload.join(", ")}.`;
74
+ }
75
+ }
76
+ if ((command === "sync" || command === "daemon") && noUpload.length > 0) {
77
+ return `${command} always uploads, so it can't be combined with ${noUpload.join(", ")}. To preview without sending, run \`npx whoburnedmore --dry-run\`.`;
78
+ }
79
+ if (command !== "run" && noUpload.length > 0) {
80
+ return `${noUpload.join(", ")} only works with the default command, not \`${command}\`.`;
81
+ }
82
+ return null;
83
+ }
36
84
  function parseValueFlag(args, name) {
37
85
  const prefix = `${name}=`;
38
86
  const eq = args.find((a) => a.startsWith(prefix));
@@ -44,14 +92,13 @@ function parseValueFlag(args, name) {
44
92
  return void 0;
45
93
  }
46
94
  function resolveCommand(args) {
47
- const valueFlags = /* @__PURE__ */ new Set(["--board", "--org", "--token", "--pass", "--code"]);
48
95
  const flags = [];
49
96
  const words = [];
50
97
  for (let i = 0; i < args.length; i++) {
51
98
  const a = args[i];
52
99
  if (a.startsWith("-")) {
53
100
  flags.push(a);
54
- if (valueFlags.has(a)) i++;
101
+ if (VALUE_FLAGS.has(a) && args[i + 1] !== void 0 && !args[i + 1].startsWith("-")) i++;
55
102
  continue;
56
103
  }
57
104
  words.push(a);
@@ -64,6 +111,10 @@ function resolveCommand(args) {
64
111
  }
65
112
  return words[0] ?? "run";
66
113
  }
114
+ function confirmAnswer(answer, defaultYes) {
115
+ const a = answer.trim();
116
+ return a === "" ? defaultYes : /^y(es)?$/i.test(a);
117
+ }
67
118
 
68
119
  // src/http-bounds.ts
69
120
  async function readResponseBytesCapped(response, maxBytes) {
@@ -422,6 +473,7 @@ function loadConfig(dir = defaultConfigDir()) {
422
473
  if (typeof parsed.deviceBoundAt === "number" && Number.isFinite(parsed.deviceBoundAt)) {
423
474
  config.deviceBoundAt = parsed.deviceBoundAt;
424
475
  }
476
+ if (parsed.autoSyncOptOut === true) config.autoSyncOptOut = true;
425
477
  return Object.keys(config).length > 0 ? config : null;
426
478
  } catch {
427
479
  return null;
@@ -448,6 +500,15 @@ function recordDeviceBound(dir = defaultConfigDir(), when = Date.now()) {
448
500
  const config = loadConfig(dir) ?? {};
449
501
  saveConfig(dir, { ...config, deviceBoundAt: when });
450
502
  }
503
+ function setAutoSyncOptOut(optOut, dir = defaultConfigDir()) {
504
+ const config = loadConfig(dir);
505
+ if (!optOut && !config?.autoSyncOptOut) return;
506
+ const { autoSyncOptOut: _o, ...rest } = config ?? {};
507
+ saveConfig(dir, optOut ? { ...rest, autoSyncOptOut: true } : rest);
508
+ }
509
+ function autoSyncOptedOut(dir = defaultConfigDir()) {
510
+ return loadConfig(dir)?.autoSyncOptOut === true;
511
+ }
451
512
  function saveAuth(dir = defaultConfigDir(), auth = { cliToken: "" }) {
452
513
  const config = loadConfig(dir) ?? {};
453
514
  saveConfig(dir, {
@@ -706,8 +767,13 @@ var FORWARDED_ENV_VARS = [
706
767
  // dashboard URL shown to the user
707
768
  "XDG_CONFIG_HOME",
708
769
  // base for the default config dir
709
- "CLAUDE_CONFIG_DIR"
770
+ "CLAUDE_CONFIG_DIR",
710
771
  // a primary usage-collection source
772
+ // Usage is bucketed by local calendar day and the native parse caches record
773
+ // the zone: a shell TZ that differs from the system zone must reach the
774
+ // background job too, or the two runs bucket differently and keep
775
+ // invalidating each other's caches.
776
+ "TZ"
711
777
  ];
712
778
  function syncEnv(opts) {
713
779
  const env = opts?.env ?? process.env;
@@ -1153,8 +1219,7 @@ import { promisify } from "node:util";
1153
1219
 
1154
1220
  // src/attribution.ts
1155
1221
  import { readdirSync, statSync as statSync2 } from "node:fs";
1156
- import { homedir as homedir4 } from "node:os";
1157
- import { join as join6 } from "node:path";
1222
+ import { join as join7 } from "node:path";
1158
1223
 
1159
1224
  // src/native/claude.ts
1160
1225
  import { readdir } from "node:fs/promises";
@@ -6540,6 +6605,7 @@ function normalizeModelKey(model) {
6540
6605
  /^(?:us|eu|au|jp|apac|global|anthropic|amazon|meta|openai|google|vertex|azure|mistral)\./.test(prefix) ? "" : prefix
6541
6606
  ));
6542
6607
  k = k.replace(/:\d+$/, "").replace(/:latest$/, "");
6608
+ k = k.replace(/\[[^\]]*\]$/, "");
6543
6609
  return k;
6544
6610
  }
6545
6611
  function lookup(key) {
@@ -6555,7 +6621,7 @@ function resolveModelPrice(model) {
6555
6621
  resolveCache.set(model, resolved);
6556
6622
  return resolved;
6557
6623
  }
6558
- function resolveModelPriceUncached(model) {
6624
+ function exactCandidates(model) {
6559
6625
  const norm2 = normalizeModelKey(model);
6560
6626
  const candidates = [norm2];
6561
6627
  const dateless = norm2.replace(/[-@]20\d{6}$/, "").replace(/-20\d{2}-\d{2}-\d{2}$/, "");
@@ -6563,12 +6629,19 @@ function resolveModelPriceUncached(model) {
6563
6629
  candidates.push(dateless);
6564
6630
  if (norm2.endsWith("-latest"))
6565
6631
  candidates.push(norm2.slice(0, -"-latest".length));
6566
- let row;
6567
- for (const c of candidates) {
6568
- row = lookup(c);
6632
+ return { candidates, dateless };
6633
+ }
6634
+ function lookupExact(model) {
6635
+ for (const c of exactCandidates(model).candidates) {
6636
+ const row = lookup(c);
6569
6637
  if (row)
6570
- break;
6638
+ return row;
6571
6639
  }
6640
+ return void 0;
6641
+ }
6642
+ function resolveModelPriceUncached(model) {
6643
+ const { dateless } = exactCandidates(model);
6644
+ let row = lookupExact(model);
6572
6645
  if (!row) {
6573
6646
  let base = dateless;
6574
6647
  for (let i = 0; i < 3 && !row; i++) {
@@ -6995,12 +7068,20 @@ function nativeCachePath(reader, env = process.env) {
6995
7068
  const dir = override || defaultConfigDir();
6996
7069
  return join4(dir, `native-cache-${reader}.json`);
6997
7070
  }
7071
+ function cacheTimeZone() {
7072
+ try {
7073
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "unknown";
7074
+ } catch {
7075
+ return "unknown";
7076
+ }
7077
+ }
6998
7078
  async function loadCache(path, version, maxBytes) {
6999
7079
  try {
7000
7080
  const read = await readTextFileCapped(path, { maxBytes });
7001
7081
  if (!read.ok) return {};
7002
7082
  const parsed = JSON.parse(read.content);
7003
- if (parsed && parsed.v === version && parsed.files && typeof parsed.files === "object") {
7083
+ const sameZone = parsed?.tz === void 0 || parsed.tz === cacheTimeZone();
7084
+ if (parsed && parsed.v === version && sameZone && parsed.files && typeof parsed.files === "object") {
7004
7085
  return parsed.files;
7005
7086
  }
7006
7087
  } catch {
@@ -7014,7 +7095,7 @@ function saveCache(path, version, files, maxBytes) {
7014
7095
  mkdirSync3(dir, { recursive: true, mode: 448 });
7015
7096
  chmodSync2(dir, 448);
7016
7097
  tmp = `${path}.tmp-${process.pid}-${randomBytes3(8).toString("hex")}`;
7017
- const serialized = JSON.stringify({ v: version, files });
7098
+ const serialized = JSON.stringify({ v: version, tz: cacheTimeZone(), files });
7018
7099
  if (Buffer.byteLength(serialized, "utf8") > maxBytes) return;
7019
7100
  writeFileSync4(tmp, serialized, { encoding: "utf8", flag: "wx", mode: 384 });
7020
7101
  chmodSync2(tmp, 384);
@@ -7037,6 +7118,7 @@ async function readFilesWithCache(opts) {
7037
7118
  const cached = await loadCache(opts.cachePath, opts.version, maxCacheBytes);
7038
7119
  const fresh = {};
7039
7120
  const itemsByFile = [];
7121
+ const permissionDenied = [];
7040
7122
  let filesRead = 0;
7041
7123
  for (const f of opts.files) {
7042
7124
  let size;
@@ -7070,6 +7152,10 @@ async function readFilesWithCache(opts) {
7070
7152
  }
7071
7153
  if (opts.streamFile) {
7072
7154
  const streamed = await opts.streamFile(f).catch(() => ({ ok: false, reason: "unreadable" }));
7155
+ if (!streamed.ok && streamed.reason === "permission-denied") {
7156
+ permissionDenied.push(f);
7157
+ continue;
7158
+ }
7073
7159
  if (!streamed.ok) {
7074
7160
  const progress = { ...cached, ...fresh };
7075
7161
  delete progress[f];
@@ -7099,7 +7185,12 @@ async function readFilesWithCache(opts) {
7099
7185
  filesRead += 1;
7100
7186
  }
7101
7187
  saveCache(opts.cachePath, opts.version, fresh, maxCacheBytes);
7102
- return { itemsByFile, filesRead, timedOut: false };
7188
+ return {
7189
+ itemsByFile,
7190
+ filesRead,
7191
+ timedOut: false,
7192
+ ...permissionDenied.length > 0 ? { permissionDenied } : {}
7193
+ };
7103
7194
  }
7104
7195
 
7105
7196
  // src/native/jsonl-stream.ts
@@ -7146,7 +7237,9 @@ async function readJsonlStream(path, onLine, opts) {
7146
7237
  if (pendingBytes > 0) onLine(Buffer.concat(parts, pendingBytes).toString("utf8"));
7147
7238
  if (now() > opts.deadline) return { ok: false, reason: "timed-out" };
7148
7239
  return { ok: true };
7149
- } catch {
7240
+ } catch (err) {
7241
+ const code = err?.code;
7242
+ if (code === "EACCES" || code === "EPERM") return { ok: false, reason: "permission-denied" };
7150
7243
  return { ok: false, reason: "unreadable" };
7151
7244
  } finally {
7152
7245
  await handle?.close().catch(() => void 0);
@@ -7196,6 +7289,7 @@ function parseClaudeLine(raw) {
7196
7289
  } catch {
7197
7290
  return null;
7198
7291
  }
7292
+ if (!obj || typeof obj !== "object") return null;
7199
7293
  const message = obj.message;
7200
7294
  if (!message || typeof message !== "object") return null;
7201
7295
  const usage = message.usage;
@@ -7406,7 +7500,10 @@ async function collectClaudeNative(env = process.env, opts = {}) {
7406
7500
  return {
7407
7501
  entries: finalizeClaudeEntries(acc),
7408
7502
  found: true,
7409
- filesScanned: res.filesRead
7503
+ filesScanned: res.filesRead,
7504
+ // Skipped files mean this scan is partial: keep its rows, but flag it the
7505
+ // way a timeout is flagged so request fingerprints are never lowered.
7506
+ ...res.permissionDenied ? { timedOut: true, permissionDenied: res.permissionDenied } : {}
7410
7507
  };
7411
7508
  }
7412
7509
  async function collectClaudeRequests(env = process.env, opts = {}) {
@@ -7418,6 +7515,7 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
7418
7515
  const deadline = now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS);
7419
7516
  const acc = /* @__PURE__ */ new Map();
7420
7517
  const onLine = boundedLineAccumulator(acc, opts.maxRequestBytes);
7518
+ let skipped = false;
7421
7519
  for (const f of files) {
7422
7520
  if (now() > deadline) {
7423
7521
  return { requests: [], found: false, timedOut: true };
@@ -7427,164 +7525,432 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
7427
7525
  deadline,
7428
7526
  now
7429
7527
  });
7528
+ if (!read.ok && read.reason === "permission-denied") {
7529
+ skipped = true;
7530
+ continue;
7531
+ }
7430
7532
  if (!read.ok) {
7431
7533
  return { requests: [], found: false, timedOut: true };
7432
7534
  }
7433
7535
  }
7434
- return { requests: [...acc.values()], found: true };
7536
+ return { requests: [...acc.values()], found: true, ...skipped ? { timedOut: true } : {} };
7435
7537
  }
7436
7538
 
7437
- // src/attribution.ts
7438
- var CLAUDE_PROJECTS = join6(homedir4(), ".claude", "projects");
7439
- var CODEX_SESSIONS = join6(homedir4(), ".codex", "sessions");
7440
- var MAX_FILES = 5e3;
7441
- var TIME_BUDGET_MS = NATIVE_READ_BUDGET_MS;
7442
- var ATTRIBUTION_CACHE_VERSION = 1;
7443
- var MAX_STATS = 300;
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);
7539
+ // src/native/codex.ts
7540
+ import { readdir as readdir2 } from "node:fs/promises";
7541
+ import { homedir as homedir4 } from "node:os";
7542
+ import { basename, join as join6, resolve as resolve2 } from "node:path";
7543
+ function num2(n) {
7544
+ const v = Math.round(Number(n));
7545
+ return Number.isFinite(v) && v > 0 ? v : 0;
7469
7546
  }
7470
- function hasHumanText(content) {
7471
- const isHuman = (t) => {
7472
- const s = t.trim();
7473
- return s.length > 0 && !s.startsWith("<system-reminder") && !s.startsWith("<command-") && !s.startsWith("Caveat:");
7547
+ function readTokenFields(payload) {
7548
+ const info = payload.info;
7549
+ const src = info?.total_token_usage ?? payload;
7550
+ const input = src.input_tokens;
7551
+ const output = src.output_tokens;
7552
+ if (input === void 0 && output === void 0) return null;
7553
+ return {
7554
+ input: num2(input),
7555
+ cached: num2(src.cached_input_tokens),
7556
+ output: num2(output)
7474
7557
  };
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
7558
  }
7481
- function processRecord(rec, acc, ctx) {
7482
- if (!rec || typeof rec !== "object") return;
7483
- const r = rec;
7484
- const recTokens = recordTokens(r.message?.usage);
7485
- if ((r.type === "user" || r.message?.role === "user") && r.isSidechain !== true && r.isMeta !== true && hasHumanText(r.message?.content)) {
7486
- acc.agent.userMessageCount += 1;
7487
- }
7488
- if (typeof r.attributionSkill === "string" && r.attributionSkill) {
7489
- const s = r.attributionSkill.slice(0, 128);
7490
- const sk = acc.skills.get(s) ?? { count: 0, tokens: 0 };
7491
- sk.count += 1;
7492
- sk.tokens += recTokens;
7493
- acc.skills.set(s, sk);
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);
7559
+ function inspectCodexRollout(lines) {
7560
+ let model = "unknown";
7561
+ let sawSessionMeta = false;
7562
+ let replayedRollout = false;
7563
+ const perDay = /* @__PURE__ */ new Map();
7564
+ let lastSeenDate = null;
7565
+ for (const raw of lines) {
7566
+ const trimmed = raw.trim();
7567
+ if (!trimmed) continue;
7568
+ let obj;
7569
+ try {
7570
+ obj = JSON.parse(trimmed);
7571
+ } catch {
7572
+ continue;
7516
7573
  }
7517
- const tokens = recTokens;
7518
- acc.agent.messageCount += 1;
7519
- acc.agent.totalTokens += tokens;
7520
- const sidechain = r.isSidechain === true;
7521
- if (sidechain) {
7522
- acc.agent.subagentMessages += 1;
7523
- acc.agent.subagentTokens += tokens;
7574
+ if (!obj || typeof obj !== "object") continue;
7575
+ const payload = obj.payload;
7576
+ if (!payload || typeof payload !== "object") continue;
7577
+ const kind = obj.type;
7578
+ if (kind === "session_meta" && !sawSessionMeta) {
7579
+ sawSessionMeta = true;
7580
+ const source = payload.source;
7581
+ if (typeof payload.forked_from_id === "string" || typeof payload.parent_thread_id === "string" || source && typeof source === "object" && source.subagent !== void 0) {
7582
+ replayedRollout = true;
7583
+ }
7524
7584
  }
7525
- if (typeof r.sessionId === "string" && r.sessionId) {
7526
- acc.sessionMessages.set(
7527
- r.sessionId,
7528
- (acc.sessionMessages.get(r.sessionId) ?? 0) + 1
7529
- );
7585
+ if (kind === "session_meta" || kind === "turn_context") {
7586
+ if (typeof payload.model === "string" && payload.model) model = payload.model;
7530
7587
  }
7531
- } else if (isUser) {
7532
- for (const block of content) {
7533
- if (block && typeof block === "object" && block.type === "tool_result" && block.is_error === true) {
7534
- const id = block.tool_use_id;
7535
- const name = typeof id === "string" ? ctx.toolNames.get(id) : void 0;
7536
- if (name) {
7537
- const t = acc.tools.get(name) ?? { count: 0, errors: 0, tokens: 0 };
7538
- t.errors += 1;
7539
- acc.tools.set(name, t);
7540
- }
7588
+ if (payload.type === "token_count") {
7589
+ const fields = readTokenFields(payload);
7590
+ if (!fields) continue;
7591
+ const parsed = localUsageDate(Date.parse(String(obj.timestamp ?? "")));
7592
+ const day = parsed ?? lastSeenDate;
7593
+ if (!day) continue;
7594
+ lastSeenDate = day;
7595
+ const e = perDay.get(day);
7596
+ if (e) {
7597
+ e.cum = fields;
7598
+ e.turns += 1;
7599
+ } else {
7600
+ perDay.set(day, { cum: fields, turns: 1 });
7541
7601
  }
7542
7602
  }
7543
7603
  }
7544
- }
7545
- function toSkillStats(map) {
7546
- 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 });
7547
- }
7548
- function toToolStats(map) {
7549
- 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) => {
7550
- const base = { name: s.name, count: s.count };
7551
- if (s.errors > 0) base.errors = s.errors;
7552
- if (s.tokens > 0) base.tokens = s.tokens;
7553
- return base;
7554
- });
7555
- }
7556
- function accumulatorToResult(acc) {
7604
+ if (perDay.size === 0)
7605
+ return { sessions: [], replaySessions: [], replayCandidateDates: [] };
7606
+ const dates = [...perDay.keys()].sort();
7607
+ const out = [];
7608
+ let prev = { input: 0, cached: 0, output: 0 };
7609
+ for (const date of dates) {
7610
+ const { cum, turns } = perDay.get(date);
7611
+ const dInput = Math.max(0, cum.input - prev.input);
7612
+ const dCached = Math.max(0, cum.cached - prev.cached);
7613
+ const dOutput = Math.max(0, cum.output - prev.output);
7614
+ prev = cum;
7615
+ const cacheReadTokens = Math.min(dCached, dInput);
7616
+ const inputTokens = dInput - cacheReadTokens;
7617
+ const outputTokens = dOutput;
7618
+ if (inputTokens + outputTokens + cacheReadTokens === 0) continue;
7619
+ out.push({
7620
+ date,
7621
+ model,
7622
+ inputTokens,
7623
+ outputTokens,
7624
+ cacheCreationTokens: 0,
7625
+ cacheReadTokens,
7626
+ turnCount: turns
7627
+ });
7628
+ }
7557
7629
  return {
7558
- tools: toToolStats(acc.tools),
7559
- skills: toSkillStats(acc.skills),
7560
- agent: { ...acc.agent },
7561
- sessionMessages: acc.sessionMessages,
7562
- complete: true
7630
+ sessions: replayedRollout ? [] : out,
7631
+ replaySessions: replayedRollout ? out : [],
7632
+ replayCandidateDates: replayedRollout ? dates : []
7563
7633
  };
7564
7634
  }
7565
- function numTok(v) {
7566
- const x = Math.round(Number(v));
7567
- return Number.isFinite(x) && x > 0 ? x : 0;
7568
- }
7569
- function createCodexContext() {
7570
- return { pending: [] };
7571
- }
7572
- function processCodexRecord(rec, acc, ctx) {
7573
- if (!rec || typeof rec !== "object") return;
7574
- const r = rec;
7575
- if (!r.payload || typeof r.payload !== "object") return;
7576
- const pl = r.payload;
7577
- const ptype = pl.type;
7578
- if (r.type === "session_meta" || r.type === "turn_context") return;
7579
- if (ptype === "function_call" || ptype === "custom_tool_call" || ptype === "local_shell_call") {
7580
- const raw = typeof pl.name === "string" ? pl.name : ptype === "local_shell_call" ? "local_shell" : "";
7581
- const name = raw.slice(0, 128);
7582
- if (!name) return;
7583
- const id = typeof pl.call_id === "string" ? pl.call_id : void 0;
7584
- ctx.pending.push({ name, id });
7585
- const t = acc.tools.get(name) ?? { count: 0, errors: 0, tokens: 0 };
7586
- t.count += 1;
7587
- acc.tools.set(name, t);
7635
+ function foldCodexSessions(acc, sessions) {
7636
+ for (const s of sessions) {
7637
+ const k = `${s.date}|${s.model}`;
7638
+ let b = acc.get(k);
7639
+ if (!b) {
7640
+ b = {
7641
+ date: s.date,
7642
+ model: s.model,
7643
+ inputTokens: 0,
7644
+ outputTokens: 0,
7645
+ cacheCreationTokens: 0,
7646
+ cacheReadTokens: 0,
7647
+ requestCount: 0
7648
+ };
7649
+ acc.set(k, b);
7650
+ }
7651
+ b.inputTokens += s.inputTokens;
7652
+ b.outputTokens += s.outputTokens;
7653
+ b.cacheCreationTokens += s.cacheCreationTokens;
7654
+ b.cacheReadTokens += s.cacheReadTokens;
7655
+ b.requestCount += s.turnCount;
7656
+ }
7657
+ }
7658
+ function finalizeCodexEntries(acc) {
7659
+ const entries = [];
7660
+ for (const b of acc.values()) {
7661
+ entries.push({
7662
+ date: b.date,
7663
+ tool: "codex",
7664
+ model: b.model,
7665
+ inputTokens: b.inputTokens,
7666
+ outputTokens: b.outputTokens,
7667
+ cacheCreationTokens: b.cacheCreationTokens,
7668
+ cacheReadTokens: b.cacheReadTokens,
7669
+ costUSD: estimateCostUSD(b.model, b),
7670
+ origin: "cli",
7671
+ verified: false,
7672
+ requestCount: b.requestCount
7673
+ });
7674
+ }
7675
+ return entries;
7676
+ }
7677
+ function resolveCodexHome(env = process.env) {
7678
+ return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join6(homedir4(), ".codex");
7679
+ }
7680
+ function resolveCodexSessionsDirs(env = process.env) {
7681
+ const home = resolve2(resolveCodexHome(env));
7682
+ return [join6(home, "sessions"), join6(home, "archived_sessions")];
7683
+ }
7684
+ async function listJsonl2(dir) {
7685
+ let dirents;
7686
+ try {
7687
+ dirents = await readdir2(dir, { withFileTypes: true });
7688
+ } catch {
7689
+ return [];
7690
+ }
7691
+ const out = [];
7692
+ for (const d of dirents) {
7693
+ const full = join6(dir, d.name);
7694
+ if (d.isDirectory()) out.push(...await listJsonl2(full));
7695
+ else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
7696
+ }
7697
+ return out;
7698
+ }
7699
+ function* splitLines2(content) {
7700
+ let start = 0;
7701
+ for (let i = 0; i < content.length; i++) {
7702
+ if (content[i] === "\n") {
7703
+ yield content.slice(start, i);
7704
+ start = i + 1;
7705
+ }
7706
+ }
7707
+ if (start < content.length) yield content.slice(start);
7708
+ }
7709
+ var NATIVE_READ_BUDGET_MS2 = 45e3;
7710
+ var CODEX_CACHE_VERSION = 7;
7711
+ function toCachedSession(s, kind = "usage") {
7712
+ return [
7713
+ kind,
7714
+ s.date,
7715
+ s.model,
7716
+ s.inputTokens,
7717
+ s.outputTokens,
7718
+ s.cacheCreationTokens,
7719
+ s.cacheReadTokens,
7720
+ s.turnCount
7721
+ ];
7722
+ }
7723
+ function fromCachedSession(t) {
7724
+ return {
7725
+ date: t[1],
7726
+ model: t[2],
7727
+ inputTokens: t[3],
7728
+ outputTokens: t[4],
7729
+ cacheCreationTokens: t[5],
7730
+ cacheReadTokens: t[6],
7731
+ turnCount: t[7]
7732
+ };
7733
+ }
7734
+ async function collectCodexNative(env = process.env, opts = {}) {
7735
+ const dirs = resolveCodexSessionsDirs(env);
7736
+ const files = [];
7737
+ const primaryFiles = /* @__PURE__ */ new Set();
7738
+ const seenIdentities = /* @__PURE__ */ new Set();
7739
+ for (const dir of dirs) {
7740
+ for (const file of await listJsonl2(dir)) {
7741
+ const key = basename(file);
7742
+ files.push(file);
7743
+ if (!seenIdentities.has(key)) {
7744
+ seenIdentities.add(key);
7745
+ primaryFiles.add(file);
7746
+ }
7747
+ }
7748
+ }
7749
+ if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
7750
+ const now = opts.now ?? Date.now;
7751
+ const res = await readFilesWithCache({
7752
+ files,
7753
+ cachePath: opts.cachePath ?? nativeCachePath("codex", env),
7754
+ version: CODEX_CACHE_VERSION,
7755
+ parseFile: (content, path) => {
7756
+ const inspected = inspectCodexRollout(splitLines2(content));
7757
+ const primary = primaryFiles.has(path);
7758
+ return [
7759
+ // Do not pass toCachedSession directly to map: Array.map's numeric
7760
+ // index would be supplied as the optional `kind` argument.
7761
+ ...inspected.sessions.map(
7762
+ (session) => toCachedSession(session, primary ? "usage" : "duplicate")
7763
+ ),
7764
+ ...inspected.replaySessions.map(
7765
+ (session) => toCachedSession(
7766
+ session,
7767
+ primary ? "replay" : "replay-duplicate"
7768
+ )
7769
+ )
7770
+ ];
7771
+ },
7772
+ deadline: now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS2),
7773
+ now
7774
+ });
7775
+ if (!res.itemsByFile) {
7776
+ return {
7777
+ entries: [],
7778
+ found: false,
7779
+ filesScanned: res.filesRead,
7780
+ timedOut: true
7781
+ };
7782
+ }
7783
+ const acc = /* @__PURE__ */ new Map();
7784
+ const legacyAcc = /* @__PURE__ */ new Map();
7785
+ const replayCandidateDates = /* @__PURE__ */ new Set();
7786
+ for (const items of res.itemsByFile) {
7787
+ const usage = items.filter((item) => item[0] === "usage").map(fromCachedSession);
7788
+ const legacy = items.map(fromCachedSession);
7789
+ const replay = items.filter(
7790
+ (item) => item[0] === "replay" || item[0] === "replay-duplicate"
7791
+ ).map(fromCachedSession);
7792
+ foldCodexSessions(acc, usage);
7793
+ foldCodexSessions(legacyAcc, legacy);
7794
+ for (const session of replay) replayCandidateDates.add(session.date);
7795
+ }
7796
+ return {
7797
+ entries: finalizeCodexEntries(acc),
7798
+ found: true,
7799
+ filesScanned: res.filesRead,
7800
+ replayCandidateDates: [...replayCandidateDates].sort(),
7801
+ legacyEntries: finalizeCodexEntries(legacyAcc)
7802
+ };
7803
+ }
7804
+
7805
+ // src/attribution.ts
7806
+ var MAX_FILES = 5e3;
7807
+ var TIME_BUDGET_MS = NATIVE_READ_BUDGET_MS;
7808
+ var ATTRIBUTION_CACHE_VERSION = 1;
7809
+ var MAX_STATS = 300;
7810
+ function createAccumulator() {
7811
+ return {
7812
+ tools: /* @__PURE__ */ new Map(),
7813
+ skills: /* @__PURE__ */ new Map(),
7814
+ agent: {
7815
+ messageCount: 0,
7816
+ subagentMessages: 0,
7817
+ subagentTokens: 0,
7818
+ totalTokens: 0,
7819
+ userMessageCount: 0
7820
+ },
7821
+ sessionMessages: /* @__PURE__ */ new Map()
7822
+ };
7823
+ }
7824
+ function createFileContext() {
7825
+ return { toolNames: /* @__PURE__ */ new Map() };
7826
+ }
7827
+ function recordTokens(usage) {
7828
+ if (!usage || typeof usage !== "object") return 0;
7829
+ const u = usage;
7830
+ const n = (v) => {
7831
+ const x = Math.round(Number(v));
7832
+ return Number.isFinite(x) && x > 0 ? x : 0;
7833
+ };
7834
+ return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens) + n(u.cache_read_input_tokens);
7835
+ }
7836
+ function hasHumanText(content) {
7837
+ const isHuman = (t) => {
7838
+ const s = t.trim();
7839
+ return s.length > 0 && !s.startsWith("<system-reminder") && !s.startsWith("<command-") && !s.startsWith("Caveat:");
7840
+ };
7841
+ if (typeof content === "string") return isHuman(content);
7842
+ if (!Array.isArray(content)) return false;
7843
+ return content.some(
7844
+ (b) => b !== null && typeof b === "object" && b.type === "text" && typeof b.text === "string" && isHuman(b.text)
7845
+ );
7846
+ }
7847
+ function processRecord(rec, acc, ctx) {
7848
+ if (!rec || typeof rec !== "object") return;
7849
+ const r = rec;
7850
+ const recTokens = recordTokens(r.message?.usage);
7851
+ if ((r.type === "user" || r.message?.role === "user") && r.isSidechain !== true && r.isMeta !== true && hasHumanText(r.message?.content)) {
7852
+ acc.agent.userMessageCount += 1;
7853
+ }
7854
+ if (typeof r.attributionSkill === "string" && r.attributionSkill) {
7855
+ const s = r.attributionSkill.slice(0, 128);
7856
+ const sk = acc.skills.get(s) ?? { count: 0, tokens: 0 };
7857
+ sk.count += 1;
7858
+ sk.tokens += recTokens;
7859
+ acc.skills.set(s, sk);
7860
+ }
7861
+ const content = r.message?.content;
7862
+ if (!Array.isArray(content)) return;
7863
+ const isAssistant = r.type === "assistant" || r.message?.role === "assistant";
7864
+ const isUser = r.type === "user" || r.message?.role === "user";
7865
+ if (isAssistant) {
7866
+ const toolUses = [];
7867
+ for (const block of content) {
7868
+ if (block && typeof block === "object" && block.type === "tool_use" && typeof block.name === "string") {
7869
+ const name = block.name.slice(0, 128);
7870
+ if (!name) continue;
7871
+ const id = block.id;
7872
+ toolUses.push({ name, id: typeof id === "string" ? id : void 0 });
7873
+ }
7874
+ }
7875
+ const perToolTokens = toolUses.length > 0 ? Math.floor(recTokens / toolUses.length) : 0;
7876
+ for (const tu of toolUses) {
7877
+ const t = acc.tools.get(tu.name) ?? { count: 0, errors: 0, tokens: 0 };
7878
+ t.count += 1;
7879
+ t.tokens += perToolTokens;
7880
+ acc.tools.set(tu.name, t);
7881
+ if (tu.id) ctx.toolNames.set(tu.id, tu.name);
7882
+ }
7883
+ const tokens = recTokens;
7884
+ acc.agent.messageCount += 1;
7885
+ acc.agent.totalTokens += tokens;
7886
+ const sidechain = r.isSidechain === true;
7887
+ if (sidechain) {
7888
+ acc.agent.subagentMessages += 1;
7889
+ acc.agent.subagentTokens += tokens;
7890
+ }
7891
+ if (typeof r.sessionId === "string" && r.sessionId) {
7892
+ acc.sessionMessages.set(
7893
+ r.sessionId,
7894
+ (acc.sessionMessages.get(r.sessionId) ?? 0) + 1
7895
+ );
7896
+ }
7897
+ } else if (isUser) {
7898
+ for (const block of content) {
7899
+ if (block && typeof block === "object" && block.type === "tool_result" && block.is_error === true) {
7900
+ const id = block.tool_use_id;
7901
+ const name = typeof id === "string" ? ctx.toolNames.get(id) : void 0;
7902
+ if (name) {
7903
+ const t = acc.tools.get(name) ?? { count: 0, errors: 0, tokens: 0 };
7904
+ t.errors += 1;
7905
+ acc.tools.set(name, t);
7906
+ }
7907
+ }
7908
+ }
7909
+ }
7910
+ }
7911
+ function toSkillStats(map) {
7912
+ 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 });
7913
+ }
7914
+ function toToolStats(map) {
7915
+ 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) => {
7916
+ const base = { name: s.name, count: s.count };
7917
+ if (s.errors > 0) base.errors = s.errors;
7918
+ if (s.tokens > 0) base.tokens = s.tokens;
7919
+ return base;
7920
+ });
7921
+ }
7922
+ function accumulatorToResult(acc) {
7923
+ return {
7924
+ tools: toToolStats(acc.tools),
7925
+ skills: toSkillStats(acc.skills),
7926
+ agent: { ...acc.agent },
7927
+ sessionMessages: acc.sessionMessages,
7928
+ complete: true
7929
+ };
7930
+ }
7931
+ function numTok(v) {
7932
+ const x = Math.round(Number(v));
7933
+ return Number.isFinite(x) && x > 0 ? x : 0;
7934
+ }
7935
+ function createCodexContext() {
7936
+ return { pending: [] };
7937
+ }
7938
+ function processCodexRecord(rec, acc, ctx) {
7939
+ if (!rec || typeof rec !== "object") return;
7940
+ const r = rec;
7941
+ if (!r.payload || typeof r.payload !== "object") return;
7942
+ const pl = r.payload;
7943
+ const ptype = pl.type;
7944
+ if (r.type === "session_meta" || r.type === "turn_context") return;
7945
+ if (ptype === "function_call" || ptype === "custom_tool_call" || ptype === "local_shell_call") {
7946
+ const raw = typeof pl.name === "string" ? pl.name : ptype === "local_shell_call" ? "local_shell" : "";
7947
+ const name = raw.slice(0, 128);
7948
+ if (!name) return;
7949
+ const id = typeof pl.call_id === "string" ? pl.call_id : void 0;
7950
+ ctx.pending.push({ name, id });
7951
+ const t = acc.tools.get(name) ?? { count: 0, errors: 0, tokens: 0 };
7952
+ t.count += 1;
7953
+ acc.tools.set(name, t);
7588
7954
  return;
7589
7955
  }
7590
7956
  if (ptype === "token_count") {
@@ -7609,7 +7975,7 @@ function processCodexRecord(rec, acc, ctx) {
7609
7975
  ctx.pending = [];
7610
7976
  }
7611
7977
  }
7612
- function* splitLines2(content) {
7978
+ function* splitLines3(content) {
7613
7979
  let start = 0;
7614
7980
  for (let i = 0; i < content.length; i++) {
7615
7981
  if (content[i] === "\n") {
@@ -7659,7 +8025,7 @@ function mergeAgg(acc, agg) {
7659
8025
  function parseClaudeAttrFile(content) {
7660
8026
  const acc = createAccumulator();
7661
8027
  const ctx = createFileContext();
7662
- for (const line of splitLines2(content)) {
8028
+ for (const line of splitLines3(content)) {
7663
8029
  if (!line) continue;
7664
8030
  try {
7665
8031
  processRecord(JSON.parse(line), acc, ctx);
@@ -7671,7 +8037,7 @@ function parseClaudeAttrFile(content) {
7671
8037
  function parseCodexAttrFile(content) {
7672
8038
  const acc = createAccumulator();
7673
8039
  const ctx = createCodexContext();
7674
- for (const line of splitLines2(content)) {
8040
+ for (const line of splitLines3(content)) {
7675
8041
  if (!line) continue;
7676
8042
  try {
7677
8043
  processCodexRecord(JSON.parse(line), acc, ctx);
@@ -7680,13 +8046,6 @@ function parseCodexAttrFile(content) {
7680
8046
  }
7681
8047
  return [serializeAcc(acc)];
7682
8048
  }
7683
- function claudeProjectDirs() {
7684
- const dirs = [CLAUDE_PROJECTS];
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)];
7689
- }
7690
8049
  function listTranscripts(dir) {
7691
8050
  const out = [];
7692
8051
  const walk = (d) => {
@@ -7699,7 +8058,7 @@ function listTranscripts(dir) {
7699
8058
  }
7700
8059
  for (const e of entries) {
7701
8060
  if (out.length >= MAX_FILES) return;
7702
- const p = join6(d, e.name);
8061
+ const p = join7(d, e.name);
7703
8062
  if (e.isDirectory()) walk(p);
7704
8063
  else if (e.isFile() && e.name.endsWith(".jsonl")) {
7705
8064
  try {
@@ -7725,7 +8084,7 @@ async function collectAttribution(env = process.env) {
7725
8084
  };
7726
8085
  try {
7727
8086
  const claudeFiles = [];
7728
- for (const dir of claudeProjectDirs()) claudeFiles.push(...listTranscripts(dir));
8087
+ for (const dir of resolveClaudeProjectDirs(env)) claudeFiles.push(...listTranscripts(dir));
7729
8088
  if (claudeFiles.length > 0) {
7730
8089
  const res = await readFilesWithCache({
7731
8090
  files: claudeFiles,
@@ -7736,7 +8095,7 @@ async function collectAttribution(env = process.env) {
7736
8095
  });
7737
8096
  mergeInto(res.itemsByFile);
7738
8097
  }
7739
- const codexFiles = listTranscripts(CODEX_SESSIONS);
8098
+ const codexFiles = resolveCodexSessionsDirs(env).flatMap((dir) => listTranscripts(dir));
7740
8099
  if (codexFiles.length > 0) {
7741
8100
  const res = await readFilesWithCache({
7742
8101
  files: codexFiles,
@@ -7754,10 +8113,10 @@ async function collectAttribution(env = process.env) {
7754
8113
  }
7755
8114
 
7756
8115
  // src/continue.ts
7757
- import { readdir as readdir2 } from "node:fs/promises";
8116
+ import { readdir as readdir3 } from "node:fs/promises";
7758
8117
  import { homedir as homedir5 } from "node:os";
7759
- import { join as join7 } from "node:path";
7760
- function num2(n) {
8118
+ import { join as join8 } from "node:path";
8119
+ function num3(n) {
7761
8120
  const v = Math.round(Number(n));
7762
8121
  return Number.isFinite(v) && v > 0 ? v : 0;
7763
8122
  }
@@ -7785,8 +8144,8 @@ function mapContinueRecords(records) {
7785
8144
  if (ms === null) continue;
7786
8145
  const date = localUsageDate(ms);
7787
8146
  if (!date) continue;
7788
- const inputTokens = num2(rec.promptTokens ?? rec.prompt_tokens);
7789
- const outputTokens = num2(rec.generatedTokens ?? rec.generated_tokens);
8147
+ const inputTokens = num3(rec.promptTokens ?? rec.prompt_tokens);
8148
+ const outputTokens = num3(rec.generatedTokens ?? rec.generated_tokens);
7790
8149
  if (inputTokens + outputTokens === 0) continue;
7791
8150
  const key = `${date}|${model}`;
7792
8151
  let b = byKey.get(key);
@@ -7834,7 +8193,7 @@ function parseContinueJsonl(content) {
7834
8193
  }
7835
8194
  return mapContinueRecords(records);
7836
8195
  }
7837
- async function listJsonl2(root, opts) {
8196
+ async function listJsonl3(root, opts) {
7838
8197
  const queue = [{ dir: root, depth: 0 }];
7839
8198
  let queueIndex = 0;
7840
8199
  const files = [];
@@ -7845,7 +8204,7 @@ async function listJsonl2(root, opts) {
7845
8204
  const current = queue[queueIndex++];
7846
8205
  let dirents;
7847
8206
  try {
7848
- dirents = await readdir2(current.dir, { withFileTypes: true });
8207
+ dirents = await readdir3(current.dir, { withFileTypes: true });
7849
8208
  } catch {
7850
8209
  continue;
7851
8210
  }
@@ -7854,7 +8213,7 @@ async function listJsonl2(root, opts) {
7854
8213
  if (visitedEntries > maxEntries || opts.now() > opts.deadline) {
7855
8214
  return { files: [], aborted: true };
7856
8215
  }
7857
- const full = join7(current.dir, d.name);
8216
+ const full = join8(current.dir, d.name);
7858
8217
  if (d.isDirectory()) {
7859
8218
  if (current.depth >= 32) return { files: [], aborted: true };
7860
8219
  queue.push({ dir: full, depth: current.depth + 1 });
@@ -7869,10 +8228,10 @@ async function listJsonl2(root, opts) {
7869
8228
  var CONTINUE_CACHE_VERSION = 2;
7870
8229
  async function collectContinue(opts = {}) {
7871
8230
  const env = opts.env ?? process.env;
7872
- const home = opts.continueDir ?? join7(env.HOME || homedir5(), ".continue");
8231
+ const home = opts.continueDir ?? join8(env.HOME || homedir5(), ".continue");
7873
8232
  const now = opts.now ?? Date.now;
7874
8233
  const deadline = now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS);
7875
- const discovery = await listJsonl2(join7(home, "dev_data"), {
8234
+ const discovery = await listJsonl3(join8(home, "dev_data"), {
7876
8235
  deadline,
7877
8236
  now,
7878
8237
  maxFiles: Math.max(1, opts.maxFiles ?? 1e4)
@@ -7936,14 +8295,14 @@ import { spawnSync as spawnSync4 } from "node:child_process";
7936
8295
  import { existsSync as existsSync4, realpathSync, statSync as statSync3 } from "node:fs";
7937
8296
  import { createRequire as createRequire3 } from "node:module";
7938
8297
  import { homedir as homedir6, platform as platform2 } from "node:os";
7939
- import { join as join9 } from "node:path";
8298
+ import { join as join10 } from "node:path";
7940
8299
 
7941
8300
  // src/tokscale.ts
7942
8301
  import { spawnSync as spawnSync3 } from "node:child_process";
7943
8302
  import { createRequire as createRequire2 } from "node:module";
7944
- import { dirname as dirname4, join as join8 } from "node:path";
8303
+ import { dirname as dirname4, join as join9 } from "node:path";
7945
8304
  var LOOKBACK_DAYS = 30;
7946
- function num3(n) {
8305
+ function num4(n) {
7947
8306
  const v = Math.round(Number(n));
7948
8307
  return Number.isFinite(v) && v > 0 ? v : 0;
7949
8308
  }
@@ -7956,10 +8315,10 @@ function mapTokscaleDay(date, json) {
7956
8315
  if (!Array.isArray(entries)) return [];
7957
8316
  const out = [];
7958
8317
  for (const e of entries) {
7959
- const inputTokens = num3(e.input);
7960
- const outputTokens = num3(e.output) + num3(e.reasoning);
7961
- const cacheCreationTokens = num3(e.cacheWrite);
7962
- const cacheReadTokens = num3(e.cacheRead);
8318
+ const inputTokens = num4(e.input);
8319
+ const outputTokens = num4(e.output) + num4(e.reasoning);
8320
+ const cacheCreationTokens = num4(e.cacheWrite);
8321
+ const cacheReadTokens = num4(e.cacheRead);
7963
8322
  const costUSD2 = numCost(e.cost);
7964
8323
  const total = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
7965
8324
  if (total === 0 && costUSD2 === 0) continue;
@@ -7985,7 +8344,7 @@ function resolveTokscaleBin() {
7985
8344
  const pkg = require4("tokscale/package.json");
7986
8345
  const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
7987
8346
  if (!rel) return null;
7988
- const binPath = join8(dirname4(pkgPath), rel);
8347
+ const binPath = join9(dirname4(pkgPath), rel);
7989
8348
  if (/\.(c|m)?js$/.test(binPath)) {
7990
8349
  return { cmd: process.execPath, prefixArgs: [binPath] };
7991
8350
  }
@@ -8046,7 +8405,7 @@ var MAX_CURSOR_PAGE_BYTES = 4 * 1024 * 1024;
8046
8405
  function cursorDbPath() {
8047
8406
  const home = homedir6();
8048
8407
  const os = platform2();
8049
- const p = os === "darwin" ? join9(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join9(process.env.APPDATA ?? join9(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join9(process.env.XDG_CONFIG_HOME ?? join9(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
8408
+ 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
8409
  return existsSync4(p) ? p : null;
8051
8410
  }
8052
8411
  function trustedSqliteBinaries() {
@@ -8099,7 +8458,7 @@ function cursorCookie(token) {
8099
8458
  return null;
8100
8459
  }
8101
8460
  }
8102
- function num4(n) {
8461
+ function num5(n) {
8103
8462
  const v = Math.round(Number(n));
8104
8463
  return Number.isFinite(v) && v > 0 ? v : 0;
8105
8464
  }
@@ -8114,10 +8473,10 @@ function mapCursorEvents(events) {
8114
8473
  const date = localUsageDate(ms);
8115
8474
  if (!date) continue;
8116
8475
  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);
8476
+ const input = num5(tu.inputTokens);
8477
+ const output = num5(tu.outputTokens);
8478
+ const cacheWrite = num5(tu.cacheWriteTokens);
8479
+ const cacheRead = num5(tu.cacheReadTokens);
8121
8480
  const cost = Math.max(0, (Number(tu.totalCents) || 0) / 100);
8122
8481
  const total = input + output + cacheWrite + cacheRead;
8123
8482
  if (total === 0 && cost === 0) continue;
@@ -8130,349 +8489,94 @@ function mapCursorEvents(events) {
8130
8489
  outputTokens: 0,
8131
8490
  cacheCreationTokens: 0,
8132
8491
  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),
8492
+ costUSD: 0,
8338
8493
  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")];
8351
- }
8352
- async function listJsonl3(dir) {
8353
- let dirents;
8354
- try {
8355
- dirents = await readdir3(dir, { withFileTypes: true });
8356
- } catch {
8357
- return [];
8358
- }
8359
- const out = [];
8360
- for (const d of dirents) {
8361
- const full = join10(dir, d.name);
8362
- if (d.isDirectory()) out.push(...await listJsonl3(full));
8363
- else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
8494
+ verified: false
8495
+ };
8496
+ day.inputTokens += input;
8497
+ day.outputTokens += output;
8498
+ day.cacheCreationTokens += cacheWrite;
8499
+ day.cacheReadTokens += cacheRead;
8500
+ day.costUSD += cost;
8501
+ byDay.set(key, day);
8502
+ const hour = new Date(
8503
+ Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours())
8504
+ ).toISOString();
8505
+ const blk = byHour.get(hour) ?? { startTime: hour, totalTokens: 0, costUSD: 0 };
8506
+ blk.totalTokens += total;
8507
+ blk.costUSD += cost;
8508
+ byHour.set(hour, blk);
8364
8509
  }
8365
- return out;
8510
+ const entries = [...byDay.values()].map((e) => ({
8511
+ ...e,
8512
+ costUSD: Number(e.costUSD.toFixed(4))
8513
+ }));
8514
+ const blocks = [...byHour.values()].map((b) => ({
8515
+ ...b,
8516
+ costUSD: Number(b.costUSD.toFixed(4))
8517
+ }));
8518
+ return { entries, blocks };
8366
8519
  }
8367
- function* splitLines3(content) {
8368
- let start = 0;
8369
- for (let i = 0; i < content.length; i++) {
8370
- if (content[i] === "\n") {
8371
- yield content.slice(start, i);
8372
- start = i + 1;
8520
+ async function fetchCursorEvents(cookie, maxPages = 30, pageSize = 500) {
8521
+ const all = [];
8522
+ let reachedEnd = false;
8523
+ for (let page = 1; page <= maxPages; page++) {
8524
+ const res = await fetch(EVENTS_URL, {
8525
+ method: "POST",
8526
+ headers: {
8527
+ "Content-Type": "application/json",
8528
+ Origin: "https://cursor.com",
8529
+ Cookie: cookie
8530
+ },
8531
+ body: JSON.stringify({ page, pageSize }),
8532
+ signal: AbortSignal.timeout(2e4),
8533
+ redirect: "error"
8534
+ });
8535
+ if (!res.ok) {
8536
+ throw new Error(`cursor usage page ${page} failed (HTTP ${res.status})`);
8537
+ }
8538
+ const body = await readJsonResponseCapped(
8539
+ res,
8540
+ MAX_CURSOR_PAGE_BYTES
8541
+ );
8542
+ const batch = body.usageEventsDisplay ?? [];
8543
+ all.push(...batch);
8544
+ if (batch.length < pageSize) {
8545
+ reachedEnd = true;
8546
+ break;
8373
8547
  }
8374
8548
  }
8375
- if (start < content.length) yield content.slice(start);
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
- ];
8549
+ return reachedEnd ? all : dropBoundaryDay(all);
8390
8550
  }
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
- };
8551
+ function dropBoundaryDay(events) {
8552
+ const dayOf = (e) => localUsageDate(Number(e.timestamp));
8553
+ const boundary = events.length > 0 ? dayOf(events[events.length - 1]) : null;
8554
+ if (!boundary) return events;
8555
+ return events.filter((e) => dayOf(e) !== boundary);
8401
8556
  }
8402
- async function collectCodexNative(env = process.env, opts = {}) {
8403
- const dirs = resolveCodexSessionsDirs(env);
8404
- const files = [];
8405
- const primaryFiles = /* @__PURE__ */ new Set();
8406
- const seenIdentities = /* @__PURE__ */ new Set();
8407
- for (const dir of dirs) {
8408
- for (const file of await listJsonl3(dir)) {
8409
- const key = basename(file);
8410
- files.push(file);
8411
- if (!seenIdentities.has(key)) {
8412
- seenIdentities.add(key);
8413
- primaryFiles.add(file);
8414
- }
8557
+ async function collectCursor(opts) {
8558
+ try {
8559
+ const db = cursorDbPath();
8560
+ const token = db ? readCursorToken(db) : null;
8561
+ const cookie = token ? cursorCookie(token) : null;
8562
+ if (cookie && !opts?.offline) {
8563
+ const events = await fetchCursorEvents(cookie);
8564
+ const { entries, blocks } = mapCursorEvents(events);
8565
+ if (entries.length > 0) return { entries, blocks, found: true };
8415
8566
  }
8567
+ } catch {
8416
8568
  }
8417
- if (files.length === 0) return { entries: [], found: false, filesScanned: 0 };
8418
- const now = opts.now ?? Date.now;
8419
- const res = await readFilesWithCache({
8420
- files,
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);
8569
+ try {
8570
+ const entries = collectCursorViaTokscale();
8571
+ if (entries.length > 0) return { entries, blocks: [], found: true };
8572
+ } catch {
8463
8573
  }
8464
- return {
8465
- entries: finalizeCodexEntries(acc),
8466
- found: true,
8467
- filesScanned: res.filesRead,
8468
- replayCandidateDates: [...replayCandidateDates].sort(),
8469
- legacyEntries: finalizeCodexEntries(legacyAcc)
8470
- };
8574
+ return { entries: [], blocks: [], found: false };
8471
8575
  }
8472
8576
 
8473
8577
  // src/native/vscode-agents.ts
8474
8578
  import { readdir as readdir4 } from "node:fs/promises";
8475
- import { homedir as homedir8, platform as platform3 } from "node:os";
8579
+ import { homedir as homedir7, platform as platform3 } from "node:os";
8476
8580
  import { join as join11 } from "node:path";
8477
8581
  var VSCODE_AGENTS = [
8478
8582
  { tool: "cline", extIds: ["saoudrizwan.claude-dev"] },
@@ -8586,7 +8690,7 @@ function parseVscodeAgentMessages(tool, raw) {
8586
8690
  }
8587
8691
  function vscodeGlobalStorageRoots(env = process.env) {
8588
8692
  const apps = ["Code", "Code - Insiders", "Cursor", "VSCodium", "Windsurf"];
8589
- const home = env.HOME || homedir8();
8693
+ const home = env.HOME || homedir7();
8590
8694
  const os = platform3();
8591
8695
  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
8696
  return apps.map(base);
@@ -8969,7 +9073,12 @@ var SOURCES = [
8969
9073
  "openclaw",
8970
9074
  "hermes",
8971
9075
  "pi",
8972
- "codebuff"
9076
+ "codebuff",
9077
+ // Read by ccusage >= 20.0.29 (Antigravity's conversations/*.db ledgers,
9078
+ // Grok Build CLI's sessions/**/updates.jsonl, ZCode's cli/db/db.sqlite).
9079
+ "antigravity",
9080
+ "grok",
9081
+ "zcode"
8973
9082
  ];
8974
9083
  function norm(n) {
8975
9084
  const v = Math.round(Number(n));
@@ -8979,6 +9088,15 @@ function normCost(n) {
8979
9088
  const v = Number(n);
8980
9089
  return Number.isFinite(v) && v > 0 ? v : 0;
8981
9090
  }
9091
+ var PRICE_TABLE_COST_SOURCES = /* @__PURE__ */ new Set(["claude", "codex"]);
9092
+ function repriceCcusageCost(tool, model, tokens, ccusageCostUSD) {
9093
+ if (ccusageCostUSD > 0) return ccusageCostUSD;
9094
+ const known = model.length > 0 && model !== "unknown";
9095
+ if (known && PRICE_TABLE_COST_SOURCES.has(tool) && resolveModelPrice(model)) {
9096
+ return estimateCostUSD(model, tokens);
9097
+ }
9098
+ return 0;
9099
+ }
8982
9100
  function mapCcusageDaily(tool, json, now = Date.now()) {
8983
9101
  const daily = json?.daily;
8984
9102
  if (!Array.isArray(daily)) return [];
@@ -9036,42 +9154,13 @@ function mapCcusageDaily(tool, json, now = Date.now()) {
9036
9154
  }
9037
9155
  for (const c of candidates) {
9038
9156
  const tokens = c.inputTokens + c.outputTokens + c.cacheCreationTokens + c.cacheReadTokens;
9039
- if (tokens === 0 && c.costUSD === 0) continue;
9040
- entries.push({ date, tool, ...c, origin: "cli", verified: false });
9157
+ const costUSD2 = repriceCcusageCost(tool, c.model, c, c.costUSD);
9158
+ if (tokens === 0 && costUSD2 === 0) continue;
9159
+ entries.push({ date, tool, ...c, costUSD: costUSD2, origin: "cli", verified: false });
9041
9160
  }
9042
9161
  }
9043
9162
  return entries;
9044
9163
  }
9045
- function mapCcusageSessions(json) {
9046
- const sessions = json?.session;
9047
- if (!Array.isArray(sessions)) return [];
9048
- const out = [];
9049
- for (const raw of sessions) {
9050
- const s = raw;
9051
- const sessionId = typeof s.period === "string" ? s.period : typeof s.sessionId === "string" ? s.sessionId : null;
9052
- if (!sessionId) continue;
9053
- const inputTokens = norm(s.inputTokens);
9054
- const outputTokens = norm(s.outputTokens);
9055
- const cacheCreationTokens = norm(s.cacheCreationTokens);
9056
- const cacheReadTokens = norm(s.cacheReadTokens);
9057
- if (inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens === 0)
9058
- continue;
9059
- const models = Array.isArray(s.modelsUsed) ? s.modelsUsed.filter((m) => typeof m === "string") : [];
9060
- const meta = s.metadata ?? {};
9061
- out.push({
9062
- sessionId: sessionId.slice(0, 128),
9063
- tool: typeof s.agent === "string" ? s.agent : "unknown",
9064
- model: models[0] ?? "unknown",
9065
- inputTokens,
9066
- outputTokens,
9067
- cacheCreationTokens,
9068
- cacheReadTokens,
9069
- costUSD: normCost(s.totalCost ?? s.costUSD),
9070
- lastActivity: typeof meta.lastActivity === "string" ? meta.lastActivity : (/* @__PURE__ */ new Date()).toISOString()
9071
- });
9072
- }
9073
- return out;
9074
- }
9075
9164
  function mapCcusageBlocks(json) {
9076
9165
  const blocks = json?.blocks;
9077
9166
  if (!Array.isArray(blocks)) return [];
@@ -9299,14 +9388,9 @@ async function collectAll(onProgress, opts) {
9299
9388
  return { source, mapped: json ? mapCcusageDaily(source, json) : [] };
9300
9389
  }
9301
9390
  );
9302
- const sessionTask = runCcusage(
9303
- cmd,
9304
- [...prefixArgs, "session", "--json", "--offline"],
9305
- void 0,
9306
- CCUSAGE_AGGREGATE_TIMEOUT_MS
9307
- ).then((json) => {
9391
+ const sessionTask = Promise.resolve().then(() => {
9308
9392
  tick();
9309
- return json ? mapCcusageSessions(json) : [];
9393
+ return [];
9310
9394
  });
9311
9395
  const blockTask = runCcusage(
9312
9396
  cmd,
@@ -9325,13 +9409,19 @@ async function collectAll(onProgress, opts) {
9325
9409
  tick();
9326
9410
  return a;
9327
9411
  });
9412
+ const bailed = () => ({
9413
+ entries: [],
9414
+ found: false,
9415
+ filesScanned: 0,
9416
+ timedOut: true
9417
+ });
9328
9418
  const vscodeTasks = VSCODE_AGENTS.map(
9329
- (a) => collectVscodeAgent({ tool: a.tool, extIds: a.extIds }).then((r) => {
9419
+ (a) => collectVscodeAgent({ tool: a.tool, extIds: a.extIds }).catch(bailed).then((r) => {
9330
9420
  tick();
9331
9421
  return { tool: a.tool, result: r };
9332
9422
  })
9333
9423
  );
9334
- const continueTask = collectContinue().then((r) => {
9424
+ const continueTask = collectContinue().catch(bailed).then((r) => {
9335
9425
  tick();
9336
9426
  return r;
9337
9427
  });
@@ -9455,26 +9545,30 @@ async function collectAll(onProgress, opts) {
9455
9545
  tools: sanitizeToolStats(tools),
9456
9546
  skills: sanitizeSkillStats(skills),
9457
9547
  agent: sanitizeAgentStat(reconciled.agent),
9458
- attributionComplete: complete
9548
+ attributionComplete: complete,
9549
+ ...nativeClaude.permissionDenied ? { unreadableClaudeFiles: nativeClaude.permissionDenied } : {}
9459
9550
  };
9460
9551
  }
9461
9552
 
9462
9553
  // src/antigravity.ts
9463
9554
  import { existsSync as existsSync5 } from "node:fs";
9464
- import { homedir as homedir9 } from "node:os";
9555
+ import { homedir as homedir8 } from "node:os";
9465
9556
  import { join as join15 } from "node:path";
9466
- function antigravityDataDir(env = process.env) {
9467
- return join15(env.HOME || homedir9(), ".gemini", "antigravity");
9557
+ function antigravityDataDirs(env = process.env) {
9558
+ const gemini = join15(env.HOME || homedir8(), ".gemini");
9559
+ return ["antigravity", "antigravity-cli", "antigravity-ide"].map(
9560
+ (name) => join15(gemini, name)
9561
+ );
9468
9562
  }
9469
9563
  function detectAntigravity(env = process.env) {
9470
- return existsSync5(antigravityDataDir(env));
9564
+ return antigravityDataDirs(env).some((dir) => existsSync5(dir));
9471
9565
  }
9472
9566
  function antigravityNoticeLines() {
9473
9567
  return [
9474
- " \u2022 Google Antigravity detected \u2014 but its usage can't be counted.",
9475
- " Antigravity stores no readable local usage: its conversation logs are",
9476
- " encrypted and it bills flat-rate, so there are no per-request token",
9477
- " counts on disk to tally. It's the one major agent we can't put on the board."
9568
+ " \u2022 Google Antigravity detected. Its usage is read from the local",
9569
+ " conversation databases (~/.gemini/antigravity*/conversations/*.db).",
9570
+ " Older Antigravity versions kept only encrypted logs with no token",
9571
+ " counts; that history can't be counted. Update Antigravity if none shows."
9478
9572
  ];
9479
9573
  }
9480
9574
 
@@ -9512,7 +9606,7 @@ function buildStatusReport(s) {
9512
9606
  lines.push(" whoburnedmore \u2014 background sync status");
9513
9607
  lines.push("");
9514
9608
  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"
9609
+ 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
9610
  );
9517
9611
  if (s.installed && s.drift !== "ok") {
9518
9612
  lines.push(
@@ -9556,7 +9650,8 @@ function agentStatusReport(now = Date.now()) {
9556
9650
  now,
9557
9651
  nodePath,
9558
9652
  nodePathStable: !nodePath.includes("/Cellar/"),
9559
- logPath: syncLogPath()
9653
+ logPath: syncLogPath(),
9654
+ optedOut: cfg?.autoSyncOptOut === true
9560
9655
  });
9561
9656
  }
9562
9657
 
@@ -9616,8 +9711,12 @@ function writeLocalDashboard(dir, html) {
9616
9711
  function esc(s) {
9617
9712
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
9618
9713
  }
9714
+ function localDay(d) {
9715
+ const pad = (n) => String(n).padStart(2, "0");
9716
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
9717
+ }
9619
9718
  function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date(), connect) {
9620
- const today = generatedAt.toISOString().slice(0, 10);
9719
+ const today = localDay(generatedAt);
9621
9720
  const totals = {
9622
9721
  tokens: 0,
9623
9722
  cost: 0,
@@ -9653,7 +9752,9 @@ function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date(),
9653
9752
  const avgPerDay = activeDays > 0 ? totals.tokens / activeDays : 0;
9654
9753
  const days = [];
9655
9754
  for (let i = 59; i >= 0; i--) {
9656
- const d = new Date(generatedAt.getTime() - i * 864e5).toISOString().slice(0, 10);
9755
+ const day = new Date(generatedAt);
9756
+ day.setDate(day.getDate() - i);
9757
+ const d = localDay(day);
9657
9758
  days.push({ date: d, tokens: byDate.get(d) ?? 0 });
9658
9759
  }
9659
9760
  const maxDay = Math.max(1, ...days.map((d) => d.tokens));
@@ -9761,7 +9862,7 @@ function renderDashboardHtml(entries, generatedAt = /* @__PURE__ */ new Date(),
9761
9862
  <div class="flame">\u{1F525}</div>
9762
9863
  <div>
9763
9864
  <h1>your local burn</h1>
9764
- <div class="sub">generated ${esc(generatedAt.toISOString().slice(0, 16).replace("T", " "))} \xB7 nothing left your machine</div>
9865
+ <div class="sub">generated ${esc(`${localDay(generatedAt)} ${String(generatedAt.getHours()).padStart(2, "0")}:${String(generatedAt.getMinutes()).padStart(2, "0")}`)} \xB7 nothing left your machine</div>
9765
9866
  </div>
9766
9867
  </header>
9767
9868
  ${connectCta}
@@ -9810,7 +9911,7 @@ ${connectCta}
9810
9911
 
9811
9912
  <div class="foot">
9812
9913
  Re-run <code>npx whoburnedmore --local</code> to refresh this page.<br>
9813
- ${connect ? "Use \u201CConnect your account\u201D above to save it to whoburnedmore.com and join the leaderboard." : "Run <code>npx whoburnedmore</code> (no flag) to get a shareable dashboard at whoburnedmore.com \u2014 no sign-in."}
9914
+ ${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
9915
  </div>
9815
9916
  </div>
9816
9917
  </body>
@@ -9830,6 +9931,10 @@ async function publishLocal(payload, deps) {
9830
9931
  return false;
9831
9932
  }
9832
9933
  const res = await deps.submit(auth.token, payload);
9934
+ if (!res) {
9935
+ deps.log(" Your sign-in expired and wasn't renewed \u2014 kept local. Run `npx whoburnedmore` to sign in again.");
9936
+ return false;
9937
+ }
9833
9938
  deps.log(` Published \u2014 you're on the board: ${sanitizeServerText(res.profileUrl)}`);
9834
9939
  if (isTrustedWebUrl(res.profileUrl)) {
9835
9940
  deps.openBrowser(res.profileUrl);
@@ -9907,13 +10012,14 @@ function openBrowser(url) {
9907
10012
  const [cmd, args] = os === "darwin" ? ["open", [url]] : os === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
9908
10013
  spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
9909
10014
  }
9910
- async function confirm(question) {
10015
+ async function confirm(question, defaultYes = true) {
9911
10016
  if (!process.stdin.isTTY) return false;
9912
10017
  const rl = createInterface({ input: process.stdin, output: process.stdout });
9913
- const answer = (await rl.question(`${question} ${pc2.dim("[Y/n]")} `)).trim();
10018
+ const answer = (await rl.question(`${question} ${pc2.dim(defaultYes ? "[Y/n]" : "[y/N]")} `)).trim();
9914
10019
  rl.close();
9915
- return answer === "" || /^y(es)?$/i.test(answer);
10020
+ return confirmAnswer(answer, defaultYes);
9916
10021
  }
10022
+ var confirmDefaultNo = (question) => confirm(question, false);
9917
10023
  function showLocalDashboard(payload) {
9918
10024
  const dir = defaultConfigDir();
9919
10025
  const file = writeLocalDashboard(
@@ -9924,7 +10030,7 @@ function showLocalDashboard(payload) {
9924
10030
  console.log();
9925
10031
  console.log(` Local dashboard: ${pc2.cyan(fileUrl)}`);
9926
10032
  console.log(pc2.dim(" Re-run `npx whoburnedmore --local` to refresh it. Nothing left your machine."));
9927
- openBrowser(fileUrl);
10033
+ if (process.stdout.isTTY) openBrowser(fileUrl);
9928
10034
  }
9929
10035
  async function run(flags) {
9930
10036
  if (!flags.quiet) {
@@ -9948,6 +10054,18 @@ async function run(flags) {
9948
10054
  } finally {
9949
10055
  progress.stop();
9950
10056
  }
10057
+ const unreadable = collected.unreadableClaudeFiles ?? [];
10058
+ if (unreadable.length > 0 && !flags.quiet) {
10059
+ console.log(
10060
+ pc2.yellow(
10061
+ ` \u26A0 Skipped ${unreadable.length} Claude Code transcript${unreadable.length === 1 ? "" : "s"} this account can't read (permission denied), e.g.`
10062
+ )
10063
+ );
10064
+ console.log(pc2.dim(` ${sanitizeServerText(unreadable[0])}`));
10065
+ console.log(
10066
+ pc2.dim(" Their usage isn't counted until you fix the file's owner or permissions (often left by `sudo claude`).")
10067
+ );
10068
+ }
9951
10069
  const {
9952
10070
  entries,
9953
10071
  codexReplayTombstoneDates: codexReplayTombstoneDates2,
@@ -9993,10 +10111,13 @@ async function run(flags) {
9993
10111
  showLocalDashboard(payload);
9994
10112
  if (!flags.quiet && process.stdin.isTTY) {
9995
10113
  await publishLocal(payload, {
9996
- confirm,
9997
- signIn: ensureSignedIn,
10114
+ confirm: confirmDefaultNo,
10115
+ // Stored token, then a silent refresh, then device sign-in — the same
10116
+ // order as every other signed-in command.
10117
+ signIn: () => requireSignedIn("--local"),
9998
10118
  submit: async (token, p) => {
9999
- const result = await submitFromBoundDevice(token, p);
10119
+ const result = await submitWithAuthRecovery(token, p, true);
10120
+ if (!result) return null;
10000
10121
  try {
10001
10122
  recordSync();
10002
10123
  } catch {
@@ -10088,27 +10209,26 @@ async function ensureSignedIn() {
10088
10209
  );
10089
10210
  return null;
10090
10211
  }
10091
- async function submitSignedIn(token, payload, flags, interactive) {
10092
- let result;
10212
+ async function submitWithAuthRecovery(token, payload, interactive) {
10093
10213
  try {
10094
- result = await submitFromBoundDevice(token, payload);
10214
+ return await submitFromBoundDevice(token, payload);
10095
10215
  } catch (err) {
10096
- if (err instanceof UnauthorizedError) {
10097
- const healed = await refreshCliTokenFromConfig();
10098
- if (healed) {
10099
- saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
10100
- result = await submitFromBoundDevice(healed.token, payload);
10101
- } else {
10102
- clearAuth();
10103
- if (!interactive) return;
10104
- const auth = await ensureSignedIn();
10105
- if (!auth) return;
10106
- result = await submitFromBoundDevice(auth.token, payload);
10107
- }
10108
- } else {
10109
- throw err;
10110
- }
10216
+ if (!(err instanceof UnauthorizedError)) throw err;
10217
+ const healed = await refreshCliTokenFromConfig();
10218
+ if (healed) {
10219
+ saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
10220
+ return await submitFromBoundDevice(healed.token, payload);
10221
+ }
10222
+ clearAuth();
10223
+ if (!interactive) return null;
10224
+ const auth = await ensureSignedIn();
10225
+ if (!auth) return null;
10226
+ return await submitFromBoundDevice(auth.token, payload);
10111
10227
  }
10228
+ }
10229
+ async function submitSignedIn(token, payload, flags, interactive) {
10230
+ const result = await submitWithAuthRecovery(token, payload, interactive);
10231
+ if (!result) return;
10112
10232
  try {
10113
10233
  recordSync();
10114
10234
  } catch {
@@ -10130,7 +10250,7 @@ async function submitSignedIn(token, payload, flags, interactive) {
10130
10250
  " Verify your usage to get back on \u2014 this sends a detailed per-request breakdown (timestamps + token counts, never your prompts or code)."
10131
10251
  )
10132
10252
  );
10133
- if (process.stdin.isTTY && await confirm(" Verify now?")) {
10253
+ if (process.stdin.isTTY && await confirm(" Verify now?", false)) {
10134
10254
  await runVerify();
10135
10255
  afterSubmitChores(flags);
10136
10256
  return;
@@ -10156,8 +10276,10 @@ async function submitSignedIn(token, payload, flags, interactive) {
10156
10276
  );
10157
10277
  }
10158
10278
  if (isTrustedWebUrl(baseUrl)) {
10159
- console.log(pc2.dim(" Opening your dashboard in your browser\u2026"));
10160
- openBrowser(baseUrl);
10279
+ if (process.stdout.isTTY) {
10280
+ console.log(pc2.dim(" Opening your dashboard in your browser\u2026"));
10281
+ openBrowser(baseUrl);
10282
+ }
10161
10283
  } else {
10162
10284
  console.log(
10163
10285
  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 +10324,16 @@ function afterSubmitChores(flags) {
10202
10324
  }
10203
10325
  return;
10204
10326
  }
10205
- try {
10206
- reconcileAutoSync();
10207
- } catch {
10327
+ const optedOut = autoSyncOptedOut();
10328
+ if (!optedOut) {
10329
+ try {
10330
+ reconcileAutoSync();
10331
+ } catch {
10332
+ }
10208
10333
  }
10209
10334
  console.log();
10210
10335
  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.")
10336
+ 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
10337
  );
10213
10338
  }
10214
10339
  async function readServerInstallToken() {
@@ -10256,6 +10381,7 @@ async function linkServerInstall(token) {
10256
10381
  quiet: true
10257
10382
  });
10258
10383
  try {
10384
+ setAutoSyncOptOut(false);
10259
10385
  const action = reconcileAutoSync();
10260
10386
  if (action === "installed") {
10261
10387
  console.log(pc2.dim(" Background sync installed; this machine will refresh every 15 min."));
@@ -10330,21 +10456,8 @@ async function runVerify() {
10330
10456
  " 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
10457
  )
10332
10458
  );
10333
- const cfg = loadConfig();
10334
- let token = cfg?.cliToken;
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
- }
10459
+ const auth = await requireSignedIn("verify");
10460
+ if (!auth) return;
10348
10461
  if (process.stdin.isTTY) {
10349
10462
  const ok = await confirm("\n Send this breakdown to verify your usage?");
10350
10463
  if (!ok) {
@@ -10352,8 +10465,23 @@ async function runVerify() {
10352
10465
  return;
10353
10466
  }
10354
10467
  }
10355
- console.log(pc2.dim(" Reading your local Claude Code logs\u2026"));
10356
- const { requests, found, timedOut } = await collectClaudeRequests();
10468
+ console.log(pc2.dim(" Reading your local Claude Code logs (large histories can take a few minutes)\u2026"));
10469
+ const { requests, found, timedOut } = await collectClaudeRequests(process.env, {
10470
+ budgetMs: VERIFY_READ_BUDGET_MS
10471
+ });
10472
+ if (timedOut === true && requests.length === 0) {
10473
+ console.log(
10474
+ pc2.yellow(
10475
+ " Your Claude Code logs are too large to finish reading in time, so nothing was sent."
10476
+ )
10477
+ );
10478
+ console.log(
10479
+ pc2.dim(
10480
+ " Try again when the machine is idle, or file a written appeal at whoburnedmore.com/appeal."
10481
+ )
10482
+ );
10483
+ return;
10484
+ }
10357
10485
  if (!found || requests.length === 0) {
10358
10486
  console.log(
10359
10487
  pc2.yellow(" No local Claude Code logs found on this machine to verify.")
@@ -10374,13 +10502,25 @@ async function runVerify() {
10374
10502
  ` Sending ${records.length} request records for analysis${truncated ? " (most recent, sampled)" : ""}\u2026`
10375
10503
  )
10376
10504
  );
10505
+ const body = {
10506
+ cliVersion: VERSION,
10507
+ requests: records,
10508
+ ...truncated ? { truncated: true } : {}
10509
+ };
10510
+ const verifyWithRefresh = async () => {
10511
+ try {
10512
+ return await verifyUsage(auth.token, body);
10513
+ } catch (err) {
10514
+ if (!(err instanceof UnauthorizedError)) throw err;
10515
+ const healed = await refreshCliTokenFromConfig();
10516
+ if (!healed) throw err;
10517
+ saveAuth(void 0, { cliToken: healed.token, handle: healed.handle });
10518
+ return await verifyUsage(healed.token, body);
10519
+ }
10520
+ };
10377
10521
  let result;
10378
10522
  try {
10379
- result = await verifyUsage(token, {
10380
- cliVersion: VERSION,
10381
- requests: records,
10382
- ...truncated ? { truncated: true } : {}
10383
- });
10523
+ result = await verifyWithRefresh();
10384
10524
  } catch (err) {
10385
10525
  if (err instanceof UnauthorizedError) {
10386
10526
  clearAuth();
@@ -10475,7 +10615,7 @@ async function runRemove() {
10475
10615
  console.log(
10476
10616
  pc2.dim(" Your local logs are untouched, and the account itself survives (delete it from your dashboard settings on the web).")
10477
10617
  );
10478
- if (!await confirm(" Delete your usage data?")) {
10618
+ if (!await confirmDefaultNo(" Delete your usage data?")) {
10479
10619
  console.log(pc2.dim(" Cancelled \u2014 nothing was deleted."));
10480
10620
  return;
10481
10621
  }
@@ -10502,6 +10642,7 @@ async function runRemove() {
10502
10642
  );
10503
10643
  console.log(pc2.dim(" Run `npx whoburnedmore` anytime to start fresh."));
10504
10644
  }
10645
+ var VERIFY_READ_BUDGET_MS = 18e4;
10505
10646
  async function main() {
10506
10647
  const major = Number(process.versions.node.split(".")[0]);
10507
10648
  if (major < 20) {
@@ -10511,6 +10652,13 @@ async function main() {
10511
10652
  }
10512
10653
  const args = process.argv.slice(2);
10513
10654
  const baseCommand = resolveCommand(args);
10655
+ const problem = argumentProblem(args, baseCommand);
10656
+ if (problem) {
10657
+ console.error(` ${problem}`);
10658
+ console.error(" Nothing was collected or sent. Run `npx whoburnedmore --help` for the options.");
10659
+ process.exitCode = 2;
10660
+ return;
10661
+ }
10514
10662
  const command = args.includes("--watch") && (baseCommand === "run" || baseCommand === "sync") ? "daemon" : baseCommand;
10515
10663
  const flags = {
10516
10664
  dryRun: args.includes("--dry-run"),
@@ -10561,9 +10709,11 @@ async function main() {
10561
10709
  break;
10562
10710
  case "install-sync":
10563
10711
  console.log(` ${installAutoSync()}`);
10712
+ setAutoSyncOptOut(false);
10564
10713
  break;
10565
10714
  case "uninstall-sync":
10566
10715
  console.log(` ${uninstallAutoSync()}`);
10716
+ setAutoSyncOptOut(true);
10567
10717
  break;
10568
10718
  case "help":
10569
10719
  printHelp();