@yishiguji/tokenarena 0.16.6 → 0.18.1

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 CHANGED
@@ -646,11 +646,11 @@ function readRollout(path) {
646
646
  let minLineTimestampOrder = null;
647
647
  let line = 0;
648
648
  let maxOrdinal = null;
649
- for (const text of content.split("\n")) {
649
+ for (const text2 of content.split("\n")) {
650
650
  line++;
651
- if (!text.trim()) continue;
651
+ if (!text2.trim()) continue;
652
652
  try {
653
- const event = JSON.parse(text);
653
+ const event = JSON.parse(text2);
654
654
  if (typeof event.ordinal === "number" && Number.isSafeInteger(event.ordinal) && event.ordinal >= 0 && (maxOrdinal === null || event.ordinal > maxOrdinal)) {
655
655
  maxOrdinal = event.ordinal;
656
656
  }
@@ -1358,7 +1358,7 @@ async function readSqliteRowsWithBuiltin(dbPath, query) {
1358
1358
  throw err;
1359
1359
  }
1360
1360
  }
1361
- function readSqliteRowsWithCli(dbPath, query) {
1361
+ function readSqliteRowsWithCli(dbPath, query, readOnly = false) {
1362
1362
  const candidates = [
1363
1363
  process.env.TOKEN_ARENA_SQLITE3,
1364
1364
  "sqlite3",
@@ -1367,7 +1367,8 @@ function readSqliteRowsWithCli(dbPath, query) {
1367
1367
  let lastError = null;
1368
1368
  for (const command of candidates) {
1369
1369
  try {
1370
- const output = execFileSync(command, ["-json", dbPath, query], {
1370
+ const args = [...readOnly ? ["-readonly"] : [], "-json", dbPath, query];
1371
+ const output = execFileSync(command, args, {
1371
1372
  encoding: "utf-8",
1372
1373
  maxBuffer: 100 * 1024 * 1024,
1373
1374
  timeout: 3e4,
@@ -1380,7 +1381,7 @@ function readSqliteRowsWithCli(dbPath, query) {
1380
1381
  } catch (err) {
1381
1382
  lastError = err;
1382
1383
  const nodeError = err;
1383
- if (nodeError.status === 127 || nodeError.message?.includes("ENOENT")) {
1384
+ if (nodeError.status === 127 || nodeError.code === "ENOENT") {
1384
1385
  continue;
1385
1386
  }
1386
1387
  throw err;
@@ -1390,6 +1391,37 @@ function readSqliteRowsWithCli(dbPath, query) {
1390
1391
  `sqlite3 CLI not found. Install sqlite3 or set TOKEN_ARENA_SQLITE3 to its full path. Last error: ${lastError?.message || "not found"}`
1391
1392
  );
1392
1393
  }
1394
+ async function readSqliteRowsReadonly(dbPath, query) {
1395
+ return withSuppressedSqliteWarning(async () => {
1396
+ const sqliteModuleId = "node:sqlite";
1397
+ let sqlite;
1398
+ try {
1399
+ sqlite = await import(sqliteModuleId);
1400
+ } catch (error) {
1401
+ if (error.code !== "ERR_UNKNOWN_BUILTIN_MODULE") {
1402
+ throw error;
1403
+ }
1404
+ return readSqliteRowsWithCli(dbPath, query, true);
1405
+ }
1406
+ let supportsReadOnly = false;
1407
+ const probe = new sqlite.DatabaseSync(":memory:", {
1408
+ get readOnly() {
1409
+ supportsReadOnly = true;
1410
+ return false;
1411
+ }
1412
+ });
1413
+ probe.close();
1414
+ if (!supportsReadOnly) {
1415
+ return readSqliteRowsWithCli(dbPath, query, true);
1416
+ }
1417
+ const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
1418
+ try {
1419
+ return db.prepare(query).all();
1420
+ } finally {
1421
+ db.close();
1422
+ }
1423
+ });
1424
+ }
1393
1425
  async function readSqliteRows(dbPath, query) {
1394
1426
  const builtinRows = await readSqliteRowsWithBuiltin(dbPath, query);
1395
1427
  return builtinRows ?? readSqliteRowsWithCli(dbPath, query);
@@ -4039,8 +4071,8 @@ var ClineParser = class {
4039
4071
  if (msgType === "say" && msgSay === "api_req_started") {
4040
4072
  let info = null;
4041
4073
  try {
4042
- const text = msg.text;
4043
- if (typeof text === "string") info = JSON.parse(text);
4074
+ const text2 = msg.text;
4075
+ if (typeof text2 === "string") info = JSON.parse(text2);
4044
4076
  } catch {
4045
4077
  }
4046
4078
  if (!info) continue;
@@ -4494,76 +4526,540 @@ var RooCodeParser = class {
4494
4526
  registerParser(new RooCodeParser());
4495
4527
 
4496
4528
  // src/parsers/snow.ts
4497
- import { existsSync as existsSync22 } from "fs";
4529
+ import { existsSync as existsSync22, readdirSync as readdirSync12, statSync as statSync6 } from "fs";
4498
4530
  import { homedir as homedir22 } from "os";
4499
- import { join as join23 } from "path";
4500
- var DEFAULT_DATA_DIR6 = join23(homedir22(), ".snow", "usage");
4531
+ import { basename as basename10, join as join23 } from "path";
4532
+ var DEFAULT_SNOW_DIR = join23(homedir22(), ".snow");
4533
+ var USAGE_DIR_NAME = "usage";
4534
+ var SESSIONS_DIR_NAME = "sessions";
4535
+ var NON_TRANSCRIPT_DIRS = /* @__PURE__ */ new Set(["subagent", "compressed"]);
4536
+ var SESSION_MATCH_WINDOW_MS = 5 * 60 * 1e3;
4537
+ var DAMAGED_TRANSCRIPT_SETTLE_MS = 60 * 1e3;
4538
+ var MIN_TIMESTAMP_MS = Date.UTC(2020, 0, 1);
4539
+ var MAX_TIMESTAMP_MS = Date.UTC(2080, 0, 1);
4501
4540
  function toNonNegativeNumber2(value) {
4541
+ if (typeof value !== "number" && typeof value !== "string") return 0;
4502
4542
  const numberValue = Number(value);
4503
4543
  return Number.isFinite(numberValue) && numberValue >= 0 ? numberValue : 0;
4504
4544
  }
4545
+ function toTime(value) {
4546
+ const time = typeof value === "string" ? Date.parse(value) : typeof value === "number" ? value : Number.NaN;
4547
+ return time >= MIN_TIMESTAMP_MS && time < MAX_TIMESTAMP_MS ? time : null;
4548
+ }
4549
+ function isRecord(value) {
4550
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4551
+ }
4552
+ function findSourceFiles(dir, extension, results = []) {
4553
+ let entries;
4554
+ try {
4555
+ entries = readdirSync12(dir, { withFileTypes: true });
4556
+ } catch (error) {
4557
+ const code = error.code;
4558
+ if (code === "ENOENT" || code === "ENOTDIR") return results;
4559
+ throw error;
4560
+ }
4561
+ for (const entry of entries) {
4562
+ const fullPath = join23(dir, entry.name);
4563
+ if (entry.isDirectory()) {
4564
+ if (extension === ".json" && NON_TRANSCRIPT_DIRS.has(entry.name)) {
4565
+ continue;
4566
+ }
4567
+ findSourceFiles(fullPath, extension, results);
4568
+ } else if (entry.name.endsWith(extension)) {
4569
+ results.push(fullPath);
4570
+ }
4571
+ }
4572
+ return results;
4573
+ }
4574
+ function resolveProject(session) {
4575
+ if (typeof session.projectPath === "string" && session.projectPath) {
4576
+ const segments = session.projectPath.split(/[\\/]+/).filter(Boolean);
4577
+ return segments.at(-1) || "unknown";
4578
+ }
4579
+ return "unknown";
4580
+ }
4581
+ function readTranscript(filePath) {
4582
+ const content = readFileSafe(filePath);
4583
+ if (content === null) return { status: "damaged" };
4584
+ let parsed;
4585
+ try {
4586
+ parsed = JSON.parse(content);
4587
+ } catch {
4588
+ return { status: "damaged" };
4589
+ }
4590
+ if (!isRecord(parsed) || !Array.isArray(parsed.messages)) {
4591
+ return { status: "foreign" };
4592
+ }
4593
+ const session = parsed;
4594
+ const compressedFrom = typeof session.compressedFrom === "string" && session.compressedFrom ? session.compressedFrom : void 0;
4595
+ const copiedUntil = compressedFrom !== void 0 || typeof session.branchedFrom === "string" ? Math.max(
4596
+ toTime(session.compressedAt) ?? Number.NEGATIVE_INFINITY,
4597
+ toTime(session.createdAt) ?? Number.NEGATIVE_INFINITY
4598
+ ) : Number.NEGATIVE_INFINITY;
4599
+ const messages = [];
4600
+ let lastMessageAt = 0;
4601
+ for (const message of parsed.messages) {
4602
+ if (!isRecord(message)) continue;
4603
+ if (message.role !== "user" && message.role !== "assistant") continue;
4604
+ const time = toTime(message.timestamp);
4605
+ if (time === null) continue;
4606
+ if (time > lastMessageAt) lastMessageAt = time;
4607
+ if (time <= copiedUntil) continue;
4608
+ messages.push({ role: message.role, time });
4609
+ }
4610
+ return {
4611
+ status: "ok",
4612
+ transcript: {
4613
+ filePath,
4614
+ sessionId: typeof session.id === "string" && session.id ? session.id : basename10(filePath, ".json"),
4615
+ project: resolveProject(session),
4616
+ updatedAt: toTime(session.updatedAt) ?? lastMessageAt,
4617
+ ...compressedFrom !== void 0 ? { compressedFrom } : {},
4618
+ messages
4619
+ }
4620
+ };
4621
+ }
4622
+ function isNewerCopy(candidate, current) {
4623
+ if (candidate.updatedAt !== current.updatedAt) {
4624
+ return candidate.updatedAt > current.updatedAt;
4625
+ }
4626
+ if (candidate.messages.length !== current.messages.length) {
4627
+ return candidate.messages.length > current.messages.length;
4628
+ }
4629
+ return candidate.filePath < current.filePath;
4630
+ }
4631
+ function resolveConversationId(transcript, transcripts) {
4632
+ let current = transcript;
4633
+ const visited = /* @__PURE__ */ new Set([current.sessionId]);
4634
+ while (current.compressedFrom && !visited.has(current.compressedFrom)) {
4635
+ visited.add(current.compressedFrom);
4636
+ const source = transcripts.get(current.compressedFrom);
4637
+ if (!source) return current.compressedFrom;
4638
+ current = source;
4639
+ }
4640
+ return current.sessionId;
4641
+ }
4642
+ function mayStillBeWriting(filePath, now) {
4643
+ try {
4644
+ return now - statSync6(filePath).mtimeMs < DAMAGED_TRANSCRIPT_SETTLE_MS;
4645
+ } catch {
4646
+ return true;
4647
+ }
4648
+ }
4649
+ function distanceToNearest(sortedTimes, time) {
4650
+ let low = 0;
4651
+ let high = sortedTimes.length;
4652
+ while (low < high) {
4653
+ const middle = low + high >>> 1;
4654
+ if (sortedTimes[middle] < time) low = middle + 1;
4655
+ else high = middle;
4656
+ }
4657
+ let distance = Number.POSITIVE_INFINITY;
4658
+ if (low < sortedTimes.length) distance = sortedTimes[low] - time;
4659
+ if (low > 0) distance = Math.min(distance, time - sortedTimes[low - 1]);
4660
+ return distance;
4661
+ }
4662
+ function attributeUsage(entries, windows) {
4663
+ if (windows.length === 0) return;
4664
+ const byStart = [...windows].sort((left, right) => left.start - right.start);
4665
+ const byTime = entries.map((entry) => ({ entry, time: entry.timestamp.getTime() })).sort((left, right) => left.time - right.time);
4666
+ const open = [];
4667
+ let nextWindow = 0;
4668
+ for (const { entry, time } of byTime) {
4669
+ while (nextWindow < byStart.length && byStart[nextWindow].start <= time) {
4670
+ open.push(byStart[nextWindow]);
4671
+ nextWindow++;
4672
+ }
4673
+ let kept = 0;
4674
+ let best;
4675
+ let bestDistance = Number.POSITIVE_INFINITY;
4676
+ for (const window of open) {
4677
+ if (window.end < time) continue;
4678
+ open[kept++] = window;
4679
+ const distance = distanceToNearest(window.assistantTimes, time);
4680
+ if (best === void 0 || distance < bestDistance || distance === bestDistance && window.sessionId < best.sessionId) {
4681
+ best = window;
4682
+ bestDistance = distance;
4683
+ }
4684
+ }
4685
+ open.length = kept;
4686
+ if (best) entry.sessionId = best.sessionId;
4687
+ }
4688
+ }
4505
4689
  var SnowParser = class {
4506
- constructor(dataDir = DEFAULT_DATA_DIR6) {
4507
- this.dataDir = dataDir;
4508
- this.tool = { id: "snow", name: "Snow CLI", dataDir };
4690
+ constructor(snowDir = DEFAULT_SNOW_DIR) {
4691
+ this.snowDir = snowDir;
4692
+ this.tool = {
4693
+ id: "snow",
4694
+ name: "Snow CLI",
4695
+ dataDir: join23(snowDir, USAGE_DIR_NAME)
4696
+ };
4509
4697
  }
4510
- dataDir;
4698
+ snowDir;
4511
4699
  tool;
4512
4700
  async parse() {
4701
+ const usage = this.parseUsage();
4702
+ const sessions = this.parseSessions();
4703
+ attributeUsage(usage.entries, sessions.windows);
4704
+ return {
4705
+ buckets: aggregateToBuckets(usage.entries),
4706
+ sessions: extractSessions(sessions.events, usage.entries),
4707
+ ...usage.incomplete ? { incomplete: true } : {}
4708
+ };
4709
+ }
4710
+ listSourceFiles() {
4711
+ return [
4712
+ ...findSourceFiles(join23(this.snowDir, USAGE_DIR_NAME), ".jsonl"),
4713
+ ...findSourceFiles(join23(this.snowDir, SESSIONS_DIR_NAME), ".json")
4714
+ ];
4715
+ }
4716
+ /**
4717
+ * Build session timing events plus the windows used to attribute usage
4718
+ * records. Message timestamps drive turn boundaries, so active time, session
4719
+ * duration, and message counts all come from real transcripts.
4720
+ *
4721
+ * Transcripts only feed sessions, never buckets, so a damaged one is skipped
4722
+ * rather than holding back the whole source: Snow cannot open it either and
4723
+ * will not rewrite it, so deferring would block Snow uploads for good.
4724
+ */
4725
+ parseSessions() {
4726
+ const transcripts = /* @__PURE__ */ new Map();
4727
+ const damaged = [];
4728
+ const now = Date.now();
4729
+ for (const filePath of findSourceFiles(
4730
+ join23(this.snowDir, SESSIONS_DIR_NAME),
4731
+ ".json"
4732
+ )) {
4733
+ const read = readTranscript(filePath);
4734
+ if (read.status === "damaged") {
4735
+ if (!mayStillBeWriting(filePath, now)) damaged.push(filePath);
4736
+ continue;
4737
+ }
4738
+ if (read.status === "foreign") continue;
4739
+ const current = transcripts.get(read.transcript.sessionId);
4740
+ if (!current || isNewerCopy(read.transcript, current)) {
4741
+ transcripts.set(read.transcript.sessionId, read.transcript);
4742
+ }
4743
+ }
4744
+ if (damaged.length > 0) {
4745
+ const more = damaged.length > 1 ? ` and ${damaged.length - 1} more` : "";
4746
+ logger.warn(
4747
+ `Skipped damaged Snow session file ${damaged[0]}${more}. Token usage is still counted.`
4748
+ );
4749
+ }
4750
+ const events = [];
4751
+ const windows = [];
4752
+ for (const transcript of transcripts.values()) {
4753
+ if (transcript.messages.length === 0) continue;
4754
+ const sessionId = resolveConversationId(transcript, transcripts);
4755
+ const assistantTimes = [];
4756
+ let start = Number.POSITIVE_INFINITY;
4757
+ let end = Number.NEGATIVE_INFINITY;
4758
+ for (const message of transcript.messages) {
4759
+ events.push({
4760
+ sessionId,
4761
+ source: "snow",
4762
+ project: transcript.project,
4763
+ timestamp: new Date(message.time),
4764
+ role: message.role
4765
+ });
4766
+ if (message.role === "assistant") assistantTimes.push(message.time);
4767
+ if (message.time < start) start = message.time;
4768
+ if (message.time > end) end = message.time;
4769
+ }
4770
+ windows.push({
4771
+ sessionId,
4772
+ assistantTimes: assistantTimes.sort((left, right) => left - right),
4773
+ start: start - SESSION_MATCH_WINDOW_MS,
4774
+ end: end + SESSION_MATCH_WINDOW_MS
4775
+ });
4776
+ }
4777
+ return { events, windows };
4778
+ }
4779
+ /**
4780
+ * Parse usage JSONL. Session attribution happens afterwards, because it needs
4781
+ * the transcripts.
4782
+ *
4783
+ * The bucket project stays `unknown` on purpose; see the note on
4784
+ * `SESSION_MATCH_WINDOW_MS`.
4785
+ */
4786
+ parseUsage() {
4513
4787
  const entries = [];
4514
- for (const filePath of findJsonlFiles(this.dataDir)) {
4788
+ let incomplete = false;
4789
+ for (const filePath of findSourceFiles(
4790
+ join23(this.snowDir, USAGE_DIR_NAME),
4791
+ ".jsonl"
4792
+ )) {
4515
4793
  const content = readFileSafe(filePath);
4516
- if (!content) continue;
4794
+ if (content === null) {
4795
+ if (existsSync22(filePath)) incomplete = true;
4796
+ continue;
4797
+ }
4517
4798
  for (const line of content.split("\n")) {
4518
4799
  if (!line.trim()) continue;
4800
+ let record;
4519
4801
  try {
4520
- const record = JSON.parse(line);
4521
- if (typeof record.timestamp !== "string") continue;
4522
- const timestamp = new Date(record.timestamp);
4523
- if (Number.isNaN(timestamp.getTime())) continue;
4524
- const inputTokens = toNonNegativeNumber2(record.inputTokens);
4525
- const outputTokens = toNonNegativeNumber2(record.outputTokens);
4526
- const cachedTokens = toNonNegativeNumber2(record.cacheReadInputTokens);
4527
- const reasoningTokens = toNonNegativeNumber2(record.reasoningTokens);
4528
- if (inputTokens + outputTokens + cachedTokens + reasoningTokens === 0)
4529
- continue;
4530
- entries.push({
4531
- source: "snow",
4532
- model: typeof record.model === "string" && record.model ? record.model : "unknown",
4533
- project: "unknown",
4534
- timestamp,
4535
- inputTokens,
4536
- outputTokens,
4537
- reasoningTokens,
4538
- cachedTokens
4539
- });
4802
+ const parsed = JSON.parse(line);
4803
+ if (!isRecord(parsed)) continue;
4804
+ record = parsed;
4540
4805
  } catch {
4806
+ continue;
4541
4807
  }
4808
+ const time = toTime(record.timestamp);
4809
+ if (time === null) continue;
4810
+ const reportedInputTokens = toNonNegativeNumber2(record.inputTokens);
4811
+ const outputTokens = toNonNegativeNumber2(record.outputTokens);
4812
+ const cachedTokens = toNonNegativeNumber2(record.cacheReadInputTokens);
4813
+ const cacheCreationTokens = toNonNegativeNumber2(
4814
+ record.cacheCreationInputTokens
4815
+ );
4816
+ const reasoningTokens = toNonNegativeNumber2(record.reasoningTokens);
4817
+ const inputTokens = record.cacheCreationInputTokens === void 0 && cachedTokens <= reportedInputTokens ? reportedInputTokens - cachedTokens : reportedInputTokens;
4818
+ if (inputTokens + outputTokens + cachedTokens + cacheCreationTokens + reasoningTokens === 0)
4819
+ continue;
4820
+ entries.push({
4821
+ source: "snow",
4822
+ model: typeof record.model === "string" && record.model ? record.model : "unknown",
4823
+ project: "unknown",
4824
+ timestamp: new Date(time),
4825
+ inputTokens,
4826
+ outputTokens,
4827
+ reasoningTokens,
4828
+ cachedTokens,
4829
+ cacheCreationTokens
4830
+ });
4542
4831
  }
4543
4832
  }
4544
- return { buckets: aggregateToBuckets(entries), sessions: [] };
4833
+ return { entries, incomplete };
4545
4834
  }
4546
4835
  isInstalled() {
4547
- return existsSync22(this.dataDir);
4836
+ return existsSync22(join23(this.snowDir, USAGE_DIR_NAME));
4548
4837
  }
4549
4838
  };
4550
4839
  registerParser(new SnowParser());
4551
4840
 
4841
+ // src/parsers/snow-app.ts
4842
+ import { statSync as statSync7 } from "fs";
4843
+ import { homedir as homedir23 } from "os";
4844
+ import { dirname as dirname4, join as join24 } from "path";
4845
+ var TOOL_ID14 = "snow-app";
4846
+ var DEFAULT_DB_PATH3 = join24(homedir23(), ".snowapp", "snowapp.db");
4847
+ var SNAPSHOT_QUERY = `SELECT 'usage' AS kind, u.id, u.conversation_id AS sessionId,
4848
+ u.directory_id AS directoryId,
4849
+ u.model, u.created_at AS timestamp, '' AS role,
4850
+ u.input_tokens AS inputTokens, u.output_tokens AS outputTokens,
4851
+ u.cache_read_input_tokens AS cacheReadTokens,
4852
+ u.cache_creation_input_tokens AS cacheCreationTokens,
4853
+ '' AS forkedFrom, 0 AS forkMessageCount
4854
+ FROM usage_records u
4855
+ UNION ALL
4856
+ SELECT 'message' AS kind, m.id, m.conversation_id AS sessionId,
4857
+ (SELECT u.directory_id FROM usage_records u
4858
+ WHERE u.conversation_id = m.conversation_id
4859
+ ORDER BY u.created_at, u.id LIMIT 1) AS directoryId,
4860
+ '' AS model, m.created_at AS timestamp, m.role,
4861
+ 0 AS inputTokens, 0 AS outputTokens, 0 AS cacheReadTokens, 0 AS cacheCreationTokens,
4862
+ '' AS forkedFrom, 0 AS forkMessageCount
4863
+ FROM chat_messages m
4864
+ WHERE m.role IN ('user', 'assistant')
4865
+ AND EXISTS (SELECT 1 FROM usage_records u WHERE u.conversation_id = m.conversation_id)
4866
+ UNION ALL
4867
+ SELECT 'conversation' AS kind, c.conversation_id AS id, c.conversation_id AS sessionId,
4868
+ '' AS directoryId, '' AS model, '' AS timestamp, '' AS role,
4869
+ 0 AS inputTokens, 0 AS outputTokens, 0 AS cacheReadTokens, 0 AS cacheCreationTokens,
4870
+ c.forked_from_conversation_id AS forkedFrom, c.fork_message_count AS forkMessageCount
4871
+ FROM chat_conversations c
4872
+ WHERE EXISTS (SELECT 1 FROM usage_records u WHERE u.conversation_id = c.conversation_id)
4873
+ ORDER BY timestamp, kind, id`;
4874
+ function text(value) {
4875
+ return typeof value === "string" ? value.trim() : "";
4876
+ }
4877
+ function tokenCount4(value) {
4878
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
4879
+ throw new Error("Snow App contains invalid token counts; scan deferred.");
4880
+ }
4881
+ return value;
4882
+ }
4883
+ function parseTimestamp4(value) {
4884
+ const raw = text(value);
4885
+ const match = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})?$/.exec(
4886
+ raw
4887
+ );
4888
+ if (!match)
4889
+ throw new Error("Snow App contains an invalid timestamp; scan deferred.");
4890
+ const [year, month, day, hour, minute, second] = match.slice(1, 7).map(Number);
4891
+ const calendar = new Date(
4892
+ Date.UTC(year, month - 1, day, hour, minute, second)
4893
+ );
4894
+ if (year < 1e3 || calendar.getUTCFullYear() !== year || calendar.getUTCMonth() !== month - 1 || calendar.getUTCDate() !== day || hour > 23 || minute > 59 || second > 59) {
4895
+ throw new Error(
4896
+ "Snow App contains an invalid calendar date; scan deferred."
4897
+ );
4898
+ }
4899
+ const date = new Date(raw.replace(" ", "T"));
4900
+ if (!Number.isFinite(date.getTime()) || !match[8] && (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day || date.getHours() !== hour || date.getMinutes() !== minute)) {
4901
+ throw new Error(
4902
+ "Snow App contains an invalid local timestamp; scan deferred."
4903
+ );
4904
+ }
4905
+ if (date.getTime() < Date.UTC(2020, 0, 1) || date.getTime() >= Date.UTC(2080, 0, 1)) {
4906
+ throw new Error(
4907
+ "Snow App timestamp is outside the supported range; scan deferred."
4908
+ );
4909
+ }
4910
+ if (!match[8]) {
4911
+ const nextDay = new Date(date);
4912
+ nextDay.setDate(nextDay.getDate() + 1);
4913
+ const overlapMinutes = nextDay.getTimezoneOffset() - date.getTimezoneOffset();
4914
+ if (overlapMinutes > 0) {
4915
+ const later = new Date(date.getTime() + overlapMinutes * 6e4);
4916
+ if (later.getHours() === hour && later.getMinutes() === minute && later.getDate() === day) {
4917
+ throw new Error(
4918
+ "Snow App contains an ambiguous local timestamp; scan deferred."
4919
+ );
4920
+ }
4921
+ }
4922
+ }
4923
+ const elapsed = date.getTime() - roundToHalfHour(date).getTime();
4924
+ if (elapsed < 0 || elapsed >= 30 * 6e4) {
4925
+ throw new Error(
4926
+ "Snow App timestamp cannot be bucketed safely across DST; scan deferred."
4927
+ );
4928
+ }
4929
+ return date;
4930
+ }
4931
+ function projectName(value) {
4932
+ if (value === "") return "unknown";
4933
+ if (typeof value !== "string" || !/^local:(?:[A-Za-z]:[\\/]|\/|\\\\)/.test(value)) {
4934
+ throw new Error(
4935
+ "Snow App contains an unsupported directory identity; scan deferred."
4936
+ );
4937
+ }
4938
+ return value.slice("local:".length).split(/[\\/]+/).filter(Boolean).at(-1) || "unknown";
4939
+ }
4940
+ var SnowAppParser = class {
4941
+ tool;
4942
+ dbPath;
4943
+ queryRows;
4944
+ constructor(options = {}) {
4945
+ this.dbPath = options.dbPath ?? DEFAULT_DB_PATH3;
4946
+ this.queryRows = options.queryRows ?? readSqliteRowsReadonly;
4947
+ this.tool = {
4948
+ id: TOOL_ID14,
4949
+ name: "Snow App",
4950
+ dataDir: dirname4(this.dbPath)
4951
+ };
4952
+ }
4953
+ isInstalled() {
4954
+ try {
4955
+ return statSync7(this.dbPath).isFile();
4956
+ } catch (error) {
4957
+ if (error.code === "ENOENT") return false;
4958
+ throw error;
4959
+ }
4960
+ }
4961
+ // Intentionally no parse cache: DB mtime alone misses uncheckpointed WAL writes.
4962
+ async parse() {
4963
+ if (!this.isInstalled()) return { buckets: [], sessions: [] };
4964
+ const rows = await this.queryRows(this.dbPath, SNAPSHOT_QUERY);
4965
+ const entries = [];
4966
+ const events = [];
4967
+ const seen = /* @__PURE__ */ new Map();
4968
+ for (const row of rows) {
4969
+ const id = text(row.id);
4970
+ if (!id || row.kind !== "usage" && row.kind !== "message" && row.kind !== "conversation") {
4971
+ throw new Error(
4972
+ "Snow App contains an invalid record identity; scan deferred."
4973
+ );
4974
+ }
4975
+ const key = JSON.stringify([row.kind, id]);
4976
+ const signature = JSON.stringify(row);
4977
+ const previous = seen.get(key);
4978
+ if (previous !== void 0) {
4979
+ if (previous !== signature)
4980
+ throw new Error("Conflicting Snow App record IDs; scan deferred.");
4981
+ continue;
4982
+ }
4983
+ seen.set(key, signature);
4984
+ if (row.kind === "conversation") {
4985
+ if (typeof row.forkedFrom !== "string" || row.forkedFrom !== "" || row.forkMessageCount !== 0) {
4986
+ throw new Error(
4987
+ "Snow App fork metadata is unsupported or invalid; scan deferred."
4988
+ );
4989
+ }
4990
+ continue;
4991
+ }
4992
+ const sessionId = text(row.sessionId);
4993
+ const project = projectName(row.directoryId);
4994
+ if (row.kind === "message") {
4995
+ if (!sessionId || row.role !== "user" && row.role !== "assistant") {
4996
+ throw new Error(
4997
+ "Snow App contains invalid message metadata; scan deferred."
4998
+ );
4999
+ }
5000
+ events.push({
5001
+ sessionId,
5002
+ source: TOOL_ID14,
5003
+ project,
5004
+ role: row.role,
5005
+ timestamp: parseTimestamp4(row.timestamp)
5006
+ });
5007
+ continue;
5008
+ }
5009
+ const input2 = tokenCount4(row.inputTokens);
5010
+ const outputTokens = tokenCount4(row.outputTokens);
5011
+ const cachedTokens = Math.min(input2, tokenCount4(row.cacheReadTokens));
5012
+ const cacheCreationTokens = tokenCount4(row.cacheCreationTokens);
5013
+ if (cacheCreationTokens > input2 - cachedTokens || !Number.isSafeInteger(input2 + outputTokens)) {
5014
+ throw new Error(
5015
+ "Snow App cache counts exceed inclusive input; scan deferred."
5016
+ );
5017
+ }
5018
+ if (input2 + outputTokens === 0) continue;
5019
+ entries.push({
5020
+ sessionId: sessionId || void 0,
5021
+ source: TOOL_ID14,
5022
+ project,
5023
+ model: text(row.model) || "unknown",
5024
+ timestamp: parseTimestamp4(row.timestamp),
5025
+ inputTokens: input2 - cachedTokens - cacheCreationTokens,
5026
+ outputTokens,
5027
+ cachedTokens,
5028
+ cacheCreationTokens,
5029
+ reasoningTokens: 0
5030
+ });
5031
+ }
5032
+ const buckets = aggregateToBuckets(entries);
5033
+ const sessions = extractSessions(events, entries);
5034
+ if ([...buckets, ...sessions].some(
5035
+ (row) => !Number.isSafeInteger(row.totalTokens)
5036
+ ) || sessions.some(
5037
+ (row) => row.durationSeconds > 2147483647 || row.activeSeconds > 2147483647
5038
+ )) {
5039
+ throw new Error(
5040
+ "Snow App aggregate exceeds supported counts or duration; scan deferred."
5041
+ );
5042
+ }
5043
+ return { buckets, sessions };
5044
+ }
5045
+ };
5046
+ registerParser(new SnowAppParser());
5047
+
4552
5048
  // src/parsers/cursor.ts
4553
5049
  import { execFileSync as execFileSync4 } from "child_process";
4554
5050
  import { copyFileSync as copyFileSync2, existsSync as existsSync23, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
4555
- import { homedir as homedir23, tmpdir as tmpdir2 } from "os";
4556
- import { dirname as dirname4, join as join24, resolve as resolve2 } from "path";
4557
- var TOOL_ID14 = "cursor";
5051
+ import { homedir as homedir24, tmpdir as tmpdir2 } from "os";
5052
+ import { dirname as dirname5, join as join25, resolve as resolve2 } from "path";
5053
+ var TOOL_ID15 = "cursor";
4558
5054
  var TOOL_NAME13 = "Cursor";
4559
- var STATE_DB_RELATIVE = join24("User", "globalStorage", "state.vscdb");
5055
+ var STATE_DB_RELATIVE = join25("User", "globalStorage", "state.vscdb");
4560
5056
  var ACCESS_TOKEN_KEY = "cursorAuth/accessToken";
4561
5057
  var SESSION_COOKIE = "WorkosCursorSessionToken";
4562
5058
  var FETCH_TIMEOUT_MS = 1e4;
4563
5059
  function getDefaultStateDbPath() {
4564
5060
  if (process.platform === "darwin") {
4565
- return join24(
4566
- homedir23(),
5061
+ return join25(
5062
+ homedir24(),
4567
5063
  "Library",
4568
5064
  "Application Support",
4569
5065
  "Cursor",
@@ -4571,11 +5067,11 @@ function getDefaultStateDbPath() {
4571
5067
  );
4572
5068
  }
4573
5069
  if (process.platform === "win32") {
4574
- const appData = process.env.APPDATA?.trim() || join24(homedir23(), "AppData", "Roaming");
4575
- return join24(appData, "Cursor", STATE_DB_RELATIVE);
5070
+ const appData = process.env.APPDATA?.trim() || join25(homedir24(), "AppData", "Roaming");
5071
+ return join25(appData, "Cursor", STATE_DB_RELATIVE);
4576
5072
  }
4577
- const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim() || join24(homedir23(), ".config");
4578
- return join24(xdgConfigHome, "Cursor", STATE_DB_RELATIVE);
5073
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim() || join25(homedir24(), ".config");
5074
+ return join25(xdgConfigHome, "Cursor", STATE_DB_RELATIVE);
4579
5075
  }
4580
5076
  function getCursorStateDbPath() {
4581
5077
  const explicit = process.env.CURSOR_STATE_DB_PATH?.trim();
@@ -4586,7 +5082,7 @@ function getCursorStateDbPath() {
4586
5082
  const configDirs = process.env.CURSOR_CONFIG_DIR?.trim();
4587
5083
  const candidates = configDirs ? configDirs.split(",").map((v) => v.trim()).filter(Boolean).map((v) => {
4588
5084
  const r = resolve2(v);
4589
- return r.endsWith(".vscdb") ? r : join24(r, STATE_DB_RELATIVE);
5085
+ return r.endsWith(".vscdb") ? r : join25(r, STATE_DB_RELATIVE);
4590
5086
  }) : [getDefaultStateDbPath()];
4591
5087
  for (const c of candidates) {
4592
5088
  if (existsSync23(c)) return c;
@@ -4629,8 +5125,8 @@ function readAccessToken(dbPath) {
4629
5125
  return queryAccessToken(dbPath);
4630
5126
  } catch (err) {
4631
5127
  if (!isLockError2(err)) throw err;
4632
- const snapshotDir = mkdtempSync2(join24(tmpdir2(), "tokenarena-cursor-"));
4633
- const queryPath = join24(snapshotDir, "state.vscdb");
5128
+ const snapshotDir = mkdtempSync2(join25(tmpdir2(), "tokenarena-cursor-"));
5129
+ const queryPath = join25(snapshotDir, "state.vscdb");
4634
5130
  try {
4635
5131
  copyFileSync2(dbPath, queryPath);
4636
5132
  for (const suffix of ["-shm", "-wal"]) {
@@ -4693,17 +5189,17 @@ async function fetchUsageCsv(token) {
4693
5189
  }
4694
5190
  throw new Error(`Cursor usage export auth failed (${failures.join("; ")})`);
4695
5191
  }
4696
- function parseCsv(text) {
5192
+ function parseCsv(text2) {
4697
5193
  const rows = [];
4698
5194
  let field = "";
4699
5195
  let row = [];
4700
5196
  let inQuotes = false;
4701
5197
  let i = 0;
4702
- while (i < text.length) {
4703
- const c = text[i];
5198
+ while (i < text2.length) {
5199
+ const c = text2[i];
4704
5200
  if (inQuotes) {
4705
5201
  if (c === '"') {
4706
- if (text[i + 1] === '"') {
5202
+ if (text2[i + 1] === '"') {
4707
5203
  field += '"';
4708
5204
  i += 2;
4709
5205
  continue;
@@ -4763,9 +5259,9 @@ function parseInt0(value) {
4763
5259
  }
4764
5260
  function createToolDefinition9(dbPath) {
4765
5261
  return {
4766
- id: TOOL_ID14,
5262
+ id: TOOL_ID15,
4767
5263
  name: TOOL_NAME13,
4768
- dataDir: dirname4(dbPath)
5264
+ dataDir: dirname5(dbPath)
4769
5265
  };
4770
5266
  }
4771
5267
  var CursorParser = class {
@@ -4827,7 +5323,7 @@ var CursorParser = class {
4827
5323
  const output = outputIdx >= 0 ? parseInt0(row[outputIdx]) : 0;
4828
5324
  if (inputCacheWrite + inputNoCache + cacheRead + output === 0) continue;
4829
5325
  entries.push({
4830
- source: TOOL_ID14,
5326
+ source: TOOL_ID15,
4831
5327
  model,
4832
5328
  project: "unknown",
4833
5329
  timestamp,
@@ -4852,11 +5348,11 @@ registerParser(new CursorParser());
4852
5348
  // src/parsers/zcode.ts
4853
5349
  import { createHash as createHash5 } from "crypto";
4854
5350
  import { existsSync as existsSync24 } from "fs";
4855
- import { homedir as homedir24, hostname as hostname5 } from "os";
4856
- import { dirname as dirname5, join as join25 } from "path";
4857
- var TOOL_ID15 = "zcode";
5351
+ import { homedir as homedir25, hostname as hostname5 } from "os";
5352
+ import { dirname as dirname6, join as join26 } from "path";
5353
+ var TOOL_ID16 = "zcode";
4858
5354
  var TOOL_NAME14 = "ZCode";
4859
- var DEFAULT_DB_PATH3 = join25(homedir24(), ".zcode", "cli", "db", "db.sqlite");
5355
+ var DEFAULT_DB_PATH4 = join26(homedir25(), ".zcode", "cli", "db", "db.sqlite");
4860
5356
  var MODEL_USAGE_QUERY = `SELECT
4861
5357
  model_usage.session_id as sessionId,
4862
5358
  session.directory as directory,
@@ -4865,13 +5361,15 @@ var MODEL_USAGE_QUERY = `SELECT
4865
5361
  model_usage.input_tokens as inputTokens,
4866
5362
  model_usage.output_tokens as outputTokens,
4867
5363
  model_usage.reasoning_tokens as reasoningTokens,
4868
- model_usage.cache_read_input_tokens as cacheReadInputTokens
5364
+ model_usage.cache_read_input_tokens as cacheReadInputTokens,
5365
+ model_usage.cache_creation_input_tokens as cacheCreationInputTokens
4869
5366
  FROM model_usage
4870
5367
  LEFT JOIN session ON session.id = model_usage.session_id
4871
5368
  WHERE model_usage.input_tokens > 0
4872
5369
  OR model_usage.output_tokens > 0
4873
5370
  OR model_usage.reasoning_tokens > 0
4874
- OR model_usage.cache_read_input_tokens > 0`;
5371
+ OR model_usage.cache_read_input_tokens > 0
5372
+ OR model_usage.cache_creation_input_tokens > 0`;
4875
5373
  var SESSION_QUERY = `SELECT
4876
5374
  id,
4877
5375
  directory,
@@ -4891,9 +5389,9 @@ var TURN_USAGE_QUERY = `SELECT
4891
5389
  WHERE duration_ms IS NOT NULL`;
4892
5390
  function createToolDefinition10(dbPath) {
4893
5391
  return {
4894
- id: TOOL_ID15,
5392
+ id: TOOL_ID16,
4895
5393
  name: TOOL_NAME14,
4896
- dataDir: dirname5(dbPath)
5394
+ dataDir: dirname6(dbPath)
4897
5395
  };
4898
5396
  }
4899
5397
  function toSafeNumber10(value) {
@@ -4940,7 +5438,7 @@ function getOrCreateDraft(drafts, sessionId, project) {
4940
5438
  }
4941
5439
  const next = {
4942
5440
  sessionId,
4943
- source: TOOL_ID15,
5441
+ source: TOOL_ID16,
4944
5442
  project,
4945
5443
  firstMessageAt: null,
4946
5444
  lastMessageAt: null,
@@ -4965,13 +5463,15 @@ function buildSessionUsage3(entries) {
4965
5463
  byModel = /* @__PURE__ */ new Map();
4966
5464
  usageBySession.set(entry.sessionId, byModel);
4967
5465
  }
4968
- const totalTokens = entry.inputTokens + entry.outputTokens + entry.reasoningTokens + entry.cachedTokens;
5466
+ const cacheCreationTokens = entry.cacheCreationTokens ?? 0;
5467
+ const totalTokens = entry.inputTokens + entry.outputTokens + entry.reasoningTokens + entry.cachedTokens + cacheCreationTokens;
4969
5468
  const existing = byModel.get(entry.model);
4970
5469
  if (existing) {
4971
5470
  existing.inputTokens += entry.inputTokens;
4972
5471
  existing.outputTokens += entry.outputTokens;
4973
5472
  existing.reasoningTokens += entry.reasoningTokens;
4974
5473
  existing.cachedTokens += entry.cachedTokens;
5474
+ existing.cacheCreationTokens = (existing.cacheCreationTokens ?? 0) + cacheCreationTokens;
4975
5475
  existing.totalTokens += totalTokens;
4976
5476
  continue;
4977
5477
  }
@@ -4981,6 +5481,7 @@ function buildSessionUsage3(entries) {
4981
5481
  outputTokens: entry.outputTokens,
4982
5482
  reasoningTokens: entry.reasoningTokens,
4983
5483
  cachedTokens: entry.cachedTokens,
5484
+ cacheCreationTokens,
4984
5485
  totalTokens
4985
5486
  });
4986
5487
  }
@@ -5068,6 +5569,10 @@ function buildSessions2(input2) {
5068
5569
  (sum, usage) => sum + usage.cachedTokens,
5069
5570
  0
5070
5571
  );
5572
+ const cacheCreationTokens = modelUsages.reduce(
5573
+ (sum, usage) => sum + (usage.cacheCreationTokens ?? 0),
5574
+ 0
5575
+ );
5071
5576
  const totalTokens = modelUsages.reduce(
5072
5577
  (sum, usage) => sum + usage.totalTokens,
5073
5578
  0
@@ -5094,6 +5599,7 @@ function buildSessions2(input2) {
5094
5599
  outputTokens,
5095
5600
  reasoningTokens,
5096
5601
  cachedTokens,
5602
+ cacheCreationTokens,
5097
5603
  totalTokens,
5098
5604
  primaryModel: modelUsages[0]?.model ?? "",
5099
5605
  modelUsages
@@ -5105,7 +5611,7 @@ var ZCodeParser = class {
5105
5611
  dbPath;
5106
5612
  queryRows;
5107
5613
  constructor(options = {}) {
5108
- this.dbPath = options.dbPath || DEFAULT_DB_PATH3;
5614
+ this.dbPath = options.dbPath || DEFAULT_DB_PATH4;
5109
5615
  this.queryRows = options.queryRows || readSqliteRows;
5110
5616
  this.tool = createToolDefinition10(this.dbPath);
5111
5617
  }
@@ -5123,23 +5629,31 @@ var ZCodeParser = class {
5123
5629
  if (!timestamp) {
5124
5630
  continue;
5125
5631
  }
5126
- const inputTokens = toSafeNumber10(row.inputTokens);
5127
- const outputTokens = toSafeNumber10(row.outputTokens);
5128
- const reasoningTokens = toSafeNumber10(row.reasoningTokens);
5129
5632
  const cachedTokens = toSafeNumber10(row.cacheReadInputTokens);
5130
- if (inputTokens === 0 && outputTokens === 0 && reasoningTokens === 0 && cachedTokens === 0) {
5633
+ const cacheCreationTokens = toSafeNumber10(row.cacheCreationInputTokens);
5634
+ const inputTokens = Math.max(
5635
+ 0,
5636
+ toSafeNumber10(row.inputTokens) - cachedTokens - cacheCreationTokens
5637
+ );
5638
+ const reasoningTokens = toSafeNumber10(row.reasoningTokens);
5639
+ const outputTokens = Math.max(
5640
+ 0,
5641
+ toSafeNumber10(row.outputTokens) - reasoningTokens
5642
+ );
5643
+ if (inputTokens + outputTokens + reasoningTokens + cachedTokens + cacheCreationTokens === 0) {
5131
5644
  continue;
5132
5645
  }
5133
5646
  entries.push({
5134
5647
  sessionId: getString3(row.sessionId) ?? void 0,
5135
- source: TOOL_ID15,
5648
+ source: TOOL_ID16,
5136
5649
  model: getString3(row.model) ?? "unknown",
5137
5650
  project: getPathLeaf7(getString3(row.directory)),
5138
5651
  timestamp,
5139
5652
  inputTokens,
5140
5653
  outputTokens,
5141
5654
  reasoningTokens,
5142
- cachedTokens
5655
+ cachedTokens,
5656
+ cacheCreationTokens
5143
5657
  });
5144
5658
  }
5145
5659
  let sessionRows = [];
@@ -5174,20 +5688,20 @@ var ZCodeParser = class {
5174
5688
  registerParser(new ZCodeParser());
5175
5689
 
5176
5690
  // src/parsers/qodercli.ts
5177
- import { existsSync as existsSync25, readdirSync as readdirSync12 } from "fs";
5178
- import { homedir as homedir25 } from "os";
5179
- import { basename as basename10, join as join26 } from "path";
5180
- var TOOL_ID16 = "qodercli";
5691
+ import { existsSync as existsSync25, readdirSync as readdirSync13 } from "fs";
5692
+ import { homedir as homedir26 } from "os";
5693
+ import { basename as basename11, join as join27 } from "path";
5694
+ var TOOL_ID17 = "qodercli";
5181
5695
  var TOOL_NAME15 = "Qoder CLI";
5182
- var DEFAULT_PROJECTS_DIR = join26(homedir25(), ".qoder", "projects");
5183
- var DEFAULT_LOGS_DIR = join26(homedir25(), ".qoder", "logs", "sessions");
5184
- var DEFAULT_RUNS_DIR = join26(homedir25(), ".qoder", "logs", "runs");
5696
+ var DEFAULT_PROJECTS_DIR = join27(homedir26(), ".qoder", "projects");
5697
+ var DEFAULT_LOGS_DIR = join27(homedir26(), ".qoder", "logs", "sessions");
5698
+ var DEFAULT_RUNS_DIR = join27(homedir26(), ".qoder", "logs", "runs");
5185
5699
  var CLI_ENTRYPOINT = "cli";
5186
5700
  var IDE_ENTRYPOINT = "acp";
5187
5701
  var CREDIT_MODEL_FALLBACK = "credits";
5188
5702
  function createToolDefinition11(projectsDir) {
5189
5703
  return {
5190
- id: TOOL_ID16,
5704
+ id: TOOL_ID17,
5191
5705
  name: TOOL_NAME15,
5192
5706
  dataDir: projectsDir
5193
5707
  };
@@ -5227,11 +5741,11 @@ function classifyQoderEntrypoint(content) {
5227
5741
  return "unknown";
5228
5742
  }
5229
5743
  function isQodercliBinaryPresent() {
5230
- const home = homedir25();
5744
+ const home = homedir26();
5231
5745
  const candidates = [
5232
- join26(home, ".local", "bin", "qodercli"),
5233
- join26(home, ".qoder", "bin", "qodercli"),
5234
- join26(home, ".qoder-cli")
5746
+ join27(home, ".local", "bin", "qodercli"),
5747
+ join27(home, ".qoder", "bin", "qodercli"),
5748
+ join27(home, ".qoder-cli")
5235
5749
  ];
5236
5750
  return candidates.some((path) => existsSync25(path));
5237
5751
  }
@@ -5241,7 +5755,7 @@ function projectFromEncodedSlug(slug) {
5241
5755
  }
5242
5756
  function projectFromCwd(cwd) {
5243
5757
  if (!cwd) return "unknown";
5244
- const leaf = basename10(cwd.replace(/[\\/]+$/, ""));
5758
+ const leaf = basename11(cwd.replace(/[\\/]+$/, ""));
5245
5759
  return leaf || "unknown";
5246
5760
  }
5247
5761
  function totalCreditsUsed(quota) {
@@ -5304,7 +5818,7 @@ function creditSnapshotsToEntries(snapshots) {
5304
5818
  if (creditDelta > 0) {
5305
5819
  entries.push({
5306
5820
  sessionId: snap.sessionId,
5307
- source: TOOL_ID16,
5821
+ source: TOOL_ID17,
5308
5822
  model: snap.model || CREDIT_MODEL_FALLBACK,
5309
5823
  project: snap.project,
5310
5824
  timestamp: snap.timestamp,
@@ -5344,7 +5858,7 @@ var QoderCliParser = class {
5344
5858
  parseProjectSessions(sessionEvents) {
5345
5859
  if (!existsSync25(this.projectsDir)) return;
5346
5860
  for (const filePath of findJsonlFiles(this.projectsDir)) {
5347
- if (basename10(filePath).startsWith("verified-")) continue;
5861
+ if (basename11(filePath).startsWith("verified-")) continue;
5348
5862
  const content = readFileSafe(filePath);
5349
5863
  if (!content) continue;
5350
5864
  if (classifyQoderEntrypoint(content) !== CLI_ENTRYPOINT) continue;
@@ -5364,7 +5878,7 @@ var QoderCliParser = class {
5364
5878
  if (Number.isNaN(ts.getTime())) continue;
5365
5879
  sessionEvents.push({
5366
5880
  sessionId,
5367
- source: TOOL_ID16,
5881
+ source: TOOL_ID17,
5368
5882
  project,
5369
5883
  timestamp: ts,
5370
5884
  role: obj.type === "user" ? "user" : "assistant"
@@ -5382,7 +5896,7 @@ var QoderCliParser = class {
5382
5896
  if (!existsSync25(this.runsDir)) return [];
5383
5897
  let runDirs;
5384
5898
  try {
5385
- runDirs = readdirSync12(this.runsDir, { withFileTypes: true }).filter(
5899
+ runDirs = readdirSync13(this.runsDir, { withFileTypes: true }).filter(
5386
5900
  (d) => d.isDirectory()
5387
5901
  );
5388
5902
  } catch {
@@ -5390,15 +5904,15 @@ var QoderCliParser = class {
5390
5904
  }
5391
5905
  const allSnapshots = [];
5392
5906
  for (const dir of runDirs) {
5393
- const runPath = join26(this.runsDir, dir.name);
5394
- const logPath = join26(runPath, "qodercli.log");
5907
+ const runPath = join27(this.runsDir, dir.name);
5908
+ const logPath = join27(runPath, "qodercli.log");
5395
5909
  if (!existsSync25(logPath)) continue;
5396
5910
  const logContent = readFileSafe(logPath);
5397
5911
  if (!logContent?.includes("quota/usage response:")) {
5398
5912
  continue;
5399
5913
  }
5400
5914
  const manifest = parseRunManifest(
5401
- readFileSafe(join26(runPath, "manifest.json"))
5915
+ readFileSafe(join27(runPath, "manifest.json"))
5402
5916
  );
5403
5917
  const project = manifest?.project_id ? projectFromEncodedSlug(manifest.project_id) : projectFromCwd(manifest?.cwd);
5404
5918
  const snapshots = extractCreditSnapshotsFromLog(logContent, {
@@ -5416,16 +5930,16 @@ var QoderCliParser = class {
5416
5930
  registerParser(new QoderCliParser());
5417
5931
 
5418
5932
  // src/parsers/grok-build.ts
5419
- import { existsSync as existsSync26, readdirSync as readdirSync13 } from "fs";
5420
- import { homedir as homedir26 } from "os";
5421
- import { basename as basename11, join as join27 } from "path";
5422
- var TOOL_ID17 = "grok-build";
5933
+ import { existsSync as existsSync26, readdirSync as readdirSync14 } from "fs";
5934
+ import { homedir as homedir27 } from "os";
5935
+ import { basename as basename12, join as join28 } from "path";
5936
+ var TOOL_ID18 = "grok-build";
5423
5937
  var TOOL_NAME16 = "Grok Build";
5424
- var DEFAULT_DATA_DIR7 = join27(homedir26(), ".grok", "sessions");
5425
- var DEFAULT_ARCHIVE_DIR2 = join27(homedir26(), ".grok", "archived_sessions");
5938
+ var DEFAULT_DATA_DIR6 = join28(homedir27(), ".grok", "sessions");
5939
+ var DEFAULT_ARCHIVE_DIR2 = join28(homedir27(), ".grok", "archived_sessions");
5426
5940
  function createToolDefinition12(dataDir) {
5427
5941
  return {
5428
- id: TOOL_ID17,
5942
+ id: TOOL_ID18,
5429
5943
  name: TOOL_NAME16,
5430
5944
  dataDir
5431
5945
  };
@@ -5437,10 +5951,10 @@ function toSafeNumber12(value) {
5437
5951
  function projectFromEncodedCwd(encoded) {
5438
5952
  try {
5439
5953
  const decoded = decodeURIComponent(encoded);
5440
- const leaf = basename11(decoded.replace(/[\\/]+$/, ""));
5954
+ const leaf = basename12(decoded.replace(/[\\/]+$/, ""));
5441
5955
  return leaf || "unknown";
5442
5956
  } catch {
5443
- const leaf = basename11(encoded);
5957
+ const leaf = basename12(encoded);
5444
5958
  return leaf || "unknown";
5445
5959
  }
5446
5960
  }
@@ -5477,7 +5991,7 @@ function pushUsageEntries(entries, args) {
5477
5991
  }
5478
5992
  modelEntries.push({
5479
5993
  sessionId,
5480
- source: TOOL_ID17,
5994
+ source: TOOL_ID18,
5481
5995
  model: model || fallbackModel || "unknown",
5482
5996
  project,
5483
5997
  timestamp,
@@ -5494,25 +6008,25 @@ function findSessionDirs(dataDir) {
5494
6008
  if (!existsSync26(dataDir)) return results;
5495
6009
  let projectDirs;
5496
6010
  try {
5497
- projectDirs = readdirSync13(dataDir, { withFileTypes: true });
6011
+ projectDirs = readdirSync14(dataDir, { withFileTypes: true });
5498
6012
  } catch {
5499
6013
  return results;
5500
6014
  }
5501
6015
  for (const projectEntry of projectDirs) {
5502
6016
  if (!projectEntry.isDirectory()) continue;
5503
6017
  if (projectEntry.name.endsWith(".sqlite")) continue;
5504
- const projectDir = join27(dataDir, projectEntry.name);
6018
+ const projectDir = join28(dataDir, projectEntry.name);
5505
6019
  const project = projectFromEncodedCwd(projectEntry.name);
5506
6020
  let sessionEntries;
5507
6021
  try {
5508
- sessionEntries = readdirSync13(projectDir, { withFileTypes: true });
6022
+ sessionEntries = readdirSync14(projectDir, { withFileTypes: true });
5509
6023
  } catch {
5510
6024
  continue;
5511
6025
  }
5512
6026
  for (const sessionEntry of sessionEntries) {
5513
6027
  if (!sessionEntry.isDirectory()) continue;
5514
- const sessionDir = join27(projectDir, sessionEntry.name);
5515
- if (!existsSync26(join27(sessionDir, "updates.jsonl"))) continue;
6028
+ const sessionDir = join28(projectDir, sessionEntry.name);
6029
+ if (!existsSync26(join28(sessionDir, "updates.jsonl"))) continue;
5516
6030
  results.push({
5517
6031
  sessionDir,
5518
6032
  project,
@@ -5523,7 +6037,7 @@ function findSessionDirs(dataDir) {
5523
6037
  return results;
5524
6038
  }
5525
6039
  function readFallbackModel(sessionDir) {
5526
- const summaryPath = join27(sessionDir, "summary.json");
6040
+ const summaryPath = join28(sessionDir, "summary.json");
5527
6041
  const content = readFileSafe(summaryPath);
5528
6042
  if (!content) return "unknown";
5529
6043
  try {
@@ -5534,7 +6048,7 @@ function readFallbackModel(sessionDir) {
5534
6048
  }
5535
6049
  }
5536
6050
  var GrokBuildParser = class {
5537
- constructor(dataDir = DEFAULT_DATA_DIR7, archiveDir = dataDir === DEFAULT_DATA_DIR7 ? DEFAULT_ARCHIVE_DIR2 : void 0) {
6051
+ constructor(dataDir = DEFAULT_DATA_DIR6, archiveDir = dataDir === DEFAULT_DATA_DIR6 ? DEFAULT_ARCHIVE_DIR2 : void 0) {
5538
6052
  this.dataDir = dataDir;
5539
6053
  this.archiveDir = archiveDir;
5540
6054
  this.tool = createToolDefinition12(dataDir);
@@ -5550,7 +6064,7 @@ var GrokBuildParser = class {
5550
6064
  ...findSessionDirs(this.dataDir)
5551
6065
  ];
5552
6066
  for (const { sessionDir, project, sessionId } of sessions) {
5553
- const updatesPath = join27(sessionDir, "updates.jsonl");
6067
+ const updatesPath = join28(sessionDir, "updates.jsonl");
5554
6068
  const content = readFileSafe(updatesPath);
5555
6069
  if (!content) continue;
5556
6070
  const fallbackModel = readFallbackModel(sessionDir);
@@ -5587,7 +6101,7 @@ var GrokBuildParser = class {
5587
6101
  ]),
5588
6102
  {
5589
6103
  sessionId: sid,
5590
- source: TOOL_ID17,
6104
+ source: TOOL_ID18,
5591
6105
  project,
5592
6106
  timestamp: ts,
5593
6107
  role: "user"
@@ -5603,7 +6117,7 @@ var GrokBuildParser = class {
5603
6117
  continue;
5604
6118
  sessionEvents.set(eventKey, {
5605
6119
  sessionId: sid,
5606
- source: TOOL_ID17,
6120
+ source: TOOL_ID18,
5607
6121
  project,
5608
6122
  timestamp: ts,
5609
6123
  role: "assistant"
@@ -5627,22 +6141,22 @@ var GrokBuildParser = class {
5627
6141
  };
5628
6142
  }
5629
6143
  isInstalled() {
5630
- return existsSync26(this.dataDir) || Boolean(this.archiveDir && existsSync26(this.archiveDir)) || existsSync26(join27(homedir26(), ".grok")) || existsSync26(join27(homedir26(), ".local", "bin", "grok"));
6144
+ return existsSync26(this.dataDir) || Boolean(this.archiveDir && existsSync26(this.archiveDir)) || existsSync26(join28(homedir27(), ".grok")) || existsSync26(join28(homedir27(), ".local", "bin", "grok"));
5631
6145
  }
5632
6146
  };
5633
6147
  registerParser(new GrokBuildParser());
5634
6148
 
5635
6149
  // src/parsers/atomcode.ts
5636
6150
  import { existsSync as existsSync27 } from "fs";
5637
- import { homedir as homedir27 } from "os";
5638
- import { basename as basename12, join as join28 } from "path";
5639
- var TOOL_ID18 = "atomcode";
6151
+ import { homedir as homedir28 } from "os";
6152
+ import { basename as basename13, join as join29 } from "path";
6153
+ var TOOL_ID19 = "atomcode";
5640
6154
  var TOOL_NAME17 = "AtomCode";
5641
- var DEFAULT_SESSIONS_DIR6 = join28(homedir27(), ".atomcode", "sessions");
6155
+ var DEFAULT_SESSIONS_DIR6 = join29(homedir28(), ".atomcode", "sessions");
5642
6156
  function getAtomCodeSessionsDirs(env = process.env) {
5643
6157
  const dirs = [
5644
6158
  env.TOKEN_ARENA_ATOMCODE_DIR,
5645
- env.ATOMCODE_HOME ? join28(env.ATOMCODE_HOME, "sessions") : void 0,
6159
+ env.ATOMCODE_HOME ? join29(env.ATOMCODE_HOME, "sessions") : void 0,
5646
6160
  DEFAULT_SESSIONS_DIR6
5647
6161
  ].filter((value) => Boolean(value));
5648
6162
  return Array.from(new Set(dirs));
@@ -5651,7 +6165,7 @@ function toNonNegativeNumber3(value) {
5651
6165
  const numberValue = Number(value);
5652
6166
  return Number.isFinite(numberValue) && numberValue >= 0 ? numberValue : 0;
5653
6167
  }
5654
- function parseTimestamp4(value) {
6168
+ function parseTimestamp5(value) {
5655
6169
  if (typeof value === "number" && Number.isFinite(value)) {
5656
6170
  const timestamp = new Date(value);
5657
6171
  return Number.isNaN(timestamp.getTime()) ? null : timestamp;
@@ -5690,7 +6204,7 @@ function getMetaModel(meta) {
5690
6204
  }
5691
6205
  function getMetaProject(meta) {
5692
6206
  if (typeof meta?.working_dir === "string" && meta.working_dir) {
5693
- return basename12(meta.working_dir) || "unknown";
6207
+ return basename13(meta.working_dir) || "unknown";
5694
6208
  }
5695
6209
  return "unknown";
5696
6210
  }
@@ -5700,7 +6214,7 @@ var AtomCodeParser = class {
5700
6214
  constructor(sessionsDir) {
5701
6215
  this.sessionsDirs = sessionsDir ? [sessionsDir] : getAtomCodeSessionsDirs();
5702
6216
  this.tool = {
5703
- id: TOOL_ID18,
6217
+ id: TOOL_ID19,
5704
6218
  name: TOOL_NAME17,
5705
6219
  dataDir: this.sessionsDirs[0] ?? DEFAULT_SESSIONS_DIR6
5706
6220
  };
@@ -5723,12 +6237,12 @@ var AtomCodeParser = class {
5723
6237
  const model = getMetaModel(meta);
5724
6238
  for (const row of rows) {
5725
6239
  const sessionId = typeof row.session_id === "string" && row.session_id ? row.session_id : fallbackSessionId;
5726
- const timestamp = parseTimestamp4(row.ts);
6240
+ const timestamp = parseTimestamp5(row.ts);
5727
6241
  if (!timestamp) continue;
5728
6242
  if (row.user !== void 0 || row.assistant !== void 0) {
5729
6243
  sessionEvents.push({
5730
6244
  sessionId,
5731
- source: TOOL_ID18,
6245
+ source: TOOL_ID19,
5732
6246
  project,
5733
6247
  timestamp,
5734
6248
  role: row.user !== void 0 ? "user" : "assistant"
@@ -5757,7 +6271,7 @@ var AtomCodeParser = class {
5757
6271
  seenEntryKeys.add(entryKey);
5758
6272
  entries.push({
5759
6273
  sessionId,
5760
- source: TOOL_ID18,
6274
+ source: TOOL_ID19,
5761
6275
  model,
5762
6276
  project,
5763
6277
  timestamp,
@@ -5781,18 +6295,18 @@ var AtomCodeParser = class {
5781
6295
  registerParser(new AtomCodeParser());
5782
6296
 
5783
6297
  // src/parsers/dsh.ts
5784
- import { existsSync as existsSync28, readdirSync as readdirSync14, readFileSync as readFileSync9 } from "fs";
5785
- import { homedir as homedir28 } from "os";
5786
- import { basename as basename13, join as join29 } from "path";
6298
+ import { existsSync as existsSync28, readdirSync as readdirSync15, readFileSync as readFileSync9 } from "fs";
6299
+ import { homedir as homedir29 } from "os";
6300
+ import { basename as basename14, join as join30 } from "path";
5787
6301
  import * as zlib from "zlib";
5788
- var TOOL_ID19 = "dsh";
6302
+ var TOOL_ID20 = "dsh";
5789
6303
  var TOOL_NAME18 = "DeepSeek Harness";
5790
- var DEFAULT_SESSIONS_DIR7 = join29(homedir28(), ".dsh", "sessions");
6304
+ var DEFAULT_SESSIONS_DIR7 = join30(homedir29(), ".dsh", "sessions");
5791
6305
  var LOG_BASENAME = "session";
5792
6306
  function getDshSessionsDirs(env = process.env) {
5793
6307
  const dirs = [
5794
6308
  env.TOKEN_ARENA_DSH_DIR,
5795
- env.DSH_HOME ? join29(env.DSH_HOME, "sessions") : void 0,
6309
+ env.DSH_HOME ? join30(env.DSH_HOME, "sessions") : void 0,
5796
6310
  DEFAULT_SESSIONS_DIR7
5797
6311
  ].filter((value) => Boolean(value));
5798
6312
  return Array.from(new Set(dirs));
@@ -5883,8 +6397,8 @@ function findSessionLogs(dir) {
5883
6397
  const results = [];
5884
6398
  if (!existsSync28(dir)) return results;
5885
6399
  try {
5886
- for (const entry of readdirSync14(dir, { withFileTypes: true })) {
5887
- const fullPath = join29(dir, entry.name);
6400
+ for (const entry of readdirSync15(dir, { withFileTypes: true })) {
6401
+ const fullPath = join30(dir, entry.name);
5888
6402
  if (entry.isDirectory()) {
5889
6403
  results.push(...findSessionLogs(fullPath));
5890
6404
  } else if (entry.name === `${LOG_BASENAME}.jsonl` || entry.name === `${LOG_BASENAME}.jsonl.zstd`) {
@@ -5915,7 +6429,7 @@ var DshParser = class {
5915
6429
  constructor(sessionsDir) {
5916
6430
  this.sessionsDirs = sessionsDir ? [sessionsDir] : getDshSessionsDirs();
5917
6431
  this.tool = {
5918
- id: TOOL_ID19,
6432
+ id: TOOL_ID20,
5919
6433
  name: TOOL_NAME18,
5920
6434
  dataDir: this.sessionsDirs[0] ?? DEFAULT_SESSIONS_DIR7
5921
6435
  };
@@ -5933,7 +6447,7 @@ var DshParser = class {
5933
6447
  const relativeParts = filePath.slice(sessionsDir.length + 1).split(/[\\/]/);
5934
6448
  const header = rows[0]?.type === "session" ? rows[0] : void 0;
5935
6449
  const sessionId = (header?.id ?? decodeSegment(relativeParts[1] ?? "")) || "unknown";
5936
- const project = typeof header?.cwd === "string" && header.cwd ? basename13(header.cwd) || "unknown" : projectFromDirName(relativeParts[0] ?? "");
6450
+ const project = typeof header?.cwd === "string" && header.cwd ? basename14(header.cwd) || "unknown" : projectFromDirName(relativeParts[0] ?? "");
5937
6451
  let currentModel = "unknown";
5938
6452
  for (const row of rows) {
5939
6453
  if (row.type === "request/context") {
@@ -5953,7 +6467,7 @@ var DshParser = class {
5953
6467
  if (timestamp) {
5954
6468
  sessionEvents.push({
5955
6469
  sessionId,
5956
- source: TOOL_ID19,
6470
+ source: TOOL_ID20,
5957
6471
  project,
5958
6472
  timestamp,
5959
6473
  role: "user"
@@ -5966,7 +6480,7 @@ var DshParser = class {
5966
6480
  if (timestamp && !isCompaction) {
5967
6481
  sessionEvents.push({
5968
6482
  sessionId,
5969
- source: TOOL_ID19,
6483
+ source: TOOL_ID20,
5970
6484
  project,
5971
6485
  timestamp,
5972
6486
  role: "assistant"
@@ -6001,7 +6515,7 @@ var DshParser = class {
6001
6515
  seenEntryKeys.add(entryKey);
6002
6516
  entries.push({
6003
6517
  sessionId,
6004
- source: TOOL_ID19,
6518
+ source: TOOL_ID20,
6005
6519
  model,
6006
6520
  project,
6007
6521
  timestamp,
@@ -6027,11 +6541,11 @@ registerParser(new DshParser());
6027
6541
 
6028
6542
  // src/parsers/cherry-studio.ts
6029
6543
  import { existsSync as existsSync29 } from "fs";
6030
- import { homedir as homedir29 } from "os";
6031
- import { dirname as dirname6, join as join30, resolve as resolve3 } from "path";
6032
- var TOOL_ID20 = "cherry-studio";
6544
+ import { homedir as homedir30 } from "os";
6545
+ import { dirname as dirname7, join as join31, resolve as resolve3 } from "path";
6546
+ var TOOL_ID21 = "cherry-studio";
6033
6547
  var TOOL_NAME19 = "Cherry Studio";
6034
- var DB_RELATIVE = join30("Data", "cherrystudio.sqlite");
6548
+ var DB_RELATIVE = join31("Data", "cherrystudio.sqlite");
6035
6549
  var USAGE_QUERY = `SELECT
6036
6550
  u.model_id as modelId,
6037
6551
  u.no_cache_tokens as noCacheTokens,
@@ -6054,14 +6568,14 @@ var MESSAGES_QUERY3 = `SELECT
6054
6568
  ORDER BY created_at`;
6055
6569
  function getDefaultUserDataDir(env = process.env) {
6056
6570
  if (process.platform === "darwin") {
6057
- return join30(homedir29(), "Library", "Application Support", "CherryStudio");
6571
+ return join31(homedir30(), "Library", "Application Support", "CherryStudio");
6058
6572
  }
6059
6573
  if (process.platform === "win32") {
6060
- const appData = env.APPDATA?.trim() || join30(homedir29(), "AppData", "Roaming");
6061
- return join30(appData, "CherryStudio");
6574
+ const appData = env.APPDATA?.trim() || join31(homedir30(), "AppData", "Roaming");
6575
+ return join31(appData, "CherryStudio");
6062
6576
  }
6063
- const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || join30(homedir29(), ".config");
6064
- return join30(xdgConfigHome, "CherryStudio");
6577
+ const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || join31(homedir30(), ".config");
6578
+ return join31(xdgConfigHome, "CherryStudio");
6065
6579
  }
6066
6580
  function getCherryStudioDbPaths(env = process.env) {
6067
6581
  const paths = [];
@@ -6069,10 +6583,10 @@ function getCherryStudioDbPaths(env = process.env) {
6069
6583
  if (explicit) {
6070
6584
  const resolved = resolve3(explicit);
6071
6585
  paths.push(
6072
- resolved.endsWith(".sqlite") ? resolved : join30(resolved, DB_RELATIVE)
6586
+ resolved.endsWith(".sqlite") ? resolved : join31(resolved, DB_RELATIVE)
6073
6587
  );
6074
6588
  }
6075
- paths.push(join30(getDefaultUserDataDir(env), DB_RELATIVE));
6589
+ paths.push(join31(getDefaultUserDataDir(env), DB_RELATIVE));
6076
6590
  return Array.from(new Set(paths));
6077
6591
  }
6078
6592
  function resolveCherryStudioDbPath() {
@@ -6081,9 +6595,9 @@ function resolveCherryStudioDbPath() {
6081
6595
  }
6082
6596
  function createToolDefinition13(dbPath) {
6083
6597
  return {
6084
- id: TOOL_ID20,
6598
+ id: TOOL_ID21,
6085
6599
  name: TOOL_NAME19,
6086
- dataDir: dbPath ? dirname6(dbPath) : join30(getDefaultUserDataDir(), "Data")
6600
+ dataDir: dbPath ? dirname7(dbPath) : join31(getDefaultUserDataDir(), "Data")
6087
6601
  };
6088
6602
  }
6089
6603
  function toSafeCount(value) {
@@ -6148,7 +6662,7 @@ var CherryStudioParser = class {
6148
6662
  // Rows the topic join could not resolve still count toward buckets,
6149
6663
  // they just cannot take part in session timing.
6150
6664
  sessionId: getNonEmptyString(row.sessionId) ?? void 0,
6151
- source: TOOL_ID20,
6665
+ source: TOOL_ID21,
6152
6666
  model: getNonEmptyString(row.modelId) ?? "unknown",
6153
6667
  project: "unknown",
6154
6668
  timestamp,
@@ -6181,7 +6695,7 @@ var CherryStudioParser = class {
6181
6695
  if (!timestamp) continue;
6182
6696
  sessionEvents.push({
6183
6697
  sessionId,
6184
- source: TOOL_ID20,
6698
+ source: TOOL_ID21,
6185
6699
  project: "unknown",
6186
6700
  timestamp,
6187
6701
  role
@@ -6210,25 +6724,25 @@ import {
6210
6724
  unlinkSync,
6211
6725
  writeFileSync
6212
6726
  } from "fs";
6213
- import { join as join32 } from "path";
6727
+ import { join as join33 } from "path";
6214
6728
 
6215
6729
  // src/infrastructure/xdg.ts
6216
- import { homedir as homedir30 } from "os";
6217
- import { join as join31 } from "path";
6730
+ import { homedir as homedir31 } from "os";
6731
+ import { join as join32 } from "path";
6218
6732
  function getConfigHome() {
6219
- return process.env.XDG_CONFIG_HOME || join31(homedir30(), ".config");
6733
+ return process.env.XDG_CONFIG_HOME || join32(homedir31(), ".config");
6220
6734
  }
6221
6735
  function getStateHome() {
6222
- return process.env.XDG_STATE_HOME || join31(homedir30(), ".local", "state");
6736
+ return process.env.XDG_STATE_HOME || join32(homedir31(), ".local", "state");
6223
6737
  }
6224
6738
  function getRuntimeDir() {
6225
6739
  return process.env.XDG_RUNTIME_DIR || getStateHome();
6226
6740
  }
6227
6741
 
6228
6742
  // src/infrastructure/config/manager.ts
6229
- var CONFIG_DIR = join32(getConfigHome(), "tokenarena");
6743
+ var CONFIG_DIR = join33(getConfigHome(), "tokenarena");
6230
6744
  var isDev = process.env.TOKEN_ARENA_DEV === "1";
6231
- var CONFIG_FILE = join32(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
6745
+ var CONFIG_FILE = join33(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
6232
6746
  var DEFAULT_API_URL = "https://token.guji.uno";
6233
6747
  var VALID_CONFIG_KEYS = [
6234
6748
  "apiKey",
@@ -7090,22 +7604,22 @@ import {
7090
7604
 
7091
7605
  // src/infrastructure/runtime/paths.ts
7092
7606
  import { mkdirSync as mkdirSync2 } from "fs";
7093
- import { join as join33 } from "path";
7607
+ import { join as join34 } from "path";
7094
7608
  var APP_NAME = "tokenarena";
7095
7609
  function getRuntimeDirPath() {
7096
- return join33(getRuntimeDir(), APP_NAME);
7610
+ return join34(getRuntimeDir(), APP_NAME);
7097
7611
  }
7098
7612
  function getStateDir() {
7099
- return join33(getStateHome(), APP_NAME);
7613
+ return join34(getStateHome(), APP_NAME);
7100
7614
  }
7101
7615
  function getSyncLockPath() {
7102
- return join33(getRuntimeDirPath(), "sync.lock");
7616
+ return join34(getRuntimeDirPath(), "sync.lock");
7103
7617
  }
7104
7618
  function getSyncStatePath() {
7105
- return join33(getStateDir(), "status.json");
7619
+ return join34(getStateDir(), "status.json");
7106
7620
  }
7107
7621
  function getUploadManifestPath() {
7108
- return join33(getStateDir(), "upload-manifest.json");
7622
+ return join34(getStateDir(), "upload-manifest.json");
7109
7623
  }
7110
7624
  function ensureAppDirs() {
7111
7625
  mkdirSync2(getRuntimeDirPath(), { recursive: true });
@@ -7311,19 +7825,19 @@ function saveUploadManifest(manifest) {
7311
7825
 
7312
7826
  // src/services/parse-cache.ts
7313
7827
  import { createHash as createHash7 } from "crypto";
7314
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
7828
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, statSync as statSync8, writeFileSync as writeFileSync5 } from "fs";
7315
7829
  import { hostname as hostname6 } from "os";
7316
- import { join as join35 } from "path";
7830
+ import { join as join36 } from "path";
7317
7831
 
7318
7832
  // src/infrastructure/runtime/cli-version.ts
7319
- import { dirname as dirname7, join as join34 } from "path";
7833
+ import { dirname as dirname8, join as join35 } from "path";
7320
7834
  import { fileURLToPath } from "url";
7321
7835
  var FALLBACK_VERSION = "0.0.0";
7322
7836
  var versionCache = /* @__PURE__ */ new Map();
7323
7837
  function readPackageVersion(startDir) {
7324
7838
  let dir = startDir;
7325
7839
  while (true) {
7326
- const contents = readFileSafe(join34(dir, "package.json"));
7840
+ const contents = readFileSafe(join35(dir, "package.json"));
7327
7841
  if (contents !== null) {
7328
7842
  try {
7329
7843
  const { version } = JSON.parse(contents);
@@ -7332,7 +7846,7 @@ function readPackageVersion(startDir) {
7332
7846
  return void 0;
7333
7847
  }
7334
7848
  }
7335
- const parent = dirname7(dir);
7849
+ const parent = dirname8(dir);
7336
7850
  if (parent === dir) {
7337
7851
  return void 0;
7338
7852
  }
@@ -7340,7 +7854,7 @@ function readPackageVersion(startDir) {
7340
7854
  }
7341
7855
  }
7342
7856
  function getCliVersion(metaUrl = import.meta.url) {
7343
- const startDir = dirname7(fileURLToPath(metaUrl));
7857
+ const startDir = dirname8(fileURLToPath(metaUrl));
7344
7858
  const cached = versionCache.get(startDir);
7345
7859
  if (cached !== void 0) {
7346
7860
  return cached;
@@ -7354,10 +7868,10 @@ function getCliVersion(metaUrl = import.meta.url) {
7354
7868
  var CACHE_FORMAT_VERSION = 1;
7355
7869
  var SETTLE_MS = 5e3;
7356
7870
  function getParseCacheDir() {
7357
- return join35(getStateDir(), "parse-cache");
7871
+ return join36(getStateDir(), "parse-cache");
7358
7872
  }
7359
7873
  function getParseCachePath(toolId) {
7360
- return join35(getParseCacheDir(), `${toolId}.json`);
7874
+ return join36(getParseCacheDir(), `${toolId}.json`);
7361
7875
  }
7362
7876
  function computeScanFingerprint(files, now = Date.now()) {
7363
7877
  const hash = createHash7("sha256");
@@ -7367,7 +7881,7 @@ function computeScanFingerprint(files, now = Date.now()) {
7367
7881
  for (const file of [...files].sort()) {
7368
7882
  let stats;
7369
7883
  try {
7370
- stats = statSync6(file);
7884
+ stats = statSync8(file);
7371
7885
  } catch {
7372
7886
  return null;
7373
7887
  }
@@ -7575,12 +8089,12 @@ function createProjectIdentityResolver(settings) {
7575
8089
  return identity;
7576
8090
  };
7577
8091
  }
7578
- function toUploadBuckets(buckets, settings, device, resolveProject = createProjectIdentityResolver(
8092
+ function toUploadBuckets(buckets, settings, device, resolveProject2 = createProjectIdentityResolver(
7579
8093
  settings
7580
8094
  )) {
7581
8095
  const aggregated = /* @__PURE__ */ new Map();
7582
8096
  for (const bucket of buckets) {
7583
- const project = resolveProject(bucket.project);
8097
+ const project = resolveProject2(bucket.project);
7584
8098
  const key = [
7585
8099
  bucket.source,
7586
8100
  bucket.model,
@@ -7616,11 +8130,11 @@ function toUploadBuckets(buckets, settings, device, resolveProject = createProje
7616
8130
  }
7617
8131
  return Array.from(aggregated.values());
7618
8132
  }
7619
- function toUploadSessions(sessions, settings, device, resolveProject = createProjectIdentityResolver(
8133
+ function toUploadSessions(sessions, settings, device, resolveProject2 = createProjectIdentityResolver(
7620
8134
  settings
7621
8135
  )) {
7622
8136
  return sessions.map((session) => {
7623
- const project = resolveProject(session.project);
8137
+ const project = resolveProject2(session.project);
7624
8138
  return {
7625
8139
  source: session.source,
7626
8140
  projectKey: project.projectKey,
@@ -7799,18 +8313,18 @@ async function runSync(config, opts = {}) {
7799
8313
  deviceId: device.deviceId,
7800
8314
  settings
7801
8315
  });
7802
- const resolveProject = createProjectIdentityResolver(settings);
8316
+ const resolveProject2 = createProjectIdentityResolver(settings);
7803
8317
  const uploadBuckets = toUploadBuckets(
7804
8318
  allBuckets,
7805
8319
  settings,
7806
8320
  device,
7807
- resolveProject
8321
+ resolveProject2
7808
8322
  );
7809
8323
  const uploadSessions = toUploadSessions(
7810
8324
  allSessions,
7811
8325
  settings,
7812
8326
  device,
7813
- resolveProject
8327
+ resolveProject2
7814
8328
  );
7815
8329
  const previousManifest = loadUploadManifest();
7816
8330
  const uploadDiff = diffUploadManifest({
@@ -8032,8 +8546,8 @@ View your dashboard at: ${apiUrl}/usage`);
8032
8546
  import { execFileSync as execFileSync8, spawn } from "child_process";
8033
8547
  import { existsSync as existsSync36 } from "fs";
8034
8548
  import { appendFile, mkdir, readFile } from "fs/promises";
8035
- import { homedir as homedir33, platform as platform5 } from "os";
8036
- import { dirname as dirname8, join as join36, posix as posix3, win32 } from "path";
8549
+ import { homedir as homedir34, platform as platform5 } from "os";
8550
+ import { dirname as dirname9, join as join37, posix as posix3, win32 } from "path";
8037
8551
 
8038
8552
  // src/infrastructure/service/index.ts
8039
8553
  import { platform as platform4 } from "os";
@@ -8041,7 +8555,7 @@ import { platform as platform4 } from "os";
8041
8555
  // src/infrastructure/service/linux-systemd.ts
8042
8556
  import { execFileSync as execFileSync6 } from "child_process";
8043
8557
  import { existsSync as existsSync34, mkdirSync as mkdirSync4, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
8044
- import { homedir as homedir31, platform as platform2 } from "os";
8558
+ import { homedir as homedir32, platform as platform2 } from "os";
8045
8559
  import { posix } from "path";
8046
8560
 
8047
8561
  // src/utils/command.ts
@@ -8122,10 +8636,10 @@ function escapeXml(value) {
8122
8636
 
8123
8637
  // src/infrastructure/service/linux-systemd.ts
8124
8638
  var SYSTEMD_SERVICE_NAME = "tokenarena";
8125
- function getLinuxSystemdServiceDir(homePath = homedir31()) {
8639
+ function getLinuxSystemdServiceDir(homePath = homedir32()) {
8126
8640
  return posix.join(homePath, ".config", "systemd", "user");
8127
8641
  }
8128
- function getLinuxSystemdServiceFile(homePath = homedir31()) {
8642
+ function getLinuxSystemdServiceFile(homePath = homedir32()) {
8129
8643
  return posix.join(
8130
8644
  getLinuxSystemdServiceDir(homePath),
8131
8645
  `${SYSTEMD_SERVICE_NAME}.service`
@@ -8369,16 +8883,16 @@ function createLinuxSystemdServiceBackend() {
8369
8883
  // src/infrastructure/service/macos-launchd.ts
8370
8884
  import { execFileSync as execFileSync7 } from "child_process";
8371
8885
  import { existsSync as existsSync35, mkdirSync as mkdirSync5, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
8372
- import { homedir as homedir32, platform as platform3 } from "os";
8886
+ import { homedir as homedir33, platform as platform3 } from "os";
8373
8887
  import { posix as posix2 } from "path";
8374
8888
  var MACOS_LAUNCHD_LABEL = "com.guji.tokenarena";
8375
8889
  function getCurrentUid() {
8376
8890
  return typeof process.getuid === "function" ? process.getuid() : null;
8377
8891
  }
8378
- function getMacosLaunchAgentDir(homePath = homedir32()) {
8892
+ function getMacosLaunchAgentDir(homePath = homedir33()) {
8379
8893
  return posix2.join(homePath, "Library", "LaunchAgents");
8380
8894
  }
8381
- function getMacosLaunchAgentFile(homePath = homedir32()) {
8895
+ function getMacosLaunchAgentFile(homePath = homedir33()) {
8382
8896
  return posix2.join(
8383
8897
  getMacosLaunchAgentDir(homePath),
8384
8898
  `${MACOS_LAUNCHD_LABEL}.plist`
@@ -8505,7 +9019,7 @@ function writeLaunchAgentPlist() {
8505
9019
  label: MACOS_LAUNCHD_LABEL,
8506
9020
  programArguments: [command.execPath, ...command.args],
8507
9021
  environment: getManagedServiceEnvironment(),
8508
- workingDirectory: homedir32(),
9022
+ workingDirectory: homedir33(),
8509
9023
  standardOutPath: stdoutPath,
8510
9024
  standardErrorPath: stderrPath
8511
9025
  });
@@ -8782,7 +9296,7 @@ function resolvePowerShellProfilePath() {
8782
9296
  const systemRoot = process.env.SYSTEMROOT || "C:\\Windows";
8783
9297
  const candidates = [
8784
9298
  "pwsh.exe",
8785
- join36(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
9299
+ join37(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
8786
9300
  ];
8787
9301
  for (const command of candidates) {
8788
9302
  try {
@@ -8811,7 +9325,7 @@ function resolvePowerShellProfilePath() {
8811
9325
  function resolveShellAliasSetup(options = {}) {
8812
9326
  const currentPlatform = options.currentPlatform ?? platform5();
8813
9327
  const env = options.env ?? process.env;
8814
- const homeDir = options.homeDir ?? homedir33();
9328
+ const homeDir = options.homeDir ?? homedir34();
8815
9329
  const pathExists = options.exists ?? existsSync36;
8816
9330
  const shellFromEnv = env.SHELL ? basenameLikeShell(env.SHELL).toLowerCase() : "";
8817
9331
  const shellName = shellFromEnv || (currentPlatform === "win32" ? "powershell" : "");
@@ -9007,7 +9521,7 @@ async function setupShellAlias() {
9007
9521
  return;
9008
9522
  }
9009
9523
  try {
9010
- await mkdir(dirname8(setup.configFile), { recursive: true });
9524
+ await mkdir(dirname9(setup.configFile), { recursive: true });
9011
9525
  let existingContent = "";
9012
9526
  if (existsSync36(setup.configFile)) {
9013
9527
  existingContent = await readFile(setup.configFile, "utf-8");
@@ -9171,16 +9685,16 @@ function buildToolSummary(input2) {
9171
9685
  models.add(bucket.model || "unknown");
9172
9686
  }
9173
9687
  for (const session of input2.sessions) {
9174
- const projectName = session.project || "unknown";
9688
+ const projectName2 = session.project || "unknown";
9175
9689
  const modelName = session.primaryModel || "unknown";
9176
- projects.add(projectName);
9690
+ projects.add(projectName2);
9177
9691
  models.add(modelName);
9178
- const project = projectTotals.get(projectName);
9692
+ const project = projectTotals.get(projectName2);
9179
9693
  if (project) {
9180
9694
  project.sessions += 1;
9181
9695
  } else {
9182
- projectTotals.set(projectName, {
9183
- name: projectName,
9696
+ projectTotals.set(projectName2, {
9697
+ name: projectName2,
9184
9698
  inputTokens: 0,
9185
9699
  outputTokens: 0,
9186
9700
  cachedTokens: 0,
@@ -9671,7 +10185,7 @@ async function runSyncCommand(opts = {}) {
9671
10185
 
9672
10186
  // src/commands/uninstall.ts
9673
10187
  import { existsSync as existsSync37, readFileSync as readFileSync15, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
9674
- import { homedir as homedir34, platform as platform6 } from "os";
10188
+ import { homedir as homedir35, platform as platform6 } from "os";
9675
10189
  function removeShellAlias() {
9676
10190
  const shell = process.env.SHELL;
9677
10191
  if (!shell) return;
@@ -9680,17 +10194,17 @@ function removeShellAlias() {
9680
10194
  let configFile;
9681
10195
  switch (shellName) {
9682
10196
  case "zsh":
9683
- configFile = `${homedir34()}/.zshrc`;
10197
+ configFile = `${homedir35()}/.zshrc`;
9684
10198
  break;
9685
10199
  case "bash":
9686
- if (platform6() === "darwin" && existsSync37(`${homedir34()}/.bash_profile`)) {
9687
- configFile = `${homedir34()}/.bash_profile`;
10200
+ if (platform6() === "darwin" && existsSync37(`${homedir35()}/.bash_profile`)) {
10201
+ configFile = `${homedir35()}/.bash_profile`;
9688
10202
  } else {
9689
- configFile = `${homedir34()}/.bashrc`;
10203
+ configFile = `${homedir35()}/.bashrc`;
9690
10204
  }
9691
10205
  break;
9692
10206
  case "fish":
9693
- configFile = `${homedir34()}/.config/fish/config.fish`;
10207
+ configFile = `${homedir35()}/.config/fish/config.fish`;
9694
10208
  break;
9695
10209
  default:
9696
10210
  return;