llm-usage-metrics 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -106,9 +106,6 @@ function getParsingRuntimeConfig(env = process.env) {
106
106
  };
107
107
  }
108
108
 
109
- // src/update/update-notifier.ts
110
- import { spawn as spawn2 } from "child_process";
111
-
112
109
  // src/update/update-cache-repository.ts
113
110
  import { mkdir, readFile, writeFile } from "fs/promises";
114
111
  import path2 from "path";
@@ -529,7 +526,6 @@ async function runInteractiveInstallAndRestart(options) {
529
526
 
530
527
  // src/update/update-notifier.ts
531
528
  var UPDATE_CHECK_SKIP_ENV_VAR = "LLM_USAGE_SKIP_UPDATE_CHECK";
532
- var UPDATE_CHECK_REFRESH_ENV_VAR = "LLM_USAGE_REFRESH_UPDATE_CHECK";
533
529
  function isTruthyEnvFlag(value) {
534
530
  if (value === void 0) {
535
531
  return false;
@@ -588,32 +584,6 @@ function toResolveLatestVersionOptions(options, env) {
588
584
  now: options.now
589
585
  };
590
586
  }
591
- function runDetachedCommandWithSpawn(command, args, options = {}) {
592
- const child = spawn2(command, args, {
593
- env: options.env,
594
- stdio: options.stdio ?? "ignore",
595
- detached: true
596
- });
597
- child.on("error", () => void 0);
598
- child.unref();
599
- }
600
- function scheduleBackgroundUpdateRefresh(options, env, argv) {
601
- const spawnDetachedCommand = options.spawnDetachedCommand ?? runDetachedCommandWithSpawn;
602
- spawnDetachedCommand(options.execPath ?? process.execPath, argv.slice(1), {
603
- env: {
604
- ...env,
605
- [UPDATE_CHECK_REFRESH_ENV_VAR]: "1"
606
- },
607
- stdio: "ignore"
608
- });
609
- }
610
- async function refreshUpdateCheckCache(options) {
611
- try {
612
- const env = options.env ?? process.env;
613
- await resolveLatestVersion(toResolveLatestVersionOptions(options, env));
614
- } catch {
615
- }
616
- }
617
587
  async function checkForUpdatesAndMaybeRestart(options) {
618
588
  const env = options.env ?? process.env;
619
589
  const argv = options.argv ?? process.argv;
@@ -630,19 +600,7 @@ async function checkForUpdatesAndMaybeRestart(options) {
630
600
  return { continueExecution: true };
631
601
  }
632
602
  try {
633
- const resolveOptions = toResolveLatestVersionOptions(options, env);
634
- const cacheFilePath = resolveOptions.cacheFilePath ?? getDefaultUpdateCheckCachePath();
635
- const cachePayload = await readUpdateCheckCachePayload(cacheFilePath);
636
- const cacheTtlMs = resolveOptions.cacheTtlMs ?? DEFAULT_UPDATE_CHECK_CACHE_TTL_MS;
637
- const now = resolveOptions.now ?? Date.now;
638
- if (!cachePayload || !isCacheFresh(cachePayload, cacheTtlMs, now)) {
639
- try {
640
- scheduleBackgroundUpdateRefresh(options, env, argv);
641
- } catch {
642
- }
643
- return { continueExecution: true };
644
- }
645
- const latestVersion = cachePayload.latestVersion;
603
+ const latestVersion = await resolveLatestVersion(toResolveLatestVersionOptions(options, env));
646
604
  if (!latestVersion || !shouldOfferUpdate(options.currentVersion, latestVersion)) {
647
605
  return { continueExecution: true };
648
606
  }
@@ -694,7 +652,8 @@ var knownSourceColors = {
694
652
  codex: "#22c55e",
695
653
  gemini: "#eab308",
696
654
  droid: "#3b82f6",
697
- opencode: "#a855f7"
655
+ opencode: "#a855f7",
656
+ claude: "#d97757"
698
657
  };
699
658
  var fallbackColors = ["#f97316", "#06b6d4", "#ef4444", "#84cc16", "#f43f5e"];
700
659
  function getSourceColor(source, index) {
@@ -1076,8 +1035,14 @@ function escapeBareAutolinks(value) {
1076
1035
  (_, prefix, email) => `${prefix}${email.replace("@", "\\@")}`
1077
1036
  );
1078
1037
  }
1038
+ function escapeHtmlText(value) {
1039
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1040
+ }
1079
1041
  function escapeMarkdownText(value) {
1080
- const escapedMarkdownText = value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replace(markdownSpecialCharacterPattern, "\\$&");
1042
+ const escapedMarkdownText = escapeHtmlText(value).replace(
1043
+ markdownSpecialCharacterPattern,
1044
+ "\\$&"
1045
+ );
1081
1046
  return escapeBareAutolinks(escapedMarkdownText);
1082
1047
  }
1083
1048
  function toMarkdownSafeCell(value) {
@@ -1270,7 +1235,8 @@ var sourceStylePolicies = /* @__PURE__ */ new Map([
1270
1235
  ["codex", (palette) => palette.magenta],
1271
1236
  ["gemini", (palette) => palette.yellow],
1272
1237
  ["droid", (palette) => palette.green],
1273
- ["opencode", (palette) => palette.blue]
1238
+ ["opencode", (palette) => palette.blue],
1239
+ ["claude", (palette) => palette.cyan]
1274
1240
  ]);
1275
1241
  function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
1276
1242
  const stylePolicy = sourceStylePolicies.get(source);
@@ -2651,7 +2617,7 @@ function aggregateEfficiency(options) {
2651
2617
  }
2652
2618
 
2653
2619
  // src/efficiency/git-outcome-collector.ts
2654
- import { spawn as spawn3 } from "child_process";
2620
+ import { spawn as spawn2 } from "child_process";
2655
2621
  import { createInterface as createInterface2 } from "readline";
2656
2622
  import path3 from "path";
2657
2623
  import { stat } from "fs/promises";
@@ -2850,7 +2816,7 @@ function parseGitLogShortstatLines(lines, authorEmail) {
2850
2816
  }
2851
2817
  async function runGitCommand(repoDir, args) {
2852
2818
  return await new Promise((resolve, reject) => {
2853
- const child = spawn3("git", args, {
2819
+ const child = spawn2("git", args, {
2854
2820
  cwd: repoDir,
2855
2821
  env: {
2856
2822
  ...process.env,
@@ -3431,7 +3397,7 @@ function formatEnvVarOverrides(overrides) {
3431
3397
  return lines;
3432
3398
  }
3433
3399
 
3434
- // src/sources/codex/codex-source-adapter.ts
3400
+ // src/sources/claude/claude-source-adapter.ts
3435
3401
  import os2 from "os";
3436
3402
  import path6 from "path";
3437
3403
 
@@ -3566,11 +3532,6 @@ async function discoverFiles(rootDir, options) {
3566
3532
  return toCanonicalFiles(files, resolvedOptions);
3567
3533
  }
3568
3534
 
3569
- // src/utils/discover-jsonl-files.ts
3570
- async function discoverJsonlFiles(rootDir) {
3571
- return discoverFiles(rootDir, { extension: ".jsonl" });
3572
- }
3573
-
3574
3535
  // src/utils/fs-helpers.ts
3575
3536
  import { access as access2, constants as constants2, stat as stat3 } from "fs/promises";
3576
3537
  async function pathExists(filePath) {
@@ -3652,6 +3613,22 @@ async function* readJsonlObjects(filePath, options = {}) {
3652
3613
  }
3653
3614
  }
3654
3615
 
3616
+ // src/sources/parse-diagnostics.ts
3617
+ function incrementSkippedReason(reasons, reason) {
3618
+ const current = reasons.get(reason) ?? 0;
3619
+ reasons.set(reason, current + 1);
3620
+ }
3621
+ function toSkippedRowReasonStats(reasons) {
3622
+ return [...reasons.entries()].map(([reason, count]) => ({ reason, count })).sort((left, right) => compareByCodePoint(left.reason, right.reason));
3623
+ }
3624
+ function toParseDiagnostics(events, skippedRows, skippedRowReasons) {
3625
+ return {
3626
+ events,
3627
+ skippedRows,
3628
+ skippedRowReasons: toSkippedRowReasonStats(skippedRowReasons)
3629
+ };
3630
+ }
3631
+
3655
3632
  // src/sources/parsing-utils.ts
3656
3633
  var MIN_PLAUSIBLE_UNIX_SECONDS_ABS = 1e8;
3657
3634
  var UNIX_SECONDS_ABS_CUTOFF = 1e10;
@@ -3704,8 +3681,178 @@ function normalizeTimestampCandidate(candidate) {
3704
3681
  return date.toISOString();
3705
3682
  }
3706
3683
 
3684
+ // src/sources/claude/claude-source-adapter.ts
3685
+ var defaultClaudeProjectsDir = path6.join(os2.homedir(), ".claude", "projects");
3686
+ var CLAUDE_ASSISTANT_LINE_PATTERN = /"type"\s*:\s*"assistant"/u;
3687
+ var CLAUDE_USAGE_LINE_PATTERN = /"usage"\s*:/u;
3688
+ function shouldParseClaudeJsonlLine(lineText) {
3689
+ return CLAUDE_ASSISTANT_LINE_PATTERN.test(lineText) && CLAUDE_USAGE_LINE_PATTERN.test(lineText);
3690
+ }
3691
+ function getFallbackSessionId(filePath) {
3692
+ return path6.basename(filePath, ".jsonl");
3693
+ }
3694
+ function resolveProvider(message, model) {
3695
+ const explicitProvider = asTrimmedText(message.provider);
3696
+ if (explicitProvider) {
3697
+ return explicitProvider;
3698
+ }
3699
+ if (model?.toLowerCase().startsWith("claude-")) {
3700
+ return "anthropic";
3701
+ }
3702
+ return void 0;
3703
+ }
3704
+ function parseUsage(usage) {
3705
+ const inputTokens = normalizeNonNegativeInteger(toNumberLike(usage.input_tokens));
3706
+ const outputTokens = normalizeNonNegativeInteger(toNumberLike(usage.output_tokens));
3707
+ const cacheReadTokens = normalizeNonNegativeInteger(toNumberLike(usage.cache_read_input_tokens));
3708
+ const cacheWriteTokens = normalizeNonNegativeInteger(
3709
+ toNumberLike(usage.cache_creation_input_tokens)
3710
+ );
3711
+ const totalTokens = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
3712
+ if (totalTokens === 0) {
3713
+ return void 0;
3714
+ }
3715
+ return {
3716
+ inputTokens,
3717
+ outputTokens,
3718
+ reasoningTokens: 0,
3719
+ cacheReadTokens,
3720
+ cacheWriteTokens,
3721
+ totalTokens
3722
+ };
3723
+ }
3724
+ function createDedupKey(filePath, line, message, timestamp, model) {
3725
+ const messageId = asTrimmedText(message.id);
3726
+ if (messageId) {
3727
+ return `${filePath}\0${messageId}`;
3728
+ }
3729
+ const uuid = asTrimmedText(line.uuid);
3730
+ if (uuid) {
3731
+ return `${filePath}\0${uuid}`;
3732
+ }
3733
+ return `${filePath}\0${timestamp}\0${model ?? ""}`;
3734
+ }
3735
+ function comparePendingEvents(left, right) {
3736
+ if (left.timestamp !== right.timestamp) {
3737
+ return compareByCodePoint(left.timestamp, right.timestamp);
3738
+ }
3739
+ return left.sequence - right.sequence;
3740
+ }
3741
+ var ClaudeSourceAdapter = class {
3742
+ id = "claude";
3743
+ projectsDir;
3744
+ requireProjectsDir;
3745
+ constructor(options = {}) {
3746
+ this.projectsDir = options.projectsDir ?? defaultClaudeProjectsDir;
3747
+ this.requireProjectsDir = options.requireProjectsDir ?? false;
3748
+ }
3749
+ getNormalizedProjectsDir() {
3750
+ if (isBlankText(this.projectsDir)) {
3751
+ throw new Error("Claude projects directory must be a non-empty path");
3752
+ }
3753
+ return this.projectsDir.trim();
3754
+ }
3755
+ async discoverFiles() {
3756
+ const normalizedProjectsDir = this.getNormalizedProjectsDir();
3757
+ if (this.requireProjectsDir && !await pathReadable(normalizedProjectsDir)) {
3758
+ throw new Error(
3759
+ `Claude projects directory is missing or unreadable: ${normalizedProjectsDir}`
3760
+ );
3761
+ }
3762
+ if (this.requireProjectsDir && !await pathIsDirectory(normalizedProjectsDir)) {
3763
+ throw new Error(`Claude projects directory is not a directory: ${normalizedProjectsDir}`);
3764
+ }
3765
+ return discoverFiles(normalizedProjectsDir, { extension: ".jsonl" });
3766
+ }
3767
+ async parseFile(filePath) {
3768
+ const { events } = await this.parseFileWithDiagnostics(filePath);
3769
+ return events;
3770
+ }
3771
+ async parseFileWithDiagnostics(filePath) {
3772
+ const eventsByDedupKey = /* @__PURE__ */ new Map();
3773
+ let skippedRows = 0;
3774
+ let sequence = 0;
3775
+ const skippedRowReasons = /* @__PURE__ */ new Map();
3776
+ for await (const line of readJsonlObjects(filePath, {
3777
+ shouldParseLine: shouldParseClaudeJsonlLine
3778
+ })) {
3779
+ if (asTrimmedText(line.type) !== "assistant") {
3780
+ continue;
3781
+ }
3782
+ const message = asRecord(line.message);
3783
+ if (!message || asTrimmedText(message.role) !== "assistant") {
3784
+ skippedRows++;
3785
+ incrementSkippedReason(skippedRowReasons, "invalid_assistant_message");
3786
+ continue;
3787
+ }
3788
+ const model = asTrimmedText(message.model);
3789
+ if (model === "<synthetic>") {
3790
+ skippedRows++;
3791
+ incrementSkippedReason(skippedRowReasons, "synthetic_message");
3792
+ continue;
3793
+ }
3794
+ const usage = parseUsage(asRecord(message.usage) ?? {});
3795
+ if (!usage) {
3796
+ skippedRows++;
3797
+ incrementSkippedReason(skippedRowReasons, "no_token_usage");
3798
+ continue;
3799
+ }
3800
+ const timestamp = normalizeTimestampCandidate(line.timestamp);
3801
+ if (!timestamp) {
3802
+ skippedRows++;
3803
+ incrementSkippedReason(skippedRowReasons, "invalid_timestamp");
3804
+ continue;
3805
+ }
3806
+ const dedupKey = createDedupKey(filePath, line, message, timestamp, model);
3807
+ const sessionId = asTrimmedText(line.sessionId) ?? getFallbackSessionId(filePath);
3808
+ const repoRoot = asTrimmedText(line.cwd);
3809
+ const provider = resolveProvider(message, model);
3810
+ sequence += 1;
3811
+ eventsByDedupKey.set(dedupKey, {
3812
+ sessionId,
3813
+ timestamp,
3814
+ repoRoot,
3815
+ provider,
3816
+ model,
3817
+ usage,
3818
+ sequence
3819
+ });
3820
+ }
3821
+ const events = [];
3822
+ for (const pendingEvent of [...eventsByDedupKey.values()].sort(comparePendingEvents)) {
3823
+ try {
3824
+ events.push(
3825
+ createUsageEvent({
3826
+ source: this.id,
3827
+ sessionId: pendingEvent.sessionId,
3828
+ timestamp: pendingEvent.timestamp,
3829
+ repoRoot: pendingEvent.repoRoot,
3830
+ provider: pendingEvent.provider,
3831
+ model: pendingEvent.model,
3832
+ ...pendingEvent.usage,
3833
+ costMode: "estimated"
3834
+ })
3835
+ );
3836
+ } catch {
3837
+ skippedRows++;
3838
+ incrementSkippedReason(skippedRowReasons, "event_creation_failed");
3839
+ }
3840
+ }
3841
+ return toParseDiagnostics(events, skippedRows, skippedRowReasons);
3842
+ }
3843
+ };
3844
+
3707
3845
  // src/sources/codex/codex-source-adapter.ts
3708
- var defaultSessionsDir = path6.join(os2.homedir(), ".codex", "sessions");
3846
+ import os3 from "os";
3847
+ import path7 from "path";
3848
+
3849
+ // src/utils/discover-jsonl-files.ts
3850
+ async function discoverJsonlFiles(rootDir) {
3851
+ return discoverFiles(rootDir, { extension: ".jsonl" });
3852
+ }
3853
+
3854
+ // src/sources/codex/codex-source-adapter.ts
3855
+ var defaultSessionsDir = path7.join(os3.homedir(), ".codex", "sessions");
3709
3856
  var LEGACY_CODEX_MODEL_FALLBACK = "legacy-codex-unknown";
3710
3857
  var SESSION_META_LINE_PATTERN = /"type"\s*:\s*"session_meta"/u;
3711
3858
  var TURN_CONTEXT_LINE_PATTERN = /"type"\s*:\s*"turn_context"/u;
@@ -3799,8 +3946,8 @@ function createLastUsageOnlyKey(timestamp, usage) {
3799
3946
  usage.totalTokens
3800
3947
  ].join(":");
3801
3948
  }
3802
- function getFallbackSessionId(filePath) {
3803
- return path6.basename(filePath, ".jsonl");
3949
+ function getFallbackSessionId2(filePath) {
3950
+ return path7.basename(filePath, ".jsonl");
3804
3951
  }
3805
3952
  function resolveRepoRootFromPayload(payload) {
3806
3953
  if (!payload) {
@@ -3840,7 +3987,7 @@ var CodexSourceAdapter = class {
3840
3987
  async parseFile(filePath) {
3841
3988
  const events = [];
3842
3989
  const state = {
3843
- sessionId: getFallbackSessionId(filePath),
3990
+ sessionId: getFallbackSessionId2(filePath),
3844
3991
  provider: "openai"
3845
3992
  };
3846
3993
  for await (const line of readJsonlObjects(filePath, {
@@ -3932,37 +4079,19 @@ var CodexSourceAdapter = class {
3932
4079
 
3933
4080
  // src/sources/droid/droid-source-adapter.ts
3934
4081
  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");
4082
+ import os4 from "os";
4083
+ import path8 from "path";
4084
+ var defaultSessionsDir2 = path8.join(os4.homedir(), ".factory", "sessions");
3956
4085
  var DROID_SESSION_START_LINE_PATTERN = /"type"\s*:\s*"session_start"/u;
3957
4086
  var DROID_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
3958
4087
  function shouldParseDroidJsonlLine(lineText) {
3959
4088
  return DROID_SESSION_START_LINE_PATTERN.test(lineText) || DROID_MESSAGE_LINE_PATTERN.test(lineText);
3960
4089
  }
3961
4090
  function getSettingsSessionId(filePath) {
3962
- return path7.basename(filePath, ".settings.json");
4091
+ return path8.basename(filePath, ".settings.json");
3963
4092
  }
3964
4093
  function getSiblingJsonlPath(settingsPath) {
3965
- return path7.join(path7.dirname(settingsPath), `${getSettingsSessionId(settingsPath)}.jsonl`);
4094
+ return path8.join(path8.dirname(settingsPath), `${getSettingsSessionId(settingsPath)}.jsonl`);
3966
4095
  }
3967
4096
  function isSessionStartRecord(line) {
3968
4097
  return asTrimmedText(line.type) === "session_start";
@@ -4106,11 +4235,11 @@ var DroidSourceAdapter = class {
4106
4235
 
4107
4236
  // src/sources/gemini/gemini-source-adapter.ts
4108
4237
  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");
4238
+ import os5 from "os";
4239
+ import path9 from "path";
4240
+ var defaultGeminiDir = path9.join(os5.homedir(), ".gemini");
4112
4241
  function getProjectsJsonPath(geminiDir) {
4113
- return path8.join(geminiDir, "projects.json");
4242
+ return path9.join(geminiDir, "projects.json");
4114
4243
  }
4115
4244
  function parseProjectsJson(data) {
4116
4245
  const mapping = /* @__PURE__ */ new Map();
@@ -4145,7 +4274,7 @@ async function loadProjectsJson(geminiDir) {
4145
4274
  }
4146
4275
  }
4147
4276
  async function discoverSessionFiles(geminiDir) {
4148
- const tmpDir = path8.join(geminiDir, "tmp");
4277
+ const tmpDir = path9.join(geminiDir, "tmp");
4149
4278
  let projectEntries;
4150
4279
  try {
4151
4280
  projectEntries = await readdir2(tmpDir, { withFileTypes: true, encoding: "utf8" });
@@ -4162,7 +4291,7 @@ async function discoverSessionFiles(geminiDir) {
4162
4291
  if (!projectEntry.isDirectory() && !projectEntry.isSymbolicLink()) {
4163
4292
  continue;
4164
4293
  }
4165
- const chatsDir = path8.join(tmpDir, projectEntry.name, "chats");
4294
+ const chatsDir = path9.join(tmpDir, projectEntry.name, "chats");
4166
4295
  const discoveredChatFiles = await discoverFiles(chatsDir, { extension: ".json" });
4167
4296
  allSessionFiles.push(...discoveredChatFiles);
4168
4297
  }
@@ -4176,9 +4305,9 @@ function resolveRepoRoot(filePath, sessionData, projectMapping) {
4176
4305
  return mappedRoot;
4177
4306
  }
4178
4307
  }
4179
- const chatsDir = path8.dirname(filePath);
4180
- const projectDir = path8.dirname(chatsDir);
4181
- const projectIdentifier = path8.basename(projectDir);
4308
+ const chatsDir = path9.dirname(filePath);
4309
+ const projectDir = path9.dirname(chatsDir);
4310
+ const projectIdentifier = path9.basename(projectDir);
4182
4311
  return projectMapping.get(projectIdentifier);
4183
4312
  }
4184
4313
  function toFiniteNumber(value) {
@@ -4287,7 +4416,7 @@ var GeminiSourceAdapter = class {
4287
4416
  incrementSkippedReason(skippedRowReasons, "invalid_session_data");
4288
4417
  return toParseDiagnostics(events, skippedRows, skippedRowReasons);
4289
4418
  }
4290
- const sessionId = asTrimmedText(sessionDataRecord.sessionId) ?? path8.basename(filePath, ".json");
4419
+ const sessionId = asTrimmedText(sessionDataRecord.sessionId) ?? path9.basename(filePath, ".json");
4291
4420
  const projectMapping = await this.getProjectMappingForParse();
4292
4421
  const repoRoot = resolveRepoRoot(filePath, sessionDataRecord, projectMapping);
4293
4422
  if (!Array.isArray(sessionDataRecord.messages)) {
@@ -4344,8 +4473,8 @@ var GeminiSourceAdapter = class {
4344
4473
  };
4345
4474
 
4346
4475
  // src/sources/opencode/opencode-db-path-resolver.ts
4347
- import os5 from "os";
4348
- import path9 from "path";
4476
+ import os6 from "os";
4477
+ import path10 from "path";
4349
4478
  function deduplicate(paths) {
4350
4479
  return [...new Set(paths)];
4351
4480
  }
@@ -4357,39 +4486,39 @@ function normalizeEnvPath(value) {
4357
4486
  return normalized || void 0;
4358
4487
  }
4359
4488
  function getLinuxLikeCandidates(homeDir, env) {
4360
- const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path9.join(homeDir, ".local", "share");
4489
+ const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path10.join(homeDir, ".local", "share");
4361
4490
  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")
4491
+ path10.join(xdgDataHome, "opencode", "opencode.db"),
4492
+ path10.join(xdgDataHome, "opencode", "db.sqlite"),
4493
+ path10.join(homeDir, ".opencode", "opencode.db"),
4494
+ path10.join(homeDir, ".opencode", "db.sqlite")
4366
4495
  ];
4367
4496
  }
4368
4497
  function getMacOsCandidates(homeDir) {
4369
- const appSupportDir = path9.join(homeDir, "Library", "Application Support");
4498
+ const appSupportDir = path10.join(homeDir, "Library", "Application Support");
4370
4499
  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")
4500
+ path10.join(appSupportDir, "opencode", "opencode.db"),
4501
+ path10.join(appSupportDir, "opencode", "db.sqlite"),
4502
+ path10.join(homeDir, ".opencode", "opencode.db"),
4503
+ path10.join(homeDir, ".opencode", "db.sqlite")
4375
4504
  ];
4376
4505
  }
4377
4506
  function getWindowsCandidates(homeDir, env) {
4378
4507
  const userProfile = normalizeEnvPath(env.USERPROFILE);
4379
- const roamingBase = normalizeEnvPath(env.APPDATA) ?? normalizeEnvPath(env.LOCALAPPDATA) ?? (userProfile ? path9.join(userProfile, "AppData", "Roaming") : void 0);
4508
+ const roamingBase = normalizeEnvPath(env.APPDATA) ?? normalizeEnvPath(env.LOCALAPPDATA) ?? (userProfile ? path10.join(userProfile, "AppData", "Roaming") : void 0);
4380
4509
  const roamingCandidates = roamingBase ? [
4381
- path9.join(roamingBase, "opencode", "opencode.db"),
4382
- path9.join(roamingBase, "opencode", "db.sqlite")
4510
+ path10.join(roamingBase, "opencode", "opencode.db"),
4511
+ path10.join(roamingBase, "opencode", "db.sqlite")
4383
4512
  ] : [];
4384
4513
  return [
4385
4514
  ...roamingCandidates,
4386
- path9.join(homeDir, ".opencode", "opencode.db"),
4387
- path9.join(homeDir, ".opencode", "db.sqlite")
4515
+ path10.join(homeDir, ".opencode", "opencode.db"),
4516
+ path10.join(homeDir, ".opencode", "db.sqlite")
4388
4517
  ];
4389
4518
  }
4390
4519
  function getDefaultOpenCodeDbPathCandidates(options = {}) {
4391
4520
  const platform = options.platform ?? process.platform;
4392
- const homeDir = options.homeDir ?? os5.homedir();
4521
+ const homeDir = options.homeDir ?? os6.homedir();
4393
4522
  const env = options.env ?? process.env;
4394
4523
  switch (platform) {
4395
4524
  case "win32":
@@ -4878,9 +5007,9 @@ var OpenCodeSourceAdapter = class {
4878
5007
  };
4879
5008
 
4880
5009
  // 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");
5010
+ import os7 from "os";
5011
+ import path11 from "path";
5012
+ var defaultSessionsDir3 = path11.join(os7.homedir(), ".pi", "agent", "sessions");
4884
5013
  var PI_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
4885
5014
  var PI_SESSION_LINE_PATTERN = /"type"\s*:\s*"session"/u;
4886
5015
  var PI_MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
@@ -4953,8 +5082,8 @@ function extractUsage(line, message) {
4953
5082
  }
4954
5083
  return extractUsageFromRecord(messageUsage);
4955
5084
  }
4956
- function getFallbackSessionId2(filePath) {
4957
- return path10.basename(filePath, ".jsonl");
5085
+ function getFallbackSessionId3(filePath) {
5086
+ return path11.basename(filePath, ".jsonl");
4958
5087
  }
4959
5088
  function resolveRepoRootFromRecord(record) {
4960
5089
  if (!record) {
@@ -4986,7 +5115,7 @@ var PiSourceAdapter = class {
4986
5115
  }
4987
5116
  async parseFile(filePath) {
4988
5117
  const events = [];
4989
- const state = { sessionId: getFallbackSessionId2(filePath) };
5118
+ const state = { sessionId: getFallbackSessionId3(filePath) };
4990
5119
  for await (const line of readJsonlObjects(filePath, {
4991
5120
  shouldParseLine: shouldParsePiJsonlLine
4992
5121
  })) {
@@ -5125,6 +5254,21 @@ var sourceRegistrations = [
5125
5254
  create: (options) => new OpenCodeSourceAdapter({
5126
5255
  dbPath: options.opencodeDb
5127
5256
  })
5257
+ },
5258
+ {
5259
+ id: "claude",
5260
+ sourceDirOverride: { kind: "directory" },
5261
+ create: (options, sourceDirectoryOverrides) => {
5262
+ const directoryConfig = resolveDirectoryConfig(
5263
+ "claude",
5264
+ options.claudeDir,
5265
+ sourceDirectoryOverrides
5266
+ );
5267
+ return new ClaudeSourceAdapter({
5268
+ projectsDir: directoryConfig.path,
5269
+ requireProjectsDir: directoryConfig.requireExistingPath
5270
+ });
5271
+ }
5128
5272
  }
5129
5273
  ];
5130
5274
  var sourceDirUnsupportedFlags = new Map(
@@ -5201,6 +5345,7 @@ function createDefaultAdapters(options) {
5201
5345
  validateDirectoryOverride("--codex-dir", options.codexDir);
5202
5346
  validateDirectoryOverride("--gemini-dir", options.geminiDir);
5203
5347
  validateDirectoryOverride("--droid-dir", options.droidDir);
5348
+ validateDirectoryOverride("--claude-dir", options.claudeDir);
5204
5349
  const sourceDirectoryOverrides = parseSourceDirectoryOverrides(options.sourceDir);
5205
5350
  validateSourceDirectoryOverrideIds(sourceDirectoryOverrides);
5206
5351
  return sourceRegistrations.map((source) => source.create(options, sourceDirectoryOverrides));
@@ -5316,6 +5461,9 @@ function resolveExplicitSourceIds(options, sourceFilter) {
5316
5461
  if (options.droidDir) {
5317
5462
  explicitSourceIds.add("droid");
5318
5463
  }
5464
+ if (options.claudeDir) {
5465
+ explicitSourceIds.add("claude");
5466
+ }
5319
5467
  if (options.opencodeDb) {
5320
5468
  explicitSourceIds.add("opencode");
5321
5469
  }
@@ -5444,7 +5592,7 @@ function normalizeSkippedRowReasons(value) {
5444
5592
 
5445
5593
  // src/cli/parse-file-cache.ts
5446
5594
  import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat4, writeFile as writeFile2 } from "fs/promises";
5447
- import path11 from "path";
5595
+ import path12 from "path";
5448
5596
  var PARSE_FILE_CACHE_VERSION = 5;
5449
5597
  var CACHE_KEY_SEPARATOR = "\0";
5450
5598
  function createCacheKey(source, filePath) {
@@ -5671,7 +5819,7 @@ function normalizeCacheEntry(value) {
5671
5819
  };
5672
5820
  }
5673
5821
  function getDefaultParseFileCachePath() {
5674
- return path11.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
5822
+ return path12.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
5675
5823
  }
5676
5824
  function normalizeCacheShardSource(source) {
5677
5825
  const normalizedSource = normalizeCacheSource(source);
@@ -5681,12 +5829,12 @@ function normalizeCacheShardSource(source) {
5681
5829
  return normalizedSource.replace(/[^a-z0-9._-]/gu, "_");
5682
5830
  }
5683
5831
  function getSourceShardedParseFileCachePath(cacheFilePath, source) {
5684
- const parsedPath = path11.parse(cacheFilePath);
5832
+ const parsedPath = path12.parse(cacheFilePath);
5685
5833
  const sourceShard = normalizeCacheShardSource(source);
5686
5834
  if (parsedPath.ext.length > 0) {
5687
- return path11.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
5835
+ return path12.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
5688
5836
  }
5689
- return path11.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
5837
+ return path12.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
5690
5838
  }
5691
5839
  var ParseFileCache = class _ParseFileCache {
5692
5840
  constructor(cacheFilePath, limits, now) {
@@ -5694,6 +5842,9 @@ var ParseFileCache = class _ParseFileCache {
5694
5842
  this.limits = limits;
5695
5843
  this.now = now;
5696
5844
  }
5845
+ cacheFilePath;
5846
+ limits;
5847
+ now;
5697
5848
  entriesByKey = /* @__PURE__ */ new Map();
5698
5849
  dirty = false;
5699
5850
  static async load(options) {
@@ -5780,7 +5931,7 @@ var ParseFileCache = class _ParseFileCache {
5780
5931
  keptEntries.length = bestCount;
5781
5932
  payloadText = bestPayloadText;
5782
5933
  }
5783
- await mkdir2(path11.dirname(this.cacheFilePath), { recursive: true });
5934
+ await mkdir2(path12.dirname(this.cacheFilePath), { recursive: true });
5784
5935
  const temporaryPath = `${this.cacheFilePath}.${process.pid}.${this.now()}.tmp`;
5785
5936
  try {
5786
5937
  await writeFile2(temporaryPath, payloadText, "utf8");
@@ -6267,9 +6418,92 @@ function applyPricingToEvents(events, pricingSource) {
6267
6418
  });
6268
6419
  }
6269
6420
 
6421
+ // src/pricing/pricing-override-source.ts
6422
+ import { readFile as readFile5 } from "fs/promises";
6423
+ function toFiniteUsdRate(value) {
6424
+ if (value === null || value === void 0) {
6425
+ return void 0;
6426
+ }
6427
+ if (typeof value === "string" && value.trim() === "") {
6428
+ return void 0;
6429
+ }
6430
+ const parsed = typeof value === "number" ? value : Number(value);
6431
+ return Number.isFinite(parsed) ? parsed : void 0;
6432
+ }
6433
+ function normalizeReasoningBilling(value) {
6434
+ if (value === "included-in-output" || value === "separate") {
6435
+ return value;
6436
+ }
6437
+ return void 0;
6438
+ }
6439
+ function normalizePricingOverride(raw) {
6440
+ const inputPer1MUsd = toFiniteUsdRate(toNumberLike(raw.inputPer1MUsd));
6441
+ const outputPer1MUsd = toFiniteUsdRate(toNumberLike(raw.outputPer1MUsd));
6442
+ if (inputPer1MUsd === void 0 || outputPer1MUsd === void 0) {
6443
+ return void 0;
6444
+ }
6445
+ const cacheReadPer1MUsd = toFiniteUsdRate(toNumberLike(raw.cacheReadPer1MUsd));
6446
+ const cacheWritePer1MUsd = toFiniteUsdRate(toNumberLike(raw.cacheWritePer1MUsd));
6447
+ const reasoningPer1MUsd = toFiniteUsdRate(toNumberLike(raw.reasoningPer1MUsd));
6448
+ const reasoningBilling = normalizeReasoningBilling(raw.reasoningBilling);
6449
+ return {
6450
+ inputPer1MUsd,
6451
+ outputPer1MUsd,
6452
+ ...cacheReadPer1MUsd !== void 0 ? { cacheReadPer1MUsd } : {},
6453
+ ...cacheWritePer1MUsd !== void 0 ? { cacheWritePer1MUsd } : {},
6454
+ ...reasoningPer1MUsd !== void 0 ? { reasoningPer1MUsd } : {},
6455
+ ...reasoningBilling !== void 0 ? { reasoningBilling } : {}
6456
+ };
6457
+ }
6458
+ function normalizeOverrideFile(payload) {
6459
+ const root = asRecord(payload);
6460
+ const overrides = /* @__PURE__ */ new Map();
6461
+ const modelsRecord = asRecord(root?.models);
6462
+ if (!modelsRecord) {
6463
+ return overrides;
6464
+ }
6465
+ for (const [modelName, rawPricing] of Object.entries(modelsRecord)) {
6466
+ const normalizedModelName = asTrimmedText(modelName)?.toLowerCase();
6467
+ if (!normalizedModelName) {
6468
+ continue;
6469
+ }
6470
+ const pricing = normalizePricingOverride(asRecord(rawPricing) ?? {});
6471
+ if (pricing) {
6472
+ overrides.set(normalizedModelName, pricing);
6473
+ }
6474
+ }
6475
+ return overrides;
6476
+ }
6477
+ async function loadPricingOverrides(filePath) {
6478
+ const fileContents = await readFile5(filePath, "utf8");
6479
+ const parsed = JSON.parse(fileContents);
6480
+ return normalizeOverrideFile(parsed);
6481
+ }
6482
+ var PricingOverrideSource = class {
6483
+ overrides;
6484
+ delegate;
6485
+ constructor(overrides, delegate) {
6486
+ this.overrides = overrides;
6487
+ this.delegate = delegate;
6488
+ }
6489
+ resolveModelAlias(model) {
6490
+ if (this.overrides.has(model.toLowerCase())) {
6491
+ return model;
6492
+ }
6493
+ return this.delegate.resolveModelAlias(model);
6494
+ }
6495
+ getPricing(model) {
6496
+ const override = this.overrides.get(model.toLowerCase());
6497
+ if (override) {
6498
+ return override;
6499
+ }
6500
+ return this.delegate.getPricing(model);
6501
+ }
6502
+ };
6503
+
6270
6504
  // src/pricing/litellm-pricing-fetcher.ts
6271
- import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
6272
- import path12 from "path";
6505
+ import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
6506
+ import path13 from "path";
6273
6507
 
6274
6508
  // src/pricing/litellm-model-map.json
6275
6509
  var litellm_model_map_default = {
@@ -6453,7 +6687,7 @@ function normalizeLitellmPricingPayload(payload) {
6453
6687
  return normalizedPricing;
6454
6688
  }
6455
6689
  function getDefaultLiteLLMPricingCachePath() {
6456
- return path12.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
6690
+ return path13.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
6457
6691
  }
6458
6692
  function stripProviderPrefix(model) {
6459
6693
  const slashIndex = model.lastIndexOf("/");
@@ -6812,7 +7046,7 @@ var LiteLLMPricingFetcher = class {
6812
7046
  async readCachePayload() {
6813
7047
  let content;
6814
7048
  try {
6815
- content = await readFile5(this.cacheFilePath, "utf8");
7049
+ content = await readFile6(this.cacheFilePath, "utf8");
6816
7050
  } catch {
6817
7051
  return void 0;
6818
7052
  }
@@ -6847,7 +7081,7 @@ var LiteLLMPricingFetcher = class {
6847
7081
  };
6848
7082
  }
6849
7083
  async writeCache() {
6850
- const directoryPath = path12.dirname(this.cacheFilePath);
7084
+ const directoryPath = path13.dirname(this.cacheFilePath);
6851
7085
  await mkdir3(directoryPath, { recursive: true });
6852
7086
  const payload = {
6853
7087
  fetchedAt: this.now(),
@@ -6859,7 +7093,27 @@ var LiteLLMPricingFetcher = class {
6859
7093
  };
6860
7094
 
6861
7095
  // src/cli/build-usage-data-pricing.ts
7096
+ function wrapWithPricingOverrides(overrides, delegate) {
7097
+ if (!overrides || overrides.size === 0) {
7098
+ return delegate;
7099
+ }
7100
+ return new PricingOverrideSource(overrides, delegate);
7101
+ }
6862
7102
  async function resolvePricingSource(options, runtimeConfig) {
7103
+ let pricingOverrides;
7104
+ if (options.pricingOverrides) {
7105
+ try {
7106
+ pricingOverrides = await loadPricingOverrides(options.pricingOverrides);
7107
+ } catch (error) {
7108
+ const reason = error instanceof Error ? error.message : String(error);
7109
+ throw new Error(
7110
+ `Could not load --pricing-overrides from ${options.pricingOverrides}: ${reason}`,
7111
+ {
7112
+ cause: error
7113
+ }
7114
+ );
7115
+ }
7116
+ }
6863
7117
  const litellmPricingFetcher = new LiteLLMPricingFetcher({
6864
7118
  sourceUrl: options.pricingUrl,
6865
7119
  offline: options.pricingOffline,
@@ -6868,10 +7122,11 @@ async function resolvePricingSource(options, runtimeConfig) {
6868
7122
  });
6869
7123
  try {
6870
7124
  const fromCache = await litellmPricingFetcher.load();
7125
+ const source = wrapWithPricingOverrides(pricingOverrides, litellmPricingFetcher);
6871
7126
  if (options.pricingOffline) {
6872
- return { source: litellmPricingFetcher, origin: "offline-cache" };
7127
+ return { source, origin: "offline-cache" };
6873
7128
  }
6874
- return { source: litellmPricingFetcher, origin: fromCache ? "cache" : "network" };
7129
+ return { source, origin: fromCache ? "cache" : "network" };
6875
7130
  } catch (error) {
6876
7131
  if (options.pricingOffline) {
6877
7132
  throw new Error("Offline pricing mode enabled but cached pricing is unavailable", {
@@ -6958,6 +7213,7 @@ var RuntimeProfileCollector = class {
6958
7213
  constructor(now = () => performance.now()) {
6959
7214
  this.now = now;
6960
7215
  }
7216
+ now;
6961
7217
  sourceSelection;
6962
7218
  sourceStats = /* @__PURE__ */ new Map();
6963
7219
  stageDurations = /* @__PURE__ */ new Map();
@@ -7251,6 +7507,9 @@ function resolveScopeNote(options) {
7251
7507
  if (hasActiveTextOption(options.droidDir)) {
7252
7508
  activeFilters.push("--droid-dir");
7253
7509
  }
7510
+ if (hasActiveTextOption(options.claudeDir)) {
7511
+ activeFilters.push("--claude-dir");
7512
+ }
7254
7513
  if (hasActiveTextOption(options.opencodeDb)) {
7255
7514
  activeFilters.push("--opencode-db");
7256
7515
  }
@@ -7435,11 +7694,12 @@ function emitEnvVarOverrides(activeEnvOverrides, diagnosticsLogger) {
7435
7694
  }
7436
7695
 
7437
7696
  // src/cli/share-artifact.ts
7438
- import { spawn as spawn4 } from "child_process";
7439
- import { writeFile as writeFile4 } from "fs/promises";
7440
- import path13 from "path";
7697
+ import { spawn as spawn3 } from "child_process";
7698
+ import { constants as constants3 } from "fs";
7699
+ import { access as access3, writeFile as writeFile4 } from "fs/promises";
7700
+ import path14 from "path";
7441
7701
  async function writeShareSvgFile(fileName, svgContent) {
7442
- const outputPath = path13.resolve(process.cwd(), fileName);
7702
+ const outputPath = path14.resolve(process.cwd(), fileName);
7443
7703
  await writeFile4(outputPath, svgContent, "utf8");
7444
7704
  return outputPath;
7445
7705
  }
@@ -7449,27 +7709,74 @@ function stringifyError(error) {
7449
7709
  }
7450
7710
  return String(error);
7451
7711
  }
7452
- function resolveOpenCommand(filePath, platform) {
7712
+ var WINDOWS_OPEN_COMMAND = "C:\\Windows\\System32\\rundll32.exe";
7713
+ var DARWIN_OPEN_COMMAND = "/usr/bin/open";
7714
+ var UNIX_OPEN_COMMAND = "/usr/bin/xdg-open";
7715
+ var WINDOWS_FALLBACK_COMMANDS = ["rundll32.exe"];
7716
+ var DARWIN_FALLBACK_COMMANDS = ["open"];
7717
+ var UNIX_FALLBACK_COMMANDS = ["xdg-open"];
7718
+ async function fileExists(filePath) {
7719
+ try {
7720
+ await access3(filePath, constants3.X_OK);
7721
+ return true;
7722
+ } catch {
7723
+ return false;
7724
+ }
7725
+ }
7726
+ async function resolveBinaryPath(primaryPath, fallbackNames) {
7727
+ if (await fileExists(primaryPath)) {
7728
+ return primaryPath;
7729
+ }
7730
+ const pathDirs = (process.env.PATH ?? "").split(path14.delimiter).filter(Boolean);
7731
+ for (const fallbackName of fallbackNames) {
7732
+ for (const dir of pathDirs) {
7733
+ const candidate = path14.join(dir, fallbackName);
7734
+ if (await fileExists(candidate)) {
7735
+ return candidate;
7736
+ }
7737
+ }
7738
+ }
7739
+ return void 0;
7740
+ }
7741
+ async function resolveOpenCommand(filePath, platform) {
7453
7742
  if (platform === "win32") {
7743
+ const resolvedPath2 = await resolveBinaryPath(WINDOWS_OPEN_COMMAND, WINDOWS_FALLBACK_COMMANDS);
7744
+ if (!resolvedPath2) {
7745
+ throw new Error(
7746
+ "Could not find rundll32.exe. Please ensure Windows System32 is accessible or rundll32.exe is available on PATH."
7747
+ );
7748
+ }
7454
7749
  return {
7455
- command: "cmd",
7456
- args: ["/c", "start", "", filePath]
7750
+ command: resolvedPath2,
7751
+ args: ["shell32.dll,ShellExec_RunDLL", filePath]
7457
7752
  };
7458
7753
  }
7459
7754
  if (platform === "darwin") {
7755
+ const resolvedPath2 = await resolveBinaryPath(DARWIN_OPEN_COMMAND, DARWIN_FALLBACK_COMMANDS);
7756
+ if (!resolvedPath2) {
7757
+ throw new Error(
7758
+ "Could not find open command. Please ensure macOS is properly configured or open is available on PATH."
7759
+ );
7760
+ }
7460
7761
  return {
7461
- command: "open",
7762
+ command: resolvedPath2,
7462
7763
  args: [filePath]
7463
7764
  };
7464
7765
  }
7766
+ const resolvedPath = await resolveBinaryPath(UNIX_OPEN_COMMAND, UNIX_FALLBACK_COMMANDS);
7767
+ if (!resolvedPath) {
7768
+ throw new Error(
7769
+ "Could not find xdg-open. Please install xdg-utils or ensure it is in your PATH."
7770
+ );
7771
+ }
7465
7772
  return {
7466
- command: "xdg-open",
7773
+ command: resolvedPath,
7467
7774
  args: [filePath]
7468
7775
  };
7469
7776
  }
7470
7777
  async function spawnDetached(command, args) {
7471
7778
  await new Promise((resolve, reject) => {
7472
- const child = spawn4(command, args, {
7779
+ const child = spawn3(command, args, {
7473
7780
  detached: true,
7474
7781
  stdio: "ignore",
7475
7782
  windowsHide: true
@@ -7494,7 +7801,7 @@ async function spawnDetached(command, args) {
7494
7801
  async function openShareSvgFile(filePath, deps = {}) {
7495
7802
  const platform = deps.platform ?? process.platform;
7496
7803
  const runDetached = deps.spawnDetached ?? spawnDetached;
7497
- const { command, args } = resolveOpenCommand(filePath, platform);
7804
+ const { command, args } = await resolveOpenCommand(filePath, platform);
7498
7805
  await runDetached(command, args);
7499
7806
  }
7500
7807
  async function writeAndOpenShareSvgFile(fileName, svgContent, deps = {}) {
@@ -7782,7 +8089,7 @@ function resolveTerminalContextLines(optimizeData, options) {
7782
8089
  const bestRow = rowsWithSavings.length > 0 ? rowsWithSavings.reduce(
7783
8090
  (best, current) => (current.savingsUsd ?? Number.NEGATIVE_INFINITY) > (best.savingsUsd ?? Number.NEGATIVE_INFINITY) ? current : best
7784
8091
  ) : void 0;
7785
- if (!bestRow || bestRow.savingsUsd === void 0) {
8092
+ if (bestRow?.savingsUsd === void 0) {
7786
8093
  lines.push("ALL best candidate: unavailable (missing baseline or candidate pricing)");
7787
8094
  } else if (bestRow.savingsUsd > 0) {
7788
8095
  lines.push(
@@ -9654,7 +9961,7 @@ function registerSharedReportOptions(command, profile) {
9654
9961
  const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
9655
9962
  const supportedSourcesSummary = `(${supportedSourceIds.length}): ${allowedSourcesLabel}`;
9656
9963
  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(
9964
+ 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
9965
  "--source-dir <source-id=path>",
9659
9966
  "Override source directory for directory-backed sources (repeatable)",
9660
9967
  collectRepeatedOption
@@ -9669,7 +9976,10 @@ function registerSharedReportOptions(command, profile) {
9669
9976
  "--model <name>",
9670
9977
  "Filter by model (repeatable/comma-separated; exact when exact match exists after source/provider/date filters, otherwise substring)",
9671
9978
  collectRepeatedOption
9672
- ).option("--pricing-url <url>", "Override LiteLLM pricing source URL").option("--pricing-offline", "Use cached LiteLLM pricing only (no network fetch)").option(
9979
+ ).option("--pricing-url <url>", "Override LiteLLM pricing source URL").option(
9980
+ "--pricing-overrides <path>",
9981
+ "Path to a JSON file of per-model pricing overrides (takes precedence over LiteLLM)"
9982
+ ).option("--pricing-offline", "Use cached LiteLLM pricing only (no network fetch)").option(
9673
9983
  "--ignore-pricing-failures",
9674
9984
  "Continue without estimated costs when pricing cannot be loaded"
9675
9985
  ).option("--json", "Render output as JSON");
@@ -9983,15 +10293,6 @@ var { packageName, packageVersion } = loadPackageMetadataFromRuntime();
9983
10293
  var updateRuntimeConfig = getUpdateNotifierRuntimeConfig();
9984
10294
  var cli = createCli({ version: packageVersion });
9985
10295
  async function main() {
9986
- if (process.env[UPDATE_CHECK_REFRESH_ENV_VAR] === "1") {
9987
- await refreshUpdateCheckCache({
9988
- packageName,
9989
- currentVersion: packageVersion,
9990
- cacheTtlMs: updateRuntimeConfig.cacheTtlMs,
9991
- fetchTimeoutMs: updateRuntimeConfig.fetchTimeoutMs
9992
- });
9993
- return;
9994
- }
9995
10296
  const updateResult = await checkForUpdatesAndMaybeRestart({
9996
10297
  packageName,
9997
10298
  currentVersion: packageVersion,