llm-usage-metrics 0.6.0 → 0.7.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
@@ -694,7 +694,8 @@ var knownSourceColors = {
694
694
  codex: "#22c55e",
695
695
  gemini: "#eab308",
696
696
  droid: "#3b82f6",
697
- opencode: "#a855f7"
697
+ opencode: "#a855f7",
698
+ claude: "#d97757"
698
699
  };
699
700
  var fallbackColors = ["#f97316", "#06b6d4", "#ef4444", "#84cc16", "#f43f5e"];
700
701
  function getSourceColor(source, index) {
@@ -1076,8 +1077,14 @@ function escapeBareAutolinks(value) {
1076
1077
  (_, prefix, email) => `${prefix}${email.replace("@", "\\@")}`
1077
1078
  );
1078
1079
  }
1080
+ function escapeHtmlText(value) {
1081
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1082
+ }
1079
1083
  function escapeMarkdownText(value) {
1080
- const escapedMarkdownText = value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replace(markdownSpecialCharacterPattern, "\\$&");
1084
+ const escapedMarkdownText = escapeHtmlText(value).replace(
1085
+ markdownSpecialCharacterPattern,
1086
+ "\\$&"
1087
+ );
1081
1088
  return escapeBareAutolinks(escapedMarkdownText);
1082
1089
  }
1083
1090
  function toMarkdownSafeCell(value) {
@@ -1270,7 +1277,8 @@ var sourceStylePolicies = /* @__PURE__ */ new Map([
1270
1277
  ["codex", (palette) => palette.magenta],
1271
1278
  ["gemini", (palette) => palette.yellow],
1272
1279
  ["droid", (palette) => palette.green],
1273
- ["opencode", (palette) => palette.blue]
1280
+ ["opencode", (palette) => palette.blue],
1281
+ ["claude", (palette) => palette.cyan]
1274
1282
  ]);
1275
1283
  function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
1276
1284
  const stylePolicy = sourceStylePolicies.get(source);
@@ -3431,7 +3439,7 @@ function formatEnvVarOverrides(overrides) {
3431
3439
  return lines;
3432
3440
  }
3433
3441
 
3434
- // src/sources/codex/codex-source-adapter.ts
3442
+ // src/sources/claude/claude-source-adapter.ts
3435
3443
  import os2 from "os";
3436
3444
  import path6 from "path";
3437
3445
 
@@ -3566,11 +3574,6 @@ async function discoverFiles(rootDir, options) {
3566
3574
  return toCanonicalFiles(files, resolvedOptions);
3567
3575
  }
3568
3576
 
3569
- // src/utils/discover-jsonl-files.ts
3570
- async function discoverJsonlFiles(rootDir) {
3571
- return discoverFiles(rootDir, { extension: ".jsonl" });
3572
- }
3573
-
3574
3577
  // src/utils/fs-helpers.ts
3575
3578
  import { access as access2, constants as constants2, stat as stat3 } from "fs/promises";
3576
3579
  async function pathExists(filePath) {
@@ -3652,6 +3655,22 @@ async function* readJsonlObjects(filePath, options = {}) {
3652
3655
  }
3653
3656
  }
3654
3657
 
3658
+ // src/sources/parse-diagnostics.ts
3659
+ function incrementSkippedReason(reasons, reason) {
3660
+ const current = reasons.get(reason) ?? 0;
3661
+ reasons.set(reason, current + 1);
3662
+ }
3663
+ function toSkippedRowReasonStats(reasons) {
3664
+ return [...reasons.entries()].map(([reason, count]) => ({ reason, count })).sort((left, right) => compareByCodePoint(left.reason, right.reason));
3665
+ }
3666
+ function toParseDiagnostics(events, skippedRows, skippedRowReasons) {
3667
+ return {
3668
+ events,
3669
+ skippedRows,
3670
+ skippedRowReasons: toSkippedRowReasonStats(skippedRowReasons)
3671
+ };
3672
+ }
3673
+
3655
3674
  // src/sources/parsing-utils.ts
3656
3675
  var MIN_PLAUSIBLE_UNIX_SECONDS_ABS = 1e8;
3657
3676
  var UNIX_SECONDS_ABS_CUTOFF = 1e10;
@@ -3704,8 +3723,180 @@ function normalizeTimestampCandidate(candidate) {
3704
3723
  return date.toISOString();
3705
3724
  }
3706
3725
 
3726
+ // src/sources/claude/claude-source-adapter.ts
3727
+ var defaultClaudeProjectsDir = path6.join(os2.homedir(), ".claude", "projects");
3728
+ var CLAUDE_ASSISTANT_LINE_PATTERN = /"type"\s*:\s*"assistant"/u;
3729
+ var CLAUDE_USAGE_LINE_PATTERN = /"usage"\s*:/u;
3730
+ function shouldParseClaudeJsonlLine(lineText) {
3731
+ return CLAUDE_ASSISTANT_LINE_PATTERN.test(lineText) && CLAUDE_USAGE_LINE_PATTERN.test(lineText);
3732
+ }
3733
+ function getFallbackSessionId(filePath) {
3734
+ return path6.basename(filePath, ".jsonl");
3735
+ }
3736
+ function resolveProvider(message, model) {
3737
+ const explicitProvider = asTrimmedText(message.provider);
3738
+ if (explicitProvider) {
3739
+ return explicitProvider;
3740
+ }
3741
+ if (model?.toLowerCase().startsWith("claude-")) {
3742
+ return "anthropic";
3743
+ }
3744
+ return void 0;
3745
+ }
3746
+ function parseUsage(usage) {
3747
+ const inputTokens = normalizeNonNegativeInteger(toNumberLike(usage.input_tokens));
3748
+ const outputTokens = normalizeNonNegativeInteger(toNumberLike(usage.output_tokens));
3749
+ const cacheReadTokens = normalizeNonNegativeInteger(toNumberLike(usage.cache_read_input_tokens));
3750
+ const cacheWriteTokens = normalizeNonNegativeInteger(
3751
+ toNumberLike(usage.cache_creation_input_tokens)
3752
+ );
3753
+ const totalTokens = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
3754
+ if (totalTokens === 0) {
3755
+ return void 0;
3756
+ }
3757
+ return {
3758
+ inputTokens,
3759
+ outputTokens,
3760
+ reasoningTokens: 0,
3761
+ cacheReadTokens,
3762
+ cacheWriteTokens,
3763
+ totalTokens
3764
+ };
3765
+ }
3766
+ function createDedupKey(filePath, line, message) {
3767
+ const messageId = asTrimmedText(message.id);
3768
+ if (messageId) {
3769
+ return `${filePath}\0${messageId}`;
3770
+ }
3771
+ const uuid = asTrimmedText(line.uuid);
3772
+ return uuid ? `${filePath}\0${uuid}` : void 0;
3773
+ }
3774
+ function comparePendingEvents(left, right) {
3775
+ if (left.timestamp !== right.timestamp) {
3776
+ return compareByCodePoint(left.timestamp, right.timestamp);
3777
+ }
3778
+ return left.sequence - right.sequence;
3779
+ }
3780
+ var ClaudeSourceAdapter = class {
3781
+ id = "claude";
3782
+ projectsDir;
3783
+ requireProjectsDir;
3784
+ constructor(options = {}) {
3785
+ this.projectsDir = options.projectsDir ?? defaultClaudeProjectsDir;
3786
+ this.requireProjectsDir = options.requireProjectsDir ?? false;
3787
+ }
3788
+ getNormalizedProjectsDir() {
3789
+ if (isBlankText(this.projectsDir)) {
3790
+ throw new Error("Claude projects directory must be a non-empty path");
3791
+ }
3792
+ return this.projectsDir.trim();
3793
+ }
3794
+ async discoverFiles() {
3795
+ const normalizedProjectsDir = this.getNormalizedProjectsDir();
3796
+ if (this.requireProjectsDir && !await pathReadable(normalizedProjectsDir)) {
3797
+ throw new Error(
3798
+ `Claude projects directory is missing or unreadable: ${normalizedProjectsDir}`
3799
+ );
3800
+ }
3801
+ if (this.requireProjectsDir && !await pathIsDirectory(normalizedProjectsDir)) {
3802
+ throw new Error(`Claude projects directory is not a directory: ${normalizedProjectsDir}`);
3803
+ }
3804
+ return discoverFiles(normalizedProjectsDir, { extension: ".jsonl" });
3805
+ }
3806
+ async parseFile(filePath) {
3807
+ const { events } = await this.parseFileWithDiagnostics(filePath);
3808
+ return events;
3809
+ }
3810
+ async parseFileWithDiagnostics(filePath) {
3811
+ const eventsByDedupKey = /* @__PURE__ */ new Map();
3812
+ let skippedRows = 0;
3813
+ let sequence = 0;
3814
+ const skippedRowReasons = /* @__PURE__ */ new Map();
3815
+ for await (const line of readJsonlObjects(filePath, {
3816
+ shouldParseLine: shouldParseClaudeJsonlLine
3817
+ })) {
3818
+ if (asTrimmedText(line.type) !== "assistant") {
3819
+ continue;
3820
+ }
3821
+ const message = asRecord(line.message);
3822
+ if (!message || asTrimmedText(message.role) !== "assistant") {
3823
+ skippedRows++;
3824
+ incrementSkippedReason(skippedRowReasons, "invalid_assistant_message");
3825
+ continue;
3826
+ }
3827
+ const model = asTrimmedText(message.model);
3828
+ if (model === "<synthetic>") {
3829
+ skippedRows++;
3830
+ incrementSkippedReason(skippedRowReasons, "synthetic_message");
3831
+ continue;
3832
+ }
3833
+ const usage = parseUsage(asRecord(message.usage) ?? {});
3834
+ if (!usage) {
3835
+ skippedRows++;
3836
+ incrementSkippedReason(skippedRowReasons, "no_token_usage");
3837
+ continue;
3838
+ }
3839
+ const timestamp = normalizeTimestampCandidate(line.timestamp);
3840
+ if (!timestamp) {
3841
+ skippedRows++;
3842
+ incrementSkippedReason(skippedRowReasons, "invalid_timestamp");
3843
+ continue;
3844
+ }
3845
+ const dedupKey = createDedupKey(filePath, line, message);
3846
+ if (!dedupKey) {
3847
+ skippedRows++;
3848
+ incrementSkippedReason(skippedRowReasons, "missing_message_id");
3849
+ continue;
3850
+ }
3851
+ const sessionId = asTrimmedText(line.sessionId) ?? getFallbackSessionId(filePath);
3852
+ const repoRoot = asTrimmedText(line.cwd);
3853
+ const provider = resolveProvider(message, model);
3854
+ sequence += 1;
3855
+ eventsByDedupKey.set(dedupKey, {
3856
+ sessionId,
3857
+ timestamp,
3858
+ repoRoot,
3859
+ provider,
3860
+ model,
3861
+ usage,
3862
+ sequence
3863
+ });
3864
+ }
3865
+ const events = [];
3866
+ for (const pendingEvent of [...eventsByDedupKey.values()].sort(comparePendingEvents)) {
3867
+ try {
3868
+ events.push(
3869
+ createUsageEvent({
3870
+ source: this.id,
3871
+ sessionId: pendingEvent.sessionId,
3872
+ timestamp: pendingEvent.timestamp,
3873
+ repoRoot: pendingEvent.repoRoot,
3874
+ provider: pendingEvent.provider,
3875
+ model: pendingEvent.model,
3876
+ ...pendingEvent.usage,
3877
+ costMode: "estimated"
3878
+ })
3879
+ );
3880
+ } catch {
3881
+ skippedRows++;
3882
+ incrementSkippedReason(skippedRowReasons, "event_creation_failed");
3883
+ }
3884
+ }
3885
+ return toParseDiagnostics(events, skippedRows, skippedRowReasons);
3886
+ }
3887
+ };
3888
+
3707
3889
  // src/sources/codex/codex-source-adapter.ts
3708
- var defaultSessionsDir = path6.join(os2.homedir(), ".codex", "sessions");
3890
+ import os3 from "os";
3891
+ import path7 from "path";
3892
+
3893
+ // src/utils/discover-jsonl-files.ts
3894
+ async function discoverJsonlFiles(rootDir) {
3895
+ return discoverFiles(rootDir, { extension: ".jsonl" });
3896
+ }
3897
+
3898
+ // src/sources/codex/codex-source-adapter.ts
3899
+ var defaultSessionsDir = path7.join(os3.homedir(), ".codex", "sessions");
3709
3900
  var LEGACY_CODEX_MODEL_FALLBACK = "legacy-codex-unknown";
3710
3901
  var SESSION_META_LINE_PATTERN = /"type"\s*:\s*"session_meta"/u;
3711
3902
  var TURN_CONTEXT_LINE_PATTERN = /"type"\s*:\s*"turn_context"/u;
@@ -3799,8 +3990,8 @@ function createLastUsageOnlyKey(timestamp, usage) {
3799
3990
  usage.totalTokens
3800
3991
  ].join(":");
3801
3992
  }
3802
- function getFallbackSessionId(filePath) {
3803
- return path6.basename(filePath, ".jsonl");
3993
+ function getFallbackSessionId2(filePath) {
3994
+ return path7.basename(filePath, ".jsonl");
3804
3995
  }
3805
3996
  function resolveRepoRootFromPayload(payload) {
3806
3997
  if (!payload) {
@@ -3840,7 +4031,7 @@ var CodexSourceAdapter = class {
3840
4031
  async parseFile(filePath) {
3841
4032
  const events = [];
3842
4033
  const state = {
3843
- sessionId: getFallbackSessionId(filePath),
4034
+ sessionId: getFallbackSessionId2(filePath),
3844
4035
  provider: "openai"
3845
4036
  };
3846
4037
  for await (const line of readJsonlObjects(filePath, {
@@ -3932,37 +4123,19 @@ var CodexSourceAdapter = class {
3932
4123
 
3933
4124
  // src/sources/droid/droid-source-adapter.ts
3934
4125
  import { readFile as readFile2 } from "fs/promises";
3935
- import os3 from "os";
3936
- import path7 from "path";
3937
-
3938
- // src/sources/parse-diagnostics.ts
3939
- function incrementSkippedReason(reasons, reason) {
3940
- const current = reasons.get(reason) ?? 0;
3941
- reasons.set(reason, current + 1);
3942
- }
3943
- function toSkippedRowReasonStats(reasons) {
3944
- return [...reasons.entries()].map(([reason, count]) => ({ reason, count })).sort((left, right) => compareByCodePoint(left.reason, right.reason));
3945
- }
3946
- function toParseDiagnostics(events, skippedRows, skippedRowReasons) {
3947
- return {
3948
- events,
3949
- skippedRows,
3950
- skippedRowReasons: toSkippedRowReasonStats(skippedRowReasons)
3951
- };
3952
- }
3953
-
3954
- // src/sources/droid/droid-source-adapter.ts
3955
- var defaultSessionsDir2 = path7.join(os3.homedir(), ".factory", "sessions");
4126
+ import os4 from "os";
4127
+ import path8 from "path";
4128
+ var defaultSessionsDir2 = path8.join(os4.homedir(), ".factory", "sessions");
3956
4129
  var DROID_SESSION_START_LINE_PATTERN = /"type"\s*:\s*"session_start"/u;
3957
4130
  var DROID_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
3958
4131
  function shouldParseDroidJsonlLine(lineText) {
3959
4132
  return DROID_SESSION_START_LINE_PATTERN.test(lineText) || DROID_MESSAGE_LINE_PATTERN.test(lineText);
3960
4133
  }
3961
4134
  function getSettingsSessionId(filePath) {
3962
- return path7.basename(filePath, ".settings.json");
4135
+ return path8.basename(filePath, ".settings.json");
3963
4136
  }
3964
4137
  function getSiblingJsonlPath(settingsPath) {
3965
- return path7.join(path7.dirname(settingsPath), `${getSettingsSessionId(settingsPath)}.jsonl`);
4138
+ return path8.join(path8.dirname(settingsPath), `${getSettingsSessionId(settingsPath)}.jsonl`);
3966
4139
  }
3967
4140
  function isSessionStartRecord(line) {
3968
4141
  return asTrimmedText(line.type) === "session_start";
@@ -4106,11 +4279,11 @@ var DroidSourceAdapter = class {
4106
4279
 
4107
4280
  // src/sources/gemini/gemini-source-adapter.ts
4108
4281
  import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
4109
- import os4 from "os";
4110
- import path8 from "path";
4111
- var defaultGeminiDir = path8.join(os4.homedir(), ".gemini");
4282
+ import os5 from "os";
4283
+ import path9 from "path";
4284
+ var defaultGeminiDir = path9.join(os5.homedir(), ".gemini");
4112
4285
  function getProjectsJsonPath(geminiDir) {
4113
- return path8.join(geminiDir, "projects.json");
4286
+ return path9.join(geminiDir, "projects.json");
4114
4287
  }
4115
4288
  function parseProjectsJson(data) {
4116
4289
  const mapping = /* @__PURE__ */ new Map();
@@ -4145,7 +4318,7 @@ async function loadProjectsJson(geminiDir) {
4145
4318
  }
4146
4319
  }
4147
4320
  async function discoverSessionFiles(geminiDir) {
4148
- const tmpDir = path8.join(geminiDir, "tmp");
4321
+ const tmpDir = path9.join(geminiDir, "tmp");
4149
4322
  let projectEntries;
4150
4323
  try {
4151
4324
  projectEntries = await readdir2(tmpDir, { withFileTypes: true, encoding: "utf8" });
@@ -4162,7 +4335,7 @@ async function discoverSessionFiles(geminiDir) {
4162
4335
  if (!projectEntry.isDirectory() && !projectEntry.isSymbolicLink()) {
4163
4336
  continue;
4164
4337
  }
4165
- const chatsDir = path8.join(tmpDir, projectEntry.name, "chats");
4338
+ const chatsDir = path9.join(tmpDir, projectEntry.name, "chats");
4166
4339
  const discoveredChatFiles = await discoverFiles(chatsDir, { extension: ".json" });
4167
4340
  allSessionFiles.push(...discoveredChatFiles);
4168
4341
  }
@@ -4176,9 +4349,9 @@ function resolveRepoRoot(filePath, sessionData, projectMapping) {
4176
4349
  return mappedRoot;
4177
4350
  }
4178
4351
  }
4179
- const chatsDir = path8.dirname(filePath);
4180
- const projectDir = path8.dirname(chatsDir);
4181
- const projectIdentifier = path8.basename(projectDir);
4352
+ const chatsDir = path9.dirname(filePath);
4353
+ const projectDir = path9.dirname(chatsDir);
4354
+ const projectIdentifier = path9.basename(projectDir);
4182
4355
  return projectMapping.get(projectIdentifier);
4183
4356
  }
4184
4357
  function toFiniteNumber(value) {
@@ -4287,7 +4460,7 @@ var GeminiSourceAdapter = class {
4287
4460
  incrementSkippedReason(skippedRowReasons, "invalid_session_data");
4288
4461
  return toParseDiagnostics(events, skippedRows, skippedRowReasons);
4289
4462
  }
4290
- const sessionId = asTrimmedText(sessionDataRecord.sessionId) ?? path8.basename(filePath, ".json");
4463
+ const sessionId = asTrimmedText(sessionDataRecord.sessionId) ?? path9.basename(filePath, ".json");
4291
4464
  const projectMapping = await this.getProjectMappingForParse();
4292
4465
  const repoRoot = resolveRepoRoot(filePath, sessionDataRecord, projectMapping);
4293
4466
  if (!Array.isArray(sessionDataRecord.messages)) {
@@ -4344,8 +4517,8 @@ var GeminiSourceAdapter = class {
4344
4517
  };
4345
4518
 
4346
4519
  // src/sources/opencode/opencode-db-path-resolver.ts
4347
- import os5 from "os";
4348
- import path9 from "path";
4520
+ import os6 from "os";
4521
+ import path10 from "path";
4349
4522
  function deduplicate(paths) {
4350
4523
  return [...new Set(paths)];
4351
4524
  }
@@ -4357,39 +4530,39 @@ function normalizeEnvPath(value) {
4357
4530
  return normalized || void 0;
4358
4531
  }
4359
4532
  function getLinuxLikeCandidates(homeDir, env) {
4360
- const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path9.join(homeDir, ".local", "share");
4533
+ const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path10.join(homeDir, ".local", "share");
4361
4534
  return [
4362
- path9.join(xdgDataHome, "opencode", "opencode.db"),
4363
- path9.join(xdgDataHome, "opencode", "db.sqlite"),
4364
- path9.join(homeDir, ".opencode", "opencode.db"),
4365
- path9.join(homeDir, ".opencode", "db.sqlite")
4535
+ path10.join(xdgDataHome, "opencode", "opencode.db"),
4536
+ path10.join(xdgDataHome, "opencode", "db.sqlite"),
4537
+ path10.join(homeDir, ".opencode", "opencode.db"),
4538
+ path10.join(homeDir, ".opencode", "db.sqlite")
4366
4539
  ];
4367
4540
  }
4368
4541
  function getMacOsCandidates(homeDir) {
4369
- const appSupportDir = path9.join(homeDir, "Library", "Application Support");
4542
+ const appSupportDir = path10.join(homeDir, "Library", "Application Support");
4370
4543
  return [
4371
- path9.join(appSupportDir, "opencode", "opencode.db"),
4372
- path9.join(appSupportDir, "opencode", "db.sqlite"),
4373
- path9.join(homeDir, ".opencode", "opencode.db"),
4374
- path9.join(homeDir, ".opencode", "db.sqlite")
4544
+ path10.join(appSupportDir, "opencode", "opencode.db"),
4545
+ path10.join(appSupportDir, "opencode", "db.sqlite"),
4546
+ path10.join(homeDir, ".opencode", "opencode.db"),
4547
+ path10.join(homeDir, ".opencode", "db.sqlite")
4375
4548
  ];
4376
4549
  }
4377
4550
  function getWindowsCandidates(homeDir, env) {
4378
4551
  const userProfile = normalizeEnvPath(env.USERPROFILE);
4379
- const roamingBase = normalizeEnvPath(env.APPDATA) ?? normalizeEnvPath(env.LOCALAPPDATA) ?? (userProfile ? path9.join(userProfile, "AppData", "Roaming") : void 0);
4552
+ const roamingBase = normalizeEnvPath(env.APPDATA) ?? normalizeEnvPath(env.LOCALAPPDATA) ?? (userProfile ? path10.join(userProfile, "AppData", "Roaming") : void 0);
4380
4553
  const roamingCandidates = roamingBase ? [
4381
- path9.join(roamingBase, "opencode", "opencode.db"),
4382
- path9.join(roamingBase, "opencode", "db.sqlite")
4554
+ path10.join(roamingBase, "opencode", "opencode.db"),
4555
+ path10.join(roamingBase, "opencode", "db.sqlite")
4383
4556
  ] : [];
4384
4557
  return [
4385
4558
  ...roamingCandidates,
4386
- path9.join(homeDir, ".opencode", "opencode.db"),
4387
- path9.join(homeDir, ".opencode", "db.sqlite")
4559
+ path10.join(homeDir, ".opencode", "opencode.db"),
4560
+ path10.join(homeDir, ".opencode", "db.sqlite")
4388
4561
  ];
4389
4562
  }
4390
4563
  function getDefaultOpenCodeDbPathCandidates(options = {}) {
4391
4564
  const platform = options.platform ?? process.platform;
4392
- const homeDir = options.homeDir ?? os5.homedir();
4565
+ const homeDir = options.homeDir ?? os6.homedir();
4393
4566
  const env = options.env ?? process.env;
4394
4567
  switch (platform) {
4395
4568
  case "win32":
@@ -4878,9 +5051,9 @@ var OpenCodeSourceAdapter = class {
4878
5051
  };
4879
5052
 
4880
5053
  // src/sources/pi/pi-source-adapter.ts
4881
- import os6 from "os";
4882
- import path10 from "path";
4883
- var defaultSessionsDir3 = path10.join(os6.homedir(), ".pi", "agent", "sessions");
5054
+ import os7 from "os";
5055
+ import path11 from "path";
5056
+ var defaultSessionsDir3 = path11.join(os7.homedir(), ".pi", "agent", "sessions");
4884
5057
  var PI_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
4885
5058
  var PI_SESSION_LINE_PATTERN = /"type"\s*:\s*"session"/u;
4886
5059
  var PI_MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
@@ -4953,8 +5126,8 @@ function extractUsage(line, message) {
4953
5126
  }
4954
5127
  return extractUsageFromRecord(messageUsage);
4955
5128
  }
4956
- function getFallbackSessionId2(filePath) {
4957
- return path10.basename(filePath, ".jsonl");
5129
+ function getFallbackSessionId3(filePath) {
5130
+ return path11.basename(filePath, ".jsonl");
4958
5131
  }
4959
5132
  function resolveRepoRootFromRecord(record) {
4960
5133
  if (!record) {
@@ -4986,7 +5159,7 @@ var PiSourceAdapter = class {
4986
5159
  }
4987
5160
  async parseFile(filePath) {
4988
5161
  const events = [];
4989
- const state = { sessionId: getFallbackSessionId2(filePath) };
5162
+ const state = { sessionId: getFallbackSessionId3(filePath) };
4990
5163
  for await (const line of readJsonlObjects(filePath, {
4991
5164
  shouldParseLine: shouldParsePiJsonlLine
4992
5165
  })) {
@@ -5125,6 +5298,21 @@ var sourceRegistrations = [
5125
5298
  create: (options) => new OpenCodeSourceAdapter({
5126
5299
  dbPath: options.opencodeDb
5127
5300
  })
5301
+ },
5302
+ {
5303
+ id: "claude",
5304
+ sourceDirOverride: { kind: "directory" },
5305
+ create: (options, sourceDirectoryOverrides) => {
5306
+ const directoryConfig = resolveDirectoryConfig(
5307
+ "claude",
5308
+ options.claudeDir,
5309
+ sourceDirectoryOverrides
5310
+ );
5311
+ return new ClaudeSourceAdapter({
5312
+ projectsDir: directoryConfig.path,
5313
+ requireProjectsDir: directoryConfig.requireExistingPath
5314
+ });
5315
+ }
5128
5316
  }
5129
5317
  ];
5130
5318
  var sourceDirUnsupportedFlags = new Map(
@@ -5201,6 +5389,7 @@ function createDefaultAdapters(options) {
5201
5389
  validateDirectoryOverride("--codex-dir", options.codexDir);
5202
5390
  validateDirectoryOverride("--gemini-dir", options.geminiDir);
5203
5391
  validateDirectoryOverride("--droid-dir", options.droidDir);
5392
+ validateDirectoryOverride("--claude-dir", options.claudeDir);
5204
5393
  const sourceDirectoryOverrides = parseSourceDirectoryOverrides(options.sourceDir);
5205
5394
  validateSourceDirectoryOverrideIds(sourceDirectoryOverrides);
5206
5395
  return sourceRegistrations.map((source) => source.create(options, sourceDirectoryOverrides));
@@ -5316,6 +5505,9 @@ function resolveExplicitSourceIds(options, sourceFilter) {
5316
5505
  if (options.droidDir) {
5317
5506
  explicitSourceIds.add("droid");
5318
5507
  }
5508
+ if (options.claudeDir) {
5509
+ explicitSourceIds.add("claude");
5510
+ }
5319
5511
  if (options.opencodeDb) {
5320
5512
  explicitSourceIds.add("opencode");
5321
5513
  }
@@ -5444,7 +5636,7 @@ function normalizeSkippedRowReasons(value) {
5444
5636
 
5445
5637
  // src/cli/parse-file-cache.ts
5446
5638
  import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat4, writeFile as writeFile2 } from "fs/promises";
5447
- import path11 from "path";
5639
+ import path12 from "path";
5448
5640
  var PARSE_FILE_CACHE_VERSION = 5;
5449
5641
  var CACHE_KEY_SEPARATOR = "\0";
5450
5642
  function createCacheKey(source, filePath) {
@@ -5671,7 +5863,7 @@ function normalizeCacheEntry(value) {
5671
5863
  };
5672
5864
  }
5673
5865
  function getDefaultParseFileCachePath() {
5674
- return path11.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
5866
+ return path12.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
5675
5867
  }
5676
5868
  function normalizeCacheShardSource(source) {
5677
5869
  const normalizedSource = normalizeCacheSource(source);
@@ -5681,12 +5873,12 @@ function normalizeCacheShardSource(source) {
5681
5873
  return normalizedSource.replace(/[^a-z0-9._-]/gu, "_");
5682
5874
  }
5683
5875
  function getSourceShardedParseFileCachePath(cacheFilePath, source) {
5684
- const parsedPath = path11.parse(cacheFilePath);
5876
+ const parsedPath = path12.parse(cacheFilePath);
5685
5877
  const sourceShard = normalizeCacheShardSource(source);
5686
5878
  if (parsedPath.ext.length > 0) {
5687
- return path11.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
5879
+ return path12.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
5688
5880
  }
5689
- return path11.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
5881
+ return path12.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
5690
5882
  }
5691
5883
  var ParseFileCache = class _ParseFileCache {
5692
5884
  constructor(cacheFilePath, limits, now) {
@@ -5694,6 +5886,9 @@ var ParseFileCache = class _ParseFileCache {
5694
5886
  this.limits = limits;
5695
5887
  this.now = now;
5696
5888
  }
5889
+ cacheFilePath;
5890
+ limits;
5891
+ now;
5697
5892
  entriesByKey = /* @__PURE__ */ new Map();
5698
5893
  dirty = false;
5699
5894
  static async load(options) {
@@ -5780,7 +5975,7 @@ var ParseFileCache = class _ParseFileCache {
5780
5975
  keptEntries.length = bestCount;
5781
5976
  payloadText = bestPayloadText;
5782
5977
  }
5783
- await mkdir2(path11.dirname(this.cacheFilePath), { recursive: true });
5978
+ await mkdir2(path12.dirname(this.cacheFilePath), { recursive: true });
5784
5979
  const temporaryPath = `${this.cacheFilePath}.${process.pid}.${this.now()}.tmp`;
5785
5980
  try {
5786
5981
  await writeFile2(temporaryPath, payloadText, "utf8");
@@ -6269,7 +6464,7 @@ function applyPricingToEvents(events, pricingSource) {
6269
6464
 
6270
6465
  // src/pricing/litellm-pricing-fetcher.ts
6271
6466
  import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
6272
- import path12 from "path";
6467
+ import path13 from "path";
6273
6468
 
6274
6469
  // src/pricing/litellm-model-map.json
6275
6470
  var litellm_model_map_default = {
@@ -6453,7 +6648,7 @@ function normalizeLitellmPricingPayload(payload) {
6453
6648
  return normalizedPricing;
6454
6649
  }
6455
6650
  function getDefaultLiteLLMPricingCachePath() {
6456
- return path12.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
6651
+ return path13.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
6457
6652
  }
6458
6653
  function stripProviderPrefix(model) {
6459
6654
  const slashIndex = model.lastIndexOf("/");
@@ -6847,7 +7042,7 @@ var LiteLLMPricingFetcher = class {
6847
7042
  };
6848
7043
  }
6849
7044
  async writeCache() {
6850
- const directoryPath = path12.dirname(this.cacheFilePath);
7045
+ const directoryPath = path13.dirname(this.cacheFilePath);
6851
7046
  await mkdir3(directoryPath, { recursive: true });
6852
7047
  const payload = {
6853
7048
  fetchedAt: this.now(),
@@ -6958,6 +7153,7 @@ var RuntimeProfileCollector = class {
6958
7153
  constructor(now = () => performance.now()) {
6959
7154
  this.now = now;
6960
7155
  }
7156
+ now;
6961
7157
  sourceSelection;
6962
7158
  sourceStats = /* @__PURE__ */ new Map();
6963
7159
  stageDurations = /* @__PURE__ */ new Map();
@@ -7251,6 +7447,9 @@ function resolveScopeNote(options) {
7251
7447
  if (hasActiveTextOption(options.droidDir)) {
7252
7448
  activeFilters.push("--droid-dir");
7253
7449
  }
7450
+ if (hasActiveTextOption(options.claudeDir)) {
7451
+ activeFilters.push("--claude-dir");
7452
+ }
7254
7453
  if (hasActiveTextOption(options.opencodeDb)) {
7255
7454
  activeFilters.push("--opencode-db");
7256
7455
  }
@@ -7436,10 +7635,11 @@ function emitEnvVarOverrides(activeEnvOverrides, diagnosticsLogger) {
7436
7635
 
7437
7636
  // src/cli/share-artifact.ts
7438
7637
  import { spawn as spawn4 } from "child_process";
7439
- import { writeFile as writeFile4 } from "fs/promises";
7440
- import path13 from "path";
7638
+ import { constants as constants3 } from "fs";
7639
+ import { access as access3, writeFile as writeFile4 } from "fs/promises";
7640
+ import path14 from "path";
7441
7641
  async function writeShareSvgFile(fileName, svgContent) {
7442
- const outputPath = path13.resolve(process.cwd(), fileName);
7642
+ const outputPath = path14.resolve(process.cwd(), fileName);
7443
7643
  await writeFile4(outputPath, svgContent, "utf8");
7444
7644
  return outputPath;
7445
7645
  }
@@ -7449,21 +7649,68 @@ function stringifyError(error) {
7449
7649
  }
7450
7650
  return String(error);
7451
7651
  }
7452
- function resolveOpenCommand(filePath, platform) {
7652
+ var WINDOWS_OPEN_COMMAND = "C:\\Windows\\System32\\rundll32.exe";
7653
+ var DARWIN_OPEN_COMMAND = "/usr/bin/open";
7654
+ var UNIX_OPEN_COMMAND = "/usr/bin/xdg-open";
7655
+ var WINDOWS_FALLBACK_COMMANDS = ["rundll32.exe"];
7656
+ var DARWIN_FALLBACK_COMMANDS = ["open"];
7657
+ var UNIX_FALLBACK_COMMANDS = ["xdg-open"];
7658
+ async function fileExists(filePath) {
7659
+ try {
7660
+ await access3(filePath, constants3.X_OK);
7661
+ return true;
7662
+ } catch {
7663
+ return false;
7664
+ }
7665
+ }
7666
+ async function resolveBinaryPath(primaryPath, fallbackNames) {
7667
+ if (await fileExists(primaryPath)) {
7668
+ return primaryPath;
7669
+ }
7670
+ const pathDirs = (process.env.PATH ?? "").split(path14.delimiter).filter(Boolean);
7671
+ for (const fallbackName of fallbackNames) {
7672
+ for (const dir of pathDirs) {
7673
+ const candidate = path14.join(dir, fallbackName);
7674
+ if (await fileExists(candidate)) {
7675
+ return candidate;
7676
+ }
7677
+ }
7678
+ }
7679
+ return void 0;
7680
+ }
7681
+ async function resolveOpenCommand(filePath, platform) {
7453
7682
  if (platform === "win32") {
7683
+ const resolvedPath2 = await resolveBinaryPath(WINDOWS_OPEN_COMMAND, WINDOWS_FALLBACK_COMMANDS);
7684
+ if (!resolvedPath2) {
7685
+ throw new Error(
7686
+ "Could not find rundll32.exe. Please ensure Windows System32 is accessible or rundll32.exe is available on PATH."
7687
+ );
7688
+ }
7454
7689
  return {
7455
- command: "cmd",
7456
- args: ["/c", "start", "", filePath]
7690
+ command: resolvedPath2,
7691
+ args: ["shell32.dll,ShellExec_RunDLL", filePath]
7457
7692
  };
7458
7693
  }
7459
7694
  if (platform === "darwin") {
7695
+ const resolvedPath2 = await resolveBinaryPath(DARWIN_OPEN_COMMAND, DARWIN_FALLBACK_COMMANDS);
7696
+ if (!resolvedPath2) {
7697
+ throw new Error(
7698
+ "Could not find open command. Please ensure macOS is properly configured or open is available on PATH."
7699
+ );
7700
+ }
7460
7701
  return {
7461
- command: "open",
7702
+ command: resolvedPath2,
7462
7703
  args: [filePath]
7463
7704
  };
7464
7705
  }
7706
+ const resolvedPath = await resolveBinaryPath(UNIX_OPEN_COMMAND, UNIX_FALLBACK_COMMANDS);
7707
+ if (!resolvedPath) {
7708
+ throw new Error(
7709
+ "Could not find xdg-open. Please install xdg-utils or ensure it is in your PATH."
7710
+ );
7711
+ }
7465
7712
  return {
7466
- command: "xdg-open",
7713
+ command: resolvedPath,
7467
7714
  args: [filePath]
7468
7715
  };
7469
7716
  }
@@ -7494,7 +7741,7 @@ async function spawnDetached(command, args) {
7494
7741
  async function openShareSvgFile(filePath, deps = {}) {
7495
7742
  const platform = deps.platform ?? process.platform;
7496
7743
  const runDetached = deps.spawnDetached ?? spawnDetached;
7497
- const { command, args } = resolveOpenCommand(filePath, platform);
7744
+ const { command, args } = await resolveOpenCommand(filePath, platform);
7498
7745
  await runDetached(command, args);
7499
7746
  }
7500
7747
  async function writeAndOpenShareSvgFile(fileName, svgContent, deps = {}) {
@@ -7782,7 +8029,7 @@ function resolveTerminalContextLines(optimizeData, options) {
7782
8029
  const bestRow = rowsWithSavings.length > 0 ? rowsWithSavings.reduce(
7783
8030
  (best, current) => (current.savingsUsd ?? Number.NEGATIVE_INFINITY) > (best.savingsUsd ?? Number.NEGATIVE_INFINITY) ? current : best
7784
8031
  ) : void 0;
7785
- if (!bestRow || bestRow.savingsUsd === void 0) {
8032
+ if (bestRow?.savingsUsd === void 0) {
7786
8033
  lines.push("ALL best candidate: unavailable (missing baseline or candidate pricing)");
7787
8034
  } else if (bestRow.savingsUsd > 0) {
7788
8035
  lines.push(
@@ -9654,7 +9901,7 @@ function registerSharedReportOptions(command, profile) {
9654
9901
  const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
9655
9902
  const supportedSourcesSummary = `(${supportedSourceIds.length}): ${allowedSourcesLabel}`;
9656
9903
  const profileConfig = sharedOptionProfileConfig[profile];
9657
- const configuredCommand = command.option("--pi-dir <path>", "Path to .pi sessions directory").option("--codex-dir <path>", "Path to .codex sessions directory").option("--gemini-dir <path>", "Path to .gemini directory").option("--droid-dir <path>", "Path to Droid sessions directory").option("--opencode-db <path>", "Path to OpenCode SQLite DB").option(
9904
+ const configuredCommand = command.option("--pi-dir <path>", "Path to .pi sessions directory").option("--codex-dir <path>", "Path to .codex sessions directory").option("--gemini-dir <path>", "Path to .gemini directory").option("--droid-dir <path>", "Path to Droid sessions directory").option("--claude-dir <path>", "Path to Claude projects directory").option("--opencode-db <path>", "Path to OpenCode SQLite DB").option(
9658
9905
  "--source-dir <source-id=path>",
9659
9906
  "Override source directory for directory-backed sources (repeatable)",
9660
9907
  collectRepeatedOption