@yishiguji/tokenarena 0.15.0 → 0.16.0

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
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/parsers/claude-code.ts
4
+ import { createHash as createHash2 } from "crypto";
4
5
  import { existsSync as existsSync3, realpathSync } from "fs";
5
6
  import { homedir } from "os";
6
7
  import { join as join2, sep as sep2 } from "path";
@@ -16,6 +17,8 @@ var TOKEN_COUNT_KEYS = [
16
17
  "cachedTokens"
17
18
  ];
18
19
  function hasInvalidTokenCounts(entry) {
20
+ const cacheCreation = entry.cacheCreationTokens ?? 0;
21
+ if (!Number.isSafeInteger(cacheCreation) || cacheCreation < 0) return true;
19
22
  return TOKEN_COUNT_KEYS.some((key) => {
20
23
  const value = entry[key];
21
24
  return !Number.isSafeInteger(value) || value < 0;
@@ -48,6 +51,7 @@ function aggregateToBuckets(entries) {
48
51
  outputTokens: 0,
49
52
  reasoningTokens: 0,
50
53
  cachedTokens: 0,
54
+ cacheCreationTokens: 0,
51
55
  totalTokens: 0
52
56
  });
53
57
  }
@@ -57,7 +61,8 @@ function aggregateToBuckets(entries) {
57
61
  b.outputTokens += e.outputTokens || 0;
58
62
  b.reasoningTokens += e.reasoningTokens || 0;
59
63
  b.cachedTokens += e.cachedTokens || 0;
60
- b.totalTokens += (e.inputTokens || 0) + (e.outputTokens || 0) + (e.reasoningTokens || 0) + (e.cachedTokens || 0);
64
+ b.cacheCreationTokens = (b.cacheCreationTokens ?? 0) + (e.cacheCreationTokens ?? 0);
65
+ b.totalTokens += (e.inputTokens || 0) + (e.outputTokens || 0) + (e.reasoningTokens || 0) + (e.cachedTokens || 0) + (e.cacheCreationTokens ?? 0);
61
66
  }
62
67
  return Array.from(map.values());
63
68
  }
@@ -66,7 +71,7 @@ function aggregateToBuckets(entries) {
66
71
  import { createHash } from "crypto";
67
72
  import { hostname as hostname2 } from "os";
68
73
  function toEntryTotalTokens(entry) {
69
- return entry.inputTokens + entry.outputTokens + entry.reasoningTokens + entry.cachedTokens;
74
+ return entry.inputTokens + entry.outputTokens + entry.reasoningTokens + entry.cachedTokens + (entry.cacheCreationTokens ?? 0);
70
75
  }
71
76
  function buildSessionUsage(entries) {
72
77
  const usageBySession = /* @__PURE__ */ new Map();
@@ -88,6 +93,7 @@ function buildSessionUsage(entries) {
88
93
  existing.outputTokens += entry.outputTokens;
89
94
  existing.reasoningTokens += entry.reasoningTokens;
90
95
  existing.cachedTokens += entry.cachedTokens;
96
+ existing.cacheCreationTokens = (existing.cacheCreationTokens ?? 0) + (entry.cacheCreationTokens ?? 0);
91
97
  existing.totalTokens += toEntryTotalTokens(entry);
92
98
  continue;
93
99
  }
@@ -97,6 +103,7 @@ function buildSessionUsage(entries) {
97
103
  outputTokens: entry.outputTokens,
98
104
  reasoningTokens: entry.reasoningTokens,
99
105
  cachedTokens: entry.cachedTokens,
106
+ cacheCreationTokens: entry.cacheCreationTokens ?? 0,
100
107
  totalTokens: toEntryTotalTokens(entry)
101
108
  });
102
109
  }
@@ -177,6 +184,10 @@ function extractSessions(events, entries = []) {
177
184
  (sum, usage) => sum + usage.cachedTokens,
178
185
  0
179
186
  );
187
+ const cacheCreationTokens = modelUsages.reduce(
188
+ (sum, usage) => sum + (usage.cacheCreationTokens ?? 0),
189
+ 0
190
+ );
180
191
  const totalTokens = modelUsages.reduce(
181
192
  (sum, usage) => sum + usage.totalTokens,
182
193
  0
@@ -199,6 +210,7 @@ function extractSessions(events, entries = []) {
199
210
  outputTokens,
200
211
  reasoningTokens,
201
212
  cachedTokens,
213
+ cacheCreationTokens,
202
214
  totalTokens,
203
215
  primaryModel,
204
216
  modelUsages
@@ -330,109 +342,164 @@ function extractProject(filePath, projectsDir) {
330
342
  const parts = firstSegment.split("-").filter(Boolean);
331
343
  return parts.length > 0 ? parts[parts.length - 1] : "unknown";
332
344
  }
345
+ function asRecord(value) {
346
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
347
+ }
348
+ function getIdentifier(value) {
349
+ return typeof value === "string" && value.trim() ? value.trim() : null;
350
+ }
351
+ function tokenCount(value) {
352
+ if (value == null) return 0;
353
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : Number.NaN;
354
+ }
355
+ function selectSnapshot(current, candidate) {
356
+ if (!current) return candidate;
357
+ const replace = candidate.completed !== current.completed ? candidate.completed : candidate.entry.outputTokens !== current.entry.outputTokens ? candidate.entry.outputTokens > current.entry.outputTokens : candidate.entry.timestamp > current.entry.timestamp;
358
+ const selected = replace ? candidate : current;
359
+ const owner = current.order < candidate.order ? current : candidate;
360
+ return {
361
+ ...selected,
362
+ order: owner.order,
363
+ entry: {
364
+ ...selected.entry,
365
+ sessionId: owner.entry.sessionId,
366
+ project: owner.entry.project
367
+ }
368
+ };
369
+ }
333
370
  var ClaudeCodeParser = class {
334
371
  tool = TOOL;
335
372
  async parse() {
336
- const entries = [];
337
- const sessionEvents = [];
338
- const seenUuids = /* @__PURE__ */ new Set();
339
- const seenSessionIds = /* @__PURE__ */ new Set();
340
- const seenProjectFiles = /* @__PURE__ */ new Set();
341
- const roots = getClaudeRoots();
342
- for (const root of roots) {
343
- const projectsDir = join2(root, "projects");
344
- const projectFiles = findJsonlFiles(projectsDir);
345
- for (const filePath of projectFiles) {
346
- const relative = projectRelativePath(filePath, projectsDir);
347
- if (relative !== null) {
348
- if (seenProjectFiles.has(relative)) continue;
349
- seenProjectFiles.add(relative);
350
- }
351
- const content = readFileSafe(filePath);
352
- if (!content) continue;
353
- const project = extractProject(filePath, projectsDir);
354
- const sessionId = extractSessionId(filePath);
355
- seenSessionIds.add(sessionId);
356
- for (const line of content.split("\n")) {
357
- if (!line.trim()) continue;
358
- try {
359
- const obj = JSON.parse(line);
360
- const timestamp = obj.timestamp;
361
- if (!timestamp) continue;
362
- const parsedTimestamp = new Date(timestamp);
363
- if (Number.isNaN(parsedTimestamp.getTime())) continue;
364
- if (obj.type === "user" || obj.type === "assistant" || obj.type === "tool_use" || obj.type === "tool_result") {
365
- sessionEvents.push({
366
- sessionId,
367
- source: "claude-code",
368
- project,
369
- timestamp: parsedTimestamp,
370
- role: obj.type === "user" ? "user" : "assistant"
371
- });
372
- }
373
- if (obj.type !== "assistant") continue;
374
- const message = obj.message;
375
- if (!message?.usage) continue;
376
- const usage = message.usage;
377
- if (usage.input_tokens == null && usage.output_tokens == null) {
378
- continue;
379
- }
380
- const uuid = obj.uuid;
381
- if (uuid) {
382
- if (seenUuids.has(uuid)) continue;
383
- seenUuids.add(uuid);
384
- }
385
- entries.push({
373
+ const snapshots = /* @__PURE__ */ new Map();
374
+ const sessionEvents = /* @__PURE__ */ new Map();
375
+ const projectSessionIds = /* @__PURE__ */ new Set();
376
+ let snapshotOrder = 0;
377
+ const scanFile = (filePath, fileIdentity, project, includeUsage) => {
378
+ const content = readFileSafe(filePath);
379
+ if (!content) return;
380
+ const sessionId = extractSessionId(filePath);
381
+ if (includeUsage) projectSessionIds.add(sessionId);
382
+ for (const [lineIndex, line] of content.split("\n").entries()) {
383
+ if (!line.trim()) continue;
384
+ try {
385
+ const record = asRecord(JSON.parse(line));
386
+ if (!record || typeof record.timestamp !== "string" && typeof record.timestamp !== "number") {
387
+ continue;
388
+ }
389
+ const timestamp = new Date(record.timestamp);
390
+ if (Number.isNaN(timestamp.getTime())) continue;
391
+ const message = asRecord(record.message);
392
+ const messageId = getIdentifier(message?.id);
393
+ const requestId = getIdentifier(record.requestId) ?? getIdentifier(record.request_id) ?? "";
394
+ const uuid = getIdentifier(record.uuid);
395
+ const anonymousId = uuid || messageId ? "" : createHash2("sha256").update(JSON.stringify([fileIdentity, lineIndex, line])).digest("hex");
396
+ const eventKey = uuid ? JSON.stringify(["uuid", uuid]) : messageId ? JSON.stringify([
397
+ "message",
398
+ messageId,
399
+ requestId,
400
+ record.type,
401
+ record.timestamp
402
+ ]) : JSON.stringify(["anonymous", anonymousId]);
403
+ if (!sessionEvents.has(eventKey) && (record.type === "user" || record.type === "assistant" || record.type === "tool_use" || record.type === "tool_result")) {
404
+ sessionEvents.set(eventKey, {
386
405
  sessionId,
387
406
  source: "claude-code",
388
- model: message.model || "unknown",
389
407
  project,
390
- timestamp: parsedTimestamp,
391
- inputTokens: usage.input_tokens || 0,
392
- outputTokens: usage.output_tokens || 0,
393
- reasoningTokens: 0,
394
- cachedTokens: usage.cache_read_input_tokens || 0
408
+ timestamp,
409
+ role: record.type === "user" ? "user" : "assistant"
395
410
  });
396
- } catch {
397
411
  }
412
+ if (!includeUsage || record.type !== "assistant" || !message)
413
+ continue;
414
+ const usage = asRecord(message.usage);
415
+ if (!usage) continue;
416
+ const cacheCreation = asRecord(usage.cache_creation);
417
+ const cacheCreationTokens = tokenCount(
418
+ usage.cache_creation_input_tokens ?? tokenCount(cacheCreation?.ephemeral_5m_input_tokens) + tokenCount(cacheCreation?.ephemeral_1h_input_tokens)
419
+ );
420
+ const event = sessionEvents.get(eventKey);
421
+ const entry = {
422
+ sessionId: event?.sessionId ?? sessionId,
423
+ source: "claude-code",
424
+ model: getIdentifier(message.model) ?? "unknown",
425
+ project: event?.project ?? project,
426
+ timestamp,
427
+ inputTokens: tokenCount(usage.input_tokens),
428
+ outputTokens: tokenCount(usage.output_tokens),
429
+ reasoningTokens: 0,
430
+ cachedTokens: tokenCount(usage.cache_read_input_tokens),
431
+ cacheCreationTokens
432
+ };
433
+ if (hasInvalidTokenCounts(entry) || entry.inputTokens + entry.outputTokens + entry.cachedTokens + cacheCreationTokens === 0) {
434
+ continue;
435
+ }
436
+ const messageKey = messageId ? JSON.stringify(["message", messageId]) : uuid ? JSON.stringify(["uuid", uuid]) : JSON.stringify(["anonymous", anonymousId]);
437
+ let requests = snapshots.get(messageKey);
438
+ if (!requests) {
439
+ requests = /* @__PURE__ */ new Map();
440
+ snapshots.set(messageKey, requests);
441
+ }
442
+ requests.set(
443
+ requestId,
444
+ selectSnapshot(requests.get(requestId), {
445
+ entry,
446
+ completed: getIdentifier(message.stop_reason) !== null,
447
+ order: snapshotOrder++
448
+ })
449
+ );
450
+ } catch {
398
451
  }
399
452
  }
453
+ };
454
+ const roots = getClaudeRoots();
455
+ for (const root of roots) {
456
+ const projectsDir = join2(root, "projects");
457
+ for (const filePath of findJsonlFiles(projectsDir)) {
458
+ scanFile(
459
+ filePath,
460
+ join2(
461
+ "projects",
462
+ projectRelativePath(filePath, projectsDir) ?? filePath
463
+ ),
464
+ extractProject(filePath, projectsDir),
465
+ true
466
+ );
467
+ }
400
468
  }
401
469
  for (const root of roots) {
402
- for (const transcriptsDir of ["transcripts", "sessions"]) {
403
- const transcriptFiles = findJsonlFiles(join2(root, transcriptsDir));
404
- for (const filePath of transcriptFiles) {
405
- const sessionId = extractSessionId(filePath);
406
- if (seenSessionIds.has(sessionId)) continue;
407
- seenSessionIds.add(sessionId);
408
- const content = readFileSafe(filePath);
409
- if (!content) continue;
410
- for (const line of content.split("\n")) {
411
- if (!line.trim()) continue;
412
- try {
413
- const obj = JSON.parse(line);
414
- const timestamp = obj.timestamp;
415
- if (!timestamp) continue;
416
- const parsedTimestamp = new Date(timestamp);
417
- if (Number.isNaN(parsedTimestamp.getTime())) continue;
418
- if (obj.type === "user" || obj.type === "assistant" || obj.type === "tool_use" || obj.type === "tool_result") {
419
- sessionEvents.push({
420
- sessionId,
421
- source: "claude-code",
422
- project: "unknown",
423
- timestamp: parsedTimestamp,
424
- role: obj.type === "user" ? "user" : "assistant"
425
- });
426
- }
427
- } catch {
428
- }
429
- }
470
+ for (const directory of ["transcripts", "sessions"]) {
471
+ const transcriptDir = join2(root, directory);
472
+ for (const filePath of findJsonlFiles(transcriptDir)) {
473
+ if (projectSessionIds.has(extractSessionId(filePath))) continue;
474
+ scanFile(
475
+ filePath,
476
+ join2(
477
+ directory,
478
+ projectRelativePath(filePath, transcriptDir) ?? filePath
479
+ ),
480
+ "unknown",
481
+ false
482
+ );
430
483
  }
431
484
  }
432
485
  }
486
+ const entries = [];
487
+ for (const requests of snapshots.values()) {
488
+ const unidentified = requests.get("");
489
+ if (unidentified && requests.size === 2) {
490
+ const identified = Array.from(requests.entries()).find(([id]) => id);
491
+ if (identified) {
492
+ entries.push(selectSnapshot(unidentified, identified[1]).entry);
493
+ continue;
494
+ }
495
+ }
496
+ entries.push(
497
+ ...Array.from(requests.values(), (snapshot) => snapshot.entry)
498
+ );
499
+ }
433
500
  return {
434
501
  buckets: aggregateToBuckets(entries),
435
- sessions: extractSessions(sessionEvents, entries)
502
+ sessions: extractSessions(Array.from(sessionEvents.values()), entries)
436
503
  };
437
504
  }
438
505
  isInstalled() {
@@ -445,216 +512,376 @@ var ClaudeCodeParser = class {
445
512
  registerParser(new ClaudeCodeParser());
446
513
 
447
514
  // src/parsers/codex.ts
515
+ import { existsSync as existsSync4 } from "fs";
448
516
  import { homedir as homedir2 } from "os";
449
- import { join as join3 } from "path";
517
+ import { basename as basename2, join as join3 } from "path";
518
+
519
+ // src/utils/logger.ts
520
+ var LOG_LEVELS = {
521
+ debug: 0,
522
+ info: 1,
523
+ warn: 2,
524
+ error: 3
525
+ };
526
+ var Logger = class {
527
+ level;
528
+ constructor(level = "info") {
529
+ this.level = level;
530
+ }
531
+ setLevel(level) {
532
+ this.level = level;
533
+ }
534
+ debug(msg) {
535
+ if (LOG_LEVELS[this.level] <= LOG_LEVELS.debug) {
536
+ process.stderr.write(`[debug] ${msg}
537
+ `);
538
+ }
539
+ }
540
+ info(msg) {
541
+ if (LOG_LEVELS[this.level] <= LOG_LEVELS.info) {
542
+ process.stdout.write(`${msg}
543
+ `);
544
+ }
545
+ }
546
+ warn(msg) {
547
+ if (LOG_LEVELS[this.level] <= LOG_LEVELS.warn) {
548
+ process.stderr.write(`warn: ${msg}
549
+ `);
550
+ }
551
+ }
552
+ error(msg) {
553
+ if (LOG_LEVELS[this.level] <= LOG_LEVELS.error) {
554
+ process.stderr.write(`error: ${msg}
555
+ `);
556
+ }
557
+ }
558
+ log(msg) {
559
+ this.info(msg);
560
+ }
561
+ };
562
+ var logger = new Logger();
563
+
564
+ // src/parsers/codex.ts
450
565
  var TOOL_ID = "codex";
451
- var TOOL_NAME = "Codex CLI";
452
566
  var DEFAULT_DATA_DIR = join3(homedir2(), ".codex", "sessions");
453
- function createToolDefinition(dataDir) {
567
+ var DEFAULT_ARCHIVE_DIR = join3(homedir2(), ".codex", "archived_sessions");
568
+ function safeCount(value) {
569
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
570
+ }
571
+ function readUsage(value) {
572
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
573
+ const fields = value;
574
+ const names = [
575
+ "input_tokens",
576
+ "output_tokens",
577
+ "cached_input_tokens",
578
+ "cache_read_input_tokens",
579
+ "reasoning_output_tokens",
580
+ "total_tokens"
581
+ ];
582
+ if (!names.some((name) => Object.hasOwn(fields, name))) return null;
454
583
  return {
455
- id: TOOL_ID,
456
- name: TOOL_NAME,
457
- dataDir
584
+ input: safeCount(fields.input_tokens),
585
+ output: safeCount(fields.output_tokens),
586
+ cached: safeCount(
587
+ fields.cached_input_tokens ?? fields.cache_read_input_tokens
588
+ ),
589
+ reasoning: safeCount(fields.reasoning_output_tokens),
590
+ reportedTotal: typeof fields.total_tokens === "number" ? safeCount(fields.total_tokens) : null
458
591
  };
459
592
  }
460
- function toSafeNumber(value) {
461
- const numberValue = Number(value);
462
- return Number.isFinite(numberValue) ? numberValue : 0;
593
+ function readTimestamp(value) {
594
+ if (typeof value !== "string") return null;
595
+ const date = new Date(value);
596
+ return Number.isNaN(date.getTime()) ? null : date;
463
597
  }
464
- function toNonNegativeDelta(current, previous) {
465
- return Math.max(0, toSafeNumber(current) - toSafeNumber(previous));
598
+ function timestampOrder(value, timestamp) {
599
+ if (!timestamp) return null;
600
+ const fraction = typeof value === "string" ? value.match(/\.(\d+)(?:z|[+-]\d{2}:?\d{2})?$/i)?.[1] ?? "" : "";
601
+ const remainder = fraction.slice(3, 9).padEnd(6, "0");
602
+ return BigInt(timestamp.getTime()) * 1000000n + BigInt(remainder);
466
603
  }
467
- function getPathLeaf(value) {
468
- const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
469
- const leaf = normalized.split("/").filter(Boolean).pop();
470
- return leaf || "unknown";
604
+ function readId(value) {
605
+ return typeof value === "string" && value.trim() ? value.trim() : null;
606
+ }
607
+ function filenameThreadId(path) {
608
+ return basename2(path).match(
609
+ /([\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})\.jsonl$/i
610
+ )?.[1] ?? null;
471
611
  }
472
612
  function resolveCodexProject(payload) {
473
- if (!payload) {
474
- return "unknown";
475
- }
476
- const repositoryUrl = payload.git?.repository_url;
477
- if (repositoryUrl) {
613
+ const repositoryUrl = payload?.git?.repository_url;
614
+ if (typeof repositoryUrl === "string" && repositoryUrl) {
478
615
  const match = repositoryUrl.match(/([^/]+\/[^/]+?)(?:\.git)?$/);
479
- if (match) {
480
- return match[1];
481
- }
616
+ if (match) return match[1];
482
617
  }
483
- if (payload.cwd) {
484
- return getPathLeaf(payload.cwd);
618
+ const normalized = typeof payload?.cwd === "string" ? payload.cwd.replace(/\\/g, "/").replace(/\/+$/, "") : "";
619
+ return normalized?.split("/").filter(Boolean).pop() || "unknown";
620
+ }
621
+ function readRollout(path) {
622
+ const content = readFileSafe(path);
623
+ if (!content) return null;
624
+ const tokens = [];
625
+ const prompts = [];
626
+ let rootMeta = null;
627
+ let model = "unknown";
628
+ let latestTimestampOrder = 0n;
629
+ let line = 0;
630
+ for (const text of content.split("\n")) {
631
+ line++;
632
+ if (!text.trim()) continue;
633
+ try {
634
+ const event = JSON.parse(text);
635
+ const timestamp = readTimestamp(event.timestamp);
636
+ const order = timestampOrder(event.timestamp, timestamp);
637
+ if (order !== null && order > latestTimestampOrder)
638
+ latestTimestampOrder = order;
639
+ if (event.type === "session_meta" && !rootMeta) rootMeta = event;
640
+ if (event.type === "turn_context") {
641
+ model = readId(event.payload?.model) || readId(event.payload?.info?.model) || model;
642
+ if (timestamp && order !== null)
643
+ prompts.push({ line, timestamp, timestampOrder: order });
644
+ }
645
+ if (event.type !== "event_msg" || event.payload?.type !== "token_count")
646
+ continue;
647
+ const info = event.payload.info;
648
+ if (!info) continue;
649
+ const total = readUsage(info.total_token_usage);
650
+ const last = readUsage(info.last_token_usage);
651
+ if (!total && !last) continue;
652
+ model = readId(info.model) || readId(info.model_name) || readId(event.payload.model) || model;
653
+ tokens.push({
654
+ line,
655
+ timestamp,
656
+ timestampOrder: order,
657
+ model,
658
+ total,
659
+ last,
660
+ signature: JSON.stringify([total, last]),
661
+ snapshotSource: readId(event.payload.rate_limits?.limit_id) || ""
662
+ });
663
+ } catch {
664
+ }
485
665
  }
486
- return "unknown";
666
+ const meta = rootMeta?.payload;
667
+ const threadId = readId(meta?.id) || readId(meta?.thread_id) || readId(meta?.threadId);
668
+ const forkedFrom = readId(meta?.forked_from_id);
669
+ const spawnedFrom = readId(
670
+ meta?.source?.subagent?.thread_spawn?.parent_thread_id
671
+ );
672
+ return {
673
+ path,
674
+ threadId,
675
+ sessionId: threadId ? `codex:${threadId}` : `codex-file:${path}`,
676
+ project: resolveCodexProject(meta),
677
+ parentId: forkedFrom || spawnedFrom,
678
+ parentConflict: Boolean(
679
+ forkedFrom && spawnedFrom && forkedFrom !== spawnedFrom
680
+ ),
681
+ rootTimestampOrder: timestampOrder(
682
+ rootMeta?.timestamp,
683
+ readTimestamp(rootMeta?.timestamp)
684
+ ),
685
+ latestTimestampOrder,
686
+ tokens,
687
+ prompts
688
+ };
689
+ }
690
+ function matchingReplayPrefix(tokens, history) {
691
+ let offset = 0;
692
+ let matched = 0;
693
+ for (const token of tokens) {
694
+ while (offset < history.length && (history[offset].signature !== token.signature || // 缺少累计值时,相同用量不能证明回放;还必须对应同一时刻和模型。
695
+ !token.total && (token.timestampOrder === null || token.timestampOrder !== history[offset].timestampOrder || token.model !== history[offset].model)))
696
+ offset++;
697
+ if (offset === history.length) break;
698
+ offset++;
699
+ matched++;
700
+ }
701
+ return matched;
702
+ }
703
+ function usageDelta(current, previous) {
704
+ return {
705
+ input: Math.max(0, current.input - (previous?.input ?? 0)),
706
+ output: Math.max(0, current.output - (previous?.output ?? 0)),
707
+ cached: Math.max(0, current.cached - (previous?.cached ?? 0)),
708
+ reasoning: Math.max(0, current.reasoning - (previous?.reasoning ?? 0)),
709
+ reportedTotal: null
710
+ };
711
+ }
712
+ function highWater(previous, current) {
713
+ return {
714
+ input: Math.max(previous?.input ?? 0, current.input),
715
+ output: Math.max(previous?.output ?? 0, current.output),
716
+ cached: Math.max(previous?.cached ?? 0, current.cached),
717
+ reasoning: Math.max(previous?.reasoning ?? 0, current.reasoning),
718
+ reportedTotal: null
719
+ };
487
720
  }
488
721
  var CodexParser = class {
489
- constructor(dataDir = DEFAULT_DATA_DIR) {
490
- this.dataDir = dataDir;
491
- this.tool = createToolDefinition(dataDir);
492
- }
493
- dataDir;
494
722
  tool;
723
+ dataDirs;
724
+ constructor(dataDir = DEFAULT_DATA_DIR, archiveDir = dataDir === DEFAULT_DATA_DIR ? DEFAULT_ARCHIVE_DIR : void 0) {
725
+ this.dataDirs = archiveDir ? [dataDir, archiveDir] : [dataDir];
726
+ this.tool = { id: TOOL_ID, name: "Codex CLI", dataDir };
727
+ }
728
+ isInstalled() {
729
+ return this.dataDirs.some((directory) => existsSync4(directory));
730
+ }
495
731
  async parse() {
496
732
  const entries = [];
497
733
  const sessionEvents = [];
498
- const files = findJsonlFiles(this.dataDir);
499
- if (files.length === 0) {
500
- return { buckets: [], sessions: [] };
501
- }
502
- const seenTotalStates = /* @__PURE__ */ new Set();
503
- const seenLastOnly = /* @__PURE__ */ new Set();
504
- for (const filePath of files) {
505
- const content = readFileSafe(filePath);
506
- if (!content) continue;
507
- let sessionProject = "unknown";
508
- const sessionModel = "unknown";
509
- for (const line of content.split("\n")) {
510
- if (!line.trim()) continue;
511
- try {
512
- const obj = JSON.parse(line);
513
- if (obj.type === "session_meta") {
514
- sessionProject = resolveCodexProject(obj.payload);
515
- break;
516
- }
517
- } catch {
518
- break;
519
- }
520
- }
521
- let turnContextModel = "unknown";
522
- const prevTotal = /* @__PURE__ */ new Map();
523
- for (const line of content.split("\n")) {
524
- if (!line.trim()) continue;
525
- try {
526
- const obj = JSON.parse(line);
527
- if (obj.type === "turn_context" && obj.timestamp) {
528
- const eventTimestamp = new Date(obj.timestamp);
529
- if (!Number.isNaN(eventTimestamp.getTime())) {
530
- sessionEvents.push({
531
- sessionId: filePath,
532
- source: TOOL_ID,
533
- project: sessionProject,
534
- timestamp: eventTimestamp,
535
- role: "user"
536
- });
537
- }
538
- }
539
- if (obj.type === "turn_context" && obj.payload?.model) {
540
- turnContextModel = obj.payload.model;
541
- continue;
542
- }
543
- if (obj.type !== "event_msg") continue;
544
- const payload = obj.payload;
545
- if (!payload || payload.type !== "token_count") continue;
546
- const info = payload.info;
547
- if (!info) continue;
548
- const timestamp = obj.timestamp ? new Date(obj.timestamp) : null;
549
- if (!timestamp || Number.isNaN(timestamp.getTime())) continue;
550
- const model = info.model || payload.model || turnContextModel || sessionModel;
551
- let isDuplicate = false;
552
- if (info.total_token_usage) {
553
- const curr = info.total_token_usage;
554
- const stateKey = `${model}|${toSafeNumber(curr.input_tokens)}|${toSafeNumber(curr.output_tokens)}|${toSafeNumber(curr.cached_input_tokens)}|${toSafeNumber(curr.reasoning_output_tokens)}`;
555
- isDuplicate = seenTotalStates.has(stateKey);
556
- if (!isDuplicate) seenTotalStates.add(stateKey);
557
- } else if (info.last_token_usage) {
558
- const u = info.last_token_usage;
559
- const lastKey = `${obj.timestamp}|${toSafeNumber(u.input_tokens)}|${toSafeNumber(u.output_tokens)}|${toSafeNumber(u.cached_input_tokens)}|${toSafeNumber(u.reasoning_output_tokens)}`;
560
- isDuplicate = seenLastOnly.has(lastKey);
561
- if (!isDuplicate) seenLastOnly.add(lastKey);
562
- }
563
- if (!isDuplicate) {
564
- sessionEvents.push({
565
- sessionId: filePath,
566
- source: TOOL_ID,
567
- project: sessionProject,
568
- timestamp,
569
- role: "assistant"
570
- });
571
- }
572
- let usage = info.last_token_usage;
573
- if (!usage && info.total_token_usage) {
574
- const totalKey = `${info.model || payload.model || turnContextModel || ""}`;
575
- const prev = prevTotal.get(totalKey);
576
- const curr = info.total_token_usage;
577
- if (prev) {
578
- usage = {
579
- input_tokens: toNonNegativeDelta(
580
- curr.input_tokens,
581
- prev.input_tokens
582
- ),
583
- output_tokens: toNonNegativeDelta(
584
- curr.output_tokens,
585
- prev.output_tokens
586
- ),
587
- cached_input_tokens: toNonNegativeDelta(
588
- curr.cached_input_tokens,
589
- prev.cached_input_tokens
590
- ),
591
- reasoning_output_tokens: toNonNegativeDelta(
592
- curr.reasoning_output_tokens,
593
- prev.reasoning_output_tokens
594
- )
595
- };
596
- } else {
597
- usage = curr;
598
- }
599
- prevTotal.set(totalKey, { ...curr });
734
+ const threads = /* @__PURE__ */ new Map();
735
+ const threadIndex = /* @__PURE__ */ new Map();
736
+ const files = [...new Set(this.dataDirs.flatMap(findJsonlFiles))].sort();
737
+ for (const path of files) {
738
+ const file = readRollout(path);
739
+ if (!file) continue;
740
+ let thread = threads.get(file.sessionId);
741
+ if (!thread) {
742
+ thread = {
743
+ files: [],
744
+ timeline: [],
745
+ latestTimestampOrder: 0n,
746
+ invalidTokenTimestamp: false,
747
+ status: "new"
748
+ };
749
+ threads.set(file.sessionId, thread);
750
+ }
751
+ thread.files.push(file);
752
+ if (file.latestTimestampOrder > thread.latestTimestampOrder)
753
+ thread.latestTimestampOrder = file.latestTimestampOrder;
754
+ thread.invalidTokenTimestamp ||= file.tokens.some(
755
+ (token) => !token.timestamp
756
+ );
757
+ if (file.threadId) threadIndex.set(file.threadId, thread);
758
+ const rolloutId = filenameThreadId(path);
759
+ if (rolloutId) threadIndex.set(rolloutId, thread);
760
+ }
761
+ const processThread = (thread) => {
762
+ if (thread.status !== "new") return thread.status === "done";
763
+ thread.status = "visiting";
764
+ const threadEntries = [];
765
+ const threadEvents = [];
766
+ let cumulative = null;
767
+ const eventKeys = /* @__PURE__ */ new Set();
768
+ thread.files.sort((left, right) => {
769
+ const firstTime = (file) => file.tokens.find((token) => token.timestampOrder !== null)?.timestampOrder ?? file.rootTimestampOrder ?? 0n;
770
+ const leftTime = firstTime(left);
771
+ const rightTime = firstTime(right);
772
+ return leftTime < rightTime ? -1 : leftTime > rightTime ? 1 : left.path.localeCompare(right.path);
773
+ });
774
+ for (const file of thread.files) {
775
+ let parentPrefix = 0;
776
+ if (file.parentConflict || file.parentId) {
777
+ const parent = file.parentId ? threadIndex.get(file.parentId) : void 0;
778
+ const cutoff = file.rootTimestampOrder;
779
+ if (file.parentConflict || !parent || parent === thread || cutoff === null || !processThread(parent) || parent.invalidTokenTimestamp || parent.latestTimestampOrder < cutoff) {
780
+ logger.warn(
781
+ "Codex fork replay could not be verified; skipping " + basename2(file.path)
782
+ );
783
+ thread.status = "deferred";
784
+ return false;
600
785
  }
601
- if (!usage) continue;
602
- if (isDuplicate) continue;
603
- const cachedInput = toSafeNumber(usage.cached_input_tokens);
604
- const reasoningTokens = toSafeNumber(usage.reasoning_output_tokens);
605
- const inputTokens = Math.max(
606
- 0,
607
- toSafeNumber(usage.input_tokens) - cachedInput
608
- );
609
- const outputTokens = Math.max(
610
- 0,
611
- toSafeNumber(usage.output_tokens) - reasoningTokens
786
+ parentPrefix = matchingReplayPrefix(
787
+ file.tokens,
788
+ parent.timeline.filter(
789
+ (token) => token.timestampOrder !== null && token.timestampOrder <= cutoff
790
+ )
612
791
  );
613
- if (inputTokens === 0 && outputTokens === 0 && reasoningTokens === 0 && cachedInput === 0) {
614
- continue;
615
- }
616
- entries.push({
617
- sessionId: filePath,
792
+ }
793
+ const ownPrefix = matchingReplayPrefix(file.tokens, thread.timeline);
794
+ const replayPrefix = Math.max(parentPrefix, ownPrefix);
795
+ const replayEndLine = file.tokens[replayPrefix - 1]?.line ?? -1;
796
+ const snapshots = /* @__PURE__ */ new Map();
797
+ const lastOnlyEvents = /* @__PURE__ */ new Set();
798
+ let previousSignature = null;
799
+ const addEvent = (timestamp, order, role) => {
800
+ const key = `${role}|${order.toString()}`;
801
+ if (eventKeys.has(key)) return;
802
+ eventKeys.add(key);
803
+ threadEvents.push({
804
+ sessionId: file.sessionId,
618
805
  source: TOOL_ID,
619
- model,
620
- project: sessionProject,
806
+ project: file.project,
621
807
  timestamp,
622
- inputTokens,
623
- outputTokens,
808
+ role
809
+ });
810
+ };
811
+ for (const prompt of file.prompts) {
812
+ if (prompt.line > replayEndLine)
813
+ addEvent(prompt.timestamp, prompt.timestampOrder, "user");
814
+ }
815
+ for (const [index, token] of file.tokens.entries()) {
816
+ if (!token.timestamp || token.timestampOrder === null) continue;
817
+ const duplicate = token.total ? snapshots.get(token.snapshotSource) === token.signature || previousSignature === token.signature : lastOnlyEvents.has(
818
+ token.model + "|" + token.timestampOrder.toString() + "|" + token.signature
819
+ );
820
+ if (token.total) snapshots.set(token.snapshotSource, token.signature);
821
+ else
822
+ lastOnlyEvents.add(
823
+ token.model + "|" + token.timestampOrder.toString() + "|" + token.signature
824
+ );
825
+ previousSignature = token.signature;
826
+ const usage = token.last ?? (token.total ? usageDelta(token.total, cumulative) : null);
827
+ if (token.total) cumulative = highWater(cumulative, token.total);
828
+ if (!usage || duplicate || index < replayPrefix) continue;
829
+ addEvent(token.timestamp, token.timestampOrder, "assistant");
830
+ const cachedTokens = Math.min(usage.cached, usage.input);
831
+ const reasoningTokens = Math.min(usage.reasoning, usage.output);
832
+ if (usage.input === 0 && usage.output === 0) continue;
833
+ threadEntries.push({
834
+ sessionId: file.sessionId,
835
+ source: TOOL_ID,
836
+ model: token.model,
837
+ project: file.project,
838
+ timestamp: token.timestamp,
839
+ inputTokens: usage.input - cachedTokens,
840
+ outputTokens: usage.output - reasoningTokens,
624
841
  reasoningTokens,
625
- cachedTokens: cachedInput
842
+ cachedTokens
626
843
  });
627
- } catch {
628
844
  }
845
+ for (let index = ownPrefix; index < file.tokens.length; index++)
846
+ thread.timeline.push(file.tokens[index]);
629
847
  }
848
+ for (const entry of threadEntries) entries.push(entry);
849
+ for (const event of threadEvents) sessionEvents.push(event);
850
+ thread.status = "done";
851
+ return true;
852
+ };
853
+ let incomplete = false;
854
+ for (const thread of threads.values()) {
855
+ if (!processThread(thread)) incomplete = true;
630
856
  }
631
857
  return {
632
858
  buckets: aggregateToBuckets(entries),
633
- sessions: extractSessions(sessionEvents, entries)
859
+ sessions: extractSessions(sessionEvents, entries),
860
+ ...incomplete ? { incomplete: true } : {}
634
861
  };
635
862
  }
636
863
  };
637
864
  registerParser(new CodexParser());
638
865
 
639
866
  // src/parsers/gsd.ts
640
- import { existsSync as existsSync4 } from "fs";
867
+ import { existsSync as existsSync5 } from "fs";
641
868
  import { homedir as homedir3 } from "os";
642
869
  import { join as join4 } from "path";
643
870
  var TOOL_ID2 = "gsd";
644
- var TOOL_NAME2 = "GSD-2";
871
+ var TOOL_NAME = "GSD-2";
645
872
  var DEFAULT_SESSIONS_DIR = join4(homedir3(), ".gsd", "sessions");
646
- function createToolDefinition2(dataDir) {
873
+ function createToolDefinition(dataDir) {
647
874
  return {
648
875
  id: TOOL_ID2,
649
- name: TOOL_NAME2,
876
+ name: TOOL_NAME,
650
877
  dataDir
651
878
  };
652
879
  }
653
- function toSafeNumber2(value) {
880
+ function toSafeNumber(value) {
654
881
  const numberValue = Number(value);
655
882
  return Number.isFinite(numberValue) ? numberValue : 0;
656
883
  }
657
- function getPathLeaf2(value) {
884
+ function getPathLeaf(value) {
658
885
  const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
659
886
  const leaf = normalized.split("/").filter(Boolean).pop();
660
887
  return leaf || "unknown";
@@ -665,7 +892,7 @@ function normalizeForPrefix(value) {
665
892
  function getUsageNumber(usage, ...keys) {
666
893
  for (const key of keys) {
667
894
  const value = usage[key];
668
- const numberValue = toSafeNumber2(value);
895
+ const numberValue = toSafeNumber(value);
669
896
  if (numberValue > 0) {
670
897
  return numberValue;
671
898
  }
@@ -673,7 +900,7 @@ function getUsageNumber(usage, ...keys) {
673
900
  return 0;
674
901
  }
675
902
  function extractGsdProjectFromCwd(cwd) {
676
- return getPathLeaf2(cwd);
903
+ return getPathLeaf(cwd);
677
904
  }
678
905
  function extractGsdProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR) {
679
906
  const normalizedFilePath = normalizeForPrefix(filePath);
@@ -690,7 +917,7 @@ function extractGsdProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR)
690
917
  try {
691
918
  const decoded = decodeURIComponent(firstSegment);
692
919
  if (decoded.includes("/") || decoded.includes("\\")) {
693
- return getPathLeaf2(decoded);
920
+ return getPathLeaf(decoded);
694
921
  }
695
922
  } catch {
696
923
  }
@@ -700,7 +927,7 @@ function extractGsdProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR)
700
927
  var GsdParser = class {
701
928
  constructor(sessionsDir = DEFAULT_SESSIONS_DIR) {
702
929
  this.sessionsDir = sessionsDir;
703
- this.tool = createToolDefinition2(sessionsDir);
930
+ this.tool = createToolDefinition(sessionsDir);
704
931
  }
705
932
  sessionsDir;
706
933
  tool;
@@ -789,15 +1016,15 @@ var GsdParser = class {
789
1016
  };
790
1017
  }
791
1018
  isInstalled() {
792
- return existsSync4(this.sessionsDir);
1019
+ return existsSync5(this.sessionsDir);
793
1020
  }
794
1021
  };
795
1022
  registerParser(new GsdParser());
796
1023
 
797
1024
  // src/parsers/gemini-cli.ts
798
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
1025
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
799
1026
  import { homedir as homedir4 } from "os";
800
- import { basename as basename2, join as join5 } from "path";
1027
+ import { basename as basename3, join as join5 } from "path";
801
1028
  var TOOL2 = {
802
1029
  id: "gemini-cli",
803
1030
  name: "Gemini CLI",
@@ -805,7 +1032,7 @@ var TOOL2 = {
805
1032
  };
806
1033
  function findSessionFiles(baseDir) {
807
1034
  const results = [];
808
- if (!existsSync5(baseDir)) return results;
1035
+ if (!existsSync6(baseDir)) return results;
809
1036
  let projectDirs;
810
1037
  try {
811
1038
  projectDirs = readdirSync2(baseDir, { withFileTypes: true });
@@ -847,6 +1074,7 @@ function readRecord(filePath) {
847
1074
  let directories = null;
848
1075
  let model = null;
849
1076
  let createTime = null;
1077
+ let sessionId = null;
850
1078
  for (const line of raw.split("\n")) {
851
1079
  const trimmed = line.trim();
852
1080
  if (!trimmed) continue;
@@ -856,6 +1084,8 @@ function readRecord(filePath) {
856
1084
  } catch {
857
1085
  continue;
858
1086
  }
1087
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) continue;
1088
+ if (typeof obj.sessionId === "string") sessionId = obj.sessionId;
859
1089
  if (!directories && Array.isArray(obj.directories)) {
860
1090
  directories = obj.directories;
861
1091
  if (typeof obj.model === "string") model = obj.model;
@@ -866,7 +1096,7 @@ function readRecord(filePath) {
866
1096
  messages.push(obj);
867
1097
  }
868
1098
  }
869
- return { messages, directories, model, createTime };
1099
+ return { messages, directories, model, createTime, sessionId };
870
1100
  }
871
1101
  let data;
872
1102
  try {
@@ -875,6 +1105,7 @@ function readRecord(filePath) {
875
1105
  return null;
876
1106
  }
877
1107
  return {
1108
+ sessionId: typeof data?.sessionId === "string" ? data.sessionId : null,
878
1109
  messages: Array.isArray(data?.messages) ? data.messages : Array.isArray(data?.history) ? data.history : [],
879
1110
  directories: Array.isArray(data?.directories) ? data.directories : null,
880
1111
  model: typeof data?.model === "string" ? data.model : null,
@@ -891,7 +1122,23 @@ function projectFromDirectories(directories) {
891
1122
  if (!directories || directories.length === 0) return "unknown";
892
1123
  const first = directories[0];
893
1124
  if (!first) return "unknown";
894
- return basename2(String(first).replace(/[\\/]+$/, "")) || "unknown";
1125
+ return basename3(String(first).replace(/[\\/]+$/, "")) || "unknown";
1126
+ }
1127
+ function tokenCount2(value) {
1128
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
1129
+ }
1130
+ function selectUsageSnapshot(current, candidate) {
1131
+ if (!current) return candidate;
1132
+ const total = (entry) => entry.inputTokens + entry.outputTokens + entry.reasoningTokens + entry.cachedTokens;
1133
+ const currentTotal = total(current);
1134
+ const candidateTotal = total(candidate);
1135
+ if (candidateTotal === 0 && currentTotal > 0) return current;
1136
+ if (candidate.timestamp.getTime() !== current.timestamp.getTime()) {
1137
+ return candidate.timestamp > current.timestamp ? candidate : current;
1138
+ }
1139
+ if (candidateTotal !== currentTotal)
1140
+ return candidateTotal > currentTotal ? candidate : current;
1141
+ return current.model === "unknown" && candidate.model !== "unknown" ? candidate : current;
895
1142
  }
896
1143
  var GeminiCliParser = class {
897
1144
  tool = TOOL2;
@@ -900,54 +1147,73 @@ var GeminiCliParser = class {
900
1147
  if (sessionFiles.length === 0) {
901
1148
  return { buckets: [], sessions: [] };
902
1149
  }
903
- const entries = [];
904
- const sessionEvents = [];
1150
+ const entries = /* @__PURE__ */ new Map();
1151
+ const sessionEvents = /* @__PURE__ */ new Map();
1152
+ const storeEntry = (key, entry) => {
1153
+ entries.set(key, selectUsageSnapshot(entries.get(key), entry));
1154
+ };
905
1155
  for (const filePath of sessionFiles) {
906
1156
  const record = readRecord(filePath);
907
1157
  if (!record) continue;
908
1158
  const project = projectFromDirectories(record.directories);
909
- for (const msg of record.messages) {
1159
+ const sessionId = record.sessionId || filePath;
1160
+ for (const [index, msg] of record.messages.entries()) {
1161
+ if (!msg || typeof msg !== "object") continue;
910
1162
  const role = classifyRole(msg);
911
1163
  if (!role) continue;
912
1164
  const timestamp = msg.timestamp || msg.createTime || record.createTime;
913
1165
  if (!timestamp) continue;
914
1166
  const ts = new Date(timestamp);
915
1167
  if (Number.isNaN(ts.getTime())) continue;
916
- sessionEvents.push({
917
- sessionId: filePath,
918
- source: "gemini-cli",
919
- project,
920
- timestamp: ts,
921
- role
922
- });
1168
+ const key = JSON.stringify([
1169
+ sessionId,
1170
+ msg.id || `${filePath}:${index}`
1171
+ ]);
1172
+ if ((sessionEvents.get(key)?.timestamp.getTime() ?? -Infinity) <= ts.getTime())
1173
+ sessionEvents.set(key, {
1174
+ sessionId,
1175
+ source: "gemini-cli",
1176
+ project,
1177
+ timestamp: ts,
1178
+ role
1179
+ });
1180
+ if (role !== "assistant") continue;
923
1181
  const tokens = msg.tokens;
924
1182
  const usage = msg.usage || msg.usageMetadata || msg.token_count;
925
1183
  if (!tokens && !usage) continue;
926
1184
  if (tokens) {
927
- const cached = tokens.cached || 0;
928
- const thoughts = tokens.thoughts || 0;
929
- entries.push({
930
- sessionId: filePath,
1185
+ const cached = tokenCount2(tokens.cached);
1186
+ const input2 = tokenCount2(tokens.input);
1187
+ const thoughts = tokenCount2(tokens.thoughts);
1188
+ storeEntry(key, {
1189
+ sessionId,
931
1190
  source: "gemini-cli",
932
1191
  model: msg.model || record.model || "unknown",
933
1192
  project,
934
1193
  timestamp: ts,
935
- inputTokens: (tokens.input || 0) - cached,
936
- outputTokens: (tokens.output || 0) - thoughts,
1194
+ inputTokens: input2 >= cached ? input2 - cached : input2,
1195
+ outputTokens: tokenCount2(tokens.output),
937
1196
  reasoningTokens: thoughts,
938
1197
  cachedTokens: cached
939
1198
  });
940
1199
  } else if (usage) {
941
- const cached = usage.cachedContentTokenCount || 0;
942
- const thoughts = usage.thoughtsTokenCount || 0;
943
- entries.push({
944
- sessionId: filePath,
1200
+ const cached = tokenCount2(usage.cachedContentTokenCount);
1201
+ const thoughts = tokenCount2(usage.thoughtsTokenCount);
1202
+ const input2 = tokenCount2(
1203
+ usage.promptTokenCount ?? usage.input_tokens
1204
+ );
1205
+ const output = usage.candidatesTokenCount ?? usage.output_tokens;
1206
+ storeEntry(key, {
1207
+ sessionId,
945
1208
  source: "gemini-cli",
946
1209
  model: msg.model || record.model || "unknown",
947
1210
  project,
948
1211
  timestamp: ts,
949
- inputTokens: (usage.promptTokenCount || usage.input_tokens || 0) - cached,
950
- outputTokens: (usage.candidatesTokenCount || usage.output_tokens || 0) - thoughts,
1212
+ inputTokens: input2 >= cached ? input2 - cached : input2,
1213
+ outputTokens: output !== void 0 ? tokenCount2(output) : Math.max(
1214
+ 0,
1215
+ tokenCount2(usage.totalTokenCount) - input2 - thoughts
1216
+ ),
951
1217
  reasoningTokens: thoughts,
952
1218
  cachedTokens: cached
953
1219
  });
@@ -955,15 +1221,18 @@ var GeminiCliParser = class {
955
1221
  }
956
1222
  }
957
1223
  return {
958
- buckets: aggregateToBuckets(entries),
959
- sessions: extractSessions(sessionEvents, entries)
1224
+ buckets: aggregateToBuckets([...entries.values()]),
1225
+ sessions: extractSessions(
1226
+ [...sessionEvents.values()],
1227
+ [...entries.values()]
1228
+ )
960
1229
  };
961
1230
  }
962
1231
  };
963
1232
  registerParser(new GeminiCliParser());
964
1233
 
965
1234
  // src/parsers/hermes.ts
966
- import { existsSync as existsSync6 } from "fs";
1235
+ import { existsSync as existsSync7 } from "fs";
967
1236
  import { homedir as homedir5 } from "os";
968
1237
  import { dirname, join as join6 } from "path";
969
1238
 
@@ -971,53 +1240,6 @@ import { dirname, join as join6 } from "path";
971
1240
  import { execFileSync } from "child_process";
972
1241
  import { statSync } from "fs";
973
1242
  import { pathToFileURL } from "url";
974
-
975
- // src/utils/logger.ts
976
- var LOG_LEVELS = {
977
- debug: 0,
978
- info: 1,
979
- warn: 2,
980
- error: 3
981
- };
982
- var Logger = class {
983
- level;
984
- constructor(level = "info") {
985
- this.level = level;
986
- }
987
- setLevel(level) {
988
- this.level = level;
989
- }
990
- debug(msg) {
991
- if (LOG_LEVELS[this.level] <= LOG_LEVELS.debug) {
992
- process.stderr.write(`[debug] ${msg}
993
- `);
994
- }
995
- }
996
- info(msg) {
997
- if (LOG_LEVELS[this.level] <= LOG_LEVELS.info) {
998
- process.stdout.write(`${msg}
999
- `);
1000
- }
1001
- }
1002
- warn(msg) {
1003
- if (LOG_LEVELS[this.level] <= LOG_LEVELS.warn) {
1004
- process.stderr.write(`warn: ${msg}
1005
- `);
1006
- }
1007
- }
1008
- error(msg) {
1009
- if (LOG_LEVELS[this.level] <= LOG_LEVELS.error) {
1010
- process.stderr.write(`error: ${msg}
1011
- `);
1012
- }
1013
- }
1014
- log(msg) {
1015
- this.info(msg);
1016
- }
1017
- };
1018
- var logger = new Logger();
1019
-
1020
- // src/infrastructure/sqlite.ts
1021
1243
  function withSuppressedSqliteWarning(fn) {
1022
1244
  const originalEmitWarning = process.emitWarning;
1023
1245
  process.emitWarning = ((warning, ...args) => {
@@ -1122,7 +1344,7 @@ async function readSqliteRows(dbPath, query) {
1122
1344
 
1123
1345
  // src/parsers/hermes.ts
1124
1346
  var TOOL_ID3 = "hermes";
1125
- var TOOL_NAME3 = "Hermes Agent";
1347
+ var TOOL_NAME2 = "Hermes Agent";
1126
1348
  var DEFAULT_DB_PATH = join6(homedir5(), ".hermes", "state.db");
1127
1349
  var SESSIONS_QUERY = `SELECT
1128
1350
  id,
@@ -1144,14 +1366,14 @@ var MESSAGES_QUERY = `SELECT
1144
1366
  FROM messages
1145
1367
  WHERE role IN ('user', 'assistant')
1146
1368
  ORDER BY timestamp`;
1147
- function createToolDefinition3(dbPath) {
1369
+ function createToolDefinition2(dbPath) {
1148
1370
  return {
1149
1371
  id: TOOL_ID3,
1150
- name: TOOL_NAME3,
1372
+ name: TOOL_NAME2,
1151
1373
  dataDir: dirname(dbPath)
1152
1374
  };
1153
1375
  }
1154
- function toSafeNumber3(value) {
1376
+ function toSafeNumber2(value) {
1155
1377
  const numberValue = Number(value);
1156
1378
  return Number.isFinite(numberValue) ? numberValue : 0;
1157
1379
  }
@@ -1170,10 +1392,10 @@ var HermesParser = class {
1170
1392
  constructor(options = {}) {
1171
1393
  this.dbPath = options.dbPath || DEFAULT_DB_PATH;
1172
1394
  this.queryRows = options.queryRows || readSqliteRows;
1173
- this.tool = createToolDefinition3(this.dbPath);
1395
+ this.tool = createToolDefinition2(this.dbPath);
1174
1396
  }
1175
1397
  async parse() {
1176
- if (!existsSync6(this.dbPath)) {
1398
+ if (!existsSync7(this.dbPath)) {
1177
1399
  return { buckets: [], sessions: [] };
1178
1400
  }
1179
1401
  const sessionRows = await this.queryRows(
@@ -1186,10 +1408,10 @@ var HermesParser = class {
1186
1408
  if (!sessionId) continue;
1187
1409
  const timestamp = parseUnixTimestamp(row.startedAt);
1188
1410
  if (!timestamp) continue;
1189
- const inputTokens = toSafeNumber3(row.inputTokens);
1190
- const outputTokens = toSafeNumber3(row.outputTokens);
1191
- const cachedTokens = toSafeNumber3(row.cacheReadTokens);
1192
- const reasoningTokens = toSafeNumber3(row.reasoningTokens);
1411
+ const inputTokens = toSafeNumber2(row.inputTokens);
1412
+ const outputTokens = toSafeNumber2(row.outputTokens);
1413
+ const cachedTokens = toSafeNumber2(row.cacheReadTokens);
1414
+ const reasoningTokens = toSafeNumber2(row.reasoningTokens);
1193
1415
  if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && reasoningTokens === 0) {
1194
1416
  continue;
1195
1417
  }
@@ -1239,17 +1461,17 @@ var HermesParser = class {
1239
1461
  };
1240
1462
  }
1241
1463
  isInstalled() {
1242
- return existsSync6(this.dbPath);
1464
+ return existsSync7(this.dbPath);
1243
1465
  }
1244
1466
  };
1245
1467
  registerParser(new HermesParser());
1246
1468
 
1247
1469
  // src/parsers/mimocode.ts
1248
- import { existsSync as existsSync7 } from "fs";
1470
+ import { existsSync as existsSync8 } from "fs";
1249
1471
  import { homedir as homedir6 } from "os";
1250
1472
  import { join as join7 } from "path";
1251
1473
  var TOOL_ID4 = "mimocode";
1252
- var TOOL_NAME4 = "MiMoCode";
1474
+ var TOOL_NAME3 = "MiMoCode";
1253
1475
  var DEFAULT_DB_PATH2 = join7(
1254
1476
  homedir6(),
1255
1477
  ".local",
@@ -1287,7 +1509,7 @@ function getMimocodeDbPaths() {
1287
1509
  ].filter((value) => Boolean(value));
1288
1510
  return Array.from(new Set(paths));
1289
1511
  }
1290
- function toSafeNumber4(value) {
1512
+ function toSafeNumber3(value) {
1291
1513
  const n = Number(value);
1292
1514
  return Number.isFinite(n) && n >= 0 ? Math.round(n) : 0;
1293
1515
  }
@@ -1307,7 +1529,7 @@ var MimocodeParser = class {
1307
1529
  this.resolveDbPaths = resolveDbPaths;
1308
1530
  this.tool = {
1309
1531
  id: TOOL_ID4,
1310
- name: TOOL_NAME4,
1532
+ name: TOOL_NAME3,
1311
1533
  dataDir: DEFAULT_DB_PATH2
1312
1534
  };
1313
1535
  }
@@ -1316,13 +1538,15 @@ var MimocodeParser = class {
1316
1538
  async parse() {
1317
1539
  const allEntries = [];
1318
1540
  const allSessionEvents = [];
1541
+ let incomplete = false;
1319
1542
  for (const dbPath of this.resolveDbPaths()) {
1320
- if (!existsSync7(dbPath)) continue;
1543
+ if (!existsSync8(dbPath)) continue;
1321
1544
  try {
1322
1545
  const { entries, sessionEvents } = await this.parseDb(dbPath);
1323
1546
  allEntries.push(...entries);
1324
1547
  allSessionEvents.push(...sessionEvents);
1325
1548
  } catch (err) {
1549
+ incomplete = true;
1326
1550
  process.stderr.write(
1327
1551
  `warn: mimocode parse failed for ${dbPath} (${err.message})
1328
1552
  `
@@ -1331,11 +1555,12 @@ var MimocodeParser = class {
1331
1555
  }
1332
1556
  return {
1333
1557
  buckets: aggregateToBuckets(allEntries),
1334
- sessions: extractSessions(allSessionEvents, allEntries)
1558
+ sessions: extractSessions(allSessionEvents, allEntries),
1559
+ ...incomplete ? { incomplete: true } : {}
1335
1560
  };
1336
1561
  }
1337
1562
  isInstalled() {
1338
- return this.resolveDbPaths().some((p) => existsSync7(p));
1563
+ return this.resolveDbPaths().some((p) => existsSync8(p));
1339
1564
  }
1340
1565
  async parseDb(dbPath) {
1341
1566
  const messageRows = await readSqliteRows(
@@ -1353,11 +1578,12 @@ var MimocodeParser = class {
1353
1578
  const model = typeof row.modelID === "string" ? row.modelID : "unknown";
1354
1579
  const sessionId = typeof row.sessionId === "string" ? row.sessionId : "unknown";
1355
1580
  const project = extractProject2(row.directory);
1356
- const inputTokens = toSafeNumber4(row.inputTokens);
1357
- const outputTokens = toSafeNumber4(row.outputTokens);
1358
- const reasoningTokens = toSafeNumber4(row.reasoningTokens);
1359
- const cachedTokens = toSafeNumber4(row.cacheReadTokens);
1360
- if (inputTokens + outputTokens + reasoningTokens + cachedTokens === 0) {
1581
+ const inputTokens = toSafeNumber3(row.inputTokens);
1582
+ const outputTokens = toSafeNumber3(row.outputTokens);
1583
+ const reasoningTokens = toSafeNumber3(row.reasoningTokens);
1584
+ const cachedTokens = toSafeNumber3(row.cacheReadTokens);
1585
+ const cacheCreationTokens = toSafeNumber3(row.cacheWriteTokens);
1586
+ if (inputTokens + outputTokens + reasoningTokens + cachedTokens + cacheCreationTokens === 0) {
1361
1587
  continue;
1362
1588
  }
1363
1589
  entries.push({
@@ -1369,7 +1595,8 @@ var MimocodeParser = class {
1369
1595
  inputTokens,
1370
1596
  outputTokens,
1371
1597
  reasoningTokens,
1372
- cachedTokens
1598
+ cachedTokens,
1599
+ cacheCreationTokens
1373
1600
  });
1374
1601
  }
1375
1602
  for (const row of sessionRows) {
@@ -1392,20 +1619,131 @@ var MimocodeParser = class {
1392
1619
  };
1393
1620
  registerParser(new MimocodeParser());
1394
1621
 
1395
- // src/parsers/mirasim.ts
1396
- import { createHash as createHash2 } from "crypto";
1397
- import { existsSync as existsSync8 } from "fs";
1398
- import { homedir as homedir7, hostname as hostname3 } from "os";
1622
+ // src/parsers/mcode.ts
1623
+ import { existsSync as existsSync9 } from "fs";
1624
+ import { homedir as homedir7 } from "os";
1399
1625
  import { join as join8 } from "path";
1626
+ function getMcodeDbPath(env = process.env) {
1627
+ const dataDir = [env.MINIMAX_DATA_DIR, env.MAVIS_DATA_DIR].map((value) => value?.trim()).find(Boolean) || join8(homedir7(), ".minimax");
1628
+ return join8(dataDir, "v2", "sqlite", "runtime-state.sqlite");
1629
+ }
1630
+ function tokenCount3(value) {
1631
+ const count = Number(value);
1632
+ return Number.isFinite(count) && count >= 0 ? Math.trunc(count) : 0;
1633
+ }
1634
+ var McodeParser = class {
1635
+ tool;
1636
+ dbPath;
1637
+ queryRows;
1638
+ constructor(options = {}) {
1639
+ this.dbPath = options.dbPath ?? getMcodeDbPath();
1640
+ this.queryRows = options.queryRows ?? readSqliteRows;
1641
+ this.tool = { id: "mcode", name: "MiniMax Code", dataDir: this.dbPath };
1642
+ }
1643
+ async parse() {
1644
+ if (!existsSync9(this.dbPath)) return { buckets: [], sessions: [] };
1645
+ let rows;
1646
+ try {
1647
+ rows = await this.queryRows(
1648
+ this.dbPath,
1649
+ `SELECT
1650
+ id, session_id AS sessionId, model, ts AS timestamp,
1651
+ input_tokens AS inputTokens, output_tokens AS outputTokens,
1652
+ reasoning_tokens AS reasoningTokens, cache_read_tokens AS cachedTokens,
1653
+ cache_write_tokens AS cacheCreationTokens
1654
+ FROM local_runtime_token_usage ORDER BY id`
1655
+ );
1656
+ } catch (error) {
1657
+ process.stderr.write(
1658
+ `warn: mcode usage parse failed (${error.message})
1659
+ `
1660
+ );
1661
+ return { buckets: [], sessions: [], incomplete: true };
1662
+ }
1663
+ const projects = /* @__PURE__ */ new Map();
1664
+ let incomplete = false;
1665
+ try {
1666
+ const sessions = await this.queryRows(
1667
+ this.dbPath,
1668
+ "SELECT session_id AS sessionId, workspace_dir AS directory FROM local_runtime_sessions"
1669
+ );
1670
+ for (const row of sessions) {
1671
+ const project = row.directory?.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
1672
+ if (project) projects.set(row.sessionId, project);
1673
+ }
1674
+ } catch (error) {
1675
+ if (!error.message.includes(
1676
+ "no such table: local_runtime_sessions"
1677
+ )) {
1678
+ incomplete = true;
1679
+ process.stderr.write(
1680
+ `warn: mcode session metadata parse failed (${error.message})
1681
+ `
1682
+ );
1683
+ }
1684
+ }
1685
+ const entries = [];
1686
+ const events = [];
1687
+ const seen = /* @__PURE__ */ new Set();
1688
+ for (const row of rows) {
1689
+ if (row.timestamp === null || row.timestamp === void 0) continue;
1690
+ const timestamp = new Date(Number(row.timestamp));
1691
+ if (!Number.isFinite(Number(row.timestamp)) || Number.isNaN(timestamp.getTime()))
1692
+ continue;
1693
+ const key = JSON.stringify([row.sessionId, row.id]);
1694
+ if (seen.has(key)) continue;
1695
+ seen.add(key);
1696
+ const nativeModel = row.model || "unknown";
1697
+ const model = nativeModel.includes("/") ? nativeModel.slice(nativeModel.indexOf("/") + 1) : nativeModel;
1698
+ const entry = {
1699
+ sessionId: row.sessionId,
1700
+ source: "mcode",
1701
+ project: projects.get(row.sessionId) || "unknown",
1702
+ model: model || "unknown",
1703
+ timestamp,
1704
+ inputTokens: tokenCount3(row.inputTokens),
1705
+ outputTokens: tokenCount3(row.outputTokens),
1706
+ reasoningTokens: tokenCount3(row.reasoningTokens),
1707
+ cachedTokens: tokenCount3(row.cachedTokens),
1708
+ cacheCreationTokens: tokenCount3(row.cacheCreationTokens)
1709
+ };
1710
+ if (entry.inputTokens + entry.outputTokens + entry.reasoningTokens + entry.cachedTokens + (entry.cacheCreationTokens ?? 0) === 0)
1711
+ continue;
1712
+ entries.push(entry);
1713
+ events.push({
1714
+ sessionId: row.sessionId,
1715
+ source: "mcode",
1716
+ project: entry.project,
1717
+ timestamp,
1718
+ role: "assistant"
1719
+ });
1720
+ }
1721
+ return {
1722
+ buckets: aggregateToBuckets(entries),
1723
+ sessions: extractSessions(events, entries),
1724
+ ...incomplete ? { incomplete: true } : {}
1725
+ };
1726
+ }
1727
+ isInstalled() {
1728
+ return existsSync9(this.dbPath);
1729
+ }
1730
+ };
1731
+ registerParser(new McodeParser());
1732
+
1733
+ // src/parsers/mirasim.ts
1734
+ import { createHash as createHash3 } from "crypto";
1735
+ import { existsSync as existsSync10 } from "fs";
1736
+ import { homedir as homedir8, hostname as hostname3 } from "os";
1737
+ import { join as join9 } from "path";
1400
1738
  var TOOL_ID5 = "mirasim";
1401
- var TOOL_NAME5 = "Mirasim";
1402
- var DEFAULT_INSIGHTS_DIR = join8(homedir7(), ".mirasim", "insights");
1739
+ var TOOL_NAME4 = "Mirasim";
1740
+ var DEFAULT_INSIGHTS_DIR = join9(homedir8(), ".mirasim", "insights");
1403
1741
  var USAGE_FILE_PATTERN = /^usage-\d{4}-\d{2}\.ndjson$/;
1404
1742
  var MIRASIM_OWN_AGENTS = ["gui", "pi-gui"];
1405
1743
  function getInsightsDirs(env = process.env) {
1406
1744
  const dirs = [
1407
1745
  env.TOKEN_ARENA_MIRASIM_DIR,
1408
- env.MIRASIM_HOME ? join8(env.MIRASIM_HOME, "insights") : void 0,
1746
+ env.MIRASIM_HOME ? join9(env.MIRASIM_HOME, "insights") : void 0,
1409
1747
  DEFAULT_INSIGHTS_DIR
1410
1748
  ].filter((value) => Boolean(value));
1411
1749
  return Array.from(new Set(dirs));
@@ -1435,7 +1773,7 @@ function parseIsoDate(value) {
1435
1773
  const timestamp = new Date(raw);
1436
1774
  return Number.isNaN(timestamp.getTime()) ? null : timestamp;
1437
1775
  }
1438
- function getPathLeaf3(value) {
1776
+ function getPathLeaf2(value) {
1439
1777
  if (!value) {
1440
1778
  return "unknown";
1441
1779
  }
@@ -1454,13 +1792,14 @@ function buildSessionUsage2(entries) {
1454
1792
  byModel = /* @__PURE__ */ new Map();
1455
1793
  usageBySession.set(entry.sessionId, byModel);
1456
1794
  }
1457
- const totalTokens = entry.inputTokens + entry.outputTokens + entry.reasoningTokens + entry.cachedTokens;
1795
+ const totalTokens = entry.inputTokens + entry.outputTokens + entry.reasoningTokens + entry.cachedTokens + (entry.cacheCreationTokens ?? 0);
1458
1796
  const existing = byModel.get(entry.model);
1459
1797
  if (existing) {
1460
1798
  existing.inputTokens += entry.inputTokens;
1461
1799
  existing.outputTokens += entry.outputTokens;
1462
1800
  existing.reasoningTokens += entry.reasoningTokens;
1463
1801
  existing.cachedTokens += entry.cachedTokens;
1802
+ existing.cacheCreationTokens = (existing.cacheCreationTokens ?? 0) + (entry.cacheCreationTokens ?? 0);
1464
1803
  existing.totalTokens += totalTokens;
1465
1804
  continue;
1466
1805
  }
@@ -1470,6 +1809,7 @@ function buildSessionUsage2(entries) {
1470
1809
  outputTokens: entry.outputTokens,
1471
1810
  reasoningTokens: entry.reasoningTokens,
1472
1811
  cachedTokens: entry.cachedTokens,
1812
+ cacheCreationTokens: entry.cacheCreationTokens ?? 0,
1473
1813
  totalTokens
1474
1814
  });
1475
1815
  }
@@ -1503,6 +1843,10 @@ function buildSessions(drafts, entries) {
1503
1843
  (sum, usage) => sum + usage.cachedTokens,
1504
1844
  0
1505
1845
  );
1846
+ const cacheCreationTokens = modelUsages.reduce(
1847
+ (sum, usage) => sum + (usage.cacheCreationTokens ?? 0),
1848
+ 0
1849
+ );
1506
1850
  const totalTokens = modelUsages.reduce(
1507
1851
  (sum, usage) => sum + usage.totalTokens,
1508
1852
  0
@@ -1516,7 +1860,7 @@ function buildSessions(drafts, entries) {
1516
1860
  return {
1517
1861
  source: TOOL_ID5,
1518
1862
  project: draft.project,
1519
- sessionHash: createHash2("sha256").update(draft.sessionId).digest("hex").slice(0, 16),
1863
+ sessionHash: createHash3("sha256").update(draft.sessionId).digest("hex").slice(0, 16),
1520
1864
  hostname: host,
1521
1865
  firstMessageAt: draft.firstCallAt.toISOString(),
1522
1866
  lastMessageAt: draft.lastCallEndAt.toISOString(),
@@ -1536,6 +1880,7 @@ function buildSessions(drafts, entries) {
1536
1880
  outputTokens,
1537
1881
  reasoningTokens,
1538
1882
  cachedTokens,
1883
+ cacheCreationTokens,
1539
1884
  totalTokens,
1540
1885
  primaryModel: modelUsages[0]?.model ?? "",
1541
1886
  modelUsages
@@ -1551,7 +1896,7 @@ var MirasimParser = class {
1551
1896
  this.ownAgents = new Set(options.ownAgents ?? MIRASIM_OWN_AGENTS);
1552
1897
  this.tool = {
1553
1898
  id: TOOL_ID5,
1554
- name: TOOL_NAME5,
1899
+ name: TOOL_NAME4,
1555
1900
  dataDir: this.insightsDirs[0] ?? DEFAULT_INSIGHTS_DIR
1556
1901
  };
1557
1902
  }
@@ -1575,12 +1920,13 @@ var MirasimParser = class {
1575
1920
  0,
1576
1921
  toNonNegativeInteger(row.output) - reasoningTokens
1577
1922
  );
1578
- const cachedTokens = toNonNegativeInteger(row.cacheRead) + toNonNegativeInteger(row.cacheWrite);
1579
- if (inputTokens + outputTokens + reasoningTokens + cachedTokens === 0) {
1923
+ const cachedTokens = toNonNegativeInteger(row.cacheRead);
1924
+ const cacheCreationTokens = toNonNegativeInteger(row.cacheWrite);
1925
+ if (inputTokens + outputTokens + reasoningTokens + cachedTokens + cacheCreationTokens === 0) {
1580
1926
  continue;
1581
1927
  }
1582
1928
  const sessionId = getString(row.sessionId);
1583
- const project = getPathLeaf3(getString(row.workspace));
1929
+ const project = getPathLeaf2(getString(row.workspace));
1584
1930
  const callId = getString(row.id) ?? [sessionId ?? "", timestamp.toISOString(), agent, model].join("|");
1585
1931
  if (seenCallIds.has(callId)) continue;
1586
1932
  seenCallIds.add(callId);
@@ -1593,7 +1939,8 @@ var MirasimParser = class {
1593
1939
  inputTokens,
1594
1940
  outputTokens,
1595
1941
  reasoningTokens,
1596
- cachedTokens
1942
+ cachedTokens,
1943
+ cacheCreationTokens
1597
1944
  });
1598
1945
  if (!sessionId) continue;
1599
1946
  const durationMs = toNonNegativeInteger(row.durationMs);
@@ -1626,29 +1973,29 @@ var MirasimParser = class {
1626
1973
  };
1627
1974
  }
1628
1975
  isInstalled() {
1629
- return this.insightsDirs.some((dir) => existsSync8(dir));
1976
+ return this.insightsDirs.some((dir) => existsSync10(dir));
1630
1977
  }
1631
1978
  };
1632
1979
  registerParser(new MirasimParser());
1633
1980
 
1634
1981
  // src/parsers/copilot-cli.ts
1635
- import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync3 } from "fs";
1636
- import { homedir as homedir8 } from "os";
1637
- import { basename as basename3, dirname as dirname2, join as join9 } from "path";
1638
- var ROOT_DIR = join9(homedir8(), ".copilot");
1982
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync3 } from "fs";
1983
+ import { homedir as homedir9 } from "os";
1984
+ import { basename as basename4, dirname as dirname2, join as join10 } from "path";
1985
+ var ROOT_DIR = join10(homedir9(), ".copilot");
1639
1986
  var TOOL3 = {
1640
1987
  id: "copilot-cli",
1641
1988
  name: "GitHub Copilot CLI",
1642
1989
  dataDir: ROOT_DIR
1643
1990
  };
1644
1991
  function collectEventFiles(dir, results, visited) {
1645
- if (!existsSync9(dir) || visited.has(dir)) {
1992
+ if (!existsSync11(dir) || visited.has(dir)) {
1646
1993
  return;
1647
1994
  }
1648
1995
  visited.add(dir);
1649
1996
  try {
1650
1997
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
1651
- const fullPath = join9(dir, entry.name);
1998
+ const fullPath = join10(dir, entry.name);
1652
1999
  if (entry.isDirectory()) {
1653
2000
  collectEventFiles(fullPath, results, visited);
1654
2001
  continue;
@@ -1656,7 +2003,7 @@ function collectEventFiles(dir, results, visited) {
1656
2003
  if (entry.isFile() && entry.name === "events.jsonl") {
1657
2004
  results.push({
1658
2005
  filePath: fullPath,
1659
- sessionId: basename3(dirname2(fullPath))
2006
+ sessionId: basename4(dirname2(fullPath))
1660
2007
  });
1661
2008
  }
1662
2009
  }
@@ -1671,7 +2018,7 @@ function findEventFiles(baseDir) {
1671
2018
  function getProjectFromContext(context) {
1672
2019
  const projectPath = context?.gitRoot || context?.cwd;
1673
2020
  if (!projectPath) return "unknown";
1674
- return basename3(projectPath.replace(/[\\/]+$/, "")) || "unknown";
2021
+ return basename4(projectPath.replace(/[\\/]+$/, "")) || "unknown";
1675
2022
  }
1676
2023
  var CopilotCliParser = class {
1677
2024
  tool = TOOL3;
@@ -1682,6 +2029,7 @@ var CopilotCliParser = class {
1682
2029
  }
1683
2030
  const entries = [];
1684
2031
  const sessionEvents = [];
2032
+ const cumulativeUsage = /* @__PURE__ */ new Map();
1685
2033
  for (const { filePath, sessionId } of eventFiles) {
1686
2034
  let content;
1687
2035
  try {
@@ -1724,10 +2072,42 @@ var CopilotCliParser = class {
1724
2072
  for (const [model, metrics] of Object.entries(modelMetrics)) {
1725
2073
  const usage = metrics?.usage;
1726
2074
  if (!usage) continue;
1727
- const totalInput = usage.inputTokens || 0;
1728
- const cachedRead = usage.cacheReadTokens || 0;
1729
- const output = usage.outputTokens || 0;
1730
- if (totalInput === 0 && cachedRead === 0 && output === 0) {
2075
+ const totalInput = usage.inputTokens ?? 0;
2076
+ const cachedRead = usage.cacheReadTokens ?? 0;
2077
+ const cachedWrite = usage.cacheWriteTokens ?? 0;
2078
+ const output = usage.outputTokens ?? 0;
2079
+ if ([totalInput, cachedRead, cachedWrite, output].some(
2080
+ (count) => !Number.isSafeInteger(count) || count < 0
2081
+ )) {
2082
+ continue;
2083
+ }
2084
+ const key = JSON.stringify([sessionId, model]);
2085
+ const previous = cumulativeUsage.get(key) ?? {
2086
+ inputTokens: 0,
2087
+ outputTokens: 0,
2088
+ cachedTokens: 0,
2089
+ cacheCreationTokens: 0
2090
+ };
2091
+ const inputTokens = Math.max(
2092
+ 0,
2093
+ totalInput - cachedRead - cachedWrite - previous.inputTokens
2094
+ );
2095
+ const outputTokens = Math.max(0, output - previous.outputTokens);
2096
+ const cachedTokens = Math.max(
2097
+ 0,
2098
+ cachedRead - previous.cachedTokens
2099
+ );
2100
+ const cacheCreationTokens = Math.max(
2101
+ 0,
2102
+ cachedWrite - previous.cacheCreationTokens
2103
+ );
2104
+ cumulativeUsage.set(key, {
2105
+ inputTokens: previous.inputTokens + inputTokens,
2106
+ outputTokens: previous.outputTokens + outputTokens,
2107
+ cachedTokens: previous.cachedTokens + cachedTokens,
2108
+ cacheCreationTokens: previous.cacheCreationTokens + cacheCreationTokens
2109
+ });
2110
+ if (inputTokens + outputTokens + cachedTokens + cacheCreationTokens === 0) {
1731
2111
  continue;
1732
2112
  }
1733
2113
  entries.push({
@@ -1736,10 +2116,11 @@ var CopilotCliParser = class {
1736
2116
  model,
1737
2117
  project: currentProject,
1738
2118
  timestamp,
1739
- inputTokens: Math.max(0, totalInput - cachedRead),
1740
- outputTokens: output,
2119
+ inputTokens,
2120
+ outputTokens,
1741
2121
  reasoningTokens: 0,
1742
- cachedTokens: cachedRead
2122
+ cachedTokens,
2123
+ cacheCreationTokens
1743
2124
  });
1744
2125
  }
1745
2126
  } catch {
@@ -1752,30 +2133,30 @@ var CopilotCliParser = class {
1752
2133
  };
1753
2134
  }
1754
2135
  isInstalled() {
1755
- return existsSync9(ROOT_DIR);
2136
+ return existsSync11(ROOT_DIR);
1756
2137
  }
1757
2138
  };
1758
2139
  registerParser(new CopilotCliParser());
1759
2140
 
1760
2141
  // src/parsers/oh-my-pi.ts
1761
- import { existsSync as existsSync10 } from "fs";
1762
- import { homedir as homedir9 } from "os";
1763
- import { join as join10 } from "path";
2142
+ import { existsSync as existsSync12 } from "fs";
2143
+ import { homedir as homedir10 } from "os";
2144
+ import { join as join11 } from "path";
1764
2145
  var TOOL_ID6 = "oh-my-pi";
1765
- var TOOL_NAME6 = "omp";
1766
- var DEFAULT_SESSIONS_DIR2 = join10(homedir9(), ".omp", "agent", "sessions");
1767
- function createToolDefinition4(dataDir) {
2146
+ var TOOL_NAME5 = "omp";
2147
+ var DEFAULT_SESSIONS_DIR2 = join11(homedir10(), ".omp", "agent", "sessions");
2148
+ function createToolDefinition3(dataDir) {
1768
2149
  return {
1769
2150
  id: TOOL_ID6,
1770
- name: TOOL_NAME6,
2151
+ name: TOOL_NAME5,
1771
2152
  dataDir
1772
2153
  };
1773
2154
  }
1774
- function toSafeNumber5(value) {
2155
+ function toSafeNumber4(value) {
1775
2156
  const numberValue = Number(value);
1776
2157
  return Number.isFinite(numberValue) ? numberValue : 0;
1777
2158
  }
1778
- function getPathLeaf4(value) {
2159
+ function getPathLeaf3(value) {
1779
2160
  const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
1780
2161
  const leaf = normalized.split("/").filter(Boolean).pop();
1781
2162
  return leaf || "unknown";
@@ -1786,7 +2167,7 @@ function normalizeForPrefix2(value) {
1786
2167
  function getUsageNumber2(usage, ...keys) {
1787
2168
  for (const key of keys) {
1788
2169
  const value = usage[key];
1789
- const numberValue = toSafeNumber5(value);
2170
+ const numberValue = toSafeNumber4(value);
1790
2171
  if (numberValue > 0) {
1791
2172
  return numberValue;
1792
2173
  }
@@ -1794,7 +2175,7 @@ function getUsageNumber2(usage, ...keys) {
1794
2175
  return 0;
1795
2176
  }
1796
2177
  function extractOhMyPiProjectFromCwd(cwd) {
1797
- return getPathLeaf4(cwd);
2178
+ return getPathLeaf3(cwd);
1798
2179
  }
1799
2180
  function extractOhMyPiProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR2) {
1800
2181
  const normalizedFilePath = normalizeForPrefix2(filePath);
@@ -1811,7 +2192,7 @@ function extractOhMyPiProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DI
1811
2192
  try {
1812
2193
  const decoded = decodeURIComponent(firstSegment);
1813
2194
  if (decoded.includes("/") || decoded.includes("\\")) {
1814
- return getPathLeaf4(decoded);
2195
+ return getPathLeaf3(decoded);
1815
2196
  }
1816
2197
  } catch {
1817
2198
  }
@@ -1821,7 +2202,7 @@ function extractOhMyPiProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DI
1821
2202
  var OhMyPiParser = class {
1822
2203
  constructor(sessionsDir = DEFAULT_SESSIONS_DIR2) {
1823
2204
  this.sessionsDir = sessionsDir;
1824
- this.tool = createToolDefinition4(sessionsDir);
2205
+ this.tool = createToolDefinition3(sessionsDir);
1825
2206
  }
1826
2207
  sessionsDir;
1827
2208
  tool;
@@ -1910,29 +2291,45 @@ var OhMyPiParser = class {
1910
2291
  };
1911
2292
  }
1912
2293
  isInstalled() {
1913
- return existsSync10(this.sessionsDir);
2294
+ return existsSync12(this.sessionsDir);
1914
2295
  }
1915
2296
  };
1916
2297
  registerParser(new OhMyPiParser());
1917
2298
 
1918
2299
  // src/parsers/opencode.ts
1919
2300
  import { execFileSync as execFileSync2 } from "child_process";
1920
- import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync4 } from "fs";
1921
- import { homedir as homedir10 } from "os";
1922
- import { basename as basename4, join as join11 } from "path";
1923
- var DEFAULT_DATA_DIR2 = join11(homedir10(), ".local", "share", "opencode");
2301
+ import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync4 } from "fs";
2302
+ import { homedir as homedir11 } from "os";
2303
+ import { basename as basename5, join as join12 } from "path";
2304
+ var DEFAULT_DATA_DIR2 = join12(homedir11(), ".local", "share", "opencode");
1924
2305
  var TOOL4 = {
1925
2306
  id: "opencode",
1926
2307
  name: "OpenCode",
1927
2308
  dataDir: DEFAULT_DATA_DIR2
1928
2309
  };
2310
+ function hasTokens(tokens) {
2311
+ return [
2312
+ tokens.input,
2313
+ tokens.output,
2314
+ tokens.reasoning,
2315
+ tokens.cache?.read,
2316
+ tokens.cache?.write
2317
+ ].some((value) => typeof value === "number" && value !== 0);
2318
+ }
2319
+ function seenMessage(seen, sessionId, messageId) {
2320
+ if (!sessionId || !messageId) return false;
2321
+ const key = JSON.stringify([sessionId, messageId]);
2322
+ if (seen.has(key)) return true;
2323
+ seen.add(key);
2324
+ return false;
2325
+ }
1929
2326
  function getOpenCodeDataDirs(env = process.env) {
1930
2327
  const dirs = [
1931
2328
  env.TOKEN_ARENA_OPENCODE_DIR,
1932
- env.XDG_DATA_HOME ? join11(env.XDG_DATA_HOME, "opencode") : void 0,
2329
+ env.XDG_DATA_HOME ? join12(env.XDG_DATA_HOME, "opencode") : void 0,
1933
2330
  DEFAULT_DATA_DIR2,
1934
- env.LOCALAPPDATA ? join11(env.LOCALAPPDATA, "opencode") : void 0,
1935
- env.APPDATA ? join11(env.APPDATA, "opencode") : void 0
2331
+ env.LOCALAPPDATA ? join12(env.LOCALAPPDATA, "opencode") : void 0,
2332
+ env.APPDATA ? join12(env.APPDATA, "opencode") : void 0
1936
2333
  ].filter((value) => Boolean(value));
1937
2334
  return Array.from(new Set(dirs));
1938
2335
  }
@@ -2006,16 +2403,21 @@ function readSqliteRowsWithCli2(dbPath, query) {
2006
2403
  );
2007
2404
  }
2008
2405
  var OpenCodeParser = class {
2009
- constructor(resolveRoots = getOpenCodeDataDirs) {
2406
+ constructor(resolveRoots = getOpenCodeDataDirs, queryRows) {
2010
2407
  this.resolveRoots = resolveRoots;
2408
+ this.queryRows = queryRows;
2011
2409
  }
2012
2410
  resolveRoots;
2411
+ queryRows;
2013
2412
  tool = TOOL4;
2014
2413
  async parse() {
2015
2414
  const buckets = [];
2016
2415
  const sessions = [];
2017
- for (const rootDir of this.resolveRoots()) {
2018
- const result = await this.parseRoot(rootDir);
2416
+ const seen = /* @__PURE__ */ new Set();
2417
+ let incomplete = false;
2418
+ for (const rootDir of new Set(this.resolveRoots())) {
2419
+ const result = await this.parseRoot(rootDir, seen);
2420
+ incomplete ||= Boolean(result.incomplete);
2019
2421
  if (result.buckets.length > 0) {
2020
2422
  buckets.push(...result.buckets);
2021
2423
  }
@@ -2023,28 +2425,30 @@ var OpenCodeParser = class {
2023
2425
  sessions.push(...result.sessions);
2024
2426
  }
2025
2427
  }
2026
- return { buckets, sessions };
2428
+ return { buckets, sessions, ...incomplete ? { incomplete: true } : {} };
2027
2429
  }
2028
2430
  isInstalled() {
2029
- return this.resolveRoots().some((dir) => existsSync11(dir));
2431
+ return this.resolveRoots().some((dir) => existsSync13(dir));
2030
2432
  }
2031
- async parseRoot(rootDir) {
2032
- const dbPath = join11(rootDir, "opencode.db");
2033
- const messagesDir = join11(rootDir, "storage", "message");
2034
- if (existsSync11(dbPath)) {
2433
+ async parseRoot(rootDir, seen) {
2434
+ const dbPath = join12(rootDir, "opencode.db");
2435
+ const messagesDir = join12(rootDir, "storage", "message");
2436
+ if (existsSync13(dbPath)) {
2035
2437
  try {
2036
- return await this.parseFromSqlite(dbPath);
2438
+ return await this.parseFromSqlite(dbPath, seen);
2037
2439
  } catch (err) {
2038
2440
  process.stderr.write(
2039
2441
  `warn: opencode sqlite parse failed (${err.message}), trying legacy json...
2040
2442
  `
2041
2443
  );
2444
+ return { ...this.parseFromJson(messagesDir, seen), incomplete: true };
2042
2445
  }
2043
2446
  }
2044
- return this.parseFromJson(messagesDir);
2447
+ return this.parseFromJson(messagesDir, seen);
2045
2448
  }
2046
- async parseFromSqlite(dbPath) {
2449
+ async parseFromSqlite(dbPath, seen) {
2047
2450
  const query = `SELECT
2451
+ id as messageId,
2048
2452
  session_id as sessionID,
2049
2453
  json_extract(data, '$.role') as role,
2050
2454
  json_extract(data, '$.time.created') as created,
@@ -2052,7 +2456,7 @@ var OpenCodeParser = class {
2052
2456
  json_extract(data, '$.tokens') as tokens,
2053
2457
  json_extract(data, '$.path.root') as rootPath
2054
2458
  FROM message`;
2055
- const builtinRows = await readSqliteRowsWithBuiltin2(dbPath, query);
2459
+ const builtinRows = this.queryRows ? await this.queryRows(dbPath, query) : await readSqliteRowsWithBuiltin2(dbPath, query);
2056
2460
  const rows = builtinRows ?? readSqliteRowsWithCli2(dbPath, query);
2057
2461
  if (rows.length === 0) {
2058
2462
  return { buckets: [], sessions: [] };
@@ -2062,9 +2466,10 @@ var OpenCodeParser = class {
2062
2466
  for (const row of rows) {
2063
2467
  const timestamp = new Date(row.created);
2064
2468
  if (Number.isNaN(timestamp.getTime())) continue;
2065
- const project = row.rootPath ? basename4(row.rootPath) : "unknown";
2469
+ const project = row.rootPath ? basename5(row.rootPath) : "unknown";
2066
2470
  const sessionId = row.sessionID || "unknown";
2067
2471
  if (row.role !== "user" && row.role !== "assistant") continue;
2472
+ if (seenMessage(seen, row.sessionID, row.messageId)) continue;
2068
2473
  sessionEvents.push({
2069
2474
  sessionId,
2070
2475
  source: "opencode",
@@ -2072,14 +2477,14 @@ var OpenCodeParser = class {
2072
2477
  timestamp,
2073
2478
  role: row.role === "user" ? "user" : "assistant"
2074
2479
  });
2075
- if (!row.modelID) continue;
2480
+ if (row.role !== "assistant") continue;
2076
2481
  let tokens;
2077
2482
  try {
2078
2483
  tokens = typeof row.tokens === "string" ? JSON.parse(row.tokens) : row.tokens;
2079
2484
  } catch {
2080
2485
  continue;
2081
2486
  }
2082
- if (!tokens || !tokens.input && !tokens.output) continue;
2487
+ if (!tokens || !hasTokens(tokens)) continue;
2083
2488
  entries.push({
2084
2489
  sessionId,
2085
2490
  source: "opencode",
@@ -2089,7 +2494,8 @@ var OpenCodeParser = class {
2089
2494
  inputTokens: tokens.input || 0,
2090
2495
  outputTokens: tokens.output || 0,
2091
2496
  reasoningTokens: tokens.reasoning || 0,
2092
- cachedTokens: tokens.cache?.read || 0
2497
+ cachedTokens: tokens.cache?.read || 0,
2498
+ cacheCreationTokens: tokens.cache?.write || 0
2093
2499
  });
2094
2500
  }
2095
2501
  return {
@@ -2097,8 +2503,8 @@ var OpenCodeParser = class {
2097
2503
  sessions: extractSessions(sessionEvents, entries)
2098
2504
  };
2099
2505
  }
2100
- parseFromJson(messagesDir) {
2101
- if (!existsSync11(messagesDir)) return { buckets: [], sessions: [] };
2506
+ parseFromJson(messagesDir, seen) {
2507
+ if (!existsSync13(messagesDir)) return { buckets: [], sessions: [] };
2102
2508
  const entries = [];
2103
2509
  const sessionEvents = [];
2104
2510
  let sessionDirs;
@@ -2110,7 +2516,7 @@ var OpenCodeParser = class {
2110
2516
  return { buckets: [], sessions: [] };
2111
2517
  }
2112
2518
  for (const sessionDir of sessionDirs) {
2113
- const sessionPath = join11(messagesDir, sessionDir.name);
2519
+ const sessionPath = join12(messagesDir, sessionDir.name);
2114
2520
  let messageFiles;
2115
2521
  try {
2116
2522
  messageFiles = readdirSync4(sessionPath).filter(
@@ -2120,7 +2526,7 @@ var OpenCodeParser = class {
2120
2526
  continue;
2121
2527
  }
2122
2528
  for (const file of messageFiles) {
2123
- const filePath = join11(sessionPath, file);
2529
+ const filePath = join12(sessionPath, file);
2124
2530
  let data;
2125
2531
  try {
2126
2532
  data = JSON.parse(readFileSync4(filePath, "utf-8"));
@@ -2132,8 +2538,10 @@ var OpenCodeParser = class {
2132
2538
  const timestamp = new Date(created);
2133
2539
  if (Number.isNaN(timestamp.getTime())) continue;
2134
2540
  const rootPath = data.path?.root;
2135
- const project = rootPath ? basename4(rootPath) : "unknown";
2541
+ const project = rootPath ? basename5(rootPath) : "unknown";
2136
2542
  if (data.role !== "user" && data.role !== "assistant") continue;
2543
+ if (seenMessage(seen, data.sessionID || sessionDir.name, data.id))
2544
+ continue;
2137
2545
  sessionEvents.push({
2138
2546
  sessionId: sessionDir.name,
2139
2547
  source: "opencode",
@@ -2141,9 +2549,9 @@ var OpenCodeParser = class {
2141
2549
  timestamp,
2142
2550
  role: data.role === "user" ? "user" : "assistant"
2143
2551
  });
2144
- if (!data.modelID) continue;
2552
+ if (data.role !== "assistant") continue;
2145
2553
  const tokens = data.tokens;
2146
- if (!tokens || !tokens.input && !tokens.output) continue;
2554
+ if (!tokens || !hasTokens(tokens)) continue;
2147
2555
  entries.push({
2148
2556
  sessionId: sessionDir.name,
2149
2557
  source: "opencode",
@@ -2153,7 +2561,8 @@ var OpenCodeParser = class {
2153
2561
  inputTokens: tokens.input || 0,
2154
2562
  outputTokens: tokens.output || 0,
2155
2563
  reasoningTokens: tokens.reasoning || 0,
2156
- cachedTokens: tokens.cache?.read || 0
2564
+ cachedTokens: tokens.cache?.read || 0,
2565
+ cacheCreationTokens: tokens.cache?.write || 0
2157
2566
  });
2158
2567
  }
2159
2568
  }
@@ -2166,16 +2575,16 @@ var OpenCodeParser = class {
2166
2575
  registerParser(new OpenCodeParser());
2167
2576
 
2168
2577
  // src/parsers/openclaw.ts
2169
- import { existsSync as existsSync12, readdirSync as readdirSync5, readFileSync as readFileSync5 } from "fs";
2170
- import { homedir as homedir11 } from "os";
2171
- import { join as join12 } from "path";
2578
+ import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync5 } from "fs";
2579
+ import { homedir as homedir12 } from "os";
2580
+ import { join as join13 } from "path";
2172
2581
  var TOOL_ID7 = "openclaw";
2173
- var TOOL_NAME7 = "OpenClaw";
2174
- var DEFAULT_DATA_DIR3 = join12(homedir11(), ".openclaw");
2582
+ var TOOL_NAME6 = "OpenClaw";
2583
+ var DEFAULT_DATA_DIR3 = join13(homedir12(), ".openclaw");
2175
2584
  var LEGACY_ROOT_NAMES = [".clawdbot", ".moltbot", ".moldbot"];
2176
2585
  var TOOL5 = {
2177
2586
  id: TOOL_ID7,
2178
- name: TOOL_NAME7,
2587
+ name: TOOL_NAME6,
2179
2588
  dataDir: DEFAULT_DATA_DIR3
2180
2589
  };
2181
2590
  function getTokens(usage, ...keys) {
@@ -2185,16 +2594,16 @@ function getTokens(usage, ...keys) {
2185
2594
  }
2186
2595
  return 0;
2187
2596
  }
2188
- function getOpenClawRoots(homeDir = homedir11()) {
2189
- const roots = [join12(homeDir, ".openclaw")];
2597
+ function getOpenClawRoots(homeDir = homedir12()) {
2598
+ const roots = [join13(homeDir, ".openclaw")];
2190
2599
  try {
2191
2600
  const profileRoots = readdirSync5(homeDir, { withFileTypes: true }).filter(
2192
2601
  (entry) => entry.isDirectory() && /^\.openclaw-.+/.test(entry.name)
2193
- ).map((entry) => join12(homeDir, entry.name)).sort((left, right) => left.localeCompare(right));
2602
+ ).map((entry) => join13(homeDir, entry.name)).sort((left, right) => left.localeCompare(right));
2194
2603
  roots.push(...profileRoots);
2195
2604
  } catch {
2196
2605
  }
2197
- roots.push(...LEGACY_ROOT_NAMES.map((name) => join12(homeDir, name)));
2606
+ roots.push(...LEGACY_ROOT_NAMES.map((name) => join13(homeDir, name)));
2198
2607
  return Array.from(new Set(roots));
2199
2608
  }
2200
2609
  var OpenClawParser = class {
@@ -2207,8 +2616,8 @@ var OpenClawParser = class {
2207
2616
  const entries = [];
2208
2617
  const sessionEvents = [];
2209
2618
  for (const root of this.resolveRoots()) {
2210
- const agentsDir = join12(root, "agents");
2211
- if (!existsSync12(agentsDir)) continue;
2619
+ const agentsDir = join13(root, "agents");
2620
+ if (!existsSync14(agentsDir)) continue;
2212
2621
  let agentDirs;
2213
2622
  try {
2214
2623
  agentDirs = readdirSync5(agentsDir, { withFileTypes: true }).filter(
@@ -2219,8 +2628,8 @@ var OpenClawParser = class {
2219
2628
  }
2220
2629
  for (const agentDir of agentDirs) {
2221
2630
  const project = agentDir.name;
2222
- const sessionsDir = join12(agentsDir, agentDir.name, "sessions");
2223
- if (!existsSync12(sessionsDir)) continue;
2631
+ const sessionsDir = join13(agentsDir, agentDir.name, "sessions");
2632
+ if (!existsSync14(sessionsDir)) continue;
2224
2633
  let files;
2225
2634
  try {
2226
2635
  files = readdirSync5(sessionsDir).filter((f) => f.endsWith(".jsonl"));
@@ -2228,7 +2637,7 @@ var OpenClawParser = class {
2228
2637
  continue;
2229
2638
  }
2230
2639
  for (const file of files) {
2231
- const filePath = join12(sessionsDir, file);
2640
+ const filePath = join13(sessionsDir, file);
2232
2641
  let content;
2233
2642
  try {
2234
2643
  content = readFileSync5(filePath, "utf-8");
@@ -2301,30 +2710,30 @@ var OpenClawParser = class {
2301
2710
  };
2302
2711
  }
2303
2712
  isInstalled() {
2304
- return this.resolveRoots().some((root) => existsSync12(join12(root, "agents")));
2713
+ return this.resolveRoots().some((root) => existsSync14(join13(root, "agents")));
2305
2714
  }
2306
2715
  };
2307
2716
  registerParser(new OpenClawParser());
2308
2717
 
2309
2718
  // src/parsers/qwen-code.ts
2310
- import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
2311
- import { homedir as homedir12 } from "os";
2312
- import { join as join13 } from "path";
2719
+ import { existsSync as existsSync15, readdirSync as readdirSync6 } from "fs";
2720
+ import { homedir as homedir13 } from "os";
2721
+ import { join as join14 } from "path";
2313
2722
  var TOOL_ID8 = "qwen-code";
2314
- var TOOL_NAME8 = "Qwen Code";
2315
- var DEFAULT_DATA_DIR4 = join13(homedir12(), ".qwen", "tmp");
2316
- function createToolDefinition5(dataDir) {
2723
+ var TOOL_NAME7 = "Qwen Code";
2724
+ var DEFAULT_DATA_DIR4 = join14(homedir13(), ".qwen", "tmp");
2725
+ function createToolDefinition4(dataDir) {
2317
2726
  return {
2318
2727
  id: TOOL_ID8,
2319
- name: TOOL_NAME8,
2728
+ name: TOOL_NAME7,
2320
2729
  dataDir
2321
2730
  };
2322
2731
  }
2323
- function toSafeNumber6(value) {
2732
+ function toSafeNumber5(value) {
2324
2733
  const numberValue = Number(value);
2325
2734
  return Number.isFinite(numberValue) ? numberValue : 0;
2326
2735
  }
2327
- function getPathLeaf5(value) {
2736
+ function getPathLeaf4(value) {
2328
2737
  const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
2329
2738
  const leaf = normalized.split("/").filter(Boolean).pop();
2330
2739
  return leaf || "unknown";
@@ -2334,16 +2743,16 @@ function normalizeForPrefix3(value) {
2334
2743
  }
2335
2744
  function findSessionFiles2(baseDir) {
2336
2745
  const results = [];
2337
- if (!existsSync13(baseDir)) return results;
2746
+ if (!existsSync15(baseDir)) return results;
2338
2747
  try {
2339
2748
  for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
2340
2749
  if (!entry.isDirectory()) continue;
2341
- const chatsDir = join13(baseDir, entry.name, "chats");
2342
- if (!existsSync13(chatsDir)) continue;
2750
+ const chatsDir = join14(baseDir, entry.name, "chats");
2751
+ if (!existsSync15(chatsDir)) continue;
2343
2752
  try {
2344
2753
  for (const file of readdirSync6(chatsDir)) {
2345
2754
  if (file.endsWith(".jsonl")) {
2346
- results.push(join13(chatsDir, file));
2755
+ results.push(join14(chatsDir, file));
2347
2756
  }
2348
2757
  }
2349
2758
  } catch {
@@ -2356,7 +2765,7 @@ function findSessionFiles2(baseDir) {
2356
2765
  }
2357
2766
  function resolveQwenProject(cwd, filePath, dataDir = DEFAULT_DATA_DIR4) {
2358
2767
  if (cwd) {
2359
- return getPathLeaf5(cwd);
2768
+ return getPathLeaf4(cwd);
2360
2769
  }
2361
2770
  const normalizedFilePath = normalizeForPrefix3(filePath);
2362
2771
  const normalizedDataDir = normalizeForPrefix3(dataDir);
@@ -2371,7 +2780,7 @@ function resolveQwenProject(cwd, filePath, dataDir = DEFAULT_DATA_DIR4) {
2371
2780
  var QwenCodeParser = class {
2372
2781
  constructor(dataDir = DEFAULT_DATA_DIR4) {
2373
2782
  this.dataDir = dataDir;
2374
- this.tool = createToolDefinition5(dataDir);
2783
+ this.tool = createToolDefinition4(dataDir);
2375
2784
  }
2376
2785
  dataDir;
2377
2786
  tool;
@@ -2407,10 +2816,10 @@ var QwenCodeParser = class {
2407
2816
  if (obj.type !== "assistant") continue;
2408
2817
  const usage = obj.usageMetadata || obj.usage;
2409
2818
  if (!usage) continue;
2410
- const totalInput = toSafeNumber6(usage.promptTokenCount) || toSafeNumber6(usage.input_tokens);
2411
- const totalOutput = toSafeNumber6(usage.candidatesTokenCount) || toSafeNumber6(usage.output_tokens);
2412
- const cachedTokens = toSafeNumber6(usage.cachedContentTokenCount);
2413
- const reasoningTokens = toSafeNumber6(usage.thoughtsTokenCount);
2819
+ const totalInput = toSafeNumber5(usage.promptTokenCount) || toSafeNumber5(usage.input_tokens);
2820
+ const totalOutput = toSafeNumber5(usage.candidatesTokenCount) || toSafeNumber5(usage.output_tokens);
2821
+ const cachedTokens = toSafeNumber5(usage.cachedContentTokenCount);
2822
+ const reasoningTokens = toSafeNumber5(usage.thoughtsTokenCount);
2414
2823
  if (totalInput === 0 && totalOutput === 0 && cachedTokens === 0 && reasoningTokens === 0) {
2415
2824
  continue;
2416
2825
  }
@@ -2439,19 +2848,19 @@ var QwenCodeParser = class {
2439
2848
  };
2440
2849
  }
2441
2850
  isInstalled() {
2442
- return existsSync13(this.dataDir);
2851
+ return existsSync15(this.dataDir);
2443
2852
  }
2444
2853
  };
2445
2854
  registerParser(new QwenCodeParser());
2446
2855
 
2447
2856
  // src/parsers/kimi-code.ts
2448
- import { existsSync as existsSync14, readdirSync as readdirSync7 } from "fs";
2449
- import { homedir as homedir13 } from "os";
2450
- import { join as join14 } from "path";
2857
+ import { existsSync as existsSync16, readdirSync as readdirSync7 } from "fs";
2858
+ import { homedir as homedir14 } from "os";
2859
+ import { join as join15 } from "path";
2451
2860
  var TOOL_ID9 = "kimi-code";
2452
- var TOOL_NAME9 = "Kimi Code";
2453
- var DEFAULT_SESSIONS_DIR3 = join14(homedir13(), ".kimi-code", "sessions");
2454
- var DEFAULT_CONFIG_PATH = join14(homedir13(), ".kimi-code", "workspaces.json");
2861
+ var TOOL_NAME8 = "Kimi Code";
2862
+ var DEFAULT_SESSIONS_DIR3 = join15(homedir14(), ".kimi-code", "sessions");
2863
+ var DEFAULT_CONFIG_PATH = join15(homedir14(), ".kimi-code", "workspaces.json");
2455
2864
  var USER_EVENT_TYPES = /* @__PURE__ */ new Set([
2456
2865
  "UserMessage",
2457
2866
  "user_message",
@@ -2465,18 +2874,18 @@ var ASSISTANT_EVENT_TYPES = /* @__PURE__ */ new Set([
2465
2874
  "ModelOutput",
2466
2875
  "AssistantOutput"
2467
2876
  ]);
2468
- function createToolDefinition6(dataDir) {
2877
+ function createToolDefinition5(dataDir) {
2469
2878
  return {
2470
2879
  id: TOOL_ID9,
2471
- name: TOOL_NAME9,
2880
+ name: TOOL_NAME8,
2472
2881
  dataDir
2473
2882
  };
2474
2883
  }
2475
- function toSafeNumber7(value) {
2884
+ function toSafeNumber6(value) {
2476
2885
  const numberValue = Number(value);
2477
2886
  return Number.isFinite(numberValue) ? numberValue : 0;
2478
2887
  }
2479
- function getPathLeaf6(value) {
2888
+ function getPathLeaf5(value) {
2480
2889
  const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
2481
2890
  const leaf = normalized.split("/").filter(Boolean).pop();
2482
2891
  return leaf || "unknown";
@@ -2486,29 +2895,29 @@ function isModelPlaceholder(value) {
2486
2895
  }
2487
2896
  function findWireFiles(baseDir) {
2488
2897
  const results = [];
2489
- if (!existsSync14(baseDir)) return results;
2898
+ if (!existsSync16(baseDir)) return results;
2490
2899
  try {
2491
2900
  for (const workDir of readdirSync7(baseDir, { withFileTypes: true })) {
2492
2901
  if (!workDir.isDirectory()) continue;
2493
- const workDirPath = join14(baseDir, workDir.name);
2902
+ const workDirPath = join15(baseDir, workDir.name);
2494
2903
  try {
2495
2904
  for (const session of readdirSync7(workDirPath, {
2496
2905
  withFileTypes: true
2497
2906
  })) {
2498
2907
  if (!session.isDirectory()) continue;
2499
- const newWireFile = join14(
2908
+ const newWireFile = join15(
2500
2909
  workDirPath,
2501
2910
  session.name,
2502
2911
  "agents",
2503
2912
  "main",
2504
2913
  "wire.jsonl"
2505
2914
  );
2506
- if (existsSync14(newWireFile)) {
2915
+ if (existsSync16(newWireFile)) {
2507
2916
  results.push({ filePath: newWireFile, workDirHash: workDir.name });
2508
2917
  continue;
2509
2918
  }
2510
- const legacyWireFile = join14(workDirPath, session.name, "wire.jsonl");
2511
- if (existsSync14(legacyWireFile)) {
2919
+ const legacyWireFile = join15(workDirPath, session.name, "wire.jsonl");
2920
+ if (existsSync16(legacyWireFile)) {
2512
2921
  results.push({
2513
2922
  filePath: legacyWireFile,
2514
2923
  workDirHash: workDir.name
@@ -2558,7 +2967,7 @@ function loadProjectMap(configPath) {
2558
2967
  pathValue = info.root || info.path || info.dir || void 0;
2559
2968
  }
2560
2969
  if (!pathValue) continue;
2561
- const name = typeof info === "object" && info.name ? info.name : getPathLeaf6(pathValue);
2970
+ const name = typeof info === "object" && info.name ? info.name : getPathLeaf5(pathValue);
2562
2971
  projectMap.set(hash, name);
2563
2972
  }
2564
2973
  } catch {
@@ -2572,7 +2981,7 @@ var KimiCodeParser = class {
2572
2981
  constructor(options = {}) {
2573
2982
  this.sessionsDir = options.sessionsDir || DEFAULT_SESSIONS_DIR3;
2574
2983
  this.configPath = options.configPath || DEFAULT_CONFIG_PATH;
2575
- this.tool = createToolDefinition6(this.sessionsDir);
2984
+ this.tool = createToolDefinition5(this.sessionsDir);
2576
2985
  }
2577
2986
  async parse() {
2578
2987
  const wireFiles = findWireFiles(this.sessionsDir);
@@ -2610,14 +3019,17 @@ var KimiCodeParser = class {
2610
3019
  if (!timestamp2 || Number.isNaN(timestamp2.getTime())) continue;
2611
3020
  const usage = obj.usage;
2612
3021
  if (!usage) continue;
2613
- const inputTokens2 = toSafeNumber7(
3022
+ const inputTokens2 = toSafeNumber6(
2614
3023
  usage.input_other ?? usage.inputOther
2615
3024
  );
2616
- const outputTokens2 = toSafeNumber7(usage.output);
2617
- const cachedTokens2 = toSafeNumber7(
3025
+ const outputTokens2 = toSafeNumber6(usage.output);
3026
+ const cachedTokens2 = toSafeNumber6(
2618
3027
  usage.input_cache_read ?? usage.inputCacheRead
2619
3028
  );
2620
- if (inputTokens2 === 0 && outputTokens2 === 0 && cachedTokens2 === 0) {
3029
+ const cacheCreationTokens2 = toSafeNumber6(
3030
+ usage.input_cache_creation ?? usage.inputCacheCreation
3031
+ );
3032
+ if (inputTokens2 === 0 && outputTokens2 === 0 && cachedTokens2 === 0 && cacheCreationTokens2 === 0) {
2621
3033
  continue;
2622
3034
  }
2623
3035
  sessionEvents.push({
@@ -2637,7 +3049,8 @@ var KimiCodeParser = class {
2637
3049
  inputTokens: inputTokens2,
2638
3050
  outputTokens: outputTokens2,
2639
3051
  reasoningTokens: 0,
2640
- cachedTokens: cachedTokens2
3052
+ cachedTokens: cachedTokens2,
3053
+ cacheCreationTokens: cacheCreationTokens2
2641
3054
  });
2642
3055
  continue;
2643
3056
  }
@@ -2666,11 +3079,13 @@ var KimiCodeParser = class {
2666
3079
  if (obj.type !== "StatusUpdate") continue;
2667
3080
  const tokenUsage = payload.token_usage;
2668
3081
  if (!tokenUsage || !timestamp) continue;
2669
- const inputTokens = toSafeNumber7(tokenUsage.input_other);
2670
- const outputTokens = toSafeNumber7(tokenUsage.output);
2671
- const cachedTokens = toSafeNumber7(tokenUsage.input_cache_read);
2672
- const cacheCreateTokens = toSafeNumber7(tokenUsage.input_cache_creation);
2673
- if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && cacheCreateTokens === 0) {
3082
+ const inputTokens = toSafeNumber6(tokenUsage.input_other);
3083
+ const outputTokens = toSafeNumber6(tokenUsage.output);
3084
+ const cachedTokens = toSafeNumber6(tokenUsage.input_cache_read);
3085
+ const cacheCreationTokens = toSafeNumber6(
3086
+ tokenUsage.input_cache_creation
3087
+ );
3088
+ if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && cacheCreationTokens === 0) {
2674
3089
  continue;
2675
3090
  }
2676
3091
  if (payload.message_id) {
@@ -2695,7 +3110,8 @@ var KimiCodeParser = class {
2695
3110
  inputTokens,
2696
3111
  outputTokens,
2697
3112
  reasoningTokens: 0,
2698
- cachedTokens
3113
+ cachedTokens,
3114
+ cacheCreationTokens
2699
3115
  });
2700
3116
  }
2701
3117
  }
@@ -2705,23 +3121,23 @@ var KimiCodeParser = class {
2705
3121
  };
2706
3122
  }
2707
3123
  isInstalled() {
2708
- return existsSync14(this.sessionsDir);
3124
+ return existsSync16(this.sessionsDir);
2709
3125
  }
2710
3126
  };
2711
3127
  registerParser(new KimiCodeParser());
2712
3128
 
2713
3129
  // src/parsers/letcode.ts
2714
- import { existsSync as existsSync15 } from "fs";
2715
- import { homedir as homedir14 } from "os";
2716
- import { basename as basename5, join as join15 } from "path";
3130
+ import { existsSync as existsSync17 } from "fs";
3131
+ import { homedir as homedir15 } from "os";
3132
+ import { basename as basename6, join as join16 } from "path";
2717
3133
  var TOOL_ID10 = "letcode";
2718
- var TOOL_NAME10 = "LetCode";
2719
- var DEFAULT_CONFIG_DIR = join15(homedir14(), ".config", "letcode");
2720
- var DEFAULT_SESSIONS_DIR4 = join15(DEFAULT_CONFIG_DIR, "sessions");
3134
+ var TOOL_NAME9 = "LetCode";
3135
+ var DEFAULT_CONFIG_DIR = join16(homedir15(), ".config", "letcode");
3136
+ var DEFAULT_SESSIONS_DIR4 = join16(DEFAULT_CONFIG_DIR, "sessions");
2721
3137
  function getLetcodeSessionsDirs(env = process.env) {
2722
3138
  const dirs = [
2723
3139
  env.TOKEN_ARENA_LETCODE_DIR,
2724
- env.XDG_CONFIG_HOME ? join15(env.XDG_CONFIG_HOME, "letcode", "sessions") : void 0,
3140
+ env.XDG_CONFIG_HOME ? join16(env.XDG_CONFIG_HOME, "letcode", "sessions") : void 0,
2725
3141
  DEFAULT_SESSIONS_DIR4
2726
3142
  ].filter((value) => Boolean(value));
2727
3143
  return Array.from(new Set(dirs));
@@ -2776,7 +3192,7 @@ var LetcodeParser = class {
2776
3192
  this.sessionsDirs = sessionsDir ? [sessionsDir] : getLetcodeSessionsDirs();
2777
3193
  this.tool = {
2778
3194
  id: TOOL_ID10,
2779
- name: TOOL_NAME10,
3195
+ name: TOOL_NAME9,
2780
3196
  dataDir: this.sessionsDirs[0] ?? DEFAULT_SESSIONS_DIR4
2781
3197
  };
2782
3198
  }
@@ -2790,7 +3206,7 @@ var LetcodeParser = class {
2790
3206
  if (!content) continue;
2791
3207
  const rows = parseJsonl(content);
2792
3208
  if (rows.length === 0) continue;
2793
- const fallbackSessionId = extractSessionId(filePath) || basename5(filePath);
3209
+ const fallbackSessionId = extractSessionId(filePath) || basename6(filePath);
2794
3210
  for (const row of rows) {
2795
3211
  const sessionId = typeof row.session_id === "string" && row.session_id ? row.session_id : fallbackSessionId;
2796
3212
  const timestamp = parseTimestamp2(row.timestamp_ms);
@@ -2851,31 +3267,31 @@ var LetcodeParser = class {
2851
3267
  };
2852
3268
  }
2853
3269
  isInstalled() {
2854
- return this.sessionsDirs.some((dir) => existsSync15(dir));
3270
+ return this.sessionsDirs.some((dir) => existsSync17(dir));
2855
3271
  }
2856
3272
  };
2857
3273
  registerParser(new LetcodeParser());
2858
3274
 
2859
3275
  // src/parsers/droid.ts
2860
- import { existsSync as existsSync16, readdirSync as readdirSync8 } from "fs";
2861
- import { homedir as homedir15 } from "os";
2862
- import { basename as basename6, dirname as dirname3, join as join16 } from "path";
3276
+ import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
3277
+ import { homedir as homedir16 } from "os";
3278
+ import { basename as basename7, dirname as dirname3, join as join17 } from "path";
2863
3279
  var TOOL_ID11 = "droid";
2864
- var TOOL_NAME11 = "Droid";
2865
- var DEFAULT_DATA_DIR5 = join16(homedir15(), ".factory", "sessions");
2866
- function createToolDefinition7(dataDir) {
3280
+ var TOOL_NAME10 = "Droid";
3281
+ var DEFAULT_DATA_DIR5 = join17(homedir16(), ".factory", "sessions");
3282
+ function createToolDefinition6(dataDir) {
2867
3283
  return {
2868
3284
  id: TOOL_ID11,
2869
- name: TOOL_NAME11,
3285
+ name: TOOL_NAME10,
2870
3286
  dataDir
2871
3287
  };
2872
3288
  }
2873
3289
  function findSessionFiles3(dir) {
2874
3290
  const results = [];
2875
- if (!existsSync16(dir)) return results;
3291
+ if (!existsSync18(dir)) return results;
2876
3292
  try {
2877
3293
  for (const entry of readdirSync8(dir, { withFileTypes: true })) {
2878
- const fullPath = join16(dir, entry.name);
3294
+ const fullPath = join17(dir, entry.name);
2879
3295
  if (entry.isDirectory()) {
2880
3296
  results.push(...findSessionFiles3(fullPath));
2881
3297
  } else if (entry.isFile() && entry.name.endsWith(".jsonl") && !entry.name.endsWith(".settings.json")) {
@@ -2890,14 +3306,14 @@ function extractDroidProject(slug) {
2890
3306
  const parts = slug.split("-").filter(Boolean);
2891
3307
  return parts.length > 0 ? parts[parts.length - 1] : "unknown";
2892
3308
  }
2893
- function toSafeNumber8(value) {
3309
+ function toSafeNumber7(value) {
2894
3310
  const numberValue = Number(value);
2895
3311
  return Number.isFinite(numberValue) ? numberValue : 0;
2896
3312
  }
2897
3313
  var DroidParser = class {
2898
3314
  constructor(dataDir = DEFAULT_DATA_DIR5) {
2899
3315
  this.dataDir = dataDir;
2900
- this.tool = createToolDefinition7(dataDir);
3316
+ this.tool = createToolDefinition6(dataDir);
2901
3317
  }
2902
3318
  dataDir;
2903
3319
  tool;
@@ -2910,7 +3326,7 @@ var DroidParser = class {
2910
3326
  const sessionEvents = [];
2911
3327
  for (const filePath of sessionFiles) {
2912
3328
  const sessionId = filePath;
2913
- const project = extractDroidProject(basename6(dirname3(filePath)));
3329
+ const project = extractDroidProject(basename7(dirname3(filePath)));
2914
3330
  let firstMessageTimestamp = null;
2915
3331
  const content = readFileSafe(filePath);
2916
3332
  if (!content) continue;
@@ -2938,9 +3354,9 @@ var DroidParser = class {
2938
3354
  role
2939
3355
  });
2940
3356
  }
2941
- const settingsPath = join16(
3357
+ const settingsPath = join17(
2942
3358
  dirname3(filePath),
2943
- `${basename6(filePath, ".jsonl")}.settings.json`
3359
+ `${basename7(filePath, ".jsonl")}.settings.json`
2944
3360
  );
2945
3361
  const settingsContent = readFileSafe(settingsPath);
2946
3362
  if (!settingsContent || firstMessageTimestamp === null) continue;
@@ -2952,15 +3368,15 @@ var DroidParser = class {
2952
3368
  }
2953
3369
  const tokenUsage = settings.tokenUsage;
2954
3370
  if (!tokenUsage) continue;
2955
- const cachedTokens = toSafeNumber8(tokenUsage.cacheReadTokens);
2956
- const reasoningTokens = toSafeNumber8(tokenUsage.thinkingTokens);
3371
+ const cachedTokens = toSafeNumber7(tokenUsage.cacheReadTokens);
3372
+ const reasoningTokens = toSafeNumber7(tokenUsage.thinkingTokens);
2957
3373
  const inputTokens = Math.max(
2958
3374
  0,
2959
- toSafeNumber8(tokenUsage.inputTokens) - cachedTokens
3375
+ toSafeNumber7(tokenUsage.inputTokens) - cachedTokens
2960
3376
  );
2961
3377
  const outputTokens = Math.max(
2962
3378
  0,
2963
- toSafeNumber8(tokenUsage.outputTokens) - reasoningTokens
3379
+ toSafeNumber7(tokenUsage.outputTokens) - reasoningTokens
2964
3380
  );
2965
3381
  if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && reasoningTokens === 0) {
2966
3382
  continue;
@@ -2983,30 +3399,31 @@ var DroidParser = class {
2983
3399
  };
2984
3400
  }
2985
3401
  isInstalled() {
2986
- return existsSync16(this.dataDir);
3402
+ return existsSync18(this.dataDir);
2987
3403
  }
2988
3404
  };
2989
3405
  registerParser(new DroidParser());
2990
3406
 
2991
3407
  // src/parsers/pi-coding-agent.ts
2992
- import { existsSync as existsSync17 } from "fs";
2993
- import { homedir as homedir16 } from "os";
2994
- import { join as join17 } from "path";
3408
+ import { createHash as createHash4 } from "crypto";
3409
+ import { existsSync as existsSync19, statSync as statSync2 } from "fs";
3410
+ import { homedir as homedir17 } from "os";
3411
+ import { join as join18 } from "path";
2995
3412
  var TOOL_ID12 = "pi-coding-agent";
2996
- var TOOL_NAME12 = "pi";
2997
- var DEFAULT_SESSIONS_DIR5 = join17(homedir16(), ".pi", "agent", "sessions");
2998
- function createToolDefinition8(dataDir) {
3413
+ var TOOL_NAME11 = "pi";
3414
+ var DEFAULT_SESSIONS_DIR5 = join18(homedir17(), ".pi", "agent", "sessions");
3415
+ function createToolDefinition7(dataDir) {
2999
3416
  return {
3000
3417
  id: TOOL_ID12,
3001
- name: TOOL_NAME12,
3418
+ name: TOOL_NAME11,
3002
3419
  dataDir
3003
3420
  };
3004
3421
  }
3005
- function toSafeNumber9(value) {
3422
+ function toSafeNumber8(value) {
3006
3423
  const numberValue = Number(value);
3007
- return Number.isFinite(numberValue) ? numberValue : 0;
3424
+ return Number.isFinite(numberValue) && numberValue >= 0 ? Math.trunc(numberValue) : 0;
3008
3425
  }
3009
- function getPathLeaf7(value) {
3426
+ function getPathLeaf6(value) {
3010
3427
  const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
3011
3428
  const leaf = normalized.split("/").filter(Boolean).pop();
3012
3429
  return leaf || "unknown";
@@ -3017,15 +3434,55 @@ function normalizeForPrefix4(value) {
3017
3434
  function getUsageNumber3(usage, ...keys) {
3018
3435
  for (const key of keys) {
3019
3436
  const value = usage[key];
3020
- const numberValue = toSafeNumber9(value);
3021
- if (numberValue > 0) {
3022
- return numberValue;
3437
+ if (value !== void 0 && value !== null) {
3438
+ return toSafeNumber8(value);
3023
3439
  }
3024
3440
  }
3025
3441
  return 0;
3026
3442
  }
3443
+ function parseTimestamp3(value) {
3444
+ if (value === void 0) return null;
3445
+ const timestamp = new Date(
3446
+ typeof value === "number" && Math.abs(value) <= 1e11 ? value * 1e3 : value
3447
+ );
3448
+ return Number.isNaN(timestamp.getTime()) ? null : timestamp;
3449
+ }
3450
+ function canonicalJson(value) {
3451
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
3452
+ if (value && typeof value === "object") {
3453
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
3454
+ }
3455
+ return JSON.stringify(value) ?? "null";
3456
+ }
3457
+ function usageIdentity(row, kind, usage) {
3458
+ const message = row.message;
3459
+ const semantic = createHash4("sha256").update(
3460
+ canonicalJson({
3461
+ kind,
3462
+ timestamp: row.timestamp,
3463
+ messageTimestamp: message?.timestamp,
3464
+ provider: message?.provider,
3465
+ model: message?.model,
3466
+ responseModel: message?.responseModel,
3467
+ responseId: message?.responseId,
3468
+ api: message?.api,
3469
+ toolCallId: message?.toolCallId,
3470
+ toolName: message?.toolName,
3471
+ stopReason: message?.stopReason,
3472
+ errorMessage: message?.errorMessage,
3473
+ content: message?.content,
3474
+ summary: row.summary,
3475
+ usage
3476
+ })
3477
+ ).digest("hex");
3478
+ return {
3479
+ semantic,
3480
+ request: row.id ? JSON.stringify([kind, row.id, row.timestamp]) : semantic,
3481
+ hasId: Boolean(row.id)
3482
+ };
3483
+ }
3027
3484
  function extractPiProjectFromCwd(cwd) {
3028
- return getPathLeaf7(cwd);
3485
+ return getPathLeaf6(cwd);
3029
3486
  }
3030
3487
  function extractPiProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR5) {
3031
3488
  const normalizedFilePath = normalizeForPrefix4(filePath);
@@ -3042,7 +3499,7 @@ function extractPiProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR5)
3042
3499
  try {
3043
3500
  const decoded = decodeURIComponent(firstSegment);
3044
3501
  if (decoded.includes("/") || decoded.includes("\\")) {
3045
- return getPathLeaf7(decoded);
3502
+ return getPathLeaf6(decoded);
3046
3503
  }
3047
3504
  } catch {
3048
3505
  }
@@ -3052,7 +3509,7 @@ function extractPiProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR5)
3052
3509
  var PiCodingAgentParser = class {
3053
3510
  constructor(sessionsDir = DEFAULT_SESSIONS_DIR5) {
3054
3511
  this.sessionsDir = sessionsDir;
3055
- this.tool = createToolDefinition8(sessionsDir);
3512
+ this.tool = createToolDefinition7(sessionsDir);
3056
3513
  }
3057
3514
  sessionsDir;
3058
3515
  tool;
@@ -3063,7 +3520,9 @@ var PiCodingAgentParser = class {
3063
3520
  }
3064
3521
  const entries = [];
3065
3522
  const sessionEvents = [];
3066
- const seenEntryIds = /* @__PURE__ */ new Set();
3523
+ const seenRequests = /* @__PURE__ */ new Set();
3524
+ const seenSemantics = /* @__PURE__ */ new Set();
3525
+ const legacySemantics = /* @__PURE__ */ new Set();
3067
3526
  for (const filePath of sessionFiles) {
3068
3527
  const content = readFileSafe(filePath);
3069
3528
  if (!content) continue;
@@ -3071,7 +3530,9 @@ var PiCodingAgentParser = class {
3071
3530
  if (rows.length === 0) continue;
3072
3531
  let sessionId = filePath;
3073
3532
  let project = extractPiProjectFromDir(filePath, this.sessionsDir);
3533
+ let sessionTimestamp = null;
3074
3534
  for (const row of rows) {
3535
+ if (!row || typeof row !== "object") continue;
3075
3536
  if (row.type !== "session") continue;
3076
3537
  if (row.id) {
3077
3538
  sessionId = row.id;
@@ -3079,59 +3540,107 @@ var PiCodingAgentParser = class {
3079
3540
  if (row.cwd) {
3080
3541
  project = extractPiProjectFromCwd(row.cwd);
3081
3542
  }
3543
+ sessionTimestamp = parseTimestamp3(row.timestamp);
3082
3544
  break;
3083
3545
  }
3546
+ let fallbackTimestamp = sessionTimestamp;
3547
+ if (!fallbackTimestamp) {
3548
+ try {
3549
+ fallbackTimestamp = statSync2(filePath).mtime;
3550
+ } catch {
3551
+ }
3552
+ }
3084
3553
  for (const row of rows) {
3085
- if (row.type !== "message") continue;
3554
+ if (!row || typeof row !== "object") continue;
3086
3555
  const message = row.message;
3087
- if (!message) continue;
3088
- const rawTimestamp = row.timestamp || message.timestamp;
3089
- if (!rawTimestamp) continue;
3090
- const timestamp = new Date(rawTimestamp);
3091
- if (Number.isNaN(timestamp.getTime())) continue;
3092
- if (message.role === "user" || message.role === "assistant") {
3556
+ const timestamp = parseTimestamp3(row.timestamp) ?? parseTimestamp3(message?.timestamp) ?? fallbackTimestamp;
3557
+ if (!timestamp) continue;
3558
+ if (row.type === "message" && message?.role === "user") {
3093
3559
  sessionEvents.push({
3094
3560
  sessionId,
3095
3561
  source: TOOL_ID12,
3096
3562
  project,
3097
3563
  timestamp,
3098
- role: message.role
3564
+ role: "user"
3099
3565
  });
3566
+ continue;
3567
+ }
3568
+ const kind = row.type === "message" ? message?.role : row.type;
3569
+ if (kind !== "assistant" && kind !== "toolResult" && kind !== "compaction" && kind !== "branch_summary")
3570
+ continue;
3571
+ const usage = row.type === "message" ? message?.usage : row.usage;
3572
+ if (!usage) {
3573
+ if (kind === "assistant")
3574
+ sessionEvents.push({
3575
+ sessionId,
3576
+ source: TOOL_ID12,
3577
+ project,
3578
+ timestamp,
3579
+ role: "assistant"
3580
+ });
3581
+ continue;
3100
3582
  }
3101
- if (message.role !== "assistant") continue;
3102
- const usage = message.usage;
3103
- if (!usage) continue;
3104
3583
  const inputTokens = getUsageNumber3(usage, "input", "inputTokens");
3105
- const outputTokens = getUsageNumber3(usage, "output", "outputTokens");
3584
+ const rawOutput = getUsageNumber3(usage, "output", "outputTokens");
3106
3585
  const cachedTokens = getUsageNumber3(
3107
3586
  usage,
3108
3587
  "cacheRead",
3109
3588
  "cacheReadTokens",
3110
3589
  "cache_read"
3111
3590
  );
3112
- const reasoningTokens = getUsageNumber3(
3591
+ const cacheCreationTokens = getUsageNumber3(
3113
3592
  usage,
3114
- "reasoningOutputTokens",
3115
- "thinkingTokens",
3116
- "thoughts"
3593
+ "cacheWrite",
3594
+ "cacheWriteTokens",
3595
+ "cache_write"
3117
3596
  );
3118
- if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && reasoningTokens === 0) {
3597
+ const reasoningTokens = Math.min(
3598
+ rawOutput,
3599
+ getUsageNumber3(
3600
+ usage,
3601
+ "reasoningOutputTokens",
3602
+ "thinkingTokens",
3603
+ "thoughts"
3604
+ )
3605
+ );
3606
+ const outputTokens = rawOutput - reasoningTokens;
3607
+ if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && cacheCreationTokens === 0 && reasoningTokens === 0) {
3608
+ if (kind === "assistant")
3609
+ sessionEvents.push({
3610
+ sessionId,
3611
+ source: TOOL_ID12,
3612
+ project,
3613
+ timestamp,
3614
+ role: "assistant"
3615
+ });
3119
3616
  continue;
3120
3617
  }
3121
- if (row.id) {
3122
- if (seenEntryIds.has(row.id)) continue;
3123
- seenEntryIds.add(row.id);
3124
- }
3618
+ const identity = usageIdentity(row, kind, usage);
3619
+ if (seenRequests.has(identity.request) || (identity.hasId ? legacySemantics : seenSemantics).has(
3620
+ identity.semantic
3621
+ ))
3622
+ continue;
3623
+ seenRequests.add(identity.request);
3624
+ seenSemantics.add(identity.semantic);
3625
+ if (!identity.hasId) legacySemantics.add(identity.semantic);
3626
+ sessionEvents.push({
3627
+ sessionId,
3628
+ source: TOOL_ID12,
3629
+ project,
3630
+ timestamp,
3631
+ role: "assistant"
3632
+ });
3125
3633
  entries.push({
3126
3634
  sessionId,
3127
3635
  source: TOOL_ID12,
3128
- model: message.model || "unknown",
3636
+ model: kind === "assistant" ? message?.responseModel || message?.model || "unknown" : "unknown",
3129
3637
  project,
3130
3638
  timestamp,
3131
3639
  inputTokens,
3132
3640
  outputTokens,
3133
3641
  reasoningTokens,
3134
- cachedTokens
3642
+ cachedTokens,
3643
+ cacheCreationTokens
3135
3644
  });
3136
3645
  }
3137
3646
  }
@@ -3141,27 +3650,27 @@ var PiCodingAgentParser = class {
3141
3650
  };
3142
3651
  }
3143
3652
  isInstalled() {
3144
- return existsSync17(this.sessionsDir);
3653
+ return existsSync19(this.sessionsDir);
3145
3654
  }
3146
3655
  };
3147
3656
  registerParser(new PiCodingAgentParser());
3148
3657
 
3149
3658
  // src/parsers/qwenpaw.ts
3150
- import { existsSync as existsSync18, readdirSync as readdirSync9 } from "fs";
3151
- import { homedir as homedir17, hostname as hostname4 } from "os";
3152
- import { join as join18 } from "path";
3659
+ import { existsSync as existsSync20, readdirSync as readdirSync9 } from "fs";
3660
+ import { homedir as homedir18, hostname as hostname4 } from "os";
3661
+ import { join as join19 } from "path";
3153
3662
  var TOOL_ID13 = "qwenpaw";
3154
- var TOOL_NAME13 = "QwenPaw";
3155
- var DEFAULT_USAGE_PATH = join18(homedir17(), ".qwenpaw", "token_usage.json");
3156
- var DEFAULT_WORKSPACE_PATH = join18(homedir17(), ".qwenpaw", "workspace");
3157
- function createToolDefinition9(usagePath) {
3663
+ var TOOL_NAME12 = "QwenPaw";
3664
+ var DEFAULT_USAGE_PATH = join19(homedir18(), ".qwenpaw", "token_usage.json");
3665
+ var DEFAULT_WORKSPACE_PATH = join19(homedir18(), ".qwenpaw", "workspace");
3666
+ function createToolDefinition8(usagePath) {
3158
3667
  return {
3159
3668
  id: TOOL_ID13,
3160
- name: TOOL_NAME13,
3669
+ name: TOOL_NAME12,
3161
3670
  dataDir: usagePath
3162
3671
  };
3163
3672
  }
3164
- function toSafeNumber10(value) {
3673
+ function toSafeNumber9(value) {
3165
3674
  const numberValue = Number(value);
3166
3675
  return Number.isFinite(numberValue) && numberValue > 0 ? numberValue : 0;
3167
3676
  }
@@ -3190,7 +3699,7 @@ var QwenPawParser = class {
3190
3699
  constructor(options = {}) {
3191
3700
  this.usagePath = options.usagePath || DEFAULT_USAGE_PATH;
3192
3701
  this.workspacePath = options.workspacePath || DEFAULT_WORKSPACE_PATH;
3193
- this.tool = createToolDefinition9(this.usagePath);
3702
+ this.tool = createToolDefinition8(this.usagePath);
3194
3703
  }
3195
3704
  async parse() {
3196
3705
  const entries = [];
@@ -3208,8 +3717,8 @@ var QwenPawParser = class {
3208
3717
  if (!record || typeof record !== "object") {
3209
3718
  continue;
3210
3719
  }
3211
- const inputTokens = toSafeNumber10(record.prompt_tokens);
3212
- const outputTokens = toSafeNumber10(record.completion_tokens);
3720
+ const inputTokens = toSafeNumber9(record.prompt_tokens);
3721
+ const outputTokens = toSafeNumber9(record.completion_tokens);
3213
3722
  if (inputTokens === 0 && outputTokens === 0) {
3214
3723
  continue;
3215
3724
  }
@@ -3237,15 +3746,15 @@ var QwenPawParser = class {
3237
3746
  }
3238
3747
  async parseWorkspaceSessions() {
3239
3748
  const events = [];
3240
- if (!existsSync18(this.workspacePath)) {
3749
+ if (!existsSync20(this.workspacePath)) {
3241
3750
  return events;
3242
3751
  }
3243
3752
  try {
3244
3753
  const workspaceDirs = readdirSync9(this.workspacePath);
3245
3754
  for (const workspaceDir of workspaceDirs) {
3246
- const workspacePath = join18(this.workspacePath, workspaceDir);
3247
- const chatsPath = join18(workspacePath, "chats.json");
3248
- const sessionsPath = join18(workspacePath, "sessions");
3755
+ const workspacePath = join19(this.workspacePath, workspaceDir);
3756
+ const chatsPath = join19(workspacePath, "chats.json");
3757
+ const sessionsPath = join19(workspacePath, "sessions");
3249
3758
  const chatsContent = readFileSafe(chatsPath);
3250
3759
  if (!chatsContent) {
3251
3760
  continue;
@@ -3289,7 +3798,7 @@ var QwenPawParser = class {
3289
3798
  }
3290
3799
  getSessionFiles(sessionsPath) {
3291
3800
  const files = /* @__PURE__ */ new Map();
3292
- if (!existsSync18(sessionsPath)) {
3801
+ if (!existsSync20(sessionsPath)) {
3293
3802
  return files;
3294
3803
  }
3295
3804
  try {
@@ -3298,7 +3807,7 @@ var QwenPawParser = class {
3298
3807
  if (!fileName.endsWith(".json")) {
3299
3808
  continue;
3300
3809
  }
3301
- const filePath = join18(sessionsPath, fileName);
3810
+ const filePath = join19(sessionsPath, fileName);
3302
3811
  const content = readFileSafe(filePath);
3303
3812
  if (content) {
3304
3813
  const sessionId = fileName.replace(/^[^_]+_/, "").replace(".json", "");
@@ -3396,15 +3905,15 @@ var QwenPawParser = class {
3396
3905
  });
3397
3906
  }
3398
3907
  isInstalled() {
3399
- return existsSync18(this.usagePath) || existsSync18(this.workspacePath);
3908
+ return existsSync20(this.usagePath) || existsSync20(this.workspacePath);
3400
3909
  }
3401
3910
  };
3402
3911
  registerParser(new QwenPawParser());
3403
3912
 
3404
3913
  // src/parsers/cline.ts
3405
- import { readFileSync as readFileSync6, statSync as statSync2 } from "fs";
3406
- import { homedir as homedir18 } from "os";
3407
- import { basename as basename7, join as join19 } from "path";
3914
+ import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
3915
+ import { homedir as homedir19 } from "os";
3916
+ import { basename as basename8, join as join20 } from "path";
3408
3917
  var EXTENSION_ID = "saoudrizwan.claude-dev";
3409
3918
  var HOSTS = [
3410
3919
  "Code",
@@ -3418,28 +3927,28 @@ var HOSTS = [
3418
3927
  var TOOL6 = {
3419
3928
  id: "cline",
3420
3929
  name: "Cline",
3421
- dataDir: join19(homedir18(), ".cline")
3930
+ dataDir: join20(homedir19(), ".cline")
3422
3931
  };
3423
3932
  function getHostRoots() {
3424
3933
  const out = [];
3425
3934
  if (process.platform === "darwin") {
3426
- const base = join19(homedir18(), "Library", "Application Support");
3427
- for (const h of HOSTS) out.push(join19(base, h));
3935
+ const base = join20(homedir19(), "Library", "Application Support");
3936
+ for (const h of HOSTS) out.push(join20(base, h));
3428
3937
  } else if (process.platform === "win32") {
3429
- const appData = process.env.APPDATA?.trim() || join19(homedir18(), "AppData", "Roaming");
3430
- for (const h of HOSTS) out.push(join19(appData, h));
3938
+ const appData = process.env.APPDATA?.trim() || join20(homedir19(), "AppData", "Roaming");
3939
+ for (const h of HOSTS) out.push(join20(appData, h));
3431
3940
  } else {
3432
- const xdg = process.env.XDG_CONFIG_HOME?.trim() || join19(homedir18(), ".config");
3433
- for (const h of HOSTS) out.push(join19(xdg, h));
3941
+ const xdg = process.env.XDG_CONFIG_HOME?.trim() || join20(homedir19(), ".config");
3942
+ for (const h of HOSTS) out.push(join20(xdg, h));
3434
3943
  }
3435
3944
  return out;
3436
3945
  }
3437
3946
  function findClineExtensionDirs() {
3438
3947
  const dirs = [];
3439
3948
  for (const root of getHostRoots()) {
3440
- const ext = join19(root, "User", "globalStorage", EXTENSION_ID);
3949
+ const ext = join20(root, "User", "globalStorage", EXTENSION_ID);
3441
3950
  try {
3442
- if (statSync2(ext).isDirectory()) dirs.push(ext);
3951
+ if (statSync3(ext).isDirectory()) dirs.push(ext);
3443
3952
  } catch {
3444
3953
  }
3445
3954
  }
@@ -3455,7 +3964,7 @@ function readJsonSafe(path) {
3455
3964
  function projectFromPath(absPath) {
3456
3965
  if (!absPath || typeof absPath !== "string") return "unknown";
3457
3966
  const trimmed = absPath.replace(/[\\/]+$/, "");
3458
- const name = basename7(trimmed);
3967
+ const name = basename8(trimmed);
3459
3968
  return name || "unknown";
3460
3969
  }
3461
3970
  var ClineParser = class {
@@ -3469,7 +3978,7 @@ var ClineParser = class {
3469
3978
  const entries = [];
3470
3979
  const sessionEvents = [];
3471
3980
  for (const extDir of extDirs) {
3472
- const history = readJsonSafe(join19(extDir, "state", "taskHistory.json"));
3981
+ const history = readJsonSafe(join20(extDir, "state", "taskHistory.json"));
3473
3982
  if (!Array.isArray(history)) continue;
3474
3983
  for (const item of history) {
3475
3984
  try {
@@ -3480,7 +3989,7 @@ var ClineParser = class {
3480
3989
  );
3481
3990
  const fallbackModel = item.modelId && String(item.modelId).trim() || "cline-unknown";
3482
3991
  const messages = readJsonSafe(
3483
- join19(extDir, "tasks", taskId, "ui_messages.json")
3992
+ join20(extDir, "tasks", taskId, "ui_messages.json")
3484
3993
  );
3485
3994
  if (!Array.isArray(messages)) continue;
3486
3995
  for (const msg of messages) {
@@ -3511,10 +4020,11 @@ var ClineParser = class {
3511
4020
  model,
3512
4021
  project,
3513
4022
  timestamp,
3514
- inputTokens: inputTokens + cacheWrites,
4023
+ inputTokens,
3515
4024
  outputTokens,
3516
4025
  reasoningTokens: 0,
3517
- cachedTokens: cacheReads
4026
+ cachedTokens: cacheReads,
4027
+ cacheCreationTokens: cacheWrites
3518
4028
  });
3519
4029
  }
3520
4030
  if (msgType === "ask" || msgType === "say" && msgSay === "user_feedback") {
@@ -3551,20 +4061,20 @@ registerParser(new ClineParser());
3551
4061
  import { execFileSync as execFileSync3 } from "child_process";
3552
4062
  import {
3553
4063
  copyFileSync,
3554
- existsSync as existsSync19,
4064
+ existsSync as existsSync21,
3555
4065
  mkdtempSync,
3556
4066
  readdirSync as readdirSync10,
3557
4067
  readFileSync as readFileSync7,
3558
4068
  rmSync,
3559
- statSync as statSync3
4069
+ statSync as statSync4
3560
4070
  } from "fs";
3561
- import { homedir as homedir19, tmpdir } from "os";
3562
- import { join as join20, resolve } from "path";
3563
- var KIROAGENT_RELATIVE = join20("User", "globalStorage", "kiro.kiroagent");
4071
+ import { homedir as homedir20, tmpdir } from "os";
4072
+ import { join as join21, resolve } from "path";
4073
+ var KIROAGENT_RELATIVE = join21("User", "globalStorage", "kiro.kiroagent");
3564
4074
  function getDefaultBasePath() {
3565
4075
  if (process.platform === "darwin") {
3566
- return join20(
3567
- homedir19(),
4076
+ return join21(
4077
+ homedir20(),
3568
4078
  "Library",
3569
4079
  "Application Support",
3570
4080
  "Kiro",
@@ -3572,11 +4082,11 @@ function getDefaultBasePath() {
3572
4082
  );
3573
4083
  }
3574
4084
  if (process.platform === "win32") {
3575
- const appData = process.env.APPDATA?.trim() || join20(homedir19(), "AppData", "Roaming");
3576
- return join20(appData, "Kiro", KIROAGENT_RELATIVE);
4085
+ const appData = process.env.APPDATA?.trim() || join21(homedir20(), "AppData", "Roaming");
4086
+ return join21(appData, "Kiro", KIROAGENT_RELATIVE);
3577
4087
  }
3578
- const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim() || join20(homedir19(), ".config");
3579
- return join20(xdgConfigHome, "Kiro", KIROAGENT_RELATIVE);
4088
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim() || join21(homedir20(), ".config");
4089
+ return join21(xdgConfigHome, "Kiro", KIROAGENT_RELATIVE);
3580
4090
  }
3581
4091
  var TOOL7 = {
3582
4092
  id: "kiro",
@@ -3587,10 +4097,10 @@ function getKiroBasePath() {
3587
4097
  const explicit = process.env.KIRO_BASE_PATH?.trim();
3588
4098
  if (explicit) {
3589
4099
  const r = resolve(explicit);
3590
- return existsSync19(r) ? r : null;
4100
+ return existsSync21(r) ? r : null;
3591
4101
  }
3592
4102
  const def = getDefaultBasePath();
3593
- return existsSync19(def) ? def : null;
4103
+ return existsSync21(def) ? def : null;
3594
4104
  }
3595
4105
  function isLockError(err) {
3596
4106
  return err instanceof Error && typeof err.message === "string" && /database is locked/i.test(err.message);
@@ -3611,12 +4121,12 @@ function readDb(dbPath) {
3611
4121
  return queryDb(dbPath, TOKENS_SQL);
3612
4122
  } catch (err) {
3613
4123
  if (!isLockError(err)) throw err;
3614
- const snapshotDir = mkdtempSync(join20(tmpdir(), "vibe-usage-kiro-"));
3615
- const queryPath = join20(snapshotDir, "devdata.sqlite");
4124
+ const snapshotDir = mkdtempSync(join21(tmpdir(), "vibe-usage-kiro-"));
4125
+ const queryPath = join21(snapshotDir, "devdata.sqlite");
3616
4126
  copyFileSync(dbPath, queryPath);
3617
4127
  for (const suffix of ["-shm", "-wal"]) {
3618
4128
  const companion = `${dbPath}${suffix}`;
3619
- if (existsSync19(companion))
4129
+ if (existsSync21(companion))
3620
4130
  copyFileSync(companion, `${queryPath}${suffix}`);
3621
4131
  }
3622
4132
  try {
@@ -3637,7 +4147,7 @@ function readJsonl(jsonlPath) {
3637
4147
  if (lines.length === 0) return [];
3638
4148
  let mtime;
3639
4149
  try {
3640
- mtime = statSync3(jsonlPath).mtime;
4150
+ mtime = statSync4(jsonlPath).mtime;
3641
4151
  } catch {
3642
4152
  mtime = /* @__PURE__ */ new Date();
3643
4153
  }
@@ -3668,7 +4178,7 @@ function buildModelTimeline(base) {
3668
4178
  }
3669
4179
  for (const entry of entries) {
3670
4180
  if (!entry.isDirectory() || entry.name === "dev_data") continue;
3671
- const dirPath = join20(base, entry.name);
4181
+ const dirPath = join21(base, entry.name);
3672
4182
  let files;
3673
4183
  try {
3674
4184
  files = readdirSync10(dirPath).filter((f) => f.endsWith(".chat"));
@@ -3677,7 +4187,7 @@ function buildModelTimeline(base) {
3677
4187
  }
3678
4188
  for (const file of files) {
3679
4189
  try {
3680
- const data = JSON.parse(readFileSync7(join20(dirPath, file), "utf-8"));
4190
+ const data = JSON.parse(readFileSync7(join21(dirPath, file), "utf-8"));
3681
4191
  const meta = data?.metadata;
3682
4192
  if (!meta?.modelId || !meta?.startTime) continue;
3683
4193
  const startMs = Number(meta.startTime);
@@ -3726,13 +4236,13 @@ var KiroParser = class {
3726
4236
  async parse() {
3727
4237
  const base = getKiroBasePath();
3728
4238
  if (!base) return { buckets: [], sessions: [] };
3729
- const dbPath = join20(base, "dev_data", "devdata.sqlite");
3730
- const jsonlPath = join20(base, "dev_data", "tokens_generated.jsonl");
4239
+ const dbPath = join21(base, "dev_data", "devdata.sqlite");
4240
+ const jsonlPath = join21(base, "dev_data", "tokens_generated.jsonl");
3731
4241
  let rows;
3732
4242
  try {
3733
- if (existsSync19(dbPath)) {
4243
+ if (existsSync21(dbPath)) {
3734
4244
  rows = readDb(dbPath);
3735
- } else if (existsSync19(jsonlPath)) {
4245
+ } else if (existsSync21(jsonlPath)) {
3736
4246
  rows = readJsonl(jsonlPath);
3737
4247
  } else {
3738
4248
  return { buckets: [], sessions: [] };
@@ -3782,9 +4292,9 @@ var KiroParser = class {
3782
4292
  registerParser(new KiroParser());
3783
4293
 
3784
4294
  // src/parsers/roo-code.ts
3785
- import { readdirSync as readdirSync11, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
3786
- import { homedir as homedir20 } from "os";
3787
- import { basename as basename8, join as join21 } from "path";
4295
+ import { readdirSync as readdirSync11, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
4296
+ import { homedir as homedir21 } from "os";
4297
+ import { basename as basename9, join as join22 } from "path";
3788
4298
  var EXTENSION_ID2 = "rooveterinaryinc.roo-cline";
3789
4299
  var HOSTS2 = [
3790
4300
  "Code",
@@ -3799,17 +4309,17 @@ function getHostRoots2() {
3799
4309
  const out = [];
3800
4310
  let roots;
3801
4311
  if (process.platform === "darwin") {
3802
- roots = [join21(homedir20(), "Library", "Application Support")];
4312
+ roots = [join22(homedir21(), "Library", "Application Support")];
3803
4313
  } else if (process.platform === "win32") {
3804
4314
  roots = [
3805
- process.env.APPDATA?.trim() || join21(homedir20(), "AppData", "Roaming")
4315
+ process.env.APPDATA?.trim() || join22(homedir21(), "AppData", "Roaming")
3806
4316
  ];
3807
4317
  } else {
3808
- roots = [process.env.XDG_CONFIG_HOME?.trim() || join21(homedir20(), ".config")];
4318
+ roots = [process.env.XDG_CONFIG_HOME?.trim() || join22(homedir21(), ".config")];
3809
4319
  }
3810
4320
  for (const root of roots) {
3811
4321
  for (const h of HOSTS2) {
3812
- out.push(join21(root, h));
4322
+ out.push(join22(root, h));
3813
4323
  }
3814
4324
  }
3815
4325
  return out;
@@ -3817,9 +4327,9 @@ function getHostRoots2() {
3817
4327
  function findExtensionDirs() {
3818
4328
  const dirs = [];
3819
4329
  for (const root of getHostRoots2()) {
3820
- const ext = join21(root, "User", "globalStorage", EXTENSION_ID2);
4330
+ const ext = join22(root, "User", "globalStorage", EXTENSION_ID2);
3821
4331
  try {
3822
- if (statSync4(ext).isDirectory()) dirs.push(ext);
4332
+ if (statSync5(ext).isDirectory()) dirs.push(ext);
3823
4333
  } catch {
3824
4334
  }
3825
4335
  }
@@ -3835,13 +4345,13 @@ function readJsonSafe2(path) {
3835
4345
  function projectFromPath2(absPath) {
3836
4346
  if (!absPath || typeof absPath !== "string") return "unknown";
3837
4347
  const trimmed = absPath.replace(/[\\/]+$/, "");
3838
- const name = basename8(trimmed);
4348
+ const name = basename9(trimmed);
3839
4349
  return name || "unknown";
3840
4350
  }
3841
4351
  function readHistoryItems(extDir) {
3842
- const tasksDir = join21(extDir, "tasks");
4352
+ const tasksDir = join22(extDir, "tasks");
3843
4353
  const index = readJsonSafe2(
3844
- join21(tasksDir, "_index.json")
4354
+ join22(tasksDir, "_index.json")
3845
4355
  );
3846
4356
  if (index?.entries && Array.isArray(index.entries)) return index.entries;
3847
4357
  const items = [];
@@ -3855,7 +4365,7 @@ function readHistoryItems(extDir) {
3855
4365
  if (!entry.isDirectory() || entry.name.startsWith("_") || entry.name.startsWith("."))
3856
4366
  continue;
3857
4367
  const item = readJsonSafe2(
3858
- join21(tasksDir, entry.name, "history_item.json")
4368
+ join22(tasksDir, entry.name, "history_item.json")
3859
4369
  );
3860
4370
  if (item && typeof item === "object") items.push(item);
3861
4371
  }
@@ -3882,7 +4392,7 @@ var RooCodeParser = class {
3882
4392
  const project = projectFromPath2(item.workspace);
3883
4393
  const fallbackModel = item.apiConfigName && String(item.apiConfigName).trim() || "roo-unknown";
3884
4394
  const messages = readJsonSafe2(
3885
- join21(extDir, "tasks", taskId, "ui_messages.json")
4395
+ join22(extDir, "tasks", taskId, "ui_messages.json")
3886
4396
  );
3887
4397
  if (!Array.isArray(messages)) continue;
3888
4398
  for (const msg of messages) {
@@ -3910,10 +4420,11 @@ var RooCodeParser = class {
3910
4420
  model,
3911
4421
  project,
3912
4422
  timestamp,
3913
- inputTokens: inputTokens + cacheWrites,
4423
+ inputTokens,
3914
4424
  outputTokens,
3915
4425
  reasoningTokens: 0,
3916
- cachedTokens: cacheReads
4426
+ cachedTokens: cacheReads,
4427
+ cacheCreationTokens: cacheWrites
3917
4428
  });
3918
4429
  events.push({
3919
4430
  sessionId: taskId,
@@ -3946,10 +4457,10 @@ var RooCodeParser = class {
3946
4457
  registerParser(new RooCodeParser());
3947
4458
 
3948
4459
  // src/parsers/snow.ts
3949
- import { existsSync as existsSync20 } from "fs";
3950
- import { homedir as homedir21 } from "os";
3951
- import { join as join22 } from "path";
3952
- var DEFAULT_DATA_DIR6 = join22(homedir21(), ".snow", "usage");
4460
+ import { existsSync as existsSync22 } from "fs";
4461
+ import { homedir as homedir22 } from "os";
4462
+ import { join as join23 } from "path";
4463
+ var DEFAULT_DATA_DIR6 = join23(homedir22(), ".snow", "usage");
3953
4464
  function toNonNegativeNumber2(value) {
3954
4465
  const numberValue = Number(value);
3955
4466
  return Number.isFinite(numberValue) && numberValue >= 0 ? numberValue : 0;
@@ -3996,26 +4507,26 @@ var SnowParser = class {
3996
4507
  return { buckets: aggregateToBuckets(entries), sessions: [] };
3997
4508
  }
3998
4509
  isInstalled() {
3999
- return existsSync20(this.dataDir);
4510
+ return existsSync22(this.dataDir);
4000
4511
  }
4001
4512
  };
4002
4513
  registerParser(new SnowParser());
4003
4514
 
4004
4515
  // src/parsers/cursor.ts
4005
4516
  import { execFileSync as execFileSync4 } from "child_process";
4006
- import { copyFileSync as copyFileSync2, existsSync as existsSync21, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
4007
- import { homedir as homedir22, tmpdir as tmpdir2 } from "os";
4008
- import { dirname as dirname4, join as join23, resolve as resolve2 } from "path";
4517
+ import { copyFileSync as copyFileSync2, existsSync as existsSync23, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
4518
+ import { homedir as homedir23, tmpdir as tmpdir2 } from "os";
4519
+ import { dirname as dirname4, join as join24, resolve as resolve2 } from "path";
4009
4520
  var TOOL_ID14 = "cursor";
4010
- var TOOL_NAME14 = "Cursor";
4011
- var STATE_DB_RELATIVE = join23("User", "globalStorage", "state.vscdb");
4521
+ var TOOL_NAME13 = "Cursor";
4522
+ var STATE_DB_RELATIVE = join24("User", "globalStorage", "state.vscdb");
4012
4523
  var ACCESS_TOKEN_KEY = "cursorAuth/accessToken";
4013
4524
  var SESSION_COOKIE = "WorkosCursorSessionToken";
4014
4525
  var FETCH_TIMEOUT_MS = 1e4;
4015
4526
  function getDefaultStateDbPath() {
4016
4527
  if (process.platform === "darwin") {
4017
- return join23(
4018
- homedir22(),
4528
+ return join24(
4529
+ homedir23(),
4019
4530
  "Library",
4020
4531
  "Application Support",
4021
4532
  "Cursor",
@@ -4023,25 +4534,25 @@ function getDefaultStateDbPath() {
4023
4534
  );
4024
4535
  }
4025
4536
  if (process.platform === "win32") {
4026
- const appData = process.env.APPDATA?.trim() || join23(homedir22(), "AppData", "Roaming");
4027
- return join23(appData, "Cursor", STATE_DB_RELATIVE);
4537
+ const appData = process.env.APPDATA?.trim() || join24(homedir23(), "AppData", "Roaming");
4538
+ return join24(appData, "Cursor", STATE_DB_RELATIVE);
4028
4539
  }
4029
- const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim() || join23(homedir22(), ".config");
4030
- return join23(xdgConfigHome, "Cursor", STATE_DB_RELATIVE);
4540
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim() || join24(homedir23(), ".config");
4541
+ return join24(xdgConfigHome, "Cursor", STATE_DB_RELATIVE);
4031
4542
  }
4032
4543
  function getCursorStateDbPath() {
4033
4544
  const explicit = process.env.CURSOR_STATE_DB_PATH?.trim();
4034
4545
  if (explicit) {
4035
4546
  const resolved = resolve2(explicit);
4036
- return existsSync21(resolved) ? resolved : null;
4547
+ return existsSync23(resolved) ? resolved : null;
4037
4548
  }
4038
4549
  const configDirs = process.env.CURSOR_CONFIG_DIR?.trim();
4039
4550
  const candidates = configDirs ? configDirs.split(",").map((v) => v.trim()).filter(Boolean).map((v) => {
4040
4551
  const r = resolve2(v);
4041
- return r.endsWith(".vscdb") ? r : join23(r, STATE_DB_RELATIVE);
4552
+ return r.endsWith(".vscdb") ? r : join24(r, STATE_DB_RELATIVE);
4042
4553
  }) : [getDefaultStateDbPath()];
4043
4554
  for (const c of candidates) {
4044
- if (existsSync21(c)) return c;
4555
+ if (existsSync23(c)) return c;
4045
4556
  }
4046
4557
  return null;
4047
4558
  }
@@ -4081,13 +4592,13 @@ function readAccessToken(dbPath) {
4081
4592
  return queryAccessToken(dbPath);
4082
4593
  } catch (err) {
4083
4594
  if (!isLockError2(err)) throw err;
4084
- const snapshotDir = mkdtempSync2(join23(tmpdir2(), "tokenarena-cursor-"));
4085
- const queryPath = join23(snapshotDir, "state.vscdb");
4595
+ const snapshotDir = mkdtempSync2(join24(tmpdir2(), "tokenarena-cursor-"));
4596
+ const queryPath = join24(snapshotDir, "state.vscdb");
4086
4597
  try {
4087
4598
  copyFileSync2(dbPath, queryPath);
4088
4599
  for (const suffix of ["-shm", "-wal"]) {
4089
4600
  const companion = `${dbPath}${suffix}`;
4090
- if (existsSync21(companion))
4601
+ if (existsSync23(companion))
4091
4602
  copyFileSync2(companion, `${queryPath}${suffix}`);
4092
4603
  }
4093
4604
  return queryAccessToken(queryPath);
@@ -4213,10 +4724,10 @@ function parseInt0(value) {
4213
4724
  const n = Number(String(value).replace(/,/g, "").trim());
4214
4725
  return Number.isFinite(n) && n > 0 ? Math.round(n) : 0;
4215
4726
  }
4216
- function createToolDefinition10(dbPath) {
4727
+ function createToolDefinition9(dbPath) {
4217
4728
  return {
4218
4729
  id: TOOL_ID14,
4219
- name: TOOL_NAME14,
4730
+ name: TOOL_NAME13,
4220
4731
  dataDir: dirname4(dbPath)
4221
4732
  };
4222
4733
  }
@@ -4229,10 +4740,10 @@ var CursorParser = class {
4229
4740
  this.dbPath = options.dbPath || getCursorStateDbPath() || "";
4230
4741
  this.readToken = options.readToken || readAccessToken;
4231
4742
  this.fetchCsv = options.fetchCsv || fetchUsageCsv;
4232
- this.tool = createToolDefinition10(this.dbPath);
4743
+ this.tool = createToolDefinition9(this.dbPath);
4233
4744
  }
4234
4745
  async parse() {
4235
- if (!this.dbPath || !existsSync21(this.dbPath)) {
4746
+ if (!this.dbPath || !existsSync23(this.dbPath)) {
4236
4747
  return { buckets: [], sessions: [] };
4237
4748
  }
4238
4749
  let token;
@@ -4283,9 +4794,10 @@ var CursorParser = class {
4283
4794
  model,
4284
4795
  project: "unknown",
4285
4796
  timestamp,
4286
- inputTokens: inputCacheWrite + inputNoCache,
4797
+ inputTokens: inputNoCache,
4287
4798
  outputTokens: output,
4288
4799
  cachedTokens: cacheRead,
4800
+ cacheCreationTokens: inputCacheWrite,
4289
4801
  reasoningTokens: 0
4290
4802
  });
4291
4803
  }
@@ -4295,19 +4807,19 @@ var CursorParser = class {
4295
4807
  };
4296
4808
  }
4297
4809
  isInstalled() {
4298
- return existsSync21(this.dbPath);
4810
+ return existsSync23(this.dbPath);
4299
4811
  }
4300
4812
  };
4301
4813
  registerParser(new CursorParser());
4302
4814
 
4303
4815
  // src/parsers/zcode.ts
4304
- import { createHash as createHash3 } from "crypto";
4305
- import { existsSync as existsSync22 } from "fs";
4306
- import { homedir as homedir23, hostname as hostname5 } from "os";
4307
- import { dirname as dirname5, join as join24 } from "path";
4816
+ import { createHash as createHash5 } from "crypto";
4817
+ import { existsSync as existsSync24 } from "fs";
4818
+ import { homedir as homedir24, hostname as hostname5 } from "os";
4819
+ import { dirname as dirname5, join as join25 } from "path";
4308
4820
  var TOOL_ID15 = "zcode";
4309
- var TOOL_NAME15 = "ZCode";
4310
- var DEFAULT_DB_PATH3 = join24(homedir23(), ".zcode", "cli", "db", "db.sqlite");
4821
+ var TOOL_NAME14 = "ZCode";
4822
+ var DEFAULT_DB_PATH3 = join25(homedir24(), ".zcode", "cli", "db", "db.sqlite");
4311
4823
  var MODEL_USAGE_QUERY = `SELECT
4312
4824
  model_usage.session_id as sessionId,
4313
4825
  session.directory as directory,
@@ -4340,14 +4852,14 @@ var TURN_USAGE_QUERY = `SELECT
4340
4852
  duration_ms as durationMs
4341
4853
  FROM turn_usage
4342
4854
  WHERE duration_ms IS NOT NULL`;
4343
- function createToolDefinition11(dbPath) {
4855
+ function createToolDefinition10(dbPath) {
4344
4856
  return {
4345
4857
  id: TOOL_ID15,
4346
- name: TOOL_NAME15,
4858
+ name: TOOL_NAME14,
4347
4859
  dataDir: dirname5(dbPath)
4348
4860
  };
4349
4861
  }
4350
- function toSafeNumber11(value) {
4862
+ function toSafeNumber10(value) {
4351
4863
  const numberValue = Number(value);
4352
4864
  return Number.isFinite(numberValue) && numberValue > 0 ? numberValue : 0;
4353
4865
  }
@@ -4362,7 +4874,7 @@ function parseUnixMillis(value) {
4362
4874
  const timestamp = new Date(numberValue);
4363
4875
  return Number.isNaN(timestamp.getTime()) ? null : timestamp;
4364
4876
  }
4365
- function getPathLeaf8(value) {
4877
+ function getPathLeaf7(value) {
4366
4878
  if (!value) {
4367
4879
  return "unknown";
4368
4880
  }
@@ -4447,7 +4959,7 @@ function buildSessions2(input2) {
4447
4959
  const draft = getOrCreateDraft(
4448
4960
  drafts,
4449
4961
  sessionId,
4450
- getPathLeaf8(getString3(row.directory))
4962
+ getPathLeaf7(getString3(row.directory))
4451
4963
  );
4452
4964
  draft.fallbackFirstAt = parseUnixMillis(row.timeCreated);
4453
4965
  draft.fallbackLastAt = parseUnixMillis(row.timeUpdated);
@@ -4485,7 +4997,7 @@ function buildSessions2(input2) {
4485
4997
  continue;
4486
4998
  }
4487
4999
  const draft = getOrCreateDraft(drafts, sessionId, "unknown");
4488
- draft.activeSeconds += Math.round(toSafeNumber11(row.durationMs) / 1e3);
5000
+ draft.activeSeconds += Math.round(toSafeNumber10(row.durationMs) / 1e3);
4489
5001
  }
4490
5002
  const usageBySession = buildSessionUsage3(input2.entries);
4491
5003
  const host = hostname5().replace(/\.local$/, "");
@@ -4530,7 +5042,7 @@ function buildSessions2(input2) {
4530
5042
  return {
4531
5043
  source: draft.source,
4532
5044
  project: draft.project,
4533
- sessionHash: createHash3("sha256").update(draft.sessionId).digest("hex").slice(0, 16),
5045
+ sessionHash: createHash5("sha256").update(draft.sessionId).digest("hex").slice(0, 16),
4534
5046
  hostname: host,
4535
5047
  firstMessageAt: firstMessageAt.toISOString(),
4536
5048
  lastMessageAt: lastMessageAt.toISOString(),
@@ -4558,10 +5070,10 @@ var ZCodeParser = class {
4558
5070
  constructor(options = {}) {
4559
5071
  this.dbPath = options.dbPath || DEFAULT_DB_PATH3;
4560
5072
  this.queryRows = options.queryRows || readSqliteRows;
4561
- this.tool = createToolDefinition11(this.dbPath);
5073
+ this.tool = createToolDefinition10(this.dbPath);
4562
5074
  }
4563
5075
  async parse() {
4564
- if (!existsSync22(this.dbPath)) {
5076
+ if (!existsSync24(this.dbPath)) {
4565
5077
  return { buckets: [], sessions: [] };
4566
5078
  }
4567
5079
  const usageRows = await this.queryRows(
@@ -4574,10 +5086,10 @@ var ZCodeParser = class {
4574
5086
  if (!timestamp) {
4575
5087
  continue;
4576
5088
  }
4577
- const inputTokens = toSafeNumber11(row.inputTokens);
4578
- const outputTokens = toSafeNumber11(row.outputTokens);
4579
- const reasoningTokens = toSafeNumber11(row.reasoningTokens);
4580
- const cachedTokens = toSafeNumber11(row.cacheReadInputTokens);
5089
+ const inputTokens = toSafeNumber10(row.inputTokens);
5090
+ const outputTokens = toSafeNumber10(row.outputTokens);
5091
+ const reasoningTokens = toSafeNumber10(row.reasoningTokens);
5092
+ const cachedTokens = toSafeNumber10(row.cacheReadInputTokens);
4581
5093
  if (inputTokens === 0 && outputTokens === 0 && reasoningTokens === 0 && cachedTokens === 0) {
4582
5094
  continue;
4583
5095
  }
@@ -4585,7 +5097,7 @@ var ZCodeParser = class {
4585
5097
  sessionId: getString3(row.sessionId) ?? void 0,
4586
5098
  source: TOOL_ID15,
4587
5099
  model: getString3(row.model) ?? "unknown",
4588
- project: getPathLeaf8(getString3(row.directory)),
5100
+ project: getPathLeaf7(getString3(row.directory)),
4589
5101
  timestamp,
4590
5102
  inputTokens,
4591
5103
  outputTokens,
@@ -4619,31 +5131,31 @@ var ZCodeParser = class {
4619
5131
  };
4620
5132
  }
4621
5133
  isInstalled() {
4622
- return existsSync22(this.dbPath);
5134
+ return existsSync24(this.dbPath);
4623
5135
  }
4624
5136
  };
4625
5137
  registerParser(new ZCodeParser());
4626
5138
 
4627
5139
  // src/parsers/qodercli.ts
4628
- import { existsSync as existsSync23, readdirSync as readdirSync12 } from "fs";
4629
- import { homedir as homedir24 } from "os";
4630
- import { basename as basename9, join as join25 } from "path";
5140
+ import { existsSync as existsSync25, readdirSync as readdirSync12 } from "fs";
5141
+ import { homedir as homedir25 } from "os";
5142
+ import { basename as basename10, join as join26 } from "path";
4631
5143
  var TOOL_ID16 = "qodercli";
4632
- var TOOL_NAME16 = "Qoder CLI";
4633
- var DEFAULT_PROJECTS_DIR = join25(homedir24(), ".qoder", "projects");
4634
- var DEFAULT_LOGS_DIR = join25(homedir24(), ".qoder", "logs", "sessions");
4635
- var DEFAULT_RUNS_DIR = join25(homedir24(), ".qoder", "logs", "runs");
5144
+ var TOOL_NAME15 = "Qoder CLI";
5145
+ var DEFAULT_PROJECTS_DIR = join26(homedir25(), ".qoder", "projects");
5146
+ var DEFAULT_LOGS_DIR = join26(homedir25(), ".qoder", "logs", "sessions");
5147
+ var DEFAULT_RUNS_DIR = join26(homedir25(), ".qoder", "logs", "runs");
4636
5148
  var CLI_ENTRYPOINT = "cli";
4637
5149
  var IDE_ENTRYPOINT = "acp";
4638
5150
  var CREDIT_MODEL_FALLBACK = "credits";
4639
- function createToolDefinition12(projectsDir) {
5151
+ function createToolDefinition11(projectsDir) {
4640
5152
  return {
4641
5153
  id: TOOL_ID16,
4642
- name: TOOL_NAME16,
5154
+ name: TOOL_NAME15,
4643
5155
  dataDir: projectsDir
4644
5156
  };
4645
5157
  }
4646
- function toSafeNumber12(value) {
5158
+ function toSafeNumber11(value) {
4647
5159
  const numberValue = Number(value);
4648
5160
  return Number.isFinite(numberValue) ? numberValue : 0;
4649
5161
  }
@@ -4678,13 +5190,13 @@ function classifyQoderEntrypoint(content) {
4678
5190
  return "unknown";
4679
5191
  }
4680
5192
  function isQodercliBinaryPresent() {
4681
- const home = homedir24();
5193
+ const home = homedir25();
4682
5194
  const candidates = [
4683
- join25(home, ".local", "bin", "qodercli"),
4684
- join25(home, ".qoder", "bin", "qodercli"),
4685
- join25(home, ".qoder-cli")
5195
+ join26(home, ".local", "bin", "qodercli"),
5196
+ join26(home, ".qoder", "bin", "qodercli"),
5197
+ join26(home, ".qoder-cli")
4686
5198
  ];
4687
- return candidates.some((path) => existsSync23(path));
5199
+ return candidates.some((path) => existsSync25(path));
4688
5200
  }
4689
5201
  function projectFromEncodedSlug(slug) {
4690
5202
  const parts = slug.split("-").filter(Boolean);
@@ -4692,11 +5204,11 @@ function projectFromEncodedSlug(slug) {
4692
5204
  }
4693
5205
  function projectFromCwd(cwd) {
4694
5206
  if (!cwd) return "unknown";
4695
- const leaf = basename9(cwd.replace(/[\\/]+$/, ""));
5207
+ const leaf = basename10(cwd.replace(/[\\/]+$/, ""));
4696
5208
  return leaf || "unknown";
4697
5209
  }
4698
5210
  function totalCreditsUsed(quota) {
4699
- return toSafeNumber12(quota.userQuota?.used) + toSafeNumber12(quota.orgResourcePackage?.used) + toSafeNumber12(quota.addOnQuota?.used);
5211
+ return toSafeNumber11(quota.userQuota?.used) + toSafeNumber11(quota.orgResourcePackage?.used) + toSafeNumber11(quota.addOnQuota?.used);
4700
5212
  }
4701
5213
  function parseRunManifest(content) {
4702
5214
  if (!content) return null;
@@ -4776,7 +5288,7 @@ var QoderCliParser = class {
4776
5288
  this.projectsDir = projectsDir;
4777
5289
  this.logsDir = logsDir;
4778
5290
  this.runsDir = runsDir;
4779
- this.tool = createToolDefinition12(projectsDir);
5291
+ this.tool = createToolDefinition11(projectsDir);
4780
5292
  }
4781
5293
  projectsDir;
4782
5294
  logsDir;
@@ -4793,9 +5305,9 @@ var QoderCliParser = class {
4793
5305
  };
4794
5306
  }
4795
5307
  parseProjectSessions(sessionEvents) {
4796
- if (!existsSync23(this.projectsDir)) return;
5308
+ if (!existsSync25(this.projectsDir)) return;
4797
5309
  for (const filePath of findJsonlFiles(this.projectsDir)) {
4798
- if (basename9(filePath).startsWith("verified-")) continue;
5310
+ if (basename10(filePath).startsWith("verified-")) continue;
4799
5311
  const content = readFileSafe(filePath);
4800
5312
  if (!content) continue;
4801
5313
  if (classifyQoderEntrypoint(content) !== CLI_ENTRYPOINT) continue;
@@ -4830,7 +5342,7 @@ var QoderCliParser = class {
4830
5342
  * There is no token data here — only quota/usage credit totals.
4831
5343
  */
4832
5344
  parseCreditDeltas() {
4833
- if (!existsSync23(this.runsDir)) return [];
5345
+ if (!existsSync25(this.runsDir)) return [];
4834
5346
  let runDirs;
4835
5347
  try {
4836
5348
  runDirs = readdirSync12(this.runsDir, { withFileTypes: true }).filter(
@@ -4841,15 +5353,15 @@ var QoderCliParser = class {
4841
5353
  }
4842
5354
  const allSnapshots = [];
4843
5355
  for (const dir of runDirs) {
4844
- const runPath = join25(this.runsDir, dir.name);
4845
- const logPath = join25(runPath, "qodercli.log");
4846
- if (!existsSync23(logPath)) continue;
5356
+ const runPath = join26(this.runsDir, dir.name);
5357
+ const logPath = join26(runPath, "qodercli.log");
5358
+ if (!existsSync25(logPath)) continue;
4847
5359
  const logContent = readFileSafe(logPath);
4848
5360
  if (!logContent?.includes("quota/usage response:")) {
4849
5361
  continue;
4850
5362
  }
4851
5363
  const manifest = parseRunManifest(
4852
- readFileSafe(join25(runPath, "manifest.json"))
5364
+ readFileSafe(join26(runPath, "manifest.json"))
4853
5365
  );
4854
5366
  const project = manifest?.project_id ? projectFromEncodedSlug(manifest.project_id) : projectFromCwd(manifest?.cwd);
4855
5367
  const snapshots = extractCreditSnapshotsFromLog(logContent, {
@@ -4861,67 +5373,72 @@ var QoderCliParser = class {
4861
5373
  return creditSnapshotsToEntries(allSnapshots);
4862
5374
  }
4863
5375
  isInstalled() {
4864
- return isQodercliBinaryPresent() || existsSync23(this.runsDir) || existsSync23(this.logsDir) || existsSync23(this.projectsDir);
5376
+ return isQodercliBinaryPresent() || existsSync25(this.runsDir) || existsSync25(this.logsDir) || existsSync25(this.projectsDir);
4865
5377
  }
4866
5378
  };
4867
5379
  registerParser(new QoderCliParser());
4868
5380
 
4869
5381
  // src/parsers/grok-build.ts
4870
- import { existsSync as existsSync24, readdirSync as readdirSync13 } from "fs";
4871
- import { homedir as homedir25 } from "os";
4872
- import { basename as basename10, join as join26 } from "path";
5382
+ import { existsSync as existsSync26, readdirSync as readdirSync13 } from "fs";
5383
+ import { homedir as homedir26 } from "os";
5384
+ import { basename as basename11, join as join27 } from "path";
4873
5385
  var TOOL_ID17 = "grok-build";
4874
- var TOOL_NAME17 = "Grok Build";
4875
- var DEFAULT_DATA_DIR7 = join26(homedir25(), ".grok", "sessions");
4876
- function createToolDefinition13(dataDir) {
5386
+ var TOOL_NAME16 = "Grok Build";
5387
+ var DEFAULT_DATA_DIR7 = join27(homedir26(), ".grok", "sessions");
5388
+ var DEFAULT_ARCHIVE_DIR2 = join27(homedir26(), ".grok", "archived_sessions");
5389
+ function createToolDefinition12(dataDir) {
4877
5390
  return {
4878
5391
  id: TOOL_ID17,
4879
- name: TOOL_NAME17,
5392
+ name: TOOL_NAME16,
4880
5393
  dataDir
4881
5394
  };
4882
5395
  }
4883
- function toSafeNumber13(value) {
5396
+ function toSafeNumber12(value) {
4884
5397
  const numberValue = Number(value);
4885
- return Number.isFinite(numberValue) ? numberValue : 0;
5398
+ return Number.isFinite(numberValue) && numberValue >= 0 ? Math.trunc(numberValue) : 0;
4886
5399
  }
4887
5400
  function projectFromEncodedCwd(encoded) {
4888
5401
  try {
4889
5402
  const decoded = decodeURIComponent(encoded);
4890
- const leaf = basename10(decoded.replace(/[\\/]+$/, ""));
5403
+ const leaf = basename11(decoded.replace(/[\\/]+$/, ""));
4891
5404
  return leaf || "unknown";
4892
5405
  } catch {
4893
- const leaf = basename10(encoded);
5406
+ const leaf = basename11(encoded);
4894
5407
  return leaf || "unknown";
4895
5408
  }
4896
5409
  }
4897
5410
  function resolveTimestamp(obj) {
4898
5411
  const ms = obj._meta?.agentTimestampMs ?? obj.params?.update?._meta?.agentTimestampMs;
4899
5412
  if (typeof ms === "number" && Number.isFinite(ms)) {
4900
- return new Date(ms);
5413
+ const timestamp = new Date(ms);
5414
+ return Number.isNaN(timestamp.getTime()) ? null : timestamp;
4901
5415
  }
4902
5416
  const raw = obj.timestamp;
4903
5417
  if (raw == null) return null;
4904
5418
  if (typeof raw === "number") {
4905
- return new Date(raw < 1e12 ? raw * 1e3 : raw);
5419
+ const timestamp = new Date(raw < 1e12 ? raw * 1e3 : raw);
5420
+ return Number.isNaN(timestamp.getTime()) ? null : timestamp;
4906
5421
  }
4907
5422
  const ts = new Date(raw);
4908
5423
  return Number.isNaN(ts.getTime()) ? null : ts;
4909
5424
  }
4910
5425
  function pushUsageEntries(entries, args) {
4911
- const { sessionId, project, timestamp, usage, fallbackModel } = args;
5426
+ const { eventKey, sessionId, project, timestamp, usage, fallbackModel } = args;
4912
5427
  const modelUsage = usage.modelUsage;
4913
5428
  const models = modelUsage && Object.keys(modelUsage).length > 0 ? Object.entries(modelUsage) : [[fallbackModel, usage]];
5429
+ const modelEntries = [];
4914
5430
  for (const [model, mu] of models) {
4915
- const cached = toSafeNumber13(mu.cachedReadTokens);
4916
- const reasoning = toSafeNumber13(mu.reasoningTokens);
4917
- const rawInput = toSafeNumber13(mu.inputTokens);
4918
- const rawOutput = toSafeNumber13(mu.outputTokens);
5431
+ if (!mu || typeof mu !== "object") continue;
5432
+ const cached = toSafeNumber12(mu.cachedReadTokens);
5433
+ const rawInput = toSafeNumber12(mu.inputTokens);
5434
+ const rawOutput = toSafeNumber12(mu.outputTokens);
5435
+ const reasoning = Math.min(rawOutput, toSafeNumber12(mu.reasoningTokens));
4919
5436
  const inputTokens = Math.max(0, rawInput - cached);
4920
5437
  const outputTokens = Math.max(0, rawOutput - reasoning);
4921
5438
  if (inputTokens === 0 && outputTokens === 0 && cached === 0 && reasoning === 0) {
4922
5439
  continue;
4923
5440
  }
4924
- entries.push({
5441
+ modelEntries.push({
4925
5442
  sessionId,
4926
5443
  source: TOOL_ID17,
4927
5444
  model: model || fallbackModel || "unknown",
@@ -4933,10 +5450,11 @@ function pushUsageEntries(entries, args) {
4933
5450
  cachedTokens: cached
4934
5451
  });
4935
5452
  }
5453
+ entries.set(eventKey, modelEntries);
4936
5454
  }
4937
5455
  function findSessionDirs(dataDir) {
4938
5456
  const results = [];
4939
- if (!existsSync24(dataDir)) return results;
5457
+ if (!existsSync26(dataDir)) return results;
4940
5458
  let projectDirs;
4941
5459
  try {
4942
5460
  projectDirs = readdirSync13(dataDir, { withFileTypes: true });
@@ -4946,7 +5464,7 @@ function findSessionDirs(dataDir) {
4946
5464
  for (const projectEntry of projectDirs) {
4947
5465
  if (!projectEntry.isDirectory()) continue;
4948
5466
  if (projectEntry.name.endsWith(".sqlite")) continue;
4949
- const projectDir = join26(dataDir, projectEntry.name);
5467
+ const projectDir = join27(dataDir, projectEntry.name);
4950
5468
  const project = projectFromEncodedCwd(projectEntry.name);
4951
5469
  let sessionEntries;
4952
5470
  try {
@@ -4956,8 +5474,8 @@ function findSessionDirs(dataDir) {
4956
5474
  }
4957
5475
  for (const sessionEntry of sessionEntries) {
4958
5476
  if (!sessionEntry.isDirectory()) continue;
4959
- const sessionDir = join26(projectDir, sessionEntry.name);
4960
- if (!existsSync24(join26(sessionDir, "updates.jsonl"))) continue;
5477
+ const sessionDir = join27(projectDir, sessionEntry.name);
5478
+ if (!existsSync26(join27(sessionDir, "updates.jsonl"))) continue;
4961
5479
  results.push({
4962
5480
  sessionDir,
4963
5481
  project,
@@ -4968,7 +5486,7 @@ function findSessionDirs(dataDir) {
4968
5486
  return results;
4969
5487
  }
4970
5488
  function readFallbackModel(sessionDir) {
4971
- const summaryPath = join26(sessionDir, "summary.json");
5489
+ const summaryPath = join27(sessionDir, "summary.json");
4972
5490
  const content = readFileSafe(summaryPath);
4973
5491
  if (!content) return "unknown";
4974
5492
  try {
@@ -4979,24 +5497,29 @@ function readFallbackModel(sessionDir) {
4979
5497
  }
4980
5498
  }
4981
5499
  var GrokBuildParser = class {
4982
- constructor(dataDir = DEFAULT_DATA_DIR7) {
5500
+ constructor(dataDir = DEFAULT_DATA_DIR7, archiveDir = dataDir === DEFAULT_DATA_DIR7 ? DEFAULT_ARCHIVE_DIR2 : void 0) {
4983
5501
  this.dataDir = dataDir;
4984
- this.tool = createToolDefinition13(dataDir);
5502
+ this.archiveDir = archiveDir;
5503
+ this.tool = createToolDefinition12(dataDir);
4985
5504
  }
4986
5505
  dataDir;
5506
+ archiveDir;
4987
5507
  tool;
4988
5508
  async parse() {
4989
- const entries = [];
4990
- const sessionEvents = [];
4991
- const sessions = findSessionDirs(this.dataDir);
5509
+ const entries = /* @__PURE__ */ new Map();
5510
+ const sessionEvents = /* @__PURE__ */ new Map();
5511
+ const sessions = [
5512
+ ...this.archiveDir ? findSessionDirs(this.archiveDir) : [],
5513
+ ...findSessionDirs(this.dataDir)
5514
+ ];
4992
5515
  for (const { sessionDir, project, sessionId } of sessions) {
4993
- const updatesPath = join26(sessionDir, "updates.jsonl");
5516
+ const updatesPath = join27(sessionDir, "updates.jsonl");
4994
5517
  const content = readFileSafe(updatesPath);
4995
5518
  if (!content) continue;
4996
5519
  const fallbackModel = readFallbackModel(sessionDir);
4997
5520
  const seenUserPrompts = /* @__PURE__ */ new Set();
4998
5521
  const ANON_USER_OPEN = "__anon_user_open";
4999
- for (const line of content.split("\n")) {
5522
+ for (const [lineNumber, line] of content.split("\n").entries()) {
5000
5523
  if (!line.trim()) continue;
5001
5524
  let obj;
5002
5525
  try {
@@ -5004,8 +5527,11 @@ var GrokBuildParser = class {
5004
5527
  } catch {
5005
5528
  continue;
5006
5529
  }
5530
+ if (!obj || typeof obj !== "object") continue;
5531
+ if (obj.method !== "session/update" && obj.method !== "_x.ai/session/update")
5532
+ continue;
5007
5533
  const update = obj.params?.update;
5008
- if (!update?.sessionUpdate) continue;
5534
+ if (!update) continue;
5009
5535
  const ts = resolveTimestamp(obj);
5010
5536
  if (!ts) continue;
5011
5537
  const sid = obj.params?.sessionId || sessionId;
@@ -5015,18 +5541,30 @@ var GrokBuildParser = class {
5015
5541
  const key = promptId ?? ANON_USER_OPEN;
5016
5542
  if (seenUserPrompts.has(key)) continue;
5017
5543
  seenUserPrompts.add(key);
5018
- sessionEvents.push({
5019
- sessionId: sid,
5020
- source: TOOL_ID17,
5021
- project,
5022
- timestamp: ts,
5023
- role: "user"
5024
- });
5544
+ sessionEvents.set(
5545
+ JSON.stringify([
5546
+ sid,
5547
+ "user",
5548
+ key,
5549
+ promptId ? null : ts.toISOString()
5550
+ ]),
5551
+ {
5552
+ sessionId: sid,
5553
+ source: TOOL_ID17,
5554
+ project,
5555
+ timestamp: ts,
5556
+ role: "user"
5557
+ }
5558
+ );
5025
5559
  continue;
5026
5560
  }
5027
- if (updateType !== "turn_completed") continue;
5561
+ if (updateType && updateType !== "turn_completed") continue;
5562
+ if (!updateType && !update.usage) continue;
5028
5563
  seenUserPrompts.delete(ANON_USER_OPEN);
5029
- sessionEvents.push({
5564
+ const eventKey = promptId ? JSON.stringify([sid, promptId]) : JSON.stringify([sid, "anonymous", updatesPath, lineNumber]);
5565
+ if ((sessionEvents.get(eventKey)?.timestamp.getTime() ?? -Infinity) > ts.getTime())
5566
+ continue;
5567
+ sessionEvents.set(eventKey, {
5030
5568
  sessionId: sid,
5031
5569
  source: TOOL_ID17,
5032
5570
  project,
@@ -5036,6 +5574,7 @@ var GrokBuildParser = class {
5036
5574
  const usage = update.usage;
5037
5575
  if (!usage) continue;
5038
5576
  pushUsageEntries(entries, {
5577
+ eventKey,
5039
5578
  sessionId: sid,
5040
5579
  project,
5041
5580
  timestamp: ts,
@@ -5044,28 +5583,29 @@ var GrokBuildParser = class {
5044
5583
  });
5045
5584
  }
5046
5585
  }
5586
+ const usageEntries = [...entries.values()].flat();
5047
5587
  return {
5048
- buckets: aggregateToBuckets(entries),
5049
- sessions: extractSessions(sessionEvents, entries)
5588
+ buckets: aggregateToBuckets(usageEntries),
5589
+ sessions: extractSessions([...sessionEvents.values()], usageEntries)
5050
5590
  };
5051
5591
  }
5052
5592
  isInstalled() {
5053
- return existsSync24(this.dataDir) || existsSync24(join26(homedir25(), ".grok")) || existsSync24(join26(homedir25(), ".local", "bin", "grok"));
5593
+ return existsSync26(this.dataDir) || Boolean(this.archiveDir && existsSync26(this.archiveDir)) || existsSync26(join27(homedir26(), ".grok")) || existsSync26(join27(homedir26(), ".local", "bin", "grok"));
5054
5594
  }
5055
5595
  };
5056
5596
  registerParser(new GrokBuildParser());
5057
5597
 
5058
5598
  // src/parsers/atomcode.ts
5059
- import { existsSync as existsSync25 } from "fs";
5060
- import { homedir as homedir26 } from "os";
5061
- import { basename as basename11, join as join27 } from "path";
5599
+ import { existsSync as existsSync27 } from "fs";
5600
+ import { homedir as homedir27 } from "os";
5601
+ import { basename as basename12, join as join28 } from "path";
5062
5602
  var TOOL_ID18 = "atomcode";
5063
- var TOOL_NAME18 = "AtomCode";
5064
- var DEFAULT_SESSIONS_DIR6 = join27(homedir26(), ".atomcode", "sessions");
5603
+ var TOOL_NAME17 = "AtomCode";
5604
+ var DEFAULT_SESSIONS_DIR6 = join28(homedir27(), ".atomcode", "sessions");
5065
5605
  function getAtomCodeSessionsDirs(env = process.env) {
5066
5606
  const dirs = [
5067
5607
  env.TOKEN_ARENA_ATOMCODE_DIR,
5068
- env.ATOMCODE_HOME ? join27(env.ATOMCODE_HOME, "sessions") : void 0,
5608
+ env.ATOMCODE_HOME ? join28(env.ATOMCODE_HOME, "sessions") : void 0,
5069
5609
  DEFAULT_SESSIONS_DIR6
5070
5610
  ].filter((value) => Boolean(value));
5071
5611
  return Array.from(new Set(dirs));
@@ -5074,7 +5614,7 @@ function toNonNegativeNumber3(value) {
5074
5614
  const numberValue = Number(value);
5075
5615
  return Number.isFinite(numberValue) && numberValue >= 0 ? numberValue : 0;
5076
5616
  }
5077
- function parseTimestamp3(value) {
5617
+ function parseTimestamp4(value) {
5078
5618
  if (typeof value === "number" && Number.isFinite(value)) {
5079
5619
  const timestamp = new Date(value);
5080
5620
  return Number.isNaN(timestamp.getTime()) ? null : timestamp;
@@ -5113,7 +5653,7 @@ function getMetaModel(meta) {
5113
5653
  }
5114
5654
  function getMetaProject(meta) {
5115
5655
  if (typeof meta?.working_dir === "string" && meta.working_dir) {
5116
- return basename11(meta.working_dir) || "unknown";
5656
+ return basename12(meta.working_dir) || "unknown";
5117
5657
  }
5118
5658
  return "unknown";
5119
5659
  }
@@ -5124,7 +5664,7 @@ var AtomCodeParser = class {
5124
5664
  this.sessionsDirs = sessionsDir ? [sessionsDir] : getAtomCodeSessionsDirs();
5125
5665
  this.tool = {
5126
5666
  id: TOOL_ID18,
5127
- name: TOOL_NAME18,
5667
+ name: TOOL_NAME17,
5128
5668
  dataDir: this.sessionsDirs[0] ?? DEFAULT_SESSIONS_DIR6
5129
5669
  };
5130
5670
  }
@@ -5146,7 +5686,7 @@ var AtomCodeParser = class {
5146
5686
  const model = getMetaModel(meta);
5147
5687
  for (const row of rows) {
5148
5688
  const sessionId = typeof row.session_id === "string" && row.session_id ? row.session_id : fallbackSessionId;
5149
- const timestamp = parseTimestamp3(row.ts);
5689
+ const timestamp = parseTimestamp4(row.ts);
5150
5690
  if (!timestamp) continue;
5151
5691
  if (row.user !== void 0 || row.assistant !== void 0) {
5152
5692
  sessionEvents.push({
@@ -5198,24 +5738,24 @@ var AtomCodeParser = class {
5198
5738
  };
5199
5739
  }
5200
5740
  isInstalled() {
5201
- return this.sessionsDirs.some((dir) => existsSync25(dir));
5741
+ return this.sessionsDirs.some((dir) => existsSync27(dir));
5202
5742
  }
5203
5743
  };
5204
5744
  registerParser(new AtomCodeParser());
5205
5745
 
5206
5746
  // src/parsers/dsh.ts
5207
- import { existsSync as existsSync26, readdirSync as readdirSync14, readFileSync as readFileSync9 } from "fs";
5208
- import { homedir as homedir27 } from "os";
5209
- import { basename as basename12, join as join28 } from "path";
5747
+ import { existsSync as existsSync28, readdirSync as readdirSync14, readFileSync as readFileSync9 } from "fs";
5748
+ import { homedir as homedir28 } from "os";
5749
+ import { basename as basename13, join as join29 } from "path";
5210
5750
  import * as zlib from "zlib";
5211
5751
  var TOOL_ID19 = "dsh";
5212
- var TOOL_NAME19 = "DeepSeek Harness";
5213
- var DEFAULT_SESSIONS_DIR7 = join28(homedir27(), ".dsh", "sessions");
5752
+ var TOOL_NAME18 = "DeepSeek Harness";
5753
+ var DEFAULT_SESSIONS_DIR7 = join29(homedir28(), ".dsh", "sessions");
5214
5754
  var LOG_BASENAME = "session";
5215
5755
  function getDshSessionsDirs(env = process.env) {
5216
5756
  const dirs = [
5217
5757
  env.TOKEN_ARENA_DSH_DIR,
5218
- env.DSH_HOME ? join28(env.DSH_HOME, "sessions") : void 0,
5758
+ env.DSH_HOME ? join29(env.DSH_HOME, "sessions") : void 0,
5219
5759
  DEFAULT_SESSIONS_DIR7
5220
5760
  ].filter((value) => Boolean(value));
5221
5761
  return Array.from(new Set(dirs));
@@ -5304,10 +5844,10 @@ function readSessionLog(filePath) {
5304
5844
  }
5305
5845
  function findSessionLogs(dir) {
5306
5846
  const results = [];
5307
- if (!existsSync26(dir)) return results;
5847
+ if (!existsSync28(dir)) return results;
5308
5848
  try {
5309
5849
  for (const entry of readdirSync14(dir, { withFileTypes: true })) {
5310
- const fullPath = join28(dir, entry.name);
5850
+ const fullPath = join29(dir, entry.name);
5311
5851
  if (entry.isDirectory()) {
5312
5852
  results.push(...findSessionLogs(fullPath));
5313
5853
  } else if (entry.name === `${LOG_BASENAME}.jsonl` || entry.name === `${LOG_BASENAME}.jsonl.zstd`) {
@@ -5339,7 +5879,7 @@ var DshParser = class {
5339
5879
  this.sessionsDirs = sessionsDir ? [sessionsDir] : getDshSessionsDirs();
5340
5880
  this.tool = {
5341
5881
  id: TOOL_ID19,
5342
- name: TOOL_NAME19,
5882
+ name: TOOL_NAME18,
5343
5883
  dataDir: this.sessionsDirs[0] ?? DEFAULT_SESSIONS_DIR7
5344
5884
  };
5345
5885
  }
@@ -5356,7 +5896,7 @@ var DshParser = class {
5356
5896
  const relativeParts = filePath.slice(sessionsDir.length + 1).split(/[\\/]/);
5357
5897
  const header = rows[0]?.type === "session" ? rows[0] : void 0;
5358
5898
  const sessionId = (header?.id ?? decodeSegment(relativeParts[1] ?? "")) || "unknown";
5359
- const project = typeof header?.cwd === "string" && header.cwd ? basename12(header.cwd) || "unknown" : projectFromDirName(relativeParts[0] ?? "");
5899
+ const project = typeof header?.cwd === "string" && header.cwd ? basename13(header.cwd) || "unknown" : projectFromDirName(relativeParts[0] ?? "");
5360
5900
  let currentModel = "unknown";
5361
5901
  for (const row of rows) {
5362
5902
  if (row.type === "request/context") {
@@ -5399,13 +5939,16 @@ var DshParser = class {
5399
5939
  if (!usage || timestamp === null) continue;
5400
5940
  const model = (isCompaction && typeof row.data?.model === "string" ? row.data.model : "") || currentModel;
5401
5941
  const inputTokens = toNonNegativeNumber4(usage.inputTokens);
5402
- const cachedTokens = toNonNegativeNumber4(usage.cacheReadTokens) + toNonNegativeNumber4(usage.cacheWriteTokens);
5942
+ const cachedTokens = toNonNegativeNumber4(usage.cacheReadTokens);
5943
+ const cacheCreationTokens = toNonNegativeNumber4(
5944
+ usage.cacheWriteTokens
5945
+ );
5403
5946
  const reasoningTokens = toNonNegativeNumber4(usage.reasoningTokens);
5404
5947
  const outputTokens = Math.max(
5405
5948
  0,
5406
5949
  toNonNegativeNumber4(usage.outputTokens) - reasoningTokens
5407
5950
  );
5408
- if (inputTokens + outputTokens + cachedTokens + reasoningTokens === 0)
5951
+ if (inputTokens + outputTokens + cachedTokens + cacheCreationTokens + reasoningTokens === 0)
5409
5952
  continue;
5410
5953
  const entryKey = [
5411
5954
  sessionId,
@@ -5414,6 +5957,7 @@ var DshParser = class {
5414
5957
  inputTokens,
5415
5958
  outputTokens,
5416
5959
  cachedTokens,
5960
+ cacheCreationTokens,
5417
5961
  reasoningTokens
5418
5962
  ].join("|");
5419
5963
  if (seenEntryKeys.has(entryKey)) continue;
@@ -5427,7 +5971,8 @@ var DshParser = class {
5427
5971
  inputTokens,
5428
5972
  outputTokens,
5429
5973
  reasoningTokens,
5430
- cachedTokens
5974
+ cachedTokens,
5975
+ cacheCreationTokens
5431
5976
  });
5432
5977
  }
5433
5978
  }
@@ -5438,18 +5983,18 @@ var DshParser = class {
5438
5983
  };
5439
5984
  }
5440
5985
  isInstalled() {
5441
- return this.sessionsDirs.some((dir) => existsSync26(dir));
5986
+ return this.sessionsDirs.some((dir) => existsSync28(dir));
5442
5987
  }
5443
5988
  };
5444
5989
  registerParser(new DshParser());
5445
5990
 
5446
5991
  // src/parsers/cherry-studio.ts
5447
- import { existsSync as existsSync27 } from "fs";
5448
- import { homedir as homedir28 } from "os";
5449
- import { dirname as dirname6, join as join29, resolve as resolve3 } from "path";
5992
+ import { existsSync as existsSync29 } from "fs";
5993
+ import { homedir as homedir29 } from "os";
5994
+ import { dirname as dirname6, join as join30, resolve as resolve3 } from "path";
5450
5995
  var TOOL_ID20 = "cherry-studio";
5451
- var TOOL_NAME20 = "Cherry Studio";
5452
- var DB_RELATIVE = join29("Data", "cherrystudio.sqlite");
5996
+ var TOOL_NAME19 = "Cherry Studio";
5997
+ var DB_RELATIVE = join30("Data", "cherrystudio.sqlite");
5453
5998
  var USAGE_QUERY = `SELECT
5454
5999
  u.model_id as modelId,
5455
6000
  u.no_cache_tokens as noCacheTokens,
@@ -5472,14 +6017,14 @@ var MESSAGES_QUERY3 = `SELECT
5472
6017
  ORDER BY created_at`;
5473
6018
  function getDefaultUserDataDir(env = process.env) {
5474
6019
  if (process.platform === "darwin") {
5475
- return join29(homedir28(), "Library", "Application Support", "CherryStudio");
6020
+ return join30(homedir29(), "Library", "Application Support", "CherryStudio");
5476
6021
  }
5477
6022
  if (process.platform === "win32") {
5478
- const appData = env.APPDATA?.trim() || join29(homedir28(), "AppData", "Roaming");
5479
- return join29(appData, "CherryStudio");
6023
+ const appData = env.APPDATA?.trim() || join30(homedir29(), "AppData", "Roaming");
6024
+ return join30(appData, "CherryStudio");
5480
6025
  }
5481
- const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || join29(homedir28(), ".config");
5482
- return join29(xdgConfigHome, "CherryStudio");
6026
+ const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || join30(homedir29(), ".config");
6027
+ return join30(xdgConfigHome, "CherryStudio");
5483
6028
  }
5484
6029
  function getCherryStudioDbPaths(env = process.env) {
5485
6030
  const paths = [];
@@ -5487,21 +6032,21 @@ function getCherryStudioDbPaths(env = process.env) {
5487
6032
  if (explicit) {
5488
6033
  const resolved = resolve3(explicit);
5489
6034
  paths.push(
5490
- resolved.endsWith(".sqlite") ? resolved : join29(resolved, DB_RELATIVE)
6035
+ resolved.endsWith(".sqlite") ? resolved : join30(resolved, DB_RELATIVE)
5491
6036
  );
5492
6037
  }
5493
- paths.push(join29(getDefaultUserDataDir(env), DB_RELATIVE));
6038
+ paths.push(join30(getDefaultUserDataDir(env), DB_RELATIVE));
5494
6039
  return Array.from(new Set(paths));
5495
6040
  }
5496
6041
  function resolveCherryStudioDbPath() {
5497
6042
  const candidates = getCherryStudioDbPaths();
5498
- return candidates.find((candidate) => existsSync27(candidate)) ?? "";
6043
+ return candidates.find((candidate) => existsSync29(candidate)) ?? "";
5499
6044
  }
5500
- function createToolDefinition14(dbPath) {
6045
+ function createToolDefinition13(dbPath) {
5501
6046
  return {
5502
6047
  id: TOOL_ID20,
5503
- name: TOOL_NAME20,
5504
- dataDir: dbPath ? dirname6(dbPath) : join29(getDefaultUserDataDir(), "Data")
6048
+ name: TOOL_NAME19,
6049
+ dataDir: dbPath ? dirname6(dbPath) : join30(getDefaultUserDataDir(), "Data")
5505
6050
  };
5506
6051
  }
5507
6052
  function toSafeCount(value) {
@@ -5533,10 +6078,10 @@ var CherryStudioParser = class {
5533
6078
  constructor(options = {}) {
5534
6079
  this.dbPath = options.dbPath || resolveCherryStudioDbPath();
5535
6080
  this.queryRows = options.queryRows || readSqliteRows;
5536
- this.tool = createToolDefinition14(this.dbPath);
6081
+ this.tool = createToolDefinition13(this.dbPath);
5537
6082
  }
5538
6083
  async parse() {
5539
- if (!this.dbPath || !existsSync27(this.dbPath)) {
6084
+ if (!this.dbPath || !existsSync29(this.dbPath)) {
5540
6085
  return { buckets: [], sessions: [] };
5541
6086
  }
5542
6087
  const usageRows = await this.queryRows(
@@ -5547,15 +6092,19 @@ var CherryStudioParser = class {
5547
6092
  for (const row of usageRows) {
5548
6093
  const timestamp = parseEpochMillis(row.createdAt);
5549
6094
  if (!timestamp) continue;
5550
- const cachedTokens = toSafeCount(row.cacheReadTokens) + toSafeCount(row.cacheWriteTokens);
6095
+ const cachedTokens = toSafeCount(row.cacheReadTokens);
6096
+ const cacheCreationTokens = toSafeCount(row.cacheWriteTokens);
5551
6097
  const noCacheTokens = toOptionalCount(row.noCacheTokens);
5552
- const inputTokens = noCacheTokens ?? Math.max(0, toSafeCount(row.inputTokens) - cachedTokens);
6098
+ const inputTokens = noCacheTokens ?? Math.max(
6099
+ 0,
6100
+ toSafeCount(row.inputTokens) - cachedTokens - cacheCreationTokens
6101
+ );
5553
6102
  const reasoningTokens = toSafeCount(row.reasoningTokens);
5554
6103
  const outputTokens = Math.max(
5555
6104
  0,
5556
6105
  toSafeCount(row.outputTokens) - reasoningTokens
5557
6106
  );
5558
- if (inputTokens + outputTokens + reasoningTokens + cachedTokens === 0) {
6107
+ if (inputTokens + outputTokens + reasoningTokens + cachedTokens + cacheCreationTokens === 0) {
5559
6108
  continue;
5560
6109
  }
5561
6110
  entries.push({
@@ -5569,7 +6118,8 @@ var CherryStudioParser = class {
5569
6118
  inputTokens,
5570
6119
  outputTokens,
5571
6120
  reasoningTokens,
5572
- cachedTokens
6121
+ cachedTokens,
6122
+ cacheCreationTokens
5573
6123
  });
5574
6124
  }
5575
6125
  let messageRows;
@@ -5606,7 +6156,7 @@ var CherryStudioParser = class {
5606
6156
  };
5607
6157
  }
5608
6158
  isInstalled() {
5609
- return Boolean(this.dbPath) && existsSync27(this.dbPath);
6159
+ return Boolean(this.dbPath) && existsSync29(this.dbPath);
5610
6160
  }
5611
6161
  };
5612
6162
  registerParser(new CherryStudioParser());
@@ -5617,31 +6167,31 @@ import { Command, Option } from "commander";
5617
6167
  // src/infrastructure/config/manager.ts
5618
6168
  import { randomUUID } from "crypto";
5619
6169
  import {
5620
- existsSync as existsSync28,
6170
+ existsSync as existsSync30,
5621
6171
  mkdirSync,
5622
6172
  readFileSync as readFileSync10,
5623
6173
  unlinkSync,
5624
6174
  writeFileSync
5625
6175
  } from "fs";
5626
- import { join as join31 } from "path";
6176
+ import { join as join32 } from "path";
5627
6177
 
5628
6178
  // src/infrastructure/xdg.ts
5629
- import { homedir as homedir29 } from "os";
5630
- import { join as join30 } from "path";
6179
+ import { homedir as homedir30 } from "os";
6180
+ import { join as join31 } from "path";
5631
6181
  function getConfigHome() {
5632
- return process.env.XDG_CONFIG_HOME || join30(homedir29(), ".config");
6182
+ return process.env.XDG_CONFIG_HOME || join31(homedir30(), ".config");
5633
6183
  }
5634
6184
  function getStateHome() {
5635
- return process.env.XDG_STATE_HOME || join30(homedir29(), ".local", "state");
6185
+ return process.env.XDG_STATE_HOME || join31(homedir30(), ".local", "state");
5636
6186
  }
5637
6187
  function getRuntimeDir() {
5638
6188
  return process.env.XDG_RUNTIME_DIR || getStateHome();
5639
6189
  }
5640
6190
 
5641
6191
  // src/infrastructure/config/manager.ts
5642
- var CONFIG_DIR = join31(getConfigHome(), "tokenarena");
6192
+ var CONFIG_DIR = join32(getConfigHome(), "tokenarena");
5643
6193
  var isDev = process.env.TOKEN_ARENA_DEV === "1";
5644
- var CONFIG_FILE = join31(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
6194
+ var CONFIG_FILE = join32(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
5645
6195
  var DEFAULT_API_URL = "https://token.guji.uno";
5646
6196
  var VALID_CONFIG_KEYS = [
5647
6197
  "apiKey",
@@ -5657,7 +6207,7 @@ function getConfigDir() {
5657
6207
  return CONFIG_DIR;
5658
6208
  }
5659
6209
  function loadConfig() {
5660
- if (!existsSync28(CONFIG_FILE)) return null;
6210
+ if (!existsSync30(CONFIG_FILE)) return null;
5661
6211
  try {
5662
6212
  const raw = readFileSync10(CONFIG_FILE, "utf-8");
5663
6213
  const config = JSON.parse(raw);
@@ -5675,7 +6225,7 @@ function saveConfig(config) {
5675
6225
  `, "utf-8");
5676
6226
  }
5677
6227
  function deleteConfig() {
5678
- if (existsSync28(CONFIG_FILE)) {
6228
+ if (existsSync30(CONFIG_FILE)) {
5679
6229
  unlinkSync(CONFIG_FILE);
5680
6230
  }
5681
6231
  }
@@ -6109,11 +6659,11 @@ function toProjectIdentity(input2) {
6109
6659
  }
6110
6660
 
6111
6661
  // src/domain/upload-manifest.ts
6112
- import { createHash as createHash4 } from "crypto";
6662
+ import { createHash as createHash6 } from "crypto";
6113
6663
  var MANIFEST_VERSION = 1;
6114
6664
  var SNAPSHOT_PROTOCOL_VERSION = 1;
6115
6665
  function fingerprint(value) {
6116
- return createHash4("sha256").update(value).digest("hex").slice(0, 16);
6666
+ return createHash6("sha256").update(value).digest("hex").slice(0, 16);
6117
6667
  }
6118
6668
  function normalizeApiUrl(apiUrl) {
6119
6669
  let end = apiUrl.length;
@@ -6162,6 +6712,7 @@ function getUploadBucketContentHash(bucket) {
6162
6712
  return fingerprint(
6163
6713
  JSON.stringify({
6164
6714
  cachedTokens: bucket.cachedTokens,
6715
+ cacheCreationTokens: bucket.cacheCreationTokens ?? 0,
6165
6716
  hostname: bucket.hostname,
6166
6717
  inputTokens: bucket.inputTokens,
6167
6718
  outputTokens: bucket.outputTokens,
@@ -6176,6 +6727,7 @@ function getUploadSessionContentHash(session) {
6176
6727
  JSON.stringify({
6177
6728
  activeSeconds: session.activeSeconds,
6178
6729
  cachedTokens: session.cachedTokens,
6730
+ cacheCreationTokens: session.cacheCreationTokens ?? 0,
6179
6731
  durationSeconds: session.durationSeconds,
6180
6732
  firstMessageAt: session.firstMessageAt,
6181
6733
  hostname: session.hostname,
@@ -6492,7 +7044,7 @@ var ApiClient = class {
6492
7044
  // src/infrastructure/runtime/lock.ts
6493
7045
  import {
6494
7046
  closeSync,
6495
- existsSync as existsSync29,
7047
+ existsSync as existsSync31,
6496
7048
  openSync,
6497
7049
  readFileSync as readFileSync11,
6498
7050
  rmSync as rmSync3,
@@ -6501,22 +7053,22 @@ import {
6501
7053
 
6502
7054
  // src/infrastructure/runtime/paths.ts
6503
7055
  import { mkdirSync as mkdirSync2 } from "fs";
6504
- import { join as join32 } from "path";
7056
+ import { join as join33 } from "path";
6505
7057
  var APP_NAME = "tokenarena";
6506
7058
  function getRuntimeDirPath() {
6507
- return join32(getRuntimeDir(), APP_NAME);
7059
+ return join33(getRuntimeDir(), APP_NAME);
6508
7060
  }
6509
7061
  function getStateDir() {
6510
- return join32(getStateHome(), APP_NAME);
7062
+ return join33(getStateHome(), APP_NAME);
6511
7063
  }
6512
7064
  function getSyncLockPath() {
6513
- return join32(getRuntimeDirPath(), "sync.lock");
7065
+ return join33(getRuntimeDirPath(), "sync.lock");
6514
7066
  }
6515
7067
  function getSyncStatePath() {
6516
- return join32(getStateDir(), "status.json");
7068
+ return join33(getStateDir(), "status.json");
6517
7069
  }
6518
7070
  function getUploadManifestPath() {
6519
- return join32(getStateDir(), "upload-manifest.json");
7071
+ return join33(getStateDir(), "upload-manifest.json");
6520
7072
  }
6521
7073
  function ensureAppDirs() {
6522
7074
  mkdirSync2(getRuntimeDirPath(), { recursive: true });
@@ -6534,7 +7086,7 @@ function isProcessAlive(pid) {
6534
7086
  }
6535
7087
  }
6536
7088
  function readLockMetadata(lockPath) {
6537
- if (!existsSync29(lockPath)) {
7089
+ if (!existsSync31(lockPath)) {
6538
7090
  return null;
6539
7091
  }
6540
7092
  try {
@@ -6608,13 +7160,13 @@ function describeExistingSyncLock() {
6608
7160
  }
6609
7161
 
6610
7162
  // src/infrastructure/runtime/state.ts
6611
- import { existsSync as existsSync30, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
7163
+ import { existsSync as existsSync32, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
6612
7164
  function getDefaultState() {
6613
7165
  return { status: "idle" };
6614
7166
  }
6615
7167
  function loadSyncState() {
6616
7168
  const path = getSyncStatePath();
6617
- if (!existsSync30(path)) {
7169
+ if (!existsSync32(path)) {
6618
7170
  return getDefaultState();
6619
7171
  }
6620
7172
  try {
@@ -6675,7 +7227,7 @@ function markSyncFailed(source, error, status) {
6675
7227
  }
6676
7228
 
6677
7229
  // src/infrastructure/runtime/upload-manifest.ts
6678
- import { existsSync as existsSync31, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
7230
+ import { existsSync as existsSync33, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
6679
7231
  function isRecordOfStrings(value) {
6680
7232
  if (!value || typeof value !== "object" || Array.isArray(value)) {
6681
7233
  return false;
@@ -6691,7 +7243,7 @@ function isUploadManifest(value) {
6691
7243
  }
6692
7244
  function loadUploadManifest() {
6693
7245
  const path = getUploadManifestPath();
6694
- if (!existsSync31(path)) {
7246
+ if (!existsSync33(path)) {
6695
7247
  return null;
6696
7248
  }
6697
7249
  try {
@@ -6725,11 +7277,19 @@ async function runAllParsers() {
6725
7277
  const allBuckets = [];
6726
7278
  const allSessions = [];
6727
7279
  const parserResults = [];
7280
+ const failedSources = /* @__PURE__ */ new Set();
6728
7281
  for (const parser of getAllParsers()) {
6729
7282
  try {
6730
7283
  const result = await parser.parse();
6731
7284
  const buckets = result.buckets;
6732
7285
  const sessions = result.sessions;
7286
+ if (result.incomplete) {
7287
+ failedSources.add(parser.tool.id);
7288
+ logger.warn(
7289
+ `${parser.tool.id} parser reported an incomplete scan; preserving its remote history.`
7290
+ );
7291
+ continue;
7292
+ }
6733
7293
  if (buckets.length > 0) allBuckets.push(...buckets);
6734
7294
  if (sessions.length > 0) allSessions.push(...sessions);
6735
7295
  if (buckets.length > 0 || sessions.length > 0) {
@@ -6740,10 +7300,18 @@ async function runAllParsers() {
6740
7300
  });
6741
7301
  }
6742
7302
  } catch (err) {
6743
- logger.warn(`${parser.tool.id} parser failed: ${err.message}`);
7303
+ failedSources.add(parser.tool.id);
7304
+ logger.warn(
7305
+ `${parser.tool.id} parser failed: ${err instanceof Error ? err.message : String(err)}`
7306
+ );
6744
7307
  }
6745
7308
  }
6746
- return { buckets: allBuckets, sessions: allSessions, parserResults };
7309
+ return {
7310
+ buckets: allBuckets,
7311
+ sessions: allSessions,
7312
+ parserResults,
7313
+ failedSources: Array.from(failedSources)
7314
+ };
6747
7315
  }
6748
7316
  function getDetectedTools() {
6749
7317
  return detectInstalledTools();
@@ -6788,10 +7356,7 @@ function formatScopeChangeReason(reason) {
6788
7356
  }
6789
7357
  }
6790
7358
  function resolveSnapshotResetDeviceId(input2) {
6791
- if (!input2.previous) {
6792
- return null;
6793
- }
6794
- if (input2.scopeChangedReasons.includes("server_or_api_key")) {
7359
+ if (!input2.previous || input2.scopeChangedReasons.includes("server_or_api_key")) {
6795
7360
  return null;
6796
7361
  }
6797
7362
  if (input2.scopeChangedReasons.includes("project_identity") || input2.scopeChangedReasons.includes("snapshot_protocol")) {
@@ -6837,6 +7402,7 @@ function toUploadBuckets(buckets, settings, device) {
6837
7402
  existing.outputTokens += bucket.outputTokens;
6838
7403
  existing.reasoningTokens += bucket.reasoningTokens || 0;
6839
7404
  existing.cachedTokens += bucket.cachedTokens || 0;
7405
+ existing.cacheCreationTokens = (existing.cacheCreationTokens ?? 0) + (bucket.cacheCreationTokens ?? 0);
6840
7406
  existing.totalTokens += bucket.totalTokens;
6841
7407
  continue;
6842
7408
  }
@@ -6852,6 +7418,7 @@ function toUploadBuckets(buckets, settings, device) {
6852
7418
  outputTokens: bucket.outputTokens,
6853
7419
  reasoningTokens: bucket.reasoningTokens || 0,
6854
7420
  cachedTokens: bucket.cachedTokens || 0,
7421
+ cacheCreationTokens: bucket.cacheCreationTokens ?? 0,
6855
7422
  totalTokens: bucket.totalTokens
6856
7423
  });
6857
7424
  }
@@ -6881,6 +7448,7 @@ function toUploadSessions(sessions, settings, device) {
6881
7448
  outputTokens: session.outputTokens,
6882
7449
  reasoningTokens: session.reasoningTokens,
6883
7450
  cachedTokens: session.cachedTokens,
7451
+ cacheCreationTokens: session.cacheCreationTokens ?? 0,
6884
7452
  totalTokens: session.totalTokens,
6885
7453
  primaryModel: session.primaryModel,
6886
7454
  modelUsages: session.modelUsages
@@ -6955,7 +7523,12 @@ function buildUploadBatches(device, buckets, sessions) {
6955
7523
  return batches;
6956
7524
  }
6957
7525
  async function runSync(config, opts = {}) {
6958
- const { quiet = false, source = "manual", throws = false } = opts;
7526
+ const {
7527
+ quiet = false,
7528
+ source = "manual",
7529
+ throws = false,
7530
+ rebuild = false
7531
+ } = opts;
6959
7532
  const lock = tryAcquireSyncLock(source);
6960
7533
  if (!lock) {
6961
7534
  const detail = describeExistingSyncLock();
@@ -6972,13 +7545,27 @@ async function runSync(config, opts = {}) {
6972
7545
  let totalIngested = 0;
6973
7546
  let totalSessionsSynced = 0;
6974
7547
  let caughtError = null;
7548
+ let snapshotResetAttempted = false;
6975
7549
  try {
6976
7550
  const {
6977
7551
  buckets: allBuckets,
6978
7552
  sessions: allSessions,
6979
- parserResults
7553
+ parserResults,
7554
+ failedSources = []
6980
7555
  } = await runAllParsers();
7556
+ if (rebuild && failedSources.length > 0) {
7557
+ throw new SyncFailure(
7558
+ `Cannot rebuild: incomplete or failed parsers (${failedSources.join(", ")}). Remote history has not been changed.`,
7559
+ "error"
7560
+ );
7561
+ }
6981
7562
  if (allBuckets.length === 0 && allSessions.length === 0) {
7563
+ if (rebuild) {
7564
+ throw new SyncFailure(
7565
+ "Cannot rebuild from an empty scan. Restore complete local logs first. Remote history has not been changed.",
7566
+ "error"
7567
+ );
7568
+ }
6982
7569
  if (!quiet) {
6983
7570
  logger.info("No new usage data found.");
6984
7571
  }
@@ -7030,25 +7617,25 @@ async function runSync(config, opts = {}) {
7030
7617
  scope: manifestScope,
7031
7618
  sessions: uploadSessions
7032
7619
  });
7033
- const changedBuckets = uploadDiff.bucketsToUpload;
7034
- const changedSessions = uploadDiff.sessionsToUpload;
7035
- const snapshotResetDeviceId = resolveSnapshotResetDeviceId({
7620
+ const snapshotResetDeviceId = rebuild ? device.deviceId : resolveSnapshotResetDeviceId({
7036
7621
  previous: previousManifest,
7037
7622
  scopeChangedReasons: uploadDiff.scopeChangedReasons
7038
7623
  });
7039
- if (!quiet && uploadDiff.scopeChangedReasons.length > 0) {
7624
+ if (snapshotResetDeviceId && failedSources.length > 0) {
7625
+ throw new SyncFailure(
7626
+ "Cannot replace the device snapshot: incomplete or failed parsers (" + failedSources.join(", ") + "). Remote history has not been changed.",
7627
+ "error"
7628
+ );
7629
+ }
7630
+ const changedBuckets = snapshotResetDeviceId ? uploadBuckets : uploadDiff.bucketsToUpload;
7631
+ const changedSessions = snapshotResetDeviceId ? uploadSessions : uploadDiff.sessionsToUpload;
7632
+ if (!rebuild && !quiet && uploadDiff.scopeChangedReasons.length > 0) {
7040
7633
  const scopeReasons = uploadDiff.scopeChangedReasons.map(formatScopeChangeReason).join(", ");
7041
- if (snapshotResetDeviceId) {
7042
- logger.warn(
7043
- `Upload scope changed (${scopeReasons}). TokenArena will replace the previous remote snapshot for this device before uploading the current snapshot again.`
7044
- );
7045
- } else {
7046
- logger.warn(
7047
- `Upload scope changed (${scopeReasons}). TokenArena will upload the current snapshot again, but existing remote records from the previous scope will not be deleted automatically.`
7048
- );
7049
- }
7634
+ logger.warn(
7635
+ snapshotResetDeviceId ? `Upload scope changed (${scopeReasons}). The previous device snapshot will be replaced to apply the new privacy or protocol settings.` : `Upload scope changed (${scopeReasons}). Current records will be uploaded again; remote records from the previous server, account or device are preserved.`
7636
+ );
7050
7637
  }
7051
- if (!quiet && (uploadDiff.removedBuckets > 0 || uploadDiff.removedSessions > 0)) {
7638
+ if (!rebuild && !quiet && (uploadDiff.removedBuckets > 0 || uploadDiff.removedSessions > 0)) {
7052
7639
  const parts = [];
7053
7640
  if (uploadDiff.removedBuckets > 0) {
7054
7641
  parts.push(`${uploadDiff.removedBuckets} buckets`);
@@ -7057,24 +7644,9 @@ async function runSync(config, opts = {}) {
7057
7644
  parts.push(`${uploadDiff.removedSessions} sessions`);
7058
7645
  }
7059
7646
  logger.warn(
7060
- `Detected ${parts.join(" + ")} that were present in the previous local snapshot but are missing now. Remote deletions are not supported yet, so renamed projects or removed local logs may leave stale data online.`
7647
+ `Detected ${parts.join(" + ")} that were present in the previous local snapshot but are missing now. Remote history is preserved. Use tokenarena sync --rebuild to replace it only when complete local logs are available.`
7061
7648
  );
7062
7649
  }
7063
- if (snapshotResetDeviceId) {
7064
- const deleted = await apiClient.deleteDeviceData(snapshotResetDeviceId);
7065
- if (!quiet) {
7066
- const deletedParts = [];
7067
- if (deleted.deletedBuckets > 0) {
7068
- deletedParts.push(`${deleted.deletedBuckets} buckets`);
7069
- }
7070
- if (deleted.deletedSessions > 0) {
7071
- deletedParts.push(`${deleted.deletedSessions} sessions`);
7072
- }
7073
- logger.info(
7074
- deletedParts.length > 0 ? `Cleared ${deletedParts.join(" + ")} from the previous remote snapshot before re-uploading.` : "Previous remote snapshot was already empty before re-uploading."
7075
- );
7076
- }
7077
- }
7078
7650
  if (changedBuckets.length === 0 && changedSessions.length === 0) {
7079
7651
  if (!quiet) {
7080
7652
  const skippedParts = [];
@@ -7112,6 +7684,22 @@ async function runSync(config, opts = {}) {
7112
7684
  (sum, size) => sum + size,
7113
7685
  0
7114
7686
  );
7687
+ if (snapshotResetDeviceId) {
7688
+ saveUploadManifest({
7689
+ ...uploadDiff.nextManifest,
7690
+ // 自动隐私迁移保留旧 scope;清理中断后,下次同步仍会重试删除旧快照。
7691
+ scope: !rebuild && previousManifest ? previousManifest.scope : manifestScope,
7692
+ buckets: {},
7693
+ sessions: {}
7694
+ });
7695
+ snapshotResetAttempted = true;
7696
+ const deleted = await apiClient.deleteDeviceData(snapshotResetDeviceId);
7697
+ if (!quiet) {
7698
+ logger.info(
7699
+ `Replacing the device snapshot (${deleted.deletedBuckets} buckets and ${deleted.deletedSessions} sessions cleared).`
7700
+ );
7701
+ }
7702
+ }
7115
7703
  let uploadedBytesBeforeBatch = 0;
7116
7704
  if (!quiet) {
7117
7705
  const parts = [];
@@ -7122,10 +7710,10 @@ async function runSync(config, opts = {}) {
7122
7710
  parts.push(`${changedSessions.length} sessions`);
7123
7711
  }
7124
7712
  const skippedParts = [];
7125
- if (uploadDiff.unchangedBuckets > 0) {
7713
+ if (!snapshotResetDeviceId && uploadDiff.unchangedBuckets > 0) {
7126
7714
  skippedParts.push(`${uploadDiff.unchangedBuckets} unchanged buckets`);
7127
7715
  }
7128
- if (uploadDiff.unchangedSessions > 0) {
7716
+ if (!snapshotResetDeviceId && uploadDiff.unchangedSessions > 0) {
7129
7717
  skippedParts.push(`${uploadDiff.unchangedSessions} unchanged sessions`);
7130
7718
  }
7131
7719
  logger.info(
@@ -7199,9 +7787,9 @@ View your dashboard at: ${apiUrl}/usage`);
7199
7787
  );
7200
7788
  } else if (error instanceof SyncFailure) {
7201
7789
  caughtError = error;
7202
- } else if (totalIngested > 0) {
7790
+ } else if (totalIngested > 0 || totalSessionsSynced > 0) {
7203
7791
  caughtError = new SyncFailure(
7204
- `Sync partially completed (${totalIngested} buckets uploaded). ${httpErr.message}`,
7792
+ `Sync partially completed (${totalIngested} buckets and ${totalSessionsSynced} sessions uploaded). ${httpErr.message}`,
7205
7793
  "error",
7206
7794
  error
7207
7795
  );
@@ -7212,6 +7800,13 @@ View your dashboard at: ${apiUrl}/usage`);
7212
7800
  error
7213
7801
  );
7214
7802
  }
7803
+ if (snapshotResetAttempted) {
7804
+ caughtError = new SyncFailure(
7805
+ (rebuild ? "Rebuild" : "Snapshot replacement") + " did not finish. Run tokenarena sync to retry. " + caughtError.message,
7806
+ caughtError.kind,
7807
+ caughtError.causeError
7808
+ );
7809
+ }
7215
7810
  markSyncFailed(source, caughtError.message, caughtError.kind);
7216
7811
  } finally {
7217
7812
  lock.release();
@@ -7231,18 +7826,18 @@ View your dashboard at: ${apiUrl}/usage`);
7231
7826
 
7232
7827
  // src/commands/init.ts
7233
7828
  import { execFileSync as execFileSync7, spawn } from "child_process";
7234
- import { existsSync as existsSync34 } from "fs";
7829
+ import { existsSync as existsSync36 } from "fs";
7235
7830
  import { appendFile, mkdir, readFile } from "fs/promises";
7236
- import { homedir as homedir32, platform as platform5 } from "os";
7237
- import { dirname as dirname7, join as join33, posix as posix3, win32 } from "path";
7831
+ import { homedir as homedir33, platform as platform5 } from "os";
7832
+ import { dirname as dirname7, join as join34, posix as posix3, win32 } from "path";
7238
7833
 
7239
7834
  // src/infrastructure/service/index.ts
7240
7835
  import { platform as platform4 } from "os";
7241
7836
 
7242
7837
  // src/infrastructure/service/linux-systemd.ts
7243
7838
  import { execFileSync as execFileSync5 } from "child_process";
7244
- import { existsSync as existsSync32, mkdirSync as mkdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
7245
- import { homedir as homedir30, platform as platform2 } from "os";
7839
+ import { existsSync as existsSync34, mkdirSync as mkdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
7840
+ import { homedir as homedir31, platform as platform2 } from "os";
7246
7841
  import { posix } from "path";
7247
7842
 
7248
7843
  // src/utils/command.ts
@@ -7292,11 +7887,17 @@ function getManagedServiceEnvironment(env = process.env) {
7292
7887
  }
7293
7888
  return next;
7294
7889
  }
7890
+ var TYPESCRIPT_ENTRY_PATTERN = /\.[cm]?tsx?$/i;
7295
7891
  function resolveManagedDaemonCommand(execPath = process.execPath, argv = process.argv) {
7296
7892
  const scriptPath = argv[1];
7297
7893
  if (!scriptPath) {
7298
7894
  throw new Error("\u65E0\u6CD5\u89E3\u6790 CLI \u5165\u53E3\u8DEF\u5F84\uFF0C\u8BF7\u901A\u8FC7 tokenarena \u547D\u4EE4\u91CD\u65B0\u6267\u884C\u3002");
7299
7895
  }
7896
+ if (TYPESCRIPT_ENTRY_PATTERN.test(scriptPath)) {
7897
+ throw new Error(
7898
+ "\u65E0\u6CD5\u901A\u8FC7\u672A\u6253\u5305\u7684 TypeScript \u5165\u53E3\u5B89\u88C5\u670D\u52A1\uFF0C\u8BF7\u5148\u6267\u884C pnpm build:cli\uFF0C\u518D\u4F7F\u7528\u6253\u5305\u540E\u7684 tokenarena \u547D\u4EE4\u3002"
7899
+ );
7900
+ }
7300
7901
  return {
7301
7902
  execPath,
7302
7903
  args: [scriptPath, "daemon", "--service"]
@@ -7311,10 +7912,10 @@ function escapeXml(value) {
7311
7912
 
7312
7913
  // src/infrastructure/service/linux-systemd.ts
7313
7914
  var SYSTEMD_SERVICE_NAME = "tokenarena";
7314
- function getLinuxSystemdServiceDir(homePath = homedir30()) {
7915
+ function getLinuxSystemdServiceDir(homePath = homedir31()) {
7315
7916
  return posix.join(homePath, ".config", "systemd", "user");
7316
7917
  }
7317
- function getLinuxSystemdServiceFile(homePath = homedir30()) {
7918
+ function getLinuxSystemdServiceFile(homePath = homedir31()) {
7318
7919
  return posix.join(
7319
7920
  getLinuxSystemdServiceDir(homePath),
7320
7921
  `${SYSTEMD_SERVICE_NAME}.service`
@@ -7371,7 +7972,7 @@ function ensureSystemdAvailable() {
7371
7972
  }
7372
7973
  function createLinuxSystemdServiceBackend() {
7373
7974
  function isInstalled() {
7374
- return existsSync32(getLinuxSystemdServiceFile());
7975
+ return existsSync34(getLinuxSystemdServiceFile());
7375
7976
  }
7376
7977
  async function setup(skipPrompt = false) {
7377
7978
  if (!ensureSystemdAvailable()) {
@@ -7496,7 +8097,7 @@ function createLinuxSystemdServiceBackend() {
7496
8097
  }
7497
8098
  async function uninstall(skipPrompt = false) {
7498
8099
  const serviceFile = getLinuxSystemdServiceFile();
7499
- if (!existsSync32(serviceFile)) {
8100
+ if (!existsSync34(serviceFile)) {
7500
8101
  logger.info(formatBullet("\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002", "warning"));
7501
8102
  return;
7502
8103
  }
@@ -7557,17 +8158,17 @@ function createLinuxSystemdServiceBackend() {
7557
8158
 
7558
8159
  // src/infrastructure/service/macos-launchd.ts
7559
8160
  import { execFileSync as execFileSync6 } from "child_process";
7560
- import { existsSync as existsSync33, mkdirSync as mkdirSync4, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
7561
- import { homedir as homedir31, platform as platform3 } from "os";
8161
+ import { existsSync as existsSync35, mkdirSync as mkdirSync4, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
8162
+ import { homedir as homedir32, platform as platform3 } from "os";
7562
8163
  import { posix as posix2 } from "path";
7563
8164
  var MACOS_LAUNCHD_LABEL = "com.guji.tokenarena";
7564
8165
  function getCurrentUid() {
7565
8166
  return typeof process.getuid === "function" ? process.getuid() : null;
7566
8167
  }
7567
- function getMacosLaunchAgentDir(homePath = homedir31()) {
8168
+ function getMacosLaunchAgentDir(homePath = homedir32()) {
7568
8169
  return posix2.join(homePath, "Library", "LaunchAgents");
7569
8170
  }
7570
- function getMacosLaunchAgentFile(homePath = homedir31()) {
8171
+ function getMacosLaunchAgentFile(homePath = homedir32()) {
7571
8172
  return posix2.join(
7572
8173
  getMacosLaunchAgentDir(homePath),
7573
8174
  `${MACOS_LAUNCHD_LABEL}.plist`
@@ -7694,7 +8295,7 @@ function writeLaunchAgentPlist() {
7694
8295
  label: MACOS_LAUNCHD_LABEL,
7695
8296
  programArguments: [command.execPath, ...command.args],
7696
8297
  environment: getManagedServiceEnvironment(),
7697
- workingDirectory: homedir31(),
8298
+ workingDirectory: homedir32(),
7698
8299
  standardOutPath: stdoutPath,
7699
8300
  standardErrorPath: stderrPath
7700
8301
  });
@@ -7719,7 +8320,7 @@ function bootstrapLaunchAgent() {
7719
8320
  }
7720
8321
  function createMacosLaunchdServiceBackend() {
7721
8322
  function isInstalled() {
7722
- return existsSync33(getMacosLaunchAgentFile());
8323
+ return existsSync35(getMacosLaunchAgentFile());
7723
8324
  }
7724
8325
  async function setup(skipPrompt = false) {
7725
8326
  if (!ensureLaunchctlAvailable()) {
@@ -7860,7 +8461,7 @@ function createMacosLaunchdServiceBackend() {
7860
8461
  }
7861
8462
  async function uninstall(skipPrompt = false) {
7862
8463
  const plistFile = getMacosLaunchAgentFile();
7863
- if (!existsSync33(plistFile)) {
8464
+ if (!existsSync35(plistFile)) {
7864
8465
  logger.info(formatBullet("\u670D\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728\u3002", "warning"));
7865
8466
  return;
7866
8467
  }
@@ -7971,7 +8572,7 @@ function resolvePowerShellProfilePath() {
7971
8572
  const systemRoot = process.env.SYSTEMROOT || "C:\\Windows";
7972
8573
  const candidates = [
7973
8574
  "pwsh.exe",
7974
- join33(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
8575
+ join34(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
7975
8576
  ];
7976
8577
  for (const command of candidates) {
7977
8578
  try {
@@ -8000,8 +8601,8 @@ function resolvePowerShellProfilePath() {
8000
8601
  function resolveShellAliasSetup(options = {}) {
8001
8602
  const currentPlatform = options.currentPlatform ?? platform5();
8002
8603
  const env = options.env ?? process.env;
8003
- const homeDir = options.homeDir ?? homedir32();
8004
- const pathExists = options.exists ?? existsSync34;
8604
+ const homeDir = options.homeDir ?? homedir33();
8605
+ const pathExists = options.exists ?? existsSync36;
8005
8606
  const shellFromEnv = env.SHELL ? basenameLikeShell(env.SHELL).toLowerCase() : "";
8006
8607
  const shellName = shellFromEnv || (currentPlatform === "win32" ? "powershell" : "");
8007
8608
  const aliasName = "ta";
@@ -8198,7 +8799,7 @@ async function setupShellAlias() {
8198
8799
  try {
8199
8800
  await mkdir(dirname7(setup.configFile), { recursive: true });
8200
8801
  let existingContent = "";
8201
- if (existsSync34(setup.configFile)) {
8802
+ if (existsSync36(setup.configFile)) {
8202
8803
  existingContent = await readFile(setup.configFile, "utf-8");
8203
8804
  }
8204
8805
  const normalizedContent = existingContent.toLowerCase();
@@ -8300,6 +8901,7 @@ function createTotals() {
8300
8901
  inputTokens: 0,
8301
8902
  outputTokens: 0,
8302
8903
  cachedTokens: 0,
8904
+ cacheCreationTokens: 0,
8303
8905
  reasoningTokens: 0,
8304
8906
  totalTokens: 0,
8305
8907
  buckets: 0,
@@ -8312,6 +8914,7 @@ function addBucketToTotals(totals, bucket) {
8312
8914
  totals.inputTokens += bucket.inputTokens || 0;
8313
8915
  totals.outputTokens += bucket.outputTokens || 0;
8314
8916
  totals.cachedTokens += bucket.cachedTokens || 0;
8917
+ totals.cacheCreationTokens += bucket.cacheCreationTokens || 0;
8315
8918
  totals.reasoningTokens += bucket.reasoningTokens || 0;
8316
8919
  totals.totalTokens += bucket.totalTokens || 0;
8317
8920
  totals.buckets += 1;
@@ -8323,6 +8926,7 @@ function addBucketToRank(map, key, bucket) {
8323
8926
  inputTokens: 0,
8324
8927
  outputTokens: 0,
8325
8928
  cachedTokens: 0,
8929
+ cacheCreationTokens: 0,
8326
8930
  reasoningTokens: 0,
8327
8931
  totalTokens: 0,
8328
8932
  sessions: 0
@@ -8330,6 +8934,7 @@ function addBucketToRank(map, key, bucket) {
8330
8934
  current.inputTokens += bucket.inputTokens || 0;
8331
8935
  current.outputTokens += bucket.outputTokens || 0;
8332
8936
  current.cachedTokens += bucket.cachedTokens || 0;
8937
+ current.cacheCreationTokens += bucket.cacheCreationTokens || 0;
8333
8938
  current.reasoningTokens += bucket.reasoningTokens || 0;
8334
8939
  current.totalTokens += bucket.totalTokens || 0;
8335
8940
  map.set(name, current);
@@ -8369,6 +8974,7 @@ function buildToolSummary(input2) {
8369
8974
  inputTokens: 0,
8370
8975
  outputTokens: 0,
8371
8976
  cachedTokens: 0,
8977
+ cacheCreationTokens: 0,
8372
8978
  reasoningTokens: 0,
8373
8979
  totalTokens: 0,
8374
8980
  sessions: 1
@@ -8383,6 +8989,7 @@ function buildToolSummary(input2) {
8383
8989
  inputTokens: 0,
8384
8990
  outputTokens: 0,
8385
8991
  cachedTokens: 0,
8992
+ cacheCreationTokens: 0,
8386
8993
  reasoningTokens: 0,
8387
8994
  totalTokens: 0,
8388
8995
  sessions: 1
@@ -8456,27 +9063,38 @@ function buildLocalUsageDashboardData(input2) {
8456
9063
  }
8457
9064
 
8458
9065
  // src/infrastructure/runtime/cli-version.ts
8459
- import { readFileSync as readFileSync14 } from "fs";
8460
- import { dirname as dirname8, join as join34 } from "path";
9066
+ import { dirname as dirname8, join as join35 } from "path";
8461
9067
  import { fileURLToPath } from "url";
8462
9068
  var FALLBACK_VERSION = "0.0.0";
8463
- var cachedVersion;
8464
- function getCliVersion(metaUrl = import.meta.url) {
8465
- if (cachedVersion) {
8466
- return cachedVersion;
9069
+ var versionCache = /* @__PURE__ */ new Map();
9070
+ function readPackageVersion(startDir) {
9071
+ let dir = startDir;
9072
+ while (true) {
9073
+ const contents = readFileSafe(join35(dir, "package.json"));
9074
+ if (contents !== null) {
9075
+ try {
9076
+ const { version } = JSON.parse(contents);
9077
+ return typeof version === "string" && version.length > 0 ? version : void 0;
9078
+ } catch {
9079
+ return void 0;
9080
+ }
9081
+ }
9082
+ const parent = dirname8(dir);
9083
+ if (parent === dir) {
9084
+ return void 0;
9085
+ }
9086
+ dir = parent;
8467
9087
  }
8468
- const packageJsonPath = join34(
8469
- dirname8(fileURLToPath(metaUrl)),
8470
- "..",
8471
- "package.json"
8472
- );
8473
- try {
8474
- const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
8475
- cachedVersion = typeof packageJson.version === "string" ? packageJson.version : FALLBACK_VERSION;
8476
- } catch {
8477
- cachedVersion = FALLBACK_VERSION;
9088
+ }
9089
+ function getCliVersion(metaUrl = import.meta.url) {
9090
+ const startDir = dirname8(fileURLToPath(metaUrl));
9091
+ const cached = versionCache.get(startDir);
9092
+ if (cached !== void 0) {
9093
+ return cached;
8478
9094
  }
8479
- return cachedVersion;
9095
+ const version = readPackageVersion(startDir) ?? FALLBACK_VERSION;
9096
+ versionCache.set(startDir, version);
9097
+ return version;
8480
9098
  }
8481
9099
 
8482
9100
  // src/infrastructure/ui/local-usage-dashboard.ts
@@ -8599,7 +9217,7 @@ function renderOverview(tool) {
8599
9217
  return [
8600
9218
  `${bold("Overview")} ${dim(tool.source)}`,
8601
9219
  ` Tokens ${formatCompactNumber(tool.totals.totalTokens)} total \xB7 ${formatCompactNumber(tool.totals.inputTokens)} input \xB7 ${formatCompactNumber(tool.totals.outputTokens)} output`,
8602
- ` Details ${formatCompactNumber(tool.totals.cachedTokens)} cached \xB7 ${formatCompactNumber(tool.totals.reasoningTokens)} reasoning`,
9220
+ ` Details ${formatCompactNumber(tool.totals.cachedTokens)} cache read \xB7 ${formatCompactNumber(tool.totals.cacheCreationTokens)} cache write \xB7 ${formatCompactNumber(tool.totals.reasoningTokens)} reasoning`,
8603
9221
  ` Scope ${tool.totals.sessions} sessions \xB7 ${tool.totals.buckets} buckets \xB7 ${tool.totals.projects} projects \xB7 ${tool.totals.models} models`
8604
9222
  ];
8605
9223
  }
@@ -8871,13 +9489,14 @@ async function runSyncCommand(opts = {}) {
8871
9489
  }
8872
9490
  await runSync(config, {
8873
9491
  quiet: opts.quiet,
9492
+ rebuild: opts.rebuild,
8874
9493
  source: "manual"
8875
9494
  });
8876
9495
  }
8877
9496
 
8878
9497
  // src/commands/uninstall.ts
8879
- import { existsSync as existsSync35, readFileSync as readFileSync15, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
8880
- import { homedir as homedir33, platform as platform6 } from "os";
9498
+ import { existsSync as existsSync37, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
9499
+ import { homedir as homedir34, platform as platform6 } from "os";
8881
9500
  function removeShellAlias() {
8882
9501
  const shell = process.env.SHELL;
8883
9502
  if (!shell) return;
@@ -8886,24 +9505,24 @@ function removeShellAlias() {
8886
9505
  let configFile;
8887
9506
  switch (shellName) {
8888
9507
  case "zsh":
8889
- configFile = `${homedir33()}/.zshrc`;
9508
+ configFile = `${homedir34()}/.zshrc`;
8890
9509
  break;
8891
9510
  case "bash":
8892
- if (platform6() === "darwin" && existsSync35(`${homedir33()}/.bash_profile`)) {
8893
- configFile = `${homedir33()}/.bash_profile`;
9511
+ if (platform6() === "darwin" && existsSync37(`${homedir34()}/.bash_profile`)) {
9512
+ configFile = `${homedir34()}/.bash_profile`;
8894
9513
  } else {
8895
- configFile = `${homedir33()}/.bashrc`;
9514
+ configFile = `${homedir34()}/.bashrc`;
8896
9515
  }
8897
9516
  break;
8898
9517
  case "fish":
8899
- configFile = `${homedir33()}/.config/fish/config.fish`;
9518
+ configFile = `${homedir34()}/.config/fish/config.fish`;
8900
9519
  break;
8901
9520
  default:
8902
9521
  return;
8903
9522
  }
8904
- if (!existsSync35(configFile)) return;
9523
+ if (!existsSync37(configFile)) return;
8905
9524
  try {
8906
- let content = readFileSync15(configFile, "utf-8");
9525
+ let content = readFileSync14(configFile, "utf-8");
8907
9526
  const aliasPatterns = [
8908
9527
  // zsh / bash format: alias ta="tokenarena"
8909
9528
  new RegExp(
@@ -8940,7 +9559,7 @@ async function runUninstall() {
8940
9559
  const runtimeDir = getRuntimeDirPath();
8941
9560
  const serviceBackend = getServiceBackend();
8942
9561
  const hasInstalledService = serviceBackend?.isInstalled() ?? false;
8943
- const hasLocalArtifacts = existsSync35(configPath) || existsSync35(configDir) || existsSync35(stateDir) || existsSync35(runtimeDir) || hasInstalledService;
9562
+ const hasLocalArtifacts = existsSync37(configPath) || existsSync37(configDir) || existsSync37(stateDir) || existsSync37(runtimeDir) || hasInstalledService;
8944
9563
  if (!hasLocalArtifacts) {
8945
9564
  logger.info(formatHeader("\u5378\u8F7D TokenArena"));
8946
9565
  logger.info(formatBullet("\u672A\u53D1\u73B0\u672C\u5730\u914D\u7F6E\uFF0C\u65E0\u9700\u5378\u8F7D\u3002"));
@@ -8984,22 +9603,22 @@ async function runUninstall() {
8984
9603
  }
8985
9604
  }
8986
9605
  logger.info(formatSection("\u6267\u884C\u7ED3\u679C"));
8987
- if (existsSync35(configPath)) {
9606
+ if (existsSync37(configPath)) {
8988
9607
  deleteConfig();
8989
9608
  logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\u3002", "success"));
8990
9609
  }
8991
- if (existsSync35(configDir)) {
9610
+ if (existsSync37(configDir)) {
8992
9611
  try {
8993
9612
  rmSync6(configDir, { recursive: false, force: true });
8994
9613
  logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u76EE\u5F55\u3002", "success"));
8995
9614
  } catch {
8996
9615
  }
8997
9616
  }
8998
- if (existsSync35(stateDir)) {
9617
+ if (existsSync37(stateDir)) {
8999
9618
  rmSync6(stateDir, { recursive: true, force: true });
9000
9619
  logger.info(formatBullet("\u5DF2\u5220\u9664\u72B6\u6001\u6570\u636E\u3002", "success"));
9001
9620
  }
9002
- if (existsSync35(runtimeDir)) {
9621
+ if (existsSync37(runtimeDir)) {
9003
9622
  rmSync6(runtimeDir, { recursive: true, force: true });
9004
9623
  logger.info(formatBullet("\u5DF2\u5220\u9664\u8FD0\u884C\u65F6\u6570\u636E\u3002", "success"));
9005
9624
  }
@@ -9202,7 +9821,10 @@ function createCli() {
9202
9821
  program.command("init").description("Initialize configuration with API key").option("--api-url <url>", "Custom API server URL").action(async (opts) => {
9203
9822
  await runInit(opts);
9204
9823
  });
9205
- program.command("sync").description("Manually sync usage data to server").addOption(new Option("--quiet").hideHelp()).action(async (opts) => {
9824
+ program.command("sync").description("Manually sync usage data to server").option(
9825
+ "--rebuild",
9826
+ "Replace this device's remote history; requires complete local logs"
9827
+ ).addOption(new Option("--quiet").hideHelp()).action(async (opts) => {
9206
9828
  await runSyncCommand(opts);
9207
9829
  });
9208
9830
  program.command("daemon").description("Run continuous sync (every 30 minutes by default)").option("--interval <ms>", "Sync interval in milliseconds", parseInt).addOption(new Option("--service").hideHelp()).action(async (opts) => {
@@ -9230,10 +9852,10 @@ function createCli() {
9230
9852
  }
9231
9853
 
9232
9854
  // src/infrastructure/runtime/main-module.ts
9233
- import { existsSync as existsSync36, realpathSync as realpathSync2 } from "fs";
9855
+ import { existsSync as existsSync38, realpathSync as realpathSync2 } from "fs";
9234
9856
  import { resolve as resolve4 } from "path";
9235
9857
  import { fileURLToPath as fileURLToPath2 } from "url";
9236
- function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
9858
+ function isMainModule(metaUrl, argvEntry = process.argv[1]) {
9237
9859
  if (!argvEntry) {
9238
9860
  return false;
9239
9861
  }
@@ -9241,7 +9863,7 @@ function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
9241
9863
  try {
9242
9864
  return realpathSync2(argvEntry) === realpathSync2(currentModulePath);
9243
9865
  } catch {
9244
- if (!existsSync36(argvEntry)) {
9866
+ if (!existsSync38(argvEntry)) {
9245
9867
  return false;
9246
9868
  }
9247
9869
  return resolve4(argvEntry) === resolve4(currentModulePath);
@@ -9249,6 +9871,7 @@ function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
9249
9871
  }
9250
9872
 
9251
9873
  // src/index.ts
9874
+ var EXIT_CANCELLED = 130;
9252
9875
  function normalizeArgv(argv) {
9253
9876
  return argv.filter((arg, index) => index < 2 || arg !== "--");
9254
9877
  }
@@ -9256,11 +9879,20 @@ async function run(argv = process.argv) {
9256
9879
  const program = createCli();
9257
9880
  await program.parseAsync(normalizeArgv(argv));
9258
9881
  }
9259
- if (isMainModule()) {
9260
- void run();
9882
+ function reportFatalError(error) {
9883
+ if (error instanceof Error && error.name === "ExitPromptError") {
9884
+ process.exitCode = EXIT_CANCELLED;
9885
+ return;
9886
+ }
9887
+ logger.error(error instanceof Error ? error.message : String(error));
9888
+ process.exitCode = 1;
9889
+ }
9890
+ if (isMainModule(import.meta.url)) {
9891
+ run().catch(reportFatalError);
9261
9892
  }
9262
9893
  export {
9263
9894
  normalizeArgv,
9895
+ reportFatalError,
9264
9896
  run
9265
9897
  };
9266
9898
  //# sourceMappingURL=index.js.map