llm-usage-metrics 0.3.5 → 0.3.7

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
@@ -538,7 +538,15 @@ function isTruthyEnvFlag(value) {
538
538
  }
539
539
  function shouldSkipUpdateCheckForArgv(argv) {
540
540
  const executableArgs = argv.slice(2);
541
- const commandNames = /* @__PURE__ */ new Set(["daily", "weekly", "monthly", "efficiency", "help", "version"]);
541
+ const commandNames = /* @__PURE__ */ new Set([
542
+ "daily",
543
+ "weekly",
544
+ "monthly",
545
+ "efficiency",
546
+ "optimize",
547
+ "help",
548
+ "version"
549
+ ]);
542
550
  if (executableArgs.length === 0) {
543
551
  return false;
544
552
  }
@@ -674,6 +682,35 @@ function normalizeModelList(models) {
674
682
  return [...deduplicated].sort(compareByCodePoint);
675
683
  }
676
684
 
685
+ // src/domain/provider-normalization.ts
686
+ var billingProviderAliases = /* @__PURE__ */ new Map([
687
+ ["openai-codex", "openai"],
688
+ ["github-copilot", "github"]
689
+ ]);
690
+ var billingProviderPrefixAliases = [
691
+ ["openai-", "openai"],
692
+ ["openai/", "openai"]
693
+ ];
694
+ function normalizeProviderToBillingEntity(provider) {
695
+ if (!provider) {
696
+ return void 0;
697
+ }
698
+ const normalizedProvider = provider.trim().toLowerCase();
699
+ if (normalizedProvider.length === 0) {
700
+ return void 0;
701
+ }
702
+ const aliasedProvider = billingProviderAliases.get(normalizedProvider);
703
+ if (aliasedProvider) {
704
+ return aliasedProvider;
705
+ }
706
+ for (const [prefix, billingProvider] of billingProviderPrefixAliases) {
707
+ if (normalizedProvider.startsWith(prefix)) {
708
+ return billingProvider;
709
+ }
710
+ }
711
+ return normalizedProvider;
712
+ }
713
+
677
714
  // src/domain/usage-event.ts
678
715
  function normalizeSourceId(value) {
679
716
  if (typeof value !== "string") {
@@ -696,6 +733,9 @@ function normalizeOptionalText(value) {
696
733
  const normalized = value.trim();
697
734
  return normalized || void 0;
698
735
  }
736
+ function normalizeOptionalProvider(value) {
737
+ return normalizeProviderToBillingEntity(value);
738
+ }
699
739
  function normalizeOptionalPath(value) {
700
740
  return normalizeOptionalText(value);
701
741
  }
@@ -735,7 +775,7 @@ function createUsageEvent(input) {
735
775
  sessionId: requireText(input.sessionId, "sessionId"),
736
776
  timestamp: normalizeTimestamp(input.timestamp),
737
777
  repoRoot: normalizeOptionalPath(input.repoRoot),
738
- provider: normalizeOptionalText(input.provider),
778
+ provider: normalizeOptionalProvider(input.provider),
739
779
  model: normalizeOptionalModel(input.model),
740
780
  inputTokens,
741
781
  outputTokens,
@@ -1217,7 +1257,6 @@ var DroidSourceAdapter = class {
1217
1257
  toNumberLike(tokenUsage.cacheCreationTokens)
1218
1258
  );
1219
1259
  const billableTokens = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
1220
- const totalTokens = billableTokens + reasoningTokens;
1221
1260
  if (billableTokens === 0) {
1222
1261
  skippedRows++;
1223
1262
  incrementSkippedReason(skippedRowReasons, "no_token_usage");
@@ -1225,6 +1264,7 @@ var DroidSourceAdapter = class {
1225
1264
  }
1226
1265
  const provider = asTrimmedText(settings.providerLock);
1227
1266
  const model = asTrimmedText(settings.model);
1267
+ const totalTokens = billableTokens;
1228
1268
  const primaryTimestamp = normalizeTimestampCandidate(settings.providerLockTimestamp);
1229
1269
  const hasValidPrimaryTimestamp = Boolean(primaryTimestamp);
1230
1270
  const jsonlPath = getSiblingJsonlPath(filePath);
@@ -1243,7 +1283,9 @@ var DroidSourceAdapter = class {
1243
1283
  }
1244
1284
  if (!hasValidPrimaryTimestamp && isMessageRecord(line)) {
1245
1285
  fallbackMessageTimestamp = normalizeTimestampCandidate(line.timestamp);
1246
- break;
1286
+ if (fallbackMessageTimestamp) {
1287
+ break;
1288
+ }
1247
1289
  }
1248
1290
  }
1249
1291
  } catch {
@@ -3262,6 +3304,42 @@ async function attributeUsageEventsToRepo(events, repoDir, resolveRepoRoot3 = re
3262
3304
  };
3263
3305
  }
3264
3306
 
3307
+ // src/cli/build-usage-data-diagnostics.ts
3308
+ function buildUsageDiagnostics(params) {
3309
+ const parseResultBySource = new Map(
3310
+ params.successfulParseResults.map((result) => [result.source.toLowerCase(), result])
3311
+ );
3312
+ const sessionStats = params.adaptersToParse.map((adapter) => {
3313
+ const parseResult = parseResultBySource.get(adapter.id.toLowerCase());
3314
+ return {
3315
+ source: adapter.id,
3316
+ filesFound: parseResult?.filesFound ?? 0,
3317
+ eventsParsed: parseResult?.events.length ?? 0
3318
+ };
3319
+ });
3320
+ const skippedRows = params.successfulParseResults.filter((result) => result.skippedRows > 0).map((result) => ({
3321
+ source: result.source,
3322
+ skippedRows: result.skippedRows,
3323
+ reasons: result.skippedRowReasons
3324
+ }));
3325
+ return {
3326
+ sessionStats,
3327
+ sourceFailures: params.sourceFailures,
3328
+ skippedRows,
3329
+ pricingOrigin: params.pricingOrigin,
3330
+ pricingWarning: params.pricingWarning,
3331
+ activeEnvOverrides: params.activeEnvOverrides,
3332
+ timezone: params.timezone
3333
+ };
3334
+ }
3335
+ function assembleUsageDataResult(events, rows, diagnostics) {
3336
+ return {
3337
+ events,
3338
+ rows,
3339
+ diagnostics
3340
+ };
3341
+ }
3342
+
3265
3343
  // src/config/env-var-display.ts
3266
3344
  var ENV_VARS_TO_DISPLAY = [
3267
3345
  { name: "LLM_USAGE_SKIP_UPDATE_CHECK", description: "skip startup update check" },
@@ -3302,42 +3380,6 @@ function formatEnvVarOverrides(overrides) {
3302
3380
  return lines;
3303
3381
  }
3304
3382
 
3305
- // src/cli/build-usage-data-diagnostics.ts
3306
- function buildUsageDiagnostics(params) {
3307
- const parseResultBySource = new Map(
3308
- params.successfulParseResults.map((result) => [result.source.toLowerCase(), result])
3309
- );
3310
- const sessionStats = params.adaptersToParse.map((adapter) => {
3311
- const parseResult = parseResultBySource.get(adapter.id.toLowerCase());
3312
- return {
3313
- source: adapter.id,
3314
- filesFound: parseResult?.filesFound ?? 0,
3315
- eventsParsed: parseResult?.events.length ?? 0
3316
- };
3317
- });
3318
- const skippedRows = params.successfulParseResults.filter((result) => result.skippedRows > 0).map((result) => ({
3319
- source: result.source,
3320
- skippedRows: result.skippedRows,
3321
- reasons: result.skippedRowReasons
3322
- }));
3323
- return {
3324
- sessionStats,
3325
- sourceFailures: params.sourceFailures,
3326
- skippedRows,
3327
- pricingOrigin: params.pricingOrigin,
3328
- pricingWarning: params.pricingWarning,
3329
- activeEnvOverrides: params.activeEnvOverrides,
3330
- timezone: params.timezone
3331
- };
3332
- }
3333
- function assembleUsageDataResult(events, rows, diagnostics) {
3334
- return {
3335
- events,
3336
- rows,
3337
- diagnostics
3338
- };
3339
- }
3340
-
3341
3383
  // src/cli/build-usage-data-inputs.ts
3342
3384
  function validateDateInput(value, flagName) {
3343
3385
  if (!/^\d{4}-\d{2}-\d{2}$/u.test(value)) {
@@ -3357,11 +3399,7 @@ function validateTimezone(timezone) {
3357
3399
  }
3358
3400
  }
3359
3401
  function normalizeProviderFilter(provider) {
3360
- if (!provider) {
3361
- return void 0;
3362
- }
3363
- const normalized = provider.trim().toLowerCase();
3364
- return normalized || void 0;
3402
+ return normalizeProviderToBillingEntity(provider);
3365
3403
  }
3366
3404
  function normalizeSourceFilter(source) {
3367
3405
  if (!source || Array.isArray(source) && source.length === 0) {
@@ -3508,7 +3546,7 @@ function selectAdaptersForParsing(adapters, sourceFilter) {
3508
3546
  }
3509
3547
 
3510
3548
  // src/cli/build-usage-data-parsing.ts
3511
- import { stat as stat3 } from "fs/promises";
3549
+ import { stat as stat4 } from "fs/promises";
3512
3550
 
3513
3551
  // src/cli/normalize-skipped-row-reasons.ts
3514
3552
  function toPositiveInteger(value) {
@@ -3536,9 +3574,9 @@ function normalizeSkippedRowReasons(value) {
3536
3574
  }
3537
3575
 
3538
3576
  // src/cli/parse-file-cache.ts
3539
- import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
3577
+ import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat3, writeFile as writeFile2 } from "fs/promises";
3540
3578
  import path11 from "path";
3541
- var PARSE_FILE_CACHE_VERSION = 2;
3579
+ var PARSE_FILE_CACHE_VERSION = 3;
3542
3580
  var CACHE_KEY_SEPARATOR = "\0";
3543
3581
  function createCacheKey(source, filePath) {
3544
3582
  return `${source}${CACHE_KEY_SEPARATOR}${filePath}`;
@@ -3597,7 +3635,9 @@ function normalizeCachedUsageEvent(value) {
3597
3635
  if (inputTokens === void 0 || outputTokens === void 0 || reasoningTokens === void 0 || cacheReadTokens === void 0 || cacheWriteTokens === void 0 || totalTokens === void 0) {
3598
3636
  return void 0;
3599
3637
  }
3600
- const provider = typeof record.provider === "string" ? record.provider.trim() : "";
3638
+ const provider = normalizeProviderToBillingEntity(
3639
+ typeof record.provider === "string" ? record.provider : void 0
3640
+ );
3601
3641
  const model = typeof record.model === "string" ? record.model.trim().toLowerCase() : "";
3602
3642
  const costUsd = toNonNegativeNumber2(record.costUsd);
3603
3643
  if (costMode === "explicit" && costUsd === void 0) {
@@ -3608,7 +3648,7 @@ function normalizeCachedUsageEvent(value) {
3608
3648
  sessionId,
3609
3649
  timestamp,
3610
3650
  repoRoot: repoRoot || void 0,
3611
- provider: provider || void 0,
3651
+ provider,
3612
3652
  model: model || void 0,
3613
3653
  inputTokens,
3614
3654
  outputTokens,
@@ -3627,7 +3667,7 @@ function cloneUsageEvents(events) {
3627
3667
  return events.map((event) => cloneUsageEvent(event));
3628
3668
  }
3629
3669
  function cloneSkippedRowReasons(skippedRowReasons) {
3630
- return (skippedRowReasons ?? []).map((stat4) => ({ reason: stat4.reason, count: stat4.count }));
3670
+ return (skippedRowReasons ?? []).map((stat5) => ({ reason: stat5.reason, count: stat5.count }));
3631
3671
  }
3632
3672
  function normalizeCachedEvents(value) {
3633
3673
  if (!Array.isArray(value)) {
@@ -3678,6 +3718,21 @@ function normalizeCacheEntry(value) {
3678
3718
  function getDefaultParseFileCachePath() {
3679
3719
  return path11.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
3680
3720
  }
3721
+ function normalizeCacheShardSource(source) {
3722
+ const normalizedSource = normalizeCacheSource(source);
3723
+ if (!normalizedSource) {
3724
+ return "unknown";
3725
+ }
3726
+ return normalizedSource.replace(/[^a-z0-9._-]/gu, "_");
3727
+ }
3728
+ function getSourceShardedParseFileCachePath(cacheFilePath, source) {
3729
+ const parsedPath = path11.parse(cacheFilePath);
3730
+ const sourceShard = normalizeCacheShardSource(source);
3731
+ if (parsedPath.ext.length > 0) {
3732
+ return path11.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
3733
+ }
3734
+ return path11.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
3735
+ }
3681
3736
  var ParseFileCache = class _ParseFileCache {
3682
3737
  constructor(cacheFilePath, limits, now) {
3683
3738
  this.cacheFilePath = cacheFilePath;
@@ -3763,8 +3818,15 @@ var ParseFileCache = class _ParseFileCache {
3763
3818
  payloadText = bestPayloadText;
3764
3819
  }
3765
3820
  await mkdir2(path11.dirname(this.cacheFilePath), { recursive: true });
3766
- await writeFile2(this.cacheFilePath, payloadText, "utf8");
3767
- this.dirty = false;
3821
+ const temporaryPath = `${this.cacheFilePath}.${process.pid}.${this.now()}.tmp`;
3822
+ try {
3823
+ await writeFile2(temporaryPath, payloadText, "utf8");
3824
+ await rename(temporaryPath, this.cacheFilePath);
3825
+ this.dirty = false;
3826
+ } catch (error) {
3827
+ await rm(temporaryPath, { force: true }).catch(() => void 0);
3828
+ throw error;
3829
+ }
3768
3830
  }
3769
3831
  toPayload(entries) {
3770
3832
  return {
@@ -3783,6 +3845,17 @@ var ParseFileCache = class _ParseFileCache {
3783
3845
  };
3784
3846
  }
3785
3847
  async loadFromDisk() {
3848
+ let cacheFileSizeBytes;
3849
+ try {
3850
+ const cacheStat = await stat3(this.cacheFilePath);
3851
+ cacheFileSizeBytes = cacheStat.size;
3852
+ } catch {
3853
+ return;
3854
+ }
3855
+ if (cacheFileSizeBytes > this.limits.maxBytes) {
3856
+ this.dirty = true;
3857
+ return;
3858
+ }
3786
3859
  let content;
3787
3860
  try {
3788
3861
  content = await readFile4(this.cacheFilePath, "utf8");
@@ -3808,6 +3881,7 @@ var ParseFileCache = class _ParseFileCache {
3808
3881
  }
3809
3882
  if (Buffer.byteLength(content, "utf8") > this.limits.maxBytes) {
3810
3883
  this.dirty = true;
3884
+ return;
3811
3885
  }
3812
3886
  const entries = Array.isArray(payloadRecord.entries) ? payloadRecord.entries : [];
3813
3887
  for (const rawEntry of entries) {
@@ -3867,7 +3941,7 @@ async function parseAdapterEvents(adapter, maxParallelFileParsing, parseFileCach
3867
3941
  let parseFileDiagnostics;
3868
3942
  if (parseFileCache) {
3869
3943
  try {
3870
- const fileStat = await stat3(filePath);
3944
+ const fileStat = await stat4(filePath);
3871
3945
  fileFingerprint = {
3872
3946
  size: fileStat.size,
3873
3947
  mtimeMs: fileStat.mtimeMs
@@ -3908,25 +3982,44 @@ function getErrorReason(error) {
3908
3982
  return String(error);
3909
3983
  }
3910
3984
  async function parseSelectedAdapters(adaptersToParse, maxParallelFileParsing, options = {}) {
3911
- const parseCache = options.parseCache?.enabled ? await ParseFileCache.load({
3912
- cacheFilePath: options.parseCacheFilePath,
3913
- limits: {
3985
+ const parseCacheBySource = /* @__PURE__ */ new Map();
3986
+ if (options.parseCache?.enabled) {
3987
+ const parseCacheLimits = {
3914
3988
  ttlMs: options.parseCache.ttlMs,
3915
3989
  maxEntries: options.parseCache.maxEntries,
3916
3990
  maxBytes: options.parseCache.maxBytes
3917
- },
3918
- now: options.now
3919
- }) : void 0;
3991
+ };
3992
+ const cacheFilePath = options.parseCacheFilePath ?? getDefaultParseFileCachePath();
3993
+ await Promise.all(
3994
+ adaptersToParse.map(async (adapter) => {
3995
+ const sourceId = adapter.id.toLowerCase();
3996
+ if (parseCacheBySource.has(sourceId)) {
3997
+ return;
3998
+ }
3999
+ parseCacheBySource.set(
4000
+ sourceId,
4001
+ await ParseFileCache.load({
4002
+ cacheFilePath: getSourceShardedParseFileCachePath(cacheFilePath, sourceId),
4003
+ limits: parseCacheLimits,
4004
+ now: options.now
4005
+ })
4006
+ );
4007
+ })
4008
+ );
4009
+ }
3920
4010
  const parseResults = await Promise.allSettled(
3921
4011
  adaptersToParse.map(
3922
- (adapter) => parseAdapterEvents(adapter, maxParallelFileParsing, parseCache)
4012
+ (adapter) => parseAdapterEvents(
4013
+ adapter,
4014
+ maxParallelFileParsing,
4015
+ parseCacheBySource.get(adapter.id.toLowerCase())
4016
+ )
3923
4017
  )
3924
4018
  );
3925
- if (parseCache) {
3926
- try {
3927
- await parseCache.persist();
3928
- } catch {
3929
- }
4019
+ if (parseCacheBySource.size > 0) {
4020
+ await Promise.allSettled(
4021
+ [...parseCacheBySource.values()].map(async (parseCache) => parseCache.persist())
4022
+ );
3930
4023
  }
3931
4024
  const sourceFailures = [];
3932
4025
  const successfulParseResults = [];
@@ -4200,7 +4293,7 @@ function normalizeModelPricing(rawModelPricing) {
4200
4293
  return void 0;
4201
4294
  }
4202
4295
  const cacheReadPerToken = toNonNegativeNumber3(rawModelPricing.cache_read_input_token_cost) ?? toNonNegativeNumber3(rawModelPricing.cache_read_input_token_cost_priority);
4203
- const cacheWritePerToken = toNonNegativeNumber3(rawModelPricing.cache_creation_input_token_cost);
4296
+ const cacheWritePerToken = toNonNegativeNumber3(rawModelPricing.cache_creation_input_token_cost) ?? toNonNegativeNumber3(rawModelPricing.cache_creation_input_token_cost_priority);
4204
4297
  const reasoningPerToken = toNonNegativeNumber3(rawModelPricing.output_cost_per_reasoning_token);
4205
4298
  const modelPricing = {
4206
4299
  inputPer1MUsd: inputPerToken * ONE_MILLION2,
@@ -4690,9 +4783,20 @@ function shouldLoadPricingSource(events) {
4690
4783
  }
4691
4784
  return events.some((event) => eventNeedsPricingLookup(event));
4692
4785
  }
4693
- async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = getPricingFetcherRuntimeConfig(), loadPricingSource = resolvePricingSource) {
4786
+ function hasAnyBillableTokenBuckets(events) {
4787
+ return events.some(
4788
+ (event) => event.inputTokens > 0 || event.outputTokens > 0 || event.reasoningTokens > 0 || event.cacheReadTokens > 0 || event.cacheWriteTokens > 0
4789
+ );
4790
+ }
4791
+ function shouldLoadPricingSourceForMode(events, pricingLoadMode) {
4792
+ if (pricingLoadMode === "force") {
4793
+ return hasAnyBillableTokenBuckets(events);
4794
+ }
4795
+ return shouldLoadPricingSource(events);
4796
+ }
4797
+ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = getPricingFetcherRuntimeConfig(), loadPricingSource = resolvePricingSource, pricingLoadMode = "auto") {
4694
4798
  let pricingOrigin = "none";
4695
- if (!shouldLoadPricingSource(events)) {
4799
+ if (!shouldLoadPricingSourceForMode(events, pricingLoadMode)) {
4696
4800
  return {
4697
4801
  pricedEvents: events,
4698
4802
  pricingOrigin
@@ -4716,11 +4820,12 @@ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = g
4716
4820
  pricingOrigin = pricingResult.origin;
4717
4821
  return {
4718
4822
  pricedEvents: applyPricingToEvents(events, pricingResult.source),
4719
- pricingOrigin
4823
+ pricingOrigin,
4824
+ pricingSource: pricingResult.source
4720
4825
  };
4721
4826
  }
4722
4827
 
4723
- // src/cli/build-usage-data.ts
4828
+ // src/cli/build-usage-event-dataset.ts
4724
4829
  function withNormalizedPricingUrl(options, normalizedPricingUrl) {
4725
4830
  if (options.pricingUrl === normalizedPricingUrl) {
4726
4831
  return options;
@@ -4730,13 +4835,11 @@ function withNormalizedPricingUrl(options, normalizedPricingUrl) {
4730
4835
  pricingUrl: normalizedPricingUrl
4731
4836
  };
4732
4837
  }
4733
- async function buildUsageData(granularity, options, deps = {}) {
4838
+ async function buildUsageEventDataset(options, deps = {}) {
4734
4839
  const normalizedInputs = normalizeBuildUsageInputs(options);
4735
4840
  const readParsingRuntimeConfig = deps.getParsingRuntimeConfig ?? getParsingRuntimeConfig;
4736
4841
  const readPricingRuntimeConfig = deps.getPricingFetcherRuntimeConfig ?? getPricingFetcherRuntimeConfig;
4737
4842
  const makeAdapters = deps.createAdapters ?? createDefaultAdapters;
4738
- const loadPricingSource = deps.resolvePricingSource ?? resolvePricingSource;
4739
- const readEnvVarOverrides = deps.getActiveEnvVarOverrides ?? getActiveEnvVarOverrides;
4740
4843
  const parsingRuntimeConfig = readParsingRuntimeConfig();
4741
4844
  const pricingRuntimeConfig = readPricingRuntimeConfig();
4742
4845
  const adapters = makeAdapters(options);
@@ -4761,26 +4864,53 @@ async function buildUsageData(granularity, options, deps = {}) {
4761
4864
  providerFilter: normalizedInputs.providerFilter,
4762
4865
  modelFilter: normalizedInputs.modelFilter
4763
4866
  });
4764
- const pricingOptions = withNormalizedPricingUrl(options, normalizedInputs.pricingUrl);
4765
- const { pricedEvents, pricingOrigin, pricingWarning } = await resolveAndApplyPricingToEvents(
4867
+ return {
4868
+ options,
4869
+ normalizedInputs,
4870
+ adaptersToParse,
4871
+ successfulParseResults,
4872
+ sourceFailures,
4766
4873
  filteredEvents,
4767
- pricingOptions,
4768
4874
  pricingRuntimeConfig,
4769
- loadPricingSource
4875
+ readEnvVarOverrides: deps.getActiveEnvVarOverrides ?? getActiveEnvVarOverrides
4876
+ };
4877
+ }
4878
+ async function applyPricingToUsageEventDataset(dataset, deps = {}, pricingLoadMode = "auto") {
4879
+ const loadPricingSource = deps.resolvePricingSource ?? resolvePricingSource;
4880
+ const pricingOptions = withNormalizedPricingUrl(
4881
+ dataset.options,
4882
+ dataset.normalizedInputs.pricingUrl
4883
+ );
4884
+ return resolveAndApplyPricingToEvents(
4885
+ dataset.filteredEvents,
4886
+ pricingOptions,
4887
+ dataset.pricingRuntimeConfig,
4888
+ loadPricingSource,
4889
+ pricingLoadMode
4890
+ );
4891
+ }
4892
+
4893
+ // src/cli/build-usage-data.ts
4894
+ async function buildUsageData(granularity, options, deps = {}) {
4895
+ const dataset = await buildUsageEventDataset(options, deps);
4896
+ const { pricedEvents, pricingOrigin, pricingWarning } = await applyPricingToUsageEventDataset(
4897
+ dataset,
4898
+ deps,
4899
+ "auto"
4770
4900
  );
4771
4901
  const rows = aggregateUsage(pricedEvents, {
4772
4902
  granularity,
4773
- timezone: normalizedInputs.timezone,
4774
- sourceOrder: adaptersToParse.map((adapter) => adapter.id)
4903
+ timezone: dataset.normalizedInputs.timezone,
4904
+ sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
4775
4905
  });
4776
4906
  const diagnostics = buildUsageDiagnostics({
4777
- adaptersToParse,
4778
- successfulParseResults,
4779
- sourceFailures,
4907
+ adaptersToParse: dataset.adaptersToParse,
4908
+ successfulParseResults: dataset.successfulParseResults,
4909
+ sourceFailures: dataset.sourceFailures,
4780
4910
  pricingOrigin,
4781
4911
  pricingWarning,
4782
- activeEnvOverrides: readEnvVarOverrides(),
4783
- timezone: normalizedInputs.timezone
4912
+ activeEnvOverrides: dataset.readEnvVarOverrides(),
4913
+ timezone: dataset.normalizedInputs.timezone
4784
4914
  });
4785
4915
  return assembleUsageDataResult(pricedEvents, rows, diagnostics);
4786
4916
  }
@@ -5197,6 +5327,7 @@ function warnIfTerminalTableOverflows(reportOutput, warn, stdoutState = process.
5197
5327
 
5198
5328
  // src/render/render-efficiency-report.ts
5199
5329
  import { markdownTable } from "markdown-table";
5330
+ import pc5 from "picocolors";
5200
5331
 
5201
5332
  // src/render/report-header.ts
5202
5333
  import pc2 from "picocolors";
@@ -5342,8 +5473,8 @@ function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
5342
5473
  var rowTypeStylePolicies = {
5343
5474
  period_source: (cells, palette) => {
5344
5475
  const styledCells = [...cells];
5345
- const costColumnIndex = styledCells.length - 1;
5346
- styledCells[costColumnIndex] = styleCellLines(styledCells[costColumnIndex], palette.yellow);
5476
+ const costColumnIndex2 = styledCells.length - 1;
5477
+ styledCells[costColumnIndex2] = styleCellLines(styledCells[costColumnIndex2], palette.yellow);
5347
5478
  return styledCells;
5348
5479
  },
5349
5480
  period_combined: (cells, palette) => cells.map((cell, cellIndex) => {
@@ -5786,6 +5917,16 @@ function renderTerminalTable(rows, options = {}) {
5786
5917
  }
5787
5918
 
5788
5919
  // src/render/render-efficiency-report.ts
5920
+ var periodColumnIndex = 0;
5921
+ var minimumEfficiencyColumnWidth = 1;
5922
+ var commitsColumnIndex = 1;
5923
+ var linesAddedColumnIndex = 2;
5924
+ var linesDeletedColumnIndex = 3;
5925
+ var linesChangedColumnIndex = 4;
5926
+ var costColumnIndex = 11;
5927
+ var usdPerCommitColumnIndex = 12;
5928
+ var usdPer1kLinesChangedColumnIndex = 13;
5929
+ var commitsPerUsdColumnIndex = 16;
5789
5930
  function getReportTitle(granularity) {
5790
5931
  switch (granularity) {
5791
5932
  case "daily":
@@ -5830,22 +5971,172 @@ function toTableSortRow(row) {
5830
5971
  costIncomplete: row.costIncomplete
5831
5972
  };
5832
5973
  }
5833
- function renderTerminalEfficiencyTable(rows) {
5834
- const bodyRows = toEfficiencyTableCells(rows);
5835
- const tableSortRows = rows.map((row) => toTableSortRow(row));
5836
- const periodColumnWidth = Math.max(
5837
- efficiencyTableHeaders[0].length,
5838
- ...rows.map((row) => row.periodKey.length)
5974
+ function measureRenderedTableWidth(columnWidths) {
5975
+ if (columnWidths.length === 0) {
5976
+ return 0;
5977
+ }
5978
+ return columnWidths.reduce((sum, width) => sum + width, 0) + columnWidths.length * 3 + 1;
5979
+ }
5980
+ function computeColumnWidths2(headerCells, bodyRows) {
5981
+ const columnCount = Math.max(
5982
+ headerCells.length,
5983
+ ...bodyRows.map((row) => row.length),
5984
+ efficiencyTableHeaders.length
5839
5985
  );
5840
- return renderUnicodeTable({
5841
- headerCells: efficiencyTableHeaders,
5986
+ const widths = Array.from({ length: columnCount }, () => 0);
5987
+ const measureRow = (row) => {
5988
+ for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
5989
+ for (const line of splitCellLines(row[columnIndex] ?? "")) {
5990
+ widths[columnIndex] = Math.max(widths[columnIndex], visibleWidth(line));
5991
+ }
5992
+ }
5993
+ };
5994
+ measureRow(headerCells);
5995
+ for (const row of bodyRows) {
5996
+ measureRow(row);
5997
+ }
5998
+ return widths;
5999
+ }
6000
+ function resolveWrappedCells(headerCells, bodyRows, widths) {
6001
+ let wrappedHeaderCells = [...headerCells];
6002
+ let wrappedBodyRows = bodyRows.map((row) => [...row]);
6003
+ for (let columnIndex = 0; columnIndex < widths.length; columnIndex += 1) {
6004
+ const columnWidth = widths[columnIndex] ?? 0;
6005
+ if (columnWidth <= 0) {
6006
+ continue;
6007
+ }
6008
+ wrappedHeaderCells = wrapTableColumn([wrappedHeaderCells], {
6009
+ columnIndex,
6010
+ width: columnWidth
6011
+ })[0] ?? [];
6012
+ wrappedBodyRows = wrapTableColumn(wrappedBodyRows, {
6013
+ columnIndex,
6014
+ width: columnWidth
6015
+ });
6016
+ }
6017
+ return {
6018
+ wrappedHeaderCells,
6019
+ wrappedBodyRows
6020
+ };
6021
+ }
6022
+ function fitTableCellsToTerminal(headerCells, bodyRows) {
6023
+ const naturalWidths = computeColumnWidths2(headerCells, bodyRows);
6024
+ const terminalWidth = resolveTtyColumns(process.stdout);
6025
+ if (terminalWidth === void 0 || measureRenderedTableWidth(naturalWidths) <= terminalWidth) {
6026
+ return {
6027
+ headerCells: [...headerCells],
6028
+ bodyRows: bodyRows.map((row) => [...row]),
6029
+ widths: naturalWidths
6030
+ };
6031
+ }
6032
+ const constrainedWidths = [...naturalWidths];
6033
+ let renderedTableWidth = measureRenderedTableWidth(constrainedWidths);
6034
+ while (renderedTableWidth > terminalWidth && constrainedWidths.some((width) => width > minimumEfficiencyColumnWidth)) {
6035
+ let widestIndex = -1;
6036
+ let widestWidth = -1;
6037
+ for (let columnIndex = 0; columnIndex < constrainedWidths.length; columnIndex += 1) {
6038
+ const columnWidth = constrainedWidths[columnIndex];
6039
+ if (columnWidth <= minimumEfficiencyColumnWidth || columnWidth <= widestWidth) {
6040
+ continue;
6041
+ }
6042
+ widestIndex = columnIndex;
6043
+ widestWidth = columnWidth;
6044
+ }
6045
+ if (widestIndex === -1) {
6046
+ break;
6047
+ }
6048
+ const overflowColumns = renderedTableWidth - terminalWidth;
6049
+ const maxReducibleWidth = widestWidth - minimumEfficiencyColumnWidth;
6050
+ const reduction = Math.min(overflowColumns, maxReducibleWidth);
6051
+ if (reduction <= 0) {
6052
+ break;
6053
+ }
6054
+ constrainedWidths[widestIndex] -= reduction;
6055
+ renderedTableWidth -= reduction;
6056
+ }
6057
+ const { wrappedHeaderCells, wrappedBodyRows } = resolveWrappedCells(
6058
+ headerCells,
5842
6059
  bodyRows,
5843
- measureHeaderCells: efficiencyTableHeaders,
5844
- measureBodyRows: bodyRows,
6060
+ constrainedWidths
6061
+ );
6062
+ return {
6063
+ headerCells: wrappedHeaderCells,
6064
+ bodyRows: wrappedBodyRows,
6065
+ widths: constrainedWidths
6066
+ };
6067
+ }
6068
+ function styleDeltaCell(value, formattedValue, options) {
6069
+ if (!options.useColor) {
6070
+ return formattedValue;
6071
+ }
6072
+ if (value > 0) {
6073
+ return pc5.green(formattedValue);
6074
+ }
6075
+ if (value < 0) {
6076
+ return pc5.red(formattedValue);
6077
+ }
6078
+ return pc5.dim(formattedValue);
6079
+ }
6080
+ function styleEfficiencyTerminalRows(rows, bodyRows, options) {
6081
+ return bodyRows.map((cells, rowIndex) => {
6082
+ if (!options.useColor) {
6083
+ return [...cells];
6084
+ }
6085
+ const row = rows[rowIndex];
6086
+ const styledCells = [...cells];
6087
+ const periodCell = styledCells[periodColumnIndex];
6088
+ styledCells[periodColumnIndex] = row.rowType === "grand_total" ? pc5.bold(pc5.cyan(periodCell)) : pc5.bold(periodCell);
6089
+ styledCells[commitsColumnIndex] = pc5.bold(styledCells[commitsColumnIndex]);
6090
+ styledCells[linesAddedColumnIndex] = styleDeltaCell(
6091
+ row.linesAdded,
6092
+ styledCells[linesAddedColumnIndex],
6093
+ options
6094
+ );
6095
+ styledCells[linesDeletedColumnIndex] = styleDeltaCell(
6096
+ row.linesDeleted * -1,
6097
+ styledCells[linesDeletedColumnIndex],
6098
+ options
6099
+ );
6100
+ styledCells[linesChangedColumnIndex] = styleDeltaCell(
6101
+ row.linesChanged,
6102
+ styledCells[linesChangedColumnIndex],
6103
+ options
6104
+ );
6105
+ const costValue = row.costUsd;
6106
+ if (costValue !== void 0 && costValue > 0) {
6107
+ styledCells[costColumnIndex] = pc5.yellow(styledCells[costColumnIndex]);
6108
+ }
6109
+ const usdPerCommitValue = row.usdPerCommit;
6110
+ if (usdPerCommitValue !== void 0 && usdPerCommitValue > 0) {
6111
+ styledCells[usdPerCommitColumnIndex] = pc5.yellow(styledCells[usdPerCommitColumnIndex]);
6112
+ }
6113
+ const usdPer1kLinesChangedValue = row.usdPer1kLinesChanged;
6114
+ if (usdPer1kLinesChangedValue !== void 0 && usdPer1kLinesChangedValue > 0) {
6115
+ styledCells[usdPer1kLinesChangedColumnIndex] = pc5.yellow(
6116
+ styledCells[usdPer1kLinesChangedColumnIndex]
6117
+ );
6118
+ }
6119
+ const commitsPerUsdValue = row.commitsPerUsd;
6120
+ if (commitsPerUsdValue !== void 0 && commitsPerUsdValue > 0) {
6121
+ styledCells[commitsPerUsdColumnIndex] = pc5.green(styledCells[commitsPerUsdColumnIndex]);
6122
+ }
6123
+ return styledCells;
6124
+ });
6125
+ }
6126
+ function renderTerminalEfficiencyTable(rows, options) {
6127
+ const headerCells = Array.from(efficiencyTableHeaders);
6128
+ const bodyRows = styleEfficiencyTerminalRows(rows, toEfficiencyTableCells(rows), options);
6129
+ const tableSortRows = rows.map((row) => toTableSortRow(row));
6130
+ const fittedCells = fitTableCellsToTerminal(headerCells, bodyRows);
6131
+ return renderUnicodeTable({
6132
+ headerCells: fittedCells.headerCells,
6133
+ bodyRows: fittedCells.bodyRows,
6134
+ measureHeaderCells: fittedCells.headerCells,
6135
+ measureBodyRows: fittedCells.bodyRows,
5845
6136
  usageRows: tableSortRows,
5846
6137
  tableLayout: "compact",
5847
- modelsColumnIndex: 0,
5848
- modelsColumnWidth: periodColumnWidth
6138
+ modelsColumnIndex: periodColumnIndex,
6139
+ modelsColumnWidth: fittedCells.widths[periodColumnIndex] ?? efficiencyTableHeaders[periodColumnIndex].length
5849
6140
  });
5850
6141
  }
5851
6142
  function toMarkdownSafeCell(value) {
@@ -5871,7 +6162,7 @@ function renderTerminalEfficiencyReport(efficiencyData, options) {
5871
6162
  })
5872
6163
  );
5873
6164
  outputLines.push("");
5874
- outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows));
6165
+ outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows, { useColor }));
5875
6166
  return outputLines.join("\n");
5876
6167
  }
5877
6168
  function renderEfficiencyReport(efficiencyData, format, options) {
@@ -5934,25 +6225,656 @@ async function runEfficiencyReport(granularity, options) {
5934
6225
  console.log(preparedReport.output);
5935
6226
  }
5936
6227
 
5937
- // src/render/markdown-table.ts
6228
+ // src/render/render-optimize-report.ts
5938
6229
  import { markdownTable as markdownTable2 } from "markdown-table";
5939
- var alignment = ["l", "l", "l", "r", "r", "r", "r", "r", "r", "r"];
6230
+ import pc6 from "picocolors";
6231
+ var optimizeTableHeadersWithNotes = [
6232
+ "Period",
6233
+ "Candidate",
6234
+ "Hypothetical Cost",
6235
+ "Baseline Cost",
6236
+ "Savings",
6237
+ "Savings %",
6238
+ "Notes"
6239
+ ];
6240
+ var optimizeTableHeadersWithoutNotes = [
6241
+ "Period",
6242
+ "Candidate",
6243
+ "Hypothetical Cost",
6244
+ "Baseline Cost",
6245
+ "Savings",
6246
+ "Savings %"
6247
+ ];
6248
+ var usdFormatter3 = new Intl.NumberFormat("en-US", {
6249
+ style: "currency",
6250
+ currency: "USD",
6251
+ minimumFractionDigits: 2,
6252
+ maximumFractionDigits: 2
6253
+ });
6254
+ function getReportTitle2(granularity) {
6255
+ switch (granularity) {
6256
+ case "daily":
6257
+ return "Daily Optimize Report";
6258
+ case "weekly":
6259
+ return "Weekly Optimize Report";
6260
+ case "monthly":
6261
+ return "Monthly Optimize Report";
6262
+ }
6263
+ }
6264
+ function formatUsd3(value, options = {}) {
6265
+ if (value === void 0) {
6266
+ return "-";
6267
+ }
6268
+ const formatted = usdFormatter3.format(value);
6269
+ return options.approximate ? `~${formatted}` : formatted;
6270
+ }
6271
+ function formatPercent(value) {
6272
+ if (value === void 0) {
6273
+ return "-";
6274
+ }
6275
+ return `${(value * 100).toFixed(2)}%`;
6276
+ }
6277
+ function formatNotes(notes) {
6278
+ if (!notes || notes.length === 0) {
6279
+ return "-";
6280
+ }
6281
+ return notes.join(", ");
6282
+ }
6283
+ function styleCandidateCell(candidateValue, rowType, useColor) {
6284
+ if (!useColor) {
6285
+ return candidateValue;
6286
+ }
6287
+ if (rowType === "baseline") {
6288
+ return pc6.bold(pc6.cyan(candidateValue));
6289
+ }
6290
+ return pc6.bold(candidateValue);
6291
+ }
6292
+ function styleDeltaCell2(value, formattedValue, useColor) {
6293
+ if (!useColor || value === void 0) {
6294
+ return formattedValue;
6295
+ }
6296
+ if (value > 0) {
6297
+ return pc6.green(formattedValue);
6298
+ }
6299
+ if (value < 0) {
6300
+ return pc6.red(formattedValue);
6301
+ }
6302
+ return pc6.dim(formattedValue);
6303
+ }
6304
+ function styleNotesCell(notes, formattedNotes, useColor) {
6305
+ if (!useColor || !notes || notes.length === 0) {
6306
+ return formattedNotes;
6307
+ }
6308
+ return pc6.yellow(formattedNotes);
6309
+ }
6310
+ function formatAbsoluteUsd(value) {
6311
+ return usdFormatter3.format(Math.abs(value));
6312
+ }
6313
+ function resolveTerminalContextLines(optimizeData, options) {
6314
+ const allBaselineRow = optimizeData.rows.find(
6315
+ (row) => row.rowType === "baseline" && row.periodKey === "ALL"
6316
+ );
6317
+ const allCandidateRows = optimizeData.rows.filter(
6318
+ (row) => row.rowType === "candidate" && row.periodKey === "ALL"
6319
+ );
6320
+ const lines = [];
6321
+ const providerLine = `Provider scope: ${optimizeData.diagnostics.provider}`;
6322
+ lines.push(options.useColor ? pc6.cyan(providerLine) : providerLine);
6323
+ if (allBaselineRow) {
6324
+ lines.push(
6325
+ `ALL baseline cost: ${formatUsd3(allBaselineRow.baselineCostUsd, { approximate: allBaselineRow.baselineCostIncomplete })}`
6326
+ );
6327
+ }
6328
+ if (allCandidateRows.length > 0) {
6329
+ const rowsWithSavings = allCandidateRows.filter((row) => row.savingsUsd !== void 0);
6330
+ const bestRow = rowsWithSavings.length > 0 ? rowsWithSavings.reduce(
6331
+ (best, current) => (current.savingsUsd ?? Number.NEGATIVE_INFINITY) > (best.savingsUsd ?? Number.NEGATIVE_INFINITY) ? current : best
6332
+ ) : void 0;
6333
+ if (!bestRow || bestRow.savingsUsd === void 0) {
6334
+ lines.push("ALL best candidate: unavailable (missing baseline or candidate pricing)");
6335
+ } else if (bestRow.savingsUsd > 0) {
6336
+ lines.push(
6337
+ `ALL best candidate: ${bestRow.candidateModel} saves ${formatAbsoluteUsd(bestRow.savingsUsd)} (${formatPercent(bestRow.savingsPct)})`
6338
+ );
6339
+ } else if (bestRow.savingsUsd < 0) {
6340
+ lines.push(
6341
+ `ALL best candidate: ${bestRow.candidateModel} increases cost by ${formatAbsoluteUsd(bestRow.savingsUsd)} (${formatPercent(bestRow.savingsPct)})`
6342
+ );
6343
+ } else {
6344
+ lines.push(`ALL best candidate: ${bestRow.candidateModel} matches baseline cost`);
6345
+ }
6346
+ }
6347
+ if (optimizeData.diagnostics.candidatesWithMissingPricing.length > 0) {
6348
+ const missingLine = `Missing candidate pricing: ${optimizeData.diagnostics.candidatesWithMissingPricing.join(", ")}`;
6349
+ lines.push(options.useColor ? pc6.yellow(missingLine) : missingLine);
6350
+ }
6351
+ const legendLine = "Savings = Baseline - Hypothetical (positive means cheaper candidate)";
6352
+ lines.push(options.useColor ? pc6.dim(legendLine) : legendLine);
6353
+ return lines;
6354
+ }
6355
+ function toTableCells(optimizeData, options) {
6356
+ const baselineByPeriod = new Map(
6357
+ optimizeData.rows.filter((row) => row.rowType === "baseline").map((row) => [row.periodKey, row])
6358
+ );
6359
+ return optimizeData.rows.map((row) => {
6360
+ const baselineRow = baselineByPeriod.get(row.periodKey);
6361
+ const periodCell = options.useColor && row.periodKey === "ALL" ? pc6.bold(row.periodKey) : row.periodKey;
6362
+ if (row.rowType === "baseline") {
6363
+ const baselineCells = [
6364
+ periodCell,
6365
+ styleCandidateCell("BASELINE", "baseline", options.useColor),
6366
+ "-",
6367
+ formatUsd3(row.baselineCostUsd, { approximate: row.baselineCostIncomplete }),
6368
+ "-",
6369
+ "-"
6370
+ ];
6371
+ return options.includeNotesColumn ? [...baselineCells, "-"] : baselineCells;
6372
+ }
6373
+ const savingsCell = formatUsd3(row.savingsUsd);
6374
+ const savingsPctCell = formatPercent(row.savingsPct);
6375
+ const notesCell = formatNotes(row.notes);
6376
+ const candidateCells = [
6377
+ periodCell,
6378
+ styleCandidateCell(row.candidateModel, "candidate", options.useColor),
6379
+ formatUsd3(row.hypotheticalCostUsd, { approximate: row.hypotheticalCostIncomplete }),
6380
+ formatUsd3(baselineRow?.baselineCostUsd, {
6381
+ approximate: baselineRow?.baselineCostIncomplete === true
6382
+ }),
6383
+ styleDeltaCell2(row.savingsUsd, savingsCell, options.useColor),
6384
+ styleDeltaCell2(row.savingsPct, savingsPctCell, options.useColor)
6385
+ ];
6386
+ return options.includeNotesColumn ? [...candidateCells, styleNotesCell(row.notes, notesCell, options.useColor)] : candidateCells;
6387
+ });
6388
+ }
5940
6389
  function toMarkdownSafeCell2(value) {
5941
6390
  return value.replace(/\r?\n/gu, "<br>");
5942
6391
  }
6392
+ function toSortingUsageRows(optimizeData) {
6393
+ return optimizeData.rows.map((row) => {
6394
+ return {
6395
+ rowType: "period_source",
6396
+ periodKey: row.periodKey,
6397
+ source: "combined",
6398
+ models: [],
6399
+ modelBreakdown: [],
6400
+ inputTokens: row.inputTokens,
6401
+ outputTokens: row.outputTokens,
6402
+ reasoningTokens: row.reasoningTokens,
6403
+ cacheReadTokens: row.cacheReadTokens,
6404
+ cacheWriteTokens: row.cacheWriteTokens,
6405
+ totalTokens: row.totalTokens,
6406
+ costUsd: row.rowType === "baseline" ? row.baselineCostUsd : row.hypotheticalCostUsd,
6407
+ costIncomplete: row.rowType === "baseline" ? row.baselineCostIncomplete : row.hypotheticalCostIncomplete
6408
+ };
6409
+ });
6410
+ }
6411
+ function resolveCandidateColumnWidth(tableCells) {
6412
+ return tableCells.reduce((maxWidth, row) => {
6413
+ const candidateValue = row[1] ?? "";
6414
+ return Math.max(maxWidth, visibleWidth(candidateValue));
6415
+ }, visibleWidth(optimizeTableHeadersWithNotes[1]));
6416
+ }
6417
+ function resolveIncludeNotesColumn(optimizeData) {
6418
+ return optimizeData.rows.some(
6419
+ (row) => row.rowType === "candidate" && row.notes !== void 0 && row.notes.length > 0
6420
+ );
6421
+ }
6422
+ function renderTerminalOptimizeReport(optimizeData, options) {
6423
+ const useColor = options.useColor ?? shouldUseColorByDefault();
6424
+ const includeNotesColumn = resolveIncludeNotesColumn(optimizeData);
6425
+ const tableCells = toTableCells(optimizeData, { useColor, includeNotesColumn });
6426
+ const candidateColumnWidth = resolveCandidateColumnWidth(tableCells);
6427
+ const contextLines = resolveTerminalContextLines(optimizeData, { useColor });
6428
+ const headerCells = includeNotesColumn ? [...optimizeTableHeadersWithNotes] : [...optimizeTableHeadersWithoutNotes];
6429
+ const outputLines = [];
6430
+ outputLines.push(
6431
+ renderReportHeader({
6432
+ title: getReportTitle2(options.granularity),
6433
+ useColor
6434
+ })
6435
+ );
6436
+ outputLines.push("");
6437
+ outputLines.push(...contextLines);
6438
+ outputLines.push("");
6439
+ outputLines.push(
6440
+ renderUnicodeTable({
6441
+ headerCells,
6442
+ bodyRows: tableCells,
6443
+ measureHeaderCells: headerCells,
6444
+ measureBodyRows: tableCells,
6445
+ usageRows: toSortingUsageRows(optimizeData),
6446
+ tableLayout: "compact",
6447
+ modelsColumnIndex: 1,
6448
+ modelsColumnWidth: candidateColumnWidth
6449
+ })
6450
+ );
6451
+ return outputLines.join("\n");
6452
+ }
6453
+ function renderMarkdownOptimizeReport(optimizeData) {
6454
+ const includeNotesColumn = resolveIncludeNotesColumn(optimizeData);
6455
+ const headerCells = includeNotesColumn ? [...optimizeTableHeadersWithNotes] : [...optimizeTableHeadersWithoutNotes];
6456
+ const bodyRows = toTableCells(optimizeData, {
6457
+ useColor: false,
6458
+ includeNotesColumn
6459
+ }).map((row) => row.map((cell) => toMarkdownSafeCell2(cell)));
6460
+ const tableRows = [headerCells, ...bodyRows];
6461
+ const alignment2 = headerCells.map((_, index) => index <= 1 ? "l" : "r");
6462
+ return markdownTable2(tableRows, { align: alignment2 });
6463
+ }
6464
+ function renderOptimizeReport(optimizeData, format, options) {
6465
+ switch (format) {
6466
+ case "json":
6467
+ return JSON.stringify(optimizeData.rows, null, 2);
6468
+ case "markdown":
6469
+ return renderMarkdownOptimizeReport(optimizeData);
6470
+ case "terminal":
6471
+ return renderTerminalOptimizeReport(optimizeData, options);
6472
+ }
6473
+ }
6474
+
6475
+ // src/optimize/aggregate-counterfactual.ts
6476
+ var USD_PRECISION_SCALE3 = 1e12;
6477
+ function roundUsd(value) {
6478
+ return Math.round(value * USD_PRECISION_SCALE3) / USD_PRECISION_SCALE3;
6479
+ }
6480
+ function hasZeroBillableTokenBuckets(totals) {
6481
+ return totals.inputTokens === 0 && totals.outputTokens === 0 && totals.reasoningTokens === 0 && totals.cacheReadTokens === 0 && totals.cacheWriteTokens === 0;
6482
+ }
6483
+ function parseCandidateModelsRaw(candidateModel) {
6484
+ if (!candidateModel || Array.isArray(candidateModel) && candidateModel.length === 0) {
6485
+ throw new Error("At least one --candidate-model is required");
6486
+ }
6487
+ const normalizedCandidates = (Array.isArray(candidateModel) ? candidateModel : [candidateModel]).flatMap((candidate) => candidate.split(",")).map((candidate) => candidate.trim().toLowerCase()).filter((candidate) => candidate.length > 0);
6488
+ if (normalizedCandidates.length === 0) {
6489
+ throw new Error("--candidate-model must contain at least one non-empty model name");
6490
+ }
6491
+ return [...new Set(normalizedCandidates)];
6492
+ }
6493
+ function normalizeCandidateModels(candidateModel) {
6494
+ return parseCandidateModelsRaw(candidateModel);
6495
+ }
6496
+ function parseTopOption(top) {
6497
+ if (top === void 0) {
6498
+ return void 0;
6499
+ }
6500
+ const normalized = top.trim();
6501
+ const parsed = Number.parseInt(normalized, 10);
6502
+ if (!/^\d+$/u.test(normalized) || Number.isNaN(parsed) || parsed < 1) {
6503
+ throw new Error("--top must be a positive integer");
6504
+ }
6505
+ return parsed;
6506
+ }
6507
+ function toBaselineRow(period, provider) {
6508
+ return {
6509
+ rowType: "baseline",
6510
+ periodKey: period.periodKey,
6511
+ provider,
6512
+ inputTokens: period.inputTokens,
6513
+ outputTokens: period.outputTokens,
6514
+ reasoningTokens: period.reasoningTokens,
6515
+ cacheReadTokens: period.cacheReadTokens,
6516
+ cacheWriteTokens: period.cacheWriteTokens,
6517
+ totalTokens: period.totalTokens,
6518
+ baselineCostUsd: period.baselineCostUsd,
6519
+ baselineCostIncomplete: period.baselineCostIncomplete
6520
+ };
6521
+ }
6522
+ function createSyntheticEvent(period) {
6523
+ return {
6524
+ source: "pi",
6525
+ sessionId: "optimize-period",
6526
+ timestamp: "1970-01-01T00:00:00.000Z",
6527
+ provider: void 0,
6528
+ model: "synthetic",
6529
+ inputTokens: period.inputTokens,
6530
+ outputTokens: period.outputTokens,
6531
+ reasoningTokens: period.reasoningTokens,
6532
+ cacheReadTokens: period.cacheReadTokens,
6533
+ cacheWriteTokens: period.cacheWriteTokens,
6534
+ totalTokens: period.totalTokens,
6535
+ costMode: "estimated",
6536
+ costUsd: void 0
6537
+ };
6538
+ }
6539
+ function withNotes(notes) {
6540
+ if (notes.size === 0) {
6541
+ return void 0;
6542
+ }
6543
+ return [...notes].sort(compareByCodePoint);
6544
+ }
6545
+ function evaluateCandidateForPeriod(period, provider, candidateModel, pricingSource) {
6546
+ const notes = /* @__PURE__ */ new Set();
6547
+ const zeroBillableTokens = hasZeroBillableTokenBuckets(period);
6548
+ const candidateResolvedModel = pricingSource ? pricingSource.resolveModelAlias(candidateModel) : candidateModel;
6549
+ const pricing = pricingSource ? pricingSource.getPricing(candidateResolvedModel) : void 0;
6550
+ let hypotheticalCostUsd;
6551
+ let hypotheticalCostIncomplete = false;
6552
+ if (!pricing) {
6553
+ if (zeroBillableTokens) {
6554
+ hypotheticalCostUsd = 0;
6555
+ } else {
6556
+ hypotheticalCostUsd = void 0;
6557
+ hypotheticalCostIncomplete = true;
6558
+ notes.add("missing_pricing");
6559
+ }
6560
+ } else {
6561
+ hypotheticalCostUsd = roundUsd(
6562
+ calculateEstimatedCostUsd(createSyntheticEvent(period), pricing)
6563
+ );
6564
+ }
6565
+ let savingsUsd;
6566
+ let savingsPct;
6567
+ let hasBaselineTokenMismatch = false;
6568
+ if (period.baselineCostIncomplete || period.baselineCostUsd === void 0) {
6569
+ notes.add("baseline_incomplete");
6570
+ } else if (zeroBillableTokens && period.baselineCostUsd > 0) {
6571
+ notes.add("baseline_tokens_missing");
6572
+ hasBaselineTokenMismatch = true;
6573
+ } else if (hypotheticalCostUsd !== void 0) {
6574
+ savingsUsd = roundUsd(period.baselineCostUsd - hypotheticalCostUsd);
6575
+ savingsPct = period.baselineCostUsd === 0 ? void 0 : savingsUsd / period.baselineCostUsd;
6576
+ }
6577
+ return {
6578
+ candidateRow: {
6579
+ rowType: "candidate",
6580
+ periodKey: period.periodKey,
6581
+ provider,
6582
+ inputTokens: period.inputTokens,
6583
+ outputTokens: period.outputTokens,
6584
+ reasoningTokens: period.reasoningTokens,
6585
+ cacheReadTokens: period.cacheReadTokens,
6586
+ cacheWriteTokens: period.cacheWriteTokens,
6587
+ totalTokens: period.totalTokens,
6588
+ candidateModel,
6589
+ candidateResolvedModel,
6590
+ hypotheticalCostUsd,
6591
+ hypotheticalCostIncomplete,
6592
+ savingsUsd,
6593
+ savingsPct,
6594
+ notes: withNotes(notes)
6595
+ },
6596
+ missingPricing: notes.has("missing_pricing"),
6597
+ hasBaselineTokenMismatch
6598
+ };
6599
+ }
6600
+ function compareCandidateRank(left, right) {
6601
+ if (left.hypotheticalCostUsd === void 0 && right.hypotheticalCostUsd !== void 0) {
6602
+ return 1;
6603
+ }
6604
+ if (left.hypotheticalCostUsd !== void 0 && right.hypotheticalCostUsd === void 0) {
6605
+ return -1;
6606
+ }
6607
+ if (left.hypotheticalCostUsd !== void 0 && right.hypotheticalCostUsd !== void 0) {
6608
+ if (left.hypotheticalCostUsd !== right.hypotheticalCostUsd) {
6609
+ return left.hypotheticalCostUsd - right.hypotheticalCostUsd;
6610
+ }
6611
+ }
6612
+ return compareByCodePoint(left.candidateModel, right.candidateModel);
6613
+ }
6614
+ function resolveBaselinePeriods(usageRows) {
6615
+ const periodRows = /* @__PURE__ */ new Map();
6616
+ let grandTotalRow;
6617
+ for (const row of usageRows) {
6618
+ if (row.rowType === "grand_total") {
6619
+ grandTotalRow = row;
6620
+ continue;
6621
+ }
6622
+ if (row.rowType === "period_combined") {
6623
+ periodRows.set(row.periodKey, row);
6624
+ continue;
6625
+ }
6626
+ if (!periodRows.has(row.periodKey)) {
6627
+ periodRows.set(row.periodKey, row);
6628
+ }
6629
+ }
6630
+ const sortedPeriodKeys = [...periodRows.keys()].sort(compareByCodePoint);
6631
+ const periods = sortedPeriodKeys.map((periodKey) => {
6632
+ const row = periodRows.get(periodKey);
6633
+ if (!row) {
6634
+ throw new Error(`Missing baseline row for period ${periodKey}`);
6635
+ }
6636
+ return {
6637
+ periodKey,
6638
+ inputTokens: row.inputTokens,
6639
+ outputTokens: row.outputTokens,
6640
+ reasoningTokens: row.reasoningTokens,
6641
+ cacheReadTokens: row.cacheReadTokens,
6642
+ cacheWriteTokens: row.cacheWriteTokens,
6643
+ totalTokens: row.totalTokens,
6644
+ baselineCostUsd: row.costUsd,
6645
+ baselineCostIncomplete: row.costIncomplete === true
6646
+ };
6647
+ });
6648
+ const allRow = grandTotalRow;
6649
+ if (allRow) {
6650
+ periods.push({
6651
+ periodKey: "ALL",
6652
+ inputTokens: allRow.inputTokens,
6653
+ outputTokens: allRow.outputTokens,
6654
+ reasoningTokens: allRow.reasoningTokens,
6655
+ cacheReadTokens: allRow.cacheReadTokens,
6656
+ cacheWriteTokens: allRow.cacheWriteTokens,
6657
+ totalTokens: allRow.totalTokens,
6658
+ baselineCostUsd: allRow.costUsd,
6659
+ baselineCostIncomplete: allRow.costIncomplete === true
6660
+ });
6661
+ } else {
6662
+ periods.push({
6663
+ periodKey: "ALL",
6664
+ inputTokens: 0,
6665
+ outputTokens: 0,
6666
+ reasoningTokens: 0,
6667
+ cacheReadTokens: 0,
6668
+ cacheWriteTokens: 0,
6669
+ totalTokens: 0,
6670
+ baselineCostUsd: 0,
6671
+ baselineCostIncomplete: false
6672
+ });
6673
+ }
6674
+ return periods;
6675
+ }
6676
+ function buildWarning(periodKeys) {
6677
+ if (periodKeys.length === 0) {
6678
+ return void 0;
6679
+ }
6680
+ const sortedPeriodKeys = [...new Set(periodKeys)].sort(compareByCodePoint);
6681
+ return `Baseline cost exists for zero-token periods (${sortedPeriodKeys.join(", ")}); savings were omitted.`;
6682
+ }
6683
+ function buildCounterfactualRows(input) {
6684
+ const baselinePeriods = resolveBaselinePeriods(input.usageRows);
6685
+ const allPeriod = baselinePeriods.find((period) => period.periodKey === "ALL");
6686
+ if (!allPeriod) {
6687
+ throw new Error("Missing ALL baseline totals");
6688
+ }
6689
+ const allPeriodEvaluations = input.candidateModels.map(
6690
+ (candidateModel) => evaluateCandidateForPeriod(allPeriod, input.provider, candidateModel, input.pricingSource)
6691
+ );
6692
+ const rankedCandidates = allPeriodEvaluations.map(({ candidateRow }) => ({
6693
+ candidateModel: candidateRow.candidateModel,
6694
+ hypotheticalCostUsd: candidateRow.hypotheticalCostUsd
6695
+ })).sort(compareCandidateRank).map((candidate) => candidate.candidateModel);
6696
+ const selectedCandidates = input.top === void 0 ? rankedCandidates : rankedCandidates.slice(0, input.top);
6697
+ const allEvaluationByCandidate = new Map(
6698
+ allPeriodEvaluations.map((evaluation) => [evaluation.candidateRow.candidateModel, evaluation])
6699
+ );
6700
+ const candidatesWithMissingPricing = input.candidateModels.filter(
6701
+ (candidateModel) => allEvaluationByCandidate.get(candidateModel)?.missingPricing === true
6702
+ ).sort(compareByCodePoint);
6703
+ const warningPeriods = [];
6704
+ const rows = [];
6705
+ for (const period of baselinePeriods) {
6706
+ rows.push(toBaselineRow(period, input.provider));
6707
+ for (const candidateModel of selectedCandidates) {
6708
+ const resolvedEvaluation = period.periodKey === "ALL" ? allEvaluationByCandidate.get(candidateModel) : evaluateCandidateForPeriod(period, input.provider, candidateModel, input.pricingSource);
6709
+ if (!resolvedEvaluation) {
6710
+ continue;
6711
+ }
6712
+ if (resolvedEvaluation.hasBaselineTokenMismatch) {
6713
+ warningPeriods.push(period.periodKey);
6714
+ }
6715
+ rows.push(resolvedEvaluation.candidateRow);
6716
+ }
6717
+ }
6718
+ return {
6719
+ rows,
6720
+ candidatesWithMissingPricing,
6721
+ baselineCostIncomplete: allPeriod.baselineCostIncomplete,
6722
+ warning: buildWarning(warningPeriods)
6723
+ };
6724
+ }
6725
+
6726
+ // src/cli/build-optimize-data.ts
6727
+ function resolveOptimizeProvider(providers, providerFilter) {
6728
+ const distinctProviders = [...providers].sort(compareByCodePoint);
6729
+ const normalizedProviderFilter = normalizeProviderFilter(providerFilter);
6730
+ if (distinctProviders.length > 1) {
6731
+ if (normalizedProviderFilter) {
6732
+ const matchingProviders = distinctProviders.filter(
6733
+ (provider) => provider.includes(normalizedProviderFilter)
6734
+ );
6735
+ if (matchingProviders.includes(normalizedProviderFilter)) {
6736
+ return normalizedProviderFilter;
6737
+ }
6738
+ if (matchingProviders.length === 1) {
6739
+ return matchingProviders[0];
6740
+ }
6741
+ if (matchingProviders.length === 0) {
6742
+ throw new Error(
6743
+ `Optimize --provider "${normalizedProviderFilter}" matched no providers. Available providers: ${distinctProviders.join(", ")}.`
6744
+ );
6745
+ }
6746
+ if (matchingProviders.length > 1) {
6747
+ throw new Error(
6748
+ `Optimize matched multiple providers for --provider "${normalizedProviderFilter}": ${matchingProviders.join(", ")}. Supply a more specific --provider value.`
6749
+ );
6750
+ }
6751
+ }
6752
+ throw new Error(
6753
+ `Optimize requires a single provider; found providers: ${distinctProviders.join(", ")}. Narrow with --provider.`
6754
+ );
6755
+ }
6756
+ if (distinctProviders.length === 1) {
6757
+ return distinctProviders[0];
6758
+ }
6759
+ return normalizedProviderFilter ?? "unknown";
6760
+ }
6761
+ async function buildOptimizeData(granularity, options, deps = {}) {
6762
+ const candidateModels = normalizeCandidateModels(options.candidateModel);
6763
+ const top = parseTopOption(options.top);
6764
+ const dataset = await buildUsageEventDataset(options, deps);
6765
+ const detectedProviders = new Set(
6766
+ dataset.filteredEvents.map((event) => normalizeProviderFilter(event.provider)).filter((provider2) => provider2 !== void 0)
6767
+ );
6768
+ const provider = resolveOptimizeProvider(
6769
+ detectedProviders,
6770
+ dataset.normalizedInputs.providerFilter
6771
+ );
6772
+ const { pricedEvents, pricingOrigin, pricingWarning, pricingSource } = await applyPricingToUsageEventDataset(dataset, deps, "force");
6773
+ const usageRows = aggregateUsage(pricedEvents, {
6774
+ granularity,
6775
+ timezone: dataset.normalizedInputs.timezone,
6776
+ sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
6777
+ });
6778
+ const counterfactual = buildCounterfactualRows({
6779
+ usageRows,
6780
+ provider,
6781
+ candidateModels,
6782
+ pricingSource,
6783
+ top
6784
+ });
6785
+ const usageDiagnostics = buildUsageDiagnostics({
6786
+ adaptersToParse: dataset.adaptersToParse,
6787
+ successfulParseResults: dataset.successfulParseResults,
6788
+ sourceFailures: dataset.sourceFailures,
6789
+ pricingOrigin,
6790
+ pricingWarning,
6791
+ activeEnvOverrides: dataset.readEnvVarOverrides(),
6792
+ timezone: dataset.normalizedInputs.timezone
6793
+ });
6794
+ return {
6795
+ rows: counterfactual.rows,
6796
+ diagnostics: {
6797
+ usage: usageDiagnostics,
6798
+ provider,
6799
+ baselineCostIncomplete: counterfactual.baselineCostIncomplete,
6800
+ candidatesWithMissingPricing: counterfactual.candidatesWithMissingPricing,
6801
+ warning: counterfactual.warning
6802
+ }
6803
+ };
6804
+ }
6805
+
6806
+ // src/cli/run-optimize-report.ts
6807
+ function validateOutputFormatOptions2(options) {
6808
+ if (options.markdown && options.json) {
6809
+ throw new Error("Choose either --markdown or --json, not both");
6810
+ }
6811
+ }
6812
+ function resolveReportFormat2(options) {
6813
+ if (options.json) {
6814
+ return "json";
6815
+ }
6816
+ if (options.markdown) {
6817
+ return "markdown";
6818
+ }
6819
+ return "terminal";
6820
+ }
6821
+ async function prepareOptimizeReport(granularity, options) {
6822
+ validateOutputFormatOptions2(options);
6823
+ const optimizeData = await buildOptimizeData(granularity, options);
6824
+ const format = resolveReportFormat2(options);
6825
+ return {
6826
+ format,
6827
+ diagnostics: optimizeData.diagnostics,
6828
+ candidateCount: optimizeData.rows.filter(
6829
+ (row) => row.rowType === "candidate" && row.periodKey === "ALL"
6830
+ ).length,
6831
+ output: renderOptimizeReport(optimizeData, format, {
6832
+ granularity
6833
+ })
6834
+ };
6835
+ }
6836
+ async function runOptimizeReport(granularity, options) {
6837
+ const preparedReport = await prepareOptimizeReport(granularity, options);
6838
+ emitDiagnostics(preparedReport.diagnostics.usage, logger);
6839
+ emitEnvVarOverrides(preparedReport.diagnostics.usage.activeEnvOverrides, logger);
6840
+ logger.info(
6841
+ `Optimize provider scope: ${preparedReport.diagnostics.provider}; candidate(s): ${preparedReport.candidateCount}`
6842
+ );
6843
+ if (preparedReport.diagnostics.candidatesWithMissingPricing.length > 0) {
6844
+ logger.warn(
6845
+ `Missing pricing for candidate model(s): ${preparedReport.diagnostics.candidatesWithMissingPricing.join(", ")}`
6846
+ );
6847
+ }
6848
+ if (preparedReport.diagnostics.warning) {
6849
+ logger.warn(preparedReport.diagnostics.warning);
6850
+ }
6851
+ if (preparedReport.format === "terminal") {
6852
+ warnIfTerminalTableOverflows(preparedReport.output, (message) => {
6853
+ logger.warn(message);
6854
+ });
6855
+ }
6856
+ console.log(preparedReport.output);
6857
+ }
6858
+
6859
+ // src/render/markdown-table.ts
6860
+ import { markdownTable as markdownTable3 } from "markdown-table";
6861
+ var alignment = ["l", "l", "l", "r", "r", "r", "r", "r", "r", "r"];
6862
+ function toMarkdownSafeCell3(value) {
6863
+ return value.replace(/\r?\n/gu, "<br>");
6864
+ }
5943
6865
  function renderMarkdownTable(rows, options = {}) {
5944
6866
  const tableLayout = options.tableLayout ?? "compact";
5945
6867
  const bodyRows = toUsageTableCells(rows, { layout: tableLayout }).map(
5946
- (row) => row.map((cell) => toMarkdownSafeCell2(cell))
6868
+ (row) => row.map((cell) => toMarkdownSafeCell3(cell))
5947
6869
  );
5948
6870
  const tableRows = [Array.from(usageTableHeaders), ...bodyRows];
5949
- return markdownTable2(tableRows, {
6871
+ return markdownTable3(tableRows, {
5950
6872
  align: alignment
5951
6873
  });
5952
6874
  }
5953
6875
 
5954
6876
  // src/render/render-usage-report.ts
5955
- function getReportTitle2(granularity) {
6877
+ function getReportTitle3(granularity) {
5956
6878
  switch (granularity) {
5957
6879
  case "daily":
5958
6880
  return "Daily Token Usage Report";
@@ -5968,7 +6890,7 @@ function renderTerminalUsageReport(usageData, options) {
5968
6890
  const tableLayout = options.tableLayout ?? "compact";
5969
6891
  outputLines.push(
5970
6892
  renderReportHeader({
5971
- title: getReportTitle2(options.granularity),
6893
+ title: getReportTitle3(options.granularity),
5972
6894
  useColor
5973
6895
  })
5974
6896
  );
@@ -5989,12 +6911,12 @@ function renderUsageReport(usageData, format, options) {
5989
6911
  }
5990
6912
 
5991
6913
  // src/cli/run-usage-report.ts
5992
- function validateOutputFormatOptions2(options) {
6914
+ function validateOutputFormatOptions3(options) {
5993
6915
  if (options.markdown && options.json) {
5994
6916
  throw new Error("Choose either --markdown or --json, not both");
5995
6917
  }
5996
6918
  }
5997
- function resolveReportFormat2(options) {
6919
+ function resolveReportFormat3(options) {
5998
6920
  if (options.json) {
5999
6921
  return "json";
6000
6922
  }
@@ -6007,9 +6929,9 @@ function resolveTableLayout(options) {
6007
6929
  return options.perModelColumns ? "per_model_columns" : "compact";
6008
6930
  }
6009
6931
  async function prepareUsageReport(granularity, options) {
6010
- validateOutputFormatOptions2(options);
6932
+ validateOutputFormatOptions3(options);
6011
6933
  const usageData = await buildUsageData(granularity, options);
6012
- const format = resolveReportFormat2(options);
6934
+ const format = resolveReportFormat3(options);
6013
6935
  return {
6014
6936
  format,
6015
6937
  diagnostics: usageData.diagnostics,
@@ -6057,7 +6979,10 @@ function addSharedOptions(command, options = {}) {
6057
6979
  `Filter by source id (repeatable or comma-separated, supported sources ${supportedSourcesSummary})`,
6058
6980
  collectRepeatedOption,
6059
6981
  []
6060
- ).option("--since <YYYY-MM-DD>", "Inclusive start date filter").option("--until <YYYY-MM-DD>", "Inclusive end date filter").option("--timezone <iana>", "Timezone for bucketing", defaultTimezone).option("--provider <name>", "Provider filter (substring match, optional)").option(
6982
+ ).option("--since <YYYY-MM-DD>", "Inclusive start date filter").option("--until <YYYY-MM-DD>", "Inclusive end date filter").option("--timezone <iana>", "Timezone for bucketing", defaultTimezone).option(
6983
+ "--provider <name>",
6984
+ "Billing-provider filter (substring match, optional; e.g. openai, anthropic, google)"
6985
+ ).option(
6061
6986
  "--model <name>",
6062
6987
  "Filter by model (repeatable/comma-separated; exact when exact match exists after source/provider/date filters, otherwise substring)",
6063
6988
  collectRepeatedOption,
@@ -6105,6 +7030,18 @@ function createEfficiencyCommand() {
6105
7030
  });
6106
7031
  return command;
6107
7032
  }
7033
+ function createOptimizeCommand() {
7034
+ const command = new Command("optimize");
7035
+ addSharedOptions(command, { includePerModelColumns: false }).argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option(
7036
+ "--candidate-model <name>",
7037
+ "Candidate model for counterfactual pricing (repeatable or comma-separated)",
7038
+ collectRepeatedOption,
7039
+ []
7040
+ ).option("--top <n>", "Show only the top N cheapest candidates (positive integer)").description("Show counterfactual pricing report for candidate model(s)").action(async (granularity, options) => {
7041
+ await runOptimizeReport(granularity, options);
7042
+ });
7043
+ return command;
7044
+ }
6108
7045
  function rootDescription() {
6109
7046
  const supportedSourceIds = getSupportedSourceIds();
6110
7047
  const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
@@ -6124,12 +7061,13 @@ function rootDescription() {
6124
7061
  " $ llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions",
6125
7062
  " $ llm-usage daily --pi-dir /tmp/pi-sessions --gemini-dir /tmp/.gemini --droid-dir /tmp/droid-sessions",
6126
7063
  " $ llm-usage efficiency weekly --repo-dir /path/to/repo --json",
7064
+ " $ llm-usage optimize monthly --provider openai --candidate-model gpt-4.1 --candidate-model gpt-5-codex --json",
6127
7065
  " $ npx --yes llm-usage-metrics@latest daily"
6128
7066
  ].join("\n");
6129
7067
  }
6130
7068
  function createCli(options = {}) {
6131
7069
  const program = new Command();
6132
- program.name("llm-usage").description(rootDescription()).version(options.version ?? "0.0.0").showHelpAfterError().addCommand(createCommand("daily")).addCommand(createCommand("weekly")).addCommand(createCommand("monthly")).addCommand(createEfficiencyCommand());
7070
+ program.name("llm-usage").description(rootDescription()).version(options.version ?? "0.0.0").showHelpAfterError().addCommand(createCommand("daily")).addCommand(createCommand("weekly")).addCommand(createCommand("monthly")).addCommand(createEfficiencyCommand()).addCommand(createOptimizeCommand());
6133
7071
  return program;
6134
7072
  }
6135
7073