llm-usage-metrics 0.3.6 → 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/README.md +17 -1
- package/dist/index.js +870 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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([
|
|
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:
|
|
778
|
+
provider: normalizeOptionalProvider(input.provider),
|
|
739
779
|
model: normalizeOptionalModel(input.model),
|
|
740
780
|
inputTokens,
|
|
741
781
|
outputTokens,
|
|
@@ -3264,6 +3304,42 @@ async function attributeUsageEventsToRepo(events, repoDir, resolveRepoRoot3 = re
|
|
|
3264
3304
|
};
|
|
3265
3305
|
}
|
|
3266
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
|
+
|
|
3267
3343
|
// src/config/env-var-display.ts
|
|
3268
3344
|
var ENV_VARS_TO_DISPLAY = [
|
|
3269
3345
|
{ name: "LLM_USAGE_SKIP_UPDATE_CHECK", description: "skip startup update check" },
|
|
@@ -3304,42 +3380,6 @@ function formatEnvVarOverrides(overrides) {
|
|
|
3304
3380
|
return lines;
|
|
3305
3381
|
}
|
|
3306
3382
|
|
|
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
|
-
|
|
3343
3383
|
// src/cli/build-usage-data-inputs.ts
|
|
3344
3384
|
function validateDateInput(value, flagName) {
|
|
3345
3385
|
if (!/^\d{4}-\d{2}-\d{2}$/u.test(value)) {
|
|
@@ -3359,11 +3399,7 @@ function validateTimezone(timezone) {
|
|
|
3359
3399
|
}
|
|
3360
3400
|
}
|
|
3361
3401
|
function normalizeProviderFilter(provider) {
|
|
3362
|
-
|
|
3363
|
-
return void 0;
|
|
3364
|
-
}
|
|
3365
|
-
const normalized = provider.trim().toLowerCase();
|
|
3366
|
-
return normalized || void 0;
|
|
3402
|
+
return normalizeProviderToBillingEntity(provider);
|
|
3367
3403
|
}
|
|
3368
3404
|
function normalizeSourceFilter(source) {
|
|
3369
3405
|
if (!source || Array.isArray(source) && source.length === 0) {
|
|
@@ -3540,7 +3576,7 @@ function normalizeSkippedRowReasons(value) {
|
|
|
3540
3576
|
// src/cli/parse-file-cache.ts
|
|
3541
3577
|
import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
3542
3578
|
import path11 from "path";
|
|
3543
|
-
var PARSE_FILE_CACHE_VERSION =
|
|
3579
|
+
var PARSE_FILE_CACHE_VERSION = 3;
|
|
3544
3580
|
var CACHE_KEY_SEPARATOR = "\0";
|
|
3545
3581
|
function createCacheKey(source, filePath) {
|
|
3546
3582
|
return `${source}${CACHE_KEY_SEPARATOR}${filePath}`;
|
|
@@ -3599,7 +3635,9 @@ function normalizeCachedUsageEvent(value) {
|
|
|
3599
3635
|
if (inputTokens === void 0 || outputTokens === void 0 || reasoningTokens === void 0 || cacheReadTokens === void 0 || cacheWriteTokens === void 0 || totalTokens === void 0) {
|
|
3600
3636
|
return void 0;
|
|
3601
3637
|
}
|
|
3602
|
-
const provider =
|
|
3638
|
+
const provider = normalizeProviderToBillingEntity(
|
|
3639
|
+
typeof record.provider === "string" ? record.provider : void 0
|
|
3640
|
+
);
|
|
3603
3641
|
const model = typeof record.model === "string" ? record.model.trim().toLowerCase() : "";
|
|
3604
3642
|
const costUsd = toNonNegativeNumber2(record.costUsd);
|
|
3605
3643
|
if (costMode === "explicit" && costUsd === void 0) {
|
|
@@ -3610,7 +3648,7 @@ function normalizeCachedUsageEvent(value) {
|
|
|
3610
3648
|
sessionId,
|
|
3611
3649
|
timestamp,
|
|
3612
3650
|
repoRoot: repoRoot || void 0,
|
|
3613
|
-
provider
|
|
3651
|
+
provider,
|
|
3614
3652
|
model: model || void 0,
|
|
3615
3653
|
inputTokens,
|
|
3616
3654
|
outputTokens,
|
|
@@ -4745,9 +4783,20 @@ function shouldLoadPricingSource(events) {
|
|
|
4745
4783
|
}
|
|
4746
4784
|
return events.some((event) => eventNeedsPricingLookup(event));
|
|
4747
4785
|
}
|
|
4748
|
-
|
|
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") {
|
|
4749
4798
|
let pricingOrigin = "none";
|
|
4750
|
-
if (!
|
|
4799
|
+
if (!shouldLoadPricingSourceForMode(events, pricingLoadMode)) {
|
|
4751
4800
|
return {
|
|
4752
4801
|
pricedEvents: events,
|
|
4753
4802
|
pricingOrigin
|
|
@@ -4771,11 +4820,12 @@ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = g
|
|
|
4771
4820
|
pricingOrigin = pricingResult.origin;
|
|
4772
4821
|
return {
|
|
4773
4822
|
pricedEvents: applyPricingToEvents(events, pricingResult.source),
|
|
4774
|
-
pricingOrigin
|
|
4823
|
+
pricingOrigin,
|
|
4824
|
+
pricingSource: pricingResult.source
|
|
4775
4825
|
};
|
|
4776
4826
|
}
|
|
4777
4827
|
|
|
4778
|
-
// src/cli/build-usage-
|
|
4828
|
+
// src/cli/build-usage-event-dataset.ts
|
|
4779
4829
|
function withNormalizedPricingUrl(options, normalizedPricingUrl) {
|
|
4780
4830
|
if (options.pricingUrl === normalizedPricingUrl) {
|
|
4781
4831
|
return options;
|
|
@@ -4785,13 +4835,11 @@ function withNormalizedPricingUrl(options, normalizedPricingUrl) {
|
|
|
4785
4835
|
pricingUrl: normalizedPricingUrl
|
|
4786
4836
|
};
|
|
4787
4837
|
}
|
|
4788
|
-
async function
|
|
4838
|
+
async function buildUsageEventDataset(options, deps = {}) {
|
|
4789
4839
|
const normalizedInputs = normalizeBuildUsageInputs(options);
|
|
4790
4840
|
const readParsingRuntimeConfig = deps.getParsingRuntimeConfig ?? getParsingRuntimeConfig;
|
|
4791
4841
|
const readPricingRuntimeConfig = deps.getPricingFetcherRuntimeConfig ?? getPricingFetcherRuntimeConfig;
|
|
4792
4842
|
const makeAdapters = deps.createAdapters ?? createDefaultAdapters;
|
|
4793
|
-
const loadPricingSource = deps.resolvePricingSource ?? resolvePricingSource;
|
|
4794
|
-
const readEnvVarOverrides = deps.getActiveEnvVarOverrides ?? getActiveEnvVarOverrides;
|
|
4795
4843
|
const parsingRuntimeConfig = readParsingRuntimeConfig();
|
|
4796
4844
|
const pricingRuntimeConfig = readPricingRuntimeConfig();
|
|
4797
4845
|
const adapters = makeAdapters(options);
|
|
@@ -4816,26 +4864,53 @@ async function buildUsageData(granularity, options, deps = {}) {
|
|
|
4816
4864
|
providerFilter: normalizedInputs.providerFilter,
|
|
4817
4865
|
modelFilter: normalizedInputs.modelFilter
|
|
4818
4866
|
});
|
|
4819
|
-
|
|
4820
|
-
|
|
4867
|
+
return {
|
|
4868
|
+
options,
|
|
4869
|
+
normalizedInputs,
|
|
4870
|
+
adaptersToParse,
|
|
4871
|
+
successfulParseResults,
|
|
4872
|
+
sourceFailures,
|
|
4821
4873
|
filteredEvents,
|
|
4822
|
-
pricingOptions,
|
|
4823
4874
|
pricingRuntimeConfig,
|
|
4824
|
-
|
|
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"
|
|
4825
4900
|
);
|
|
4826
4901
|
const rows = aggregateUsage(pricedEvents, {
|
|
4827
4902
|
granularity,
|
|
4828
|
-
timezone: normalizedInputs.timezone,
|
|
4829
|
-
sourceOrder: adaptersToParse.map((adapter) => adapter.id)
|
|
4903
|
+
timezone: dataset.normalizedInputs.timezone,
|
|
4904
|
+
sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
|
|
4830
4905
|
});
|
|
4831
4906
|
const diagnostics = buildUsageDiagnostics({
|
|
4832
|
-
adaptersToParse,
|
|
4833
|
-
successfulParseResults,
|
|
4834
|
-
sourceFailures,
|
|
4907
|
+
adaptersToParse: dataset.adaptersToParse,
|
|
4908
|
+
successfulParseResults: dataset.successfulParseResults,
|
|
4909
|
+
sourceFailures: dataset.sourceFailures,
|
|
4835
4910
|
pricingOrigin,
|
|
4836
4911
|
pricingWarning,
|
|
4837
|
-
activeEnvOverrides: readEnvVarOverrides(),
|
|
4838
|
-
timezone: normalizedInputs.timezone
|
|
4912
|
+
activeEnvOverrides: dataset.readEnvVarOverrides(),
|
|
4913
|
+
timezone: dataset.normalizedInputs.timezone
|
|
4839
4914
|
});
|
|
4840
4915
|
return assembleUsageDataResult(pricedEvents, rows, diagnostics);
|
|
4841
4916
|
}
|
|
@@ -5252,6 +5327,7 @@ function warnIfTerminalTableOverflows(reportOutput, warn, stdoutState = process.
|
|
|
5252
5327
|
|
|
5253
5328
|
// src/render/render-efficiency-report.ts
|
|
5254
5329
|
import { markdownTable } from "markdown-table";
|
|
5330
|
+
import pc5 from "picocolors";
|
|
5255
5331
|
|
|
5256
5332
|
// src/render/report-header.ts
|
|
5257
5333
|
import pc2 from "picocolors";
|
|
@@ -5397,8 +5473,8 @@ function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
|
|
|
5397
5473
|
var rowTypeStylePolicies = {
|
|
5398
5474
|
period_source: (cells, palette) => {
|
|
5399
5475
|
const styledCells = [...cells];
|
|
5400
|
-
const
|
|
5401
|
-
styledCells[
|
|
5476
|
+
const costColumnIndex2 = styledCells.length - 1;
|
|
5477
|
+
styledCells[costColumnIndex2] = styleCellLines(styledCells[costColumnIndex2], palette.yellow);
|
|
5402
5478
|
return styledCells;
|
|
5403
5479
|
},
|
|
5404
5480
|
period_combined: (cells, palette) => cells.map((cell, cellIndex) => {
|
|
@@ -5843,6 +5919,14 @@ function renderTerminalTable(rows, options = {}) {
|
|
|
5843
5919
|
// src/render/render-efficiency-report.ts
|
|
5844
5920
|
var periodColumnIndex = 0;
|
|
5845
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;
|
|
5846
5930
|
function getReportTitle(granularity) {
|
|
5847
5931
|
switch (granularity) {
|
|
5848
5932
|
case "daily":
|
|
@@ -5981,9 +6065,67 @@ function fitTableCellsToTerminal(headerCells, bodyRows) {
|
|
|
5981
6065
|
widths: constrainedWidths
|
|
5982
6066
|
};
|
|
5983
6067
|
}
|
|
5984
|
-
function
|
|
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) {
|
|
5985
6127
|
const headerCells = Array.from(efficiencyTableHeaders);
|
|
5986
|
-
const bodyRows = toEfficiencyTableCells(rows);
|
|
6128
|
+
const bodyRows = styleEfficiencyTerminalRows(rows, toEfficiencyTableCells(rows), options);
|
|
5987
6129
|
const tableSortRows = rows.map((row) => toTableSortRow(row));
|
|
5988
6130
|
const fittedCells = fitTableCellsToTerminal(headerCells, bodyRows);
|
|
5989
6131
|
return renderUnicodeTable({
|
|
@@ -6020,7 +6162,7 @@ function renderTerminalEfficiencyReport(efficiencyData, options) {
|
|
|
6020
6162
|
})
|
|
6021
6163
|
);
|
|
6022
6164
|
outputLines.push("");
|
|
6023
|
-
outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows));
|
|
6165
|
+
outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows, { useColor }));
|
|
6024
6166
|
return outputLines.join("\n");
|
|
6025
6167
|
}
|
|
6026
6168
|
function renderEfficiencyReport(efficiencyData, format, options) {
|
|
@@ -6083,25 +6225,656 @@ async function runEfficiencyReport(granularity, options) {
|
|
|
6083
6225
|
console.log(preparedReport.output);
|
|
6084
6226
|
}
|
|
6085
6227
|
|
|
6086
|
-
// src/render/
|
|
6228
|
+
// src/render/render-optimize-report.ts
|
|
6087
6229
|
import { markdownTable as markdownTable2 } from "markdown-table";
|
|
6088
|
-
|
|
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
|
+
}
|
|
6089
6389
|
function toMarkdownSafeCell2(value) {
|
|
6090
6390
|
return value.replace(/\r?\n/gu, "<br>");
|
|
6091
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
|
+
}
|
|
6092
6865
|
function renderMarkdownTable(rows, options = {}) {
|
|
6093
6866
|
const tableLayout = options.tableLayout ?? "compact";
|
|
6094
6867
|
const bodyRows = toUsageTableCells(rows, { layout: tableLayout }).map(
|
|
6095
|
-
(row) => row.map((cell) =>
|
|
6868
|
+
(row) => row.map((cell) => toMarkdownSafeCell3(cell))
|
|
6096
6869
|
);
|
|
6097
6870
|
const tableRows = [Array.from(usageTableHeaders), ...bodyRows];
|
|
6098
|
-
return
|
|
6871
|
+
return markdownTable3(tableRows, {
|
|
6099
6872
|
align: alignment
|
|
6100
6873
|
});
|
|
6101
6874
|
}
|
|
6102
6875
|
|
|
6103
6876
|
// src/render/render-usage-report.ts
|
|
6104
|
-
function
|
|
6877
|
+
function getReportTitle3(granularity) {
|
|
6105
6878
|
switch (granularity) {
|
|
6106
6879
|
case "daily":
|
|
6107
6880
|
return "Daily Token Usage Report";
|
|
@@ -6117,7 +6890,7 @@ function renderTerminalUsageReport(usageData, options) {
|
|
|
6117
6890
|
const tableLayout = options.tableLayout ?? "compact";
|
|
6118
6891
|
outputLines.push(
|
|
6119
6892
|
renderReportHeader({
|
|
6120
|
-
title:
|
|
6893
|
+
title: getReportTitle3(options.granularity),
|
|
6121
6894
|
useColor
|
|
6122
6895
|
})
|
|
6123
6896
|
);
|
|
@@ -6138,12 +6911,12 @@ function renderUsageReport(usageData, format, options) {
|
|
|
6138
6911
|
}
|
|
6139
6912
|
|
|
6140
6913
|
// src/cli/run-usage-report.ts
|
|
6141
|
-
function
|
|
6914
|
+
function validateOutputFormatOptions3(options) {
|
|
6142
6915
|
if (options.markdown && options.json) {
|
|
6143
6916
|
throw new Error("Choose either --markdown or --json, not both");
|
|
6144
6917
|
}
|
|
6145
6918
|
}
|
|
6146
|
-
function
|
|
6919
|
+
function resolveReportFormat3(options) {
|
|
6147
6920
|
if (options.json) {
|
|
6148
6921
|
return "json";
|
|
6149
6922
|
}
|
|
@@ -6156,9 +6929,9 @@ function resolveTableLayout(options) {
|
|
|
6156
6929
|
return options.perModelColumns ? "per_model_columns" : "compact";
|
|
6157
6930
|
}
|
|
6158
6931
|
async function prepareUsageReport(granularity, options) {
|
|
6159
|
-
|
|
6932
|
+
validateOutputFormatOptions3(options);
|
|
6160
6933
|
const usageData = await buildUsageData(granularity, options);
|
|
6161
|
-
const format =
|
|
6934
|
+
const format = resolveReportFormat3(options);
|
|
6162
6935
|
return {
|
|
6163
6936
|
format,
|
|
6164
6937
|
diagnostics: usageData.diagnostics,
|
|
@@ -6206,7 +6979,10 @@ function addSharedOptions(command, options = {}) {
|
|
|
6206
6979
|
`Filter by source id (repeatable or comma-separated, supported sources ${supportedSourcesSummary})`,
|
|
6207
6980
|
collectRepeatedOption,
|
|
6208
6981
|
[]
|
|
6209
|
-
).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(
|
|
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(
|
|
6210
6986
|
"--model <name>",
|
|
6211
6987
|
"Filter by model (repeatable/comma-separated; exact when exact match exists after source/provider/date filters, otherwise substring)",
|
|
6212
6988
|
collectRepeatedOption,
|
|
@@ -6254,6 +7030,18 @@ function createEfficiencyCommand() {
|
|
|
6254
7030
|
});
|
|
6255
7031
|
return command;
|
|
6256
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
|
+
}
|
|
6257
7045
|
function rootDescription() {
|
|
6258
7046
|
const supportedSourceIds = getSupportedSourceIds();
|
|
6259
7047
|
const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
|
|
@@ -6273,12 +7061,13 @@ function rootDescription() {
|
|
|
6273
7061
|
" $ llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions",
|
|
6274
7062
|
" $ llm-usage daily --pi-dir /tmp/pi-sessions --gemini-dir /tmp/.gemini --droid-dir /tmp/droid-sessions",
|
|
6275
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",
|
|
6276
7065
|
" $ npx --yes llm-usage-metrics@latest daily"
|
|
6277
7066
|
].join("\n");
|
|
6278
7067
|
}
|
|
6279
7068
|
function createCli(options = {}) {
|
|
6280
7069
|
const program = new Command();
|
|
6281
|
-
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());
|
|
6282
7071
|
return program;
|
|
6283
7072
|
}
|
|
6284
7073
|
|