llm-usage-metrics 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/index.js +987 -240
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -48,12 +48,16 @@ function normalizeSkippedRowReasons(value) {
48
48
  }
49
49
 
50
50
  // src/domain/normalization.ts
51
+ import { stripVTControlCharacters } from "util";
51
52
  var CONTROL_CHARACTER_CLASS = String.raw`[\u0000-\u001F\u007F-\u009F\u061C\u200E\u200F\u2028-\u202E\u2066-\u2069]`;
52
53
  var CONTROL_CHARACTERS_PATTERN = new RegExp(CONTROL_CHARACTER_CLASS, "gu");
53
54
  var CONTROL_CHARACTER_TEST_PATTERN = new RegExp(CONTROL_CHARACTER_CLASS, "u");
54
55
  function stripControlCharacters(value) {
55
56
  return value.replace(CONTROL_CHARACTERS_PATTERN, "");
56
57
  }
58
+ function toTerminalLine(value) {
59
+ return stripControlCharacters(stripVTControlCharacters(value).replaceAll(/\s+/gu, " ")).trim();
60
+ }
57
61
  function hasControlCharacters(value) {
58
62
  return CONTROL_CHARACTER_TEST_PATTERN.test(value);
59
63
  }
@@ -764,6 +768,9 @@ CREATE TEMP TABLE IF NOT EXISTS history_departed_files (
764
768
  file_path TEXT NOT NULL,
765
769
  PRIMARY KEY (source, file_path)
766
770
  );
771
+ CREATE TEMP TABLE IF NOT EXISTS history_departed_hashes (
772
+ content_hash TEXT PRIMARY KEY
773
+ );
767
774
  CREATE TEMP TABLE IF NOT EXISTS history_served_files (
768
775
  source TEXT NOT NULL,
769
776
  file_path TEXT NOT NULL,
@@ -773,6 +780,7 @@ CREATE TEMP TABLE IF NOT EXISTS history_served_files (
773
780
  DELETE FROM history_selected_sources;
774
781
  DELETE FROM history_discovered_files;
775
782
  DELETE FROM history_departed_files;
783
+ DELETE FROM history_departed_hashes;
776
784
  DELETE FROM history_served_files;
777
785
  `);
778
786
  }
@@ -849,6 +857,36 @@ function addHashCount(target, hash, count) {
849
857
  target.set(hash, (target.get(hash) ?? 0) + count);
850
858
  }
851
859
  function readLiveHashCounts(store) {
860
+ store.database.prepare(
861
+ [
862
+ "INSERT OR IGNORE INTO history_departed_hashes (content_hash)",
863
+ "SELECT events.content_hash",
864
+ "FROM history_departed_files AS departed",
865
+ "CROSS JOIN events",
866
+ "WHERE events.source = departed.source",
867
+ " AND events.file_path = departed.file_path",
868
+ " AND events.content_hash IS NOT NULL"
869
+ ].join("\n")
870
+ ).run();
871
+ store.database.prepare(
872
+ [
873
+ "INSERT OR IGNORE INTO history_departed_hashes (content_hash)",
874
+ "SELECT events.content_hash",
875
+ "FROM history_discovered_files AS discovered",
876
+ "CROSS JOIN events",
877
+ "WHERE events.source = discovered.source",
878
+ " AND events.file_path = discovered.file_path",
879
+ " AND discovered.counted = 1",
880
+ " AND events.content_hash IS NOT NULL",
881
+ " AND EXISTS (",
882
+ " SELECT 1 FROM history_departed_files AS departed",
883
+ " CROSS JOIN events AS departed_events",
884
+ " WHERE departed_events.source = departed.source",
885
+ " AND departed_events.file_path = departed.file_path",
886
+ " AND departed_events.content_hash IS NULL",
887
+ " )"
888
+ ].join("\n")
889
+ ).run();
852
890
  const rows = store.database.prepare(
853
891
  [
854
892
  "SELECT content_hash,",
@@ -857,15 +895,16 @@ function readLiveHashCounts(store) {
857
895
  "FROM (",
858
896
  " SELECT events.content_hash AS content_hash,",
859
897
  " selected.repeats_events AS repeats_events, COUNT(*) AS file_count",
860
- " FROM history_discovered_files AS discovered",
861
- " JOIN history_selected_sources AS selected",
862
- " ON discovered.source = selected.source",
898
+ " FROM history_departed_hashes AS departed",
863
899
  " CROSS JOIN events",
864
- " WHERE discovered.counted = 1",
865
- " AND events.source = discovered.source",
866
- " AND events.file_path = discovered.file_path",
867
- " AND events.content_hash IS NOT NULL",
868
- " GROUP BY discovered.source, discovered.file_path, events.content_hash",
900
+ " JOIN history_discovered_files AS discovered",
901
+ " ON discovered.source = events.source",
902
+ " AND discovered.file_path = events.file_path",
903
+ " JOIN history_selected_sources AS selected",
904
+ " ON selected.source = events.source",
905
+ " WHERE events.content_hash = departed.content_hash",
906
+ " AND discovered.counted = 1",
907
+ " GROUP BY events.source, events.file_path, events.content_hash",
869
908
  ")",
870
909
  "GROUP BY content_hash"
871
910
  ].join("\n")
@@ -1056,10 +1095,10 @@ function classifyAgainstLiveFiles(store, input) {
1056
1095
  if (selectedSources.size === 0) {
1057
1096
  return { classifiedFiles: [], liveHashCounts: /* @__PURE__ */ new Map() };
1058
1097
  }
1059
- const liveHashCounts = readLiveHashCounts(store);
1060
- const servedHashCounts = new Map(liveHashCounts);
1061
1098
  const departedFiles = readDepartedFiles(store);
1062
1099
  const departedHashMultisets = readDepartedHashMultisets(store);
1100
+ const liveHashCounts = readLiveHashCounts(store);
1101
+ const servedHashCounts = new Map(liveHashCounts);
1063
1102
  const classifiedFiles = [];
1064
1103
  for (const departedFile of departedFiles) {
1065
1104
  const fileHashCounts = departedHashMultisets.get(
@@ -1147,8 +1186,22 @@ CREATE TABLE IF NOT EXISTS events (
1147
1186
  );
1148
1187
  CREATE INDEX IF NOT EXISTS events_file ON events(source, file_path, event_index);
1149
1188
  CREATE INDEX IF NOT EXISTS events_content_hash ON events(content_hash);
1189
+ ${createOverrideOnlyFilesSql()}
1150
1190
  `;
1151
1191
  }
1192
+ function createOverrideOnlyFilesSql() {
1193
+ return `
1194
+ CREATE TABLE IF NOT EXISTS override_only_files (
1195
+ source TEXT NOT NULL,
1196
+ file_path TEXT NOT NULL,
1197
+ PRIMARY KEY (source, file_path)
1198
+ );`;
1199
+ }
1200
+ function ensureOverrideOnlyFilesTable(database) {
1201
+ if (!listUserTableNames(database).includes("override_only_files")) {
1202
+ database.exec(createOverrideOnlyFilesSql());
1203
+ }
1204
+ }
1152
1205
  function listUserTableNames(database) {
1153
1206
  return database.prepare(
1154
1207
  [
@@ -1267,6 +1320,10 @@ function assertSupportedSchemaVersion(database) {
1267
1320
  }
1268
1321
  }
1269
1322
  function initializeSchema(database) {
1323
+ createOrMigrateSchema(database);
1324
+ ensureOverrideOnlyFilesTable(database);
1325
+ }
1326
+ function createOrMigrateSchema(database) {
1270
1327
  if (listUserTableNames(database).length === 0) {
1271
1328
  const createdSchema = runTransaction(database, () => {
1272
1329
  if (listUserTableNames(database).length > 0) {
@@ -1602,6 +1659,21 @@ function readMetaValues(database) {
1602
1659
  }
1603
1660
  return meta;
1604
1661
  }
1662
+ var WIDE_WINDOW_ROW_SHARE = 0.15;
1663
+ function isWideWindow(database, fromTimestamp, toTimestamp) {
1664
+ const rowCount = toNonNegativeInteger(
1665
+ database.prepare("SELECT count(*) AS count FROM events").get()?.count
1666
+ );
1667
+ const threshold = Math.ceil((rowCount ?? 0) * WIDE_WINDOW_ROW_SHARE);
1668
+ const row = database.prepare(
1669
+ [
1670
+ "SELECT count(*) AS count FROM (",
1671
+ " SELECT 1 FROM events WHERE timestamp >= ? AND timestamp < ? LIMIT ?",
1672
+ ")"
1673
+ ].join("\n")
1674
+ ).get(fromTimestamp, toTimestamp, threshold);
1675
+ return (toNonNegativeInteger(row?.count) ?? 0) >= threshold;
1676
+ }
1605
1677
  async function readEventStoreEvents(filePath, window = {}, loadSqliteModule = loadEventStoreSqliteModule) {
1606
1678
  const sqliteModule = await loadSqliteModule();
1607
1679
  if (!isEventStoreSqliteModule(sqliteModule)) {
@@ -1615,21 +1687,20 @@ async function readEventStoreEvents(filePath, window = {}, loadSqliteModule = lo
1615
1687
  assertSupportedSchemaVersion(database);
1616
1688
  const meta = readMetaValues(database);
1617
1689
  const cacheWrite1hColumn = meta.get("schemaVersion") === EVENT_STORE_SCHEMA_VERSION ? "cache_write_1h_tokens" : "0 AS cache_write_1h_tokens";
1690
+ const fromTimestamp = window.fromTimestamp ?? "";
1691
+ const toTimestamp = window.toTimestamp ?? "\uFFFF";
1618
1692
  const statement = database.prepare(
1619
1693
  [
1620
1694
  "SELECT source, session_id, timestamp, model, provider, repo_root,",
1621
1695
  " input_tokens, output_tokens, reasoning_tokens, cache_read_tokens,",
1622
1696
  ` cache_write_tokens, ${cacheWrite1hColumn}, total_tokens, cost_usd, cost_mode`,
1623
- "FROM events",
1697
+ isWideWindow(database, fromTimestamp, toTimestamp) ? "FROM events INDEXED BY events_file" : "FROM events",
1624
1698
  "WHERE timestamp >= ? AND timestamp < ?",
1625
1699
  "ORDER BY source, file_path, event_index"
1626
1700
  ].join("\n")
1627
1701
  );
1628
1702
  statement.setReturnArrays(true);
1629
- const rows = statement.all(
1630
- window.fromTimestamp ?? "",
1631
- window.toTimestamp ?? "\uFFFF"
1632
- );
1703
+ const rows = statement.all(fromTimestamp, toTimestamp);
1633
1704
  const events = [];
1634
1705
  for (const row of rows) {
1635
1706
  const event = normalizeStoredEventTuple(row);
@@ -1776,11 +1847,15 @@ function deleteStoredFiles(store, files) {
1776
1847
  const deleteFile = store.database.prepare(
1777
1848
  "DELETE FROM files WHERE source = ? AND file_path = ?"
1778
1849
  );
1850
+ const deleteOverrideOnlyFile = store.database.prepare(
1851
+ "DELETE FROM override_only_files WHERE source = ? AND file_path = ?"
1852
+ );
1779
1853
  for (const file of normalizedFiles) {
1780
1854
  result.deletedEventCount += countMatchingRows(store, "events", file.source, file.filePath);
1781
1855
  result.deletedFileCount += countMatchingRows(store, "files", file.source, file.filePath);
1782
1856
  deleteEvents.run(file.source, file.filePath);
1783
1857
  deleteFile.run(file.source, file.filePath);
1858
+ deleteOverrideOnlyFile.run(file.source, file.filePath);
1784
1859
  }
1785
1860
  });
1786
1861
  return result;
@@ -1799,7 +1874,8 @@ function replaceFilesEvents(store, inputs) {
1799
1874
  skippedRows: toNonNegativeInteger(input.skippedRows) ?? 0,
1800
1875
  skippedRowReasons: stringifySkippedRowReasons(input.skippedRowReasons),
1801
1876
  ingestedAt: Math.max(0, Math.trunc(input.now)),
1802
- events: input.events.map((event) => createUsageEvent(event))
1877
+ events: input.events.map((event) => createUsageEvent(event)),
1878
+ overrideOnly: input.overrideOnly === true
1803
1879
  }));
1804
1880
  const deleteFileEvents = store.statements.deleteFileEvents ??= store.database.prepare(
1805
1881
  "DELETE FROM events WHERE source = ? AND file_path = ?"
@@ -1826,10 +1902,19 @@ function replaceFilesEvents(store, inputs) {
1826
1902
  " ingested_at = excluded.ingested_at"
1827
1903
  ].join("\n")
1828
1904
  );
1905
+ const markOverrideOnlyFile = store.statements.markOverrideOnlyFile ??= store.database.prepare(
1906
+ [
1907
+ "INSERT OR IGNORE INTO override_only_files (source, file_path)",
1908
+ "SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM files WHERE source = ? AND file_path = ?)"
1909
+ ].join("\n")
1910
+ );
1829
1911
  runTransaction(store.database, () => {
1830
1912
  for (const write of writes) {
1831
1913
  const { source, filePath } = write;
1832
1914
  deleteFileEvents.run(source, filePath);
1915
+ if (write.overrideOnly) {
1916
+ markOverrideOnlyFile.run(source, filePath, source, filePath);
1917
+ }
1833
1918
  write.events.forEach((event, eventIndex) => {
1834
1919
  insertEvent.run(
1835
1920
  source,
@@ -1863,6 +1948,41 @@ function replaceFilesEvents(store, inputs) {
1863
1948
  }
1864
1949
  });
1865
1950
  }
1951
+ function readOverrideOnlyFiles(store) {
1952
+ const rows = store.database.prepare("SELECT source, file_path FROM override_only_files ORDER BY source, file_path").all();
1953
+ const files = [];
1954
+ for (const row of rows) {
1955
+ const source = toText(row.source);
1956
+ const filePath = toText(row.file_path);
1957
+ if (source && filePath) {
1958
+ files.push({ source, filePath });
1959
+ }
1960
+ }
1961
+ return files;
1962
+ }
1963
+ function claimOverrideOnlyFiles(store, files) {
1964
+ const overrideOnlyKeys = new Set(
1965
+ readOverrideOnlyFiles(store).map((file) => `${file.source}\0${file.filePath}`)
1966
+ );
1967
+ if (overrideOnlyKeys.size === 0) {
1968
+ return;
1969
+ }
1970
+ const claimedFiles = files.map((file) => ({
1971
+ source: normalizeStoreSource(file.source),
1972
+ filePath: normalizeStoreFilePath(file.filePath)
1973
+ })).filter((file) => overrideOnlyKeys.has(`${file.source}\0${file.filePath}`));
1974
+ if (claimedFiles.length === 0) {
1975
+ return;
1976
+ }
1977
+ runTransaction(store.database, () => {
1978
+ const deleteOverrideOnlyFile = store.database.prepare(
1979
+ "DELETE FROM override_only_files WHERE source = ? AND file_path = ?"
1980
+ );
1981
+ for (const file of claimedFiles) {
1982
+ deleteOverrideOnlyFile.run(file.source, file.filePath);
1983
+ }
1984
+ });
1985
+ }
1866
1986
  function closeEventStore(store) {
1867
1987
  store.database.close();
1868
1988
  }
@@ -3820,7 +3940,7 @@ function exitQuietlyOnBrokenPipe(stream, exit = () => process.exit()) {
3820
3940
  }
3821
3941
 
3822
3942
  // src/cli/create-cli.ts
3823
- import { Command as Command6 } from "commander";
3943
+ import { Command as Command6, Help } from "commander";
3824
3944
 
3825
3945
  // src/utils/suggest-closest.ts
3826
3946
  function editDistance(left, right4) {
@@ -3863,7 +3983,7 @@ function suggestClosest(input, candidates) {
3863
3983
  }
3864
3984
 
3865
3985
  // src/cli/report-definitions/report-definitions.ts
3866
- import { Command } from "commander";
3986
+ import { Argument, Command, Option as Option2 } from "commander";
3867
3987
 
3868
3988
  // src/utils/time-buckets.ts
3869
3989
  var DAY_MS3 = 24 * 60 * 60 * 1e3;
@@ -3888,7 +4008,7 @@ function getDateTimeFormatter(timezone) {
3888
4008
  dateTimeFormatterCache.set(timezone, formatter);
3889
4009
  return formatter;
3890
4010
  }
3891
- var MAX_MEMOIZED_TIMESTAMPS = 1e5;
4011
+ var MAX_MEMOIZED_TIMESTAMPS = 1e6;
3892
4012
  var localDatePartsByTimestamp = /* @__PURE__ */ new Map();
3893
4013
  function extractLocalDateParts(timestampIso, timezone) {
3894
4014
  let partsByTimestamp = localDatePartsByTimestamp.get(timezone);
@@ -3951,7 +4071,8 @@ function coversTimestamp(window, timestampMs) {
3951
4071
  function createLocalDateWindow(dayIndex, timestampMs, offsetMs, timezone, localDateTime) {
3952
4072
  const startMs = dayIndex * DAY_MS3 - offsetMs;
3953
4073
  const endMs = startMs + DAY_MS3;
3954
- const parts = toLocalDateParts(localDateTime);
4074
+ const localDate = toLocalDateParts(localDateTime);
4075
+ const parts = { ...localDate, dateKey: formatLocalDateParts(localDate) };
3955
4076
  if (hasStableOffsetWindow(startMs, endMs, offsetMs, timezone)) {
3956
4077
  return { startMs, endMs, parts };
3957
4078
  }
@@ -4056,7 +4177,7 @@ function getIsoWeekParts(localDate) {
4056
4177
  function getPeriodKey(timestampIso, granularity, timezone) {
4057
4178
  const localDate = extractLocalDateParts(timestampIso, timezone);
4058
4179
  if (granularity === "daily") {
4059
- return formatLocalDateParts(localDate);
4180
+ return localDate.dateKey;
4060
4181
  }
4061
4182
  if (granularity === "monthly") {
4062
4183
  return `${localDate.year}-${String(localDate.month).padStart(2, "0")}`;
@@ -4075,15 +4196,13 @@ function getIsoDayOfWeekFromDateKey(localDateKey) {
4075
4196
  return toIsoDayOfWeek(createUtcDate(parseLocalDateKey(localDateKey)));
4076
4197
  }
4077
4198
  function getCurrentLocalDateKey(timezone, now = /* @__PURE__ */ new Date()) {
4078
- return formatLocalDateParts(extractLocalDateParts(now.toISOString(), timezone));
4199
+ return extractLocalDateParts(now.toISOString(), timezone).dateKey;
4079
4200
  }
4080
4201
  function shiftLocalDateKey(localDateKey, days) {
4081
- return formatLocalDateParts(
4082
- extractLocalDateParts(
4083
- addDays(createUtcDate(parseLocalDateKey(localDateKey)), days).toISOString(),
4084
- "UTC"
4085
- )
4086
- );
4202
+ return extractLocalDateParts(
4203
+ addDays(createUtcDate(parseLocalDateKey(localDateKey)), days).toISOString(),
4204
+ "UTC"
4205
+ ).dateKey;
4087
4206
  }
4088
4207
  function getLocalDateKeyRange(from, to) {
4089
4208
  if (from > to) {
@@ -5765,7 +5884,10 @@ function comparePendingEvents(left, right4) {
5765
5884
  var ClaudeSourceAdapter = class {
5766
5885
  id = "claude";
5767
5886
  parserVersion = 4;
5768
- capabilities = { eventsPrecedeFileMtime: true };
5887
+ capabilities = {
5888
+ eventsPrecedeFileMtime: true,
5889
+ sessionsCopyEvents: true
5890
+ };
5769
5891
  rootDirs;
5770
5892
  requireDir;
5771
5893
  // Sibling forks share a parent; read its message keys once per parent version.
@@ -9487,7 +9609,7 @@ function createDefaultAdapters(options) {
9487
9609
  // src/cli/build-usage-data-inputs.ts
9488
9610
  function validateDateInput(value, flagName) {
9489
9611
  if (!/^\d{4}-\d{2}-\d{2}$/u.test(value)) {
9490
- throw new Error(`${flagName} must use format YYYY-MM-DD`);
9612
+ throw new Error(`${flagName} "${value}" is not a date; use YYYY-MM-DD`);
9491
9613
  }
9492
9614
  const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
9493
9615
  if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) {
@@ -9781,6 +9903,101 @@ function formatEnvVarOverrides(overrides) {
9781
9903
  return lines;
9782
9904
  }
9783
9905
 
9906
+ // src/render/format-model-list.ts
9907
+ function limitNamedModels(models, limit) {
9908
+ if (models.length <= limit) {
9909
+ return [...models];
9910
+ }
9911
+ return [...models.slice(0, limit), `+${models.length - limit} more`];
9912
+ }
9913
+
9914
+ // src/cli/count-copied-session-events-once.ts
9915
+ function getCopyIdentity(event) {
9916
+ return [
9917
+ event.timestamp,
9918
+ event.provider ?? "",
9919
+ event.model ?? "",
9920
+ event.inputTokens,
9921
+ event.outputTokens,
9922
+ event.reasoningTokens,
9923
+ event.cacheReadTokens,
9924
+ event.cacheWriteTokens,
9925
+ event.cacheWrite1hTokens,
9926
+ event.totalTokens
9927
+ ].join("\0");
9928
+ }
9929
+ function isCopyOf(left, right4) {
9930
+ return left.totalTokens === right4.totalTokens && left.timestamp === right4.timestamp && left.model === right4.model && left.provider === right4.provider && left.inputTokens === right4.inputTokens && left.outputTokens === right4.outputTokens && left.reasoningTokens === right4.reasoningTokens && left.cacheReadTokens === right4.cacheReadTokens && left.cacheWriteTokens === right4.cacheWriteTokens && left.cacheWrite1hTokens === right4.cacheWrite1hTokens;
9931
+ }
9932
+ function findEventsOtherSessionsHold(events) {
9933
+ const firstByTimestamp = /* @__PURE__ */ new Map();
9934
+ const sameTimestampGroups = /* @__PURE__ */ new Map();
9935
+ for (const event of events) {
9936
+ const first = firstByTimestamp.get(event.timestamp);
9937
+ if (first === void 0) {
9938
+ firstByTimestamp.set(event.timestamp, event);
9939
+ continue;
9940
+ }
9941
+ const group = sameTimestampGroups.get(event.timestamp);
9942
+ if (group) {
9943
+ group.push(event);
9944
+ } else {
9945
+ sameTimestampGroups.set(event.timestamp, [first, event]);
9946
+ }
9947
+ }
9948
+ const candidates = /* @__PURE__ */ new Set();
9949
+ for (const group of sameTimestampGroups.values()) {
9950
+ for (let left = 0; left < group.length; left += 1) {
9951
+ for (let right4 = left + 1; right4 < group.length; right4 += 1) {
9952
+ if (group[left].sessionId !== group[right4].sessionId && isCopyOf(group[left], group[right4])) {
9953
+ candidates.add(group[left]);
9954
+ candidates.add(group[right4]);
9955
+ }
9956
+ }
9957
+ }
9958
+ }
9959
+ return candidates;
9960
+ }
9961
+ function countCopiedSessionEventsOnce(events) {
9962
+ const candidates = findEventsOtherSessionsHold(events);
9963
+ if (candidates.size === 0) {
9964
+ return [...events];
9965
+ }
9966
+ const sessionStarts = /* @__PURE__ */ new Map();
9967
+ const candidatesBySession = /* @__PURE__ */ new Map();
9968
+ for (const event of events) {
9969
+ const start = sessionStarts.get(event.sessionId);
9970
+ if (start === void 0 || compareByCodePoint(event.timestamp, start) < 0) {
9971
+ sessionStarts.set(event.sessionId, event.timestamp);
9972
+ }
9973
+ if (candidates.has(event)) {
9974
+ const sessionCandidates = candidatesBySession.get(event.sessionId) ?? [];
9975
+ sessionCandidates.push(event);
9976
+ candidatesBySession.set(event.sessionId, sessionCandidates);
9977
+ }
9978
+ }
9979
+ const sessionsOldestFirst = [...candidatesBySession.keys()].sort(
9980
+ (left, right4) => compareByCodePoint(sessionStarts.get(left) ?? "", sessionStarts.get(right4) ?? "") || compareByCodePoint(left, right4)
9981
+ );
9982
+ const countedIdentities = /* @__PURE__ */ new Map();
9983
+ const copies = /* @__PURE__ */ new Set();
9984
+ for (const sessionId of sessionsOldestFirst) {
9985
+ const sessionCounts = /* @__PURE__ */ new Map();
9986
+ for (const event of candidatesBySession.get(sessionId) ?? []) {
9987
+ const identity = getCopyIdentity(event);
9988
+ const seen = (sessionCounts.get(identity) ?? 0) + 1;
9989
+ sessionCounts.set(identity, seen);
9990
+ if (seen <= (countedIdentities.get(identity) ?? 0)) {
9991
+ copies.add(event);
9992
+ }
9993
+ }
9994
+ for (const [identity, count] of sessionCounts) {
9995
+ countedIdentities.set(identity, Math.max(count, countedIdentities.get(identity) ?? 0));
9996
+ }
9997
+ }
9998
+ return events.filter((event) => !copies.has(event));
9999
+ }
10000
+
9784
10001
  // src/cli/history-live-files.ts
9785
10002
  import { stat as stat4 } from "fs/promises";
9786
10003
  var MAX_CONCURRENT_STATS = 32;
@@ -9882,12 +10099,33 @@ function writeParsedFilesToEventStore(context, files) {
9882
10099
  const now = context.now();
9883
10100
  context.replaceFilesEvents(
9884
10101
  context.store,
9885
- files.map((file) => ({ ...file, now }))
10102
+ files.map((file) => ({
10103
+ ...file,
10104
+ now,
10105
+ overrideOnly: context.overrideSourceIds.has(file.source.toLowerCase())
10106
+ }))
9886
10107
  );
9887
10108
  } catch (error) {
9888
10109
  recordEventStoreFailure(context.failureState, error);
9889
10110
  }
9890
10111
  }
10112
+ function claimFilesInUsualLocations(context, files) {
10113
+ if (context.failureState.disabled) {
10114
+ return void 0;
10115
+ }
10116
+ const usualLocationFiles = files.filter(
10117
+ (file) => !context.overrideSourceIds.has(file.source.toLowerCase())
10118
+ );
10119
+ if (usualLocationFiles.length === 0) {
10120
+ return void 0;
10121
+ }
10122
+ try {
10123
+ context.claimOverrideOnlyFiles(context.store, usualLocationFiles);
10124
+ return void 0;
10125
+ } catch (error) {
10126
+ return `Event store could not record files found in their usual location: ${getErrorReason(error)}`;
10127
+ }
10128
+ }
9891
10129
 
9892
10130
  // src/cli/parse/parse-budget.ts
9893
10131
  function createParseBudget(maxParallelFileParsing) {
@@ -10588,14 +10826,17 @@ async function parseSelectedAdapters(adaptersToParse, maxParallelFileParsing, op
10588
10826
  getFileEntry: options.eventStoreDeps?.getFileEntry ?? getFileEntry,
10589
10827
  readFileEvents: options.eventStoreDeps?.readFileEvents ?? readFileEvents,
10590
10828
  replaceFilesEvents: options.eventStoreDeps?.replaceFilesEvents ?? replaceFilesEvents,
10829
+ claimOverrideOnlyFiles: options.eventStoreDeps?.claimOverrideOnlyFiles ?? claimOverrideOnlyFiles,
10591
10830
  now: options.now ?? Date.now,
10592
- failureState: eventStoreFailureState
10831
+ failureState: eventStoreFailureState,
10832
+ overrideSourceIds: options.overrideSourceIds ?? /* @__PURE__ */ new Set()
10593
10833
  };
10594
10834
  } catch (error) {
10595
10835
  recordEventStoreFailure(eventStoreFailureState, error);
10596
10836
  }
10597
10837
  }
10598
10838
  let parseResults;
10839
+ let claimWarning;
10599
10840
  try {
10600
10841
  parseResults = await Promise.allSettled(
10601
10842
  adaptersToParse.map(
@@ -10621,6 +10862,17 @@ async function parseSelectedAdapters(adaptersToParse, maxParallelFileParsing, op
10621
10862
  )
10622
10863
  )
10623
10864
  );
10865
+ if (eventStoreContext) {
10866
+ claimWarning = claimFilesInUsualLocations(
10867
+ eventStoreContext,
10868
+ parseResults.flatMap(
10869
+ (parseResult) => parseResult.status === "fulfilled" ? parseResult.value.filePaths.map((filePath) => ({
10870
+ source: parseResult.value.source,
10871
+ filePath
10872
+ })) : []
10873
+ )
10874
+ );
10875
+ }
10624
10876
  } finally {
10625
10877
  if (eventStoreContext && ownsEventStore) {
10626
10878
  try {
@@ -10655,7 +10907,9 @@ async function parseSelectedAdapters(adaptersToParse, maxParallelFileParsing, op
10655
10907
  failedFiles,
10656
10908
  eventStoreAvailable: Boolean(options.eventStore?.enabled) && !eventStoreFailureState.disabled,
10657
10909
  sourceFailures,
10658
- warnings: eventStoreFailureState.warning ? [eventStoreFailureState.warning] : []
10910
+ warnings: [eventStoreFailureState.warning, claimWarning].filter(
10911
+ (warning) => warning !== void 0
10912
+ )
10659
10913
  };
10660
10914
  }
10661
10915
  function throwOnExplicitSourceFailures(sourceFailures, explicitSourceIds) {
@@ -10707,16 +10961,46 @@ function filterByModelRules(events, modelFilter) {
10707
10961
  const modelFilterRules = resolveModelFilterRules(events, modelFilter);
10708
10962
  return events.filter((event) => matchesModel(event.model, modelFilterRules));
10709
10963
  }
10964
+ function shiftUtcDate(date, days) {
10965
+ const shifted = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
10966
+ shifted.setUTCDate(shifted.getUTCDate() + days);
10967
+ return shifted.toISOString();
10968
+ }
10969
+ function createDateWindowPrefilter(since, until) {
10970
+ const outsideBefore = since ? shiftUtcDate(since, -1) : void 0;
10971
+ const insideFrom = since ? shiftUtcDate(since, 1) : void 0;
10972
+ const insideBefore = until ? shiftUtcDate(until, 0) : void 0;
10973
+ const outsideFrom = until ? shiftUtcDate(until, 2) : void 0;
10974
+ if ([outsideBefore, insideFrom, insideBefore, outsideFrom].some(
10975
+ (bound) => bound !== void 0 && bound.length !== 24
10976
+ )) {
10977
+ return () => "near-edge";
10978
+ }
10979
+ return (timestamp) => {
10980
+ if (timestamp.length !== 24) {
10981
+ return "near-edge";
10982
+ }
10983
+ if (outsideBefore && timestamp < outsideBefore || outsideFrom && timestamp >= outsideFrom) {
10984
+ return "outside";
10985
+ }
10986
+ return (!insideFrom || timestamp >= insideFrom) && (!insideBefore || timestamp < insideBefore) ? "inside" : "near-edge";
10987
+ };
10988
+ }
10710
10989
  function collectProviderAndDateFilteredEvents(eventGroups, options) {
10711
10990
  const filteredEvents = [];
10712
10991
  const hasDateRange = Boolean(options.since ?? options.until);
10992
+ const prefilterDate = createDateWindowPrefilter(options.since, options.until);
10713
10993
  const dateByTimestamp = /* @__PURE__ */ new Map();
10714
10994
  for (const events of eventGroups) {
10715
10995
  for (const event of events) {
10716
10996
  if (!matchesCanonicalProviderFilter(event.provider, options.providerFilter)) {
10717
10997
  continue;
10718
10998
  }
10719
- if (hasDateRange) {
10999
+ const datePosition = hasDateRange ? prefilterDate(event.timestamp) : "inside";
11000
+ if (datePosition === "outside") {
11001
+ continue;
11002
+ }
11003
+ if (datePosition === "near-edge") {
10720
11004
  let eventDate = dateByTimestamp.get(event.timestamp);
10721
11005
  if (!eventDate) {
10722
11006
  eventDate = getPeriodKey(event.timestamp, "daily", options.timezone);
@@ -11077,6 +11361,9 @@ function applyMachineExport(cache, bundle, now) {
11077
11361
  function recordMachineSyncFailure(cache, reason, now) {
11078
11362
  writeEventStoreMeta(cache, { attemptedAt: String(now), lastError: reason });
11079
11363
  }
11364
+ function toStoredError(value) {
11365
+ return value === void 0 ? void 0 : toTerminalLine(value);
11366
+ }
11080
11367
  function toEpochMs(value) {
11081
11368
  const parsed = value === void 0 ? Number.NaN : Number(value);
11082
11369
  return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
@@ -11087,7 +11374,7 @@ function toSyncState(readMeta) {
11087
11374
  cliVersion: readMeta("cliVersion"),
11088
11375
  syncedAt: toEpochMs(readMeta("syncedAt")),
11089
11376
  attemptedAt: toEpochMs(readMeta("attemptedAt")),
11090
- lastError: readMeta("lastError")
11377
+ lastError: toStoredError(readMeta("lastError"))
11091
11378
  };
11092
11379
  }
11093
11380
  function readMachineSyncState(cache) {
@@ -11268,7 +11555,7 @@ async function loadMachineUsage(params) {
11268
11555
  // src/machines/machine-ssh.ts
11269
11556
  import { spawn } from "child_process";
11270
11557
  import { createInterface } from "readline";
11271
- import { stripVTControlCharacters } from "util";
11558
+ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
11272
11559
  var DEFAULT_MACHINE_COMMAND = "llm-usage";
11273
11560
  var MACHINE_EXPORT_TIMEOUT_MARK = "timed out";
11274
11561
  var STDERR_TAIL_BYTES = 4096;
@@ -11361,7 +11648,7 @@ async function detectRemoteCommand(target, options = {}) {
11361
11648
  }
11362
11649
  function readStderrTail(chunks) {
11363
11650
  const text2 = Buffer.concat(chunks).toString("utf8").slice(-STDERR_TAIL_BYTES);
11364
- return stripControlCharacters(stripVTControlCharacters(text2).replaceAll(/\s+/gu, " ")).trim();
11651
+ return toTerminalLine(text2);
11365
11652
  }
11366
11653
  function describeExportFailure(machine, exitCode, stderr) {
11367
11654
  const command = machine.command ?? DEFAULT_MACHINE_COMMAND;
@@ -11455,11 +11742,15 @@ async function fetchMachineExport(machine, knownFiles, options = {}) {
11455
11742
  cause: readError
11456
11743
  });
11457
11744
  }
11458
- const remoteWarnings = Buffer.concat(stderrChunks).toString("utf8").slice(-STDERR_TAIL_BYTES).split("\n").map((line) => stripControlCharacters(stripVTControlCharacters(line)).trim()).filter((line) => line.length > 0);
11745
+ const remoteWarnings = Buffer.concat(stderrChunks).toString("utf8").slice(-STDERR_TAIL_BYTES).split("\n").map((line) => stripControlCharacters(stripVTControlCharacters2(line)).trim()).filter((line) => line.length > 0);
11459
11746
  return { bundle, remoteWarnings };
11460
11747
  }
11461
11748
 
11462
11749
  // src/render/render-machines.ts
11750
+ var MAX_REASON_LENGTH = 300;
11751
+ function formatReason(error) {
11752
+ return error.length > MAX_REASON_LENGTH ? `${error.slice(0, MAX_REASON_LENGTH)}\u2026` : error;
11753
+ }
11463
11754
  var integerFormat = new Intl.NumberFormat("en-US");
11464
11755
  function formatAge(ageMs) {
11465
11756
  const minutes = Math.floor(Math.max(0, ageMs) / 6e4);
@@ -11494,7 +11785,7 @@ function renderSyncOutcome(outcome, now) {
11494
11785
  ];
11495
11786
  }
11496
11787
  const cacheNote = outcome.state.syncedAt === void 0 ? "nothing cached yet" : `reports keep its usage from ${formatAge(now - outcome.state.syncedAt)}`;
11497
- return [`\u2717 ${outcome.name}: ${outcome.error} (${cacheNote})`];
11788
+ return [`\u2717 ${outcome.name}: ${formatReason(outcome.error)} (${cacheNote})`];
11498
11789
  }
11499
11790
  function describeMachineStatus(entry, now) {
11500
11791
  const { state } = entry;
@@ -11502,11 +11793,11 @@ function describeMachineStatus(entry, now) {
11502
11793
  return "disabled (cached usage stays in reports)";
11503
11794
  }
11504
11795
  if (state?.syncedAt === void 0) {
11505
- return state?.lastError ? `never synced: ${state.lastError}` : "never synced";
11796
+ return state?.lastError ? `never synced: ${formatReason(state.lastError)}` : "never synced";
11506
11797
  }
11507
11798
  const synced = `synced ${formatAge(now - state.syncedAt)}, ${formatCachedTotals(entry.fileCount, entry.eventCount)}`;
11508
11799
  if (state.lastError && (state.attemptedAt ?? 0) >= state.syncedAt) {
11509
- return `${synced}; last attempt failed ${formatAge(now - (state.attemptedAt ?? 0))}: ${state.lastError}`;
11800
+ return `${synced}; last attempt failed ${formatAge(now - (state.attemptedAt ?? 0))}: ${formatReason(state.lastError)}`;
11510
11801
  }
11511
11802
  return synced;
11512
11803
  }
@@ -11526,12 +11817,9 @@ function describeIncludedMachine(machine, now, localVersion) {
11526
11817
  return state?.syncedAt === void 0 ? `${machine.name} (disabled, nothing cached)` : `${machine.name} (disabled, usage from ${formatAge(now - state.syncedAt)})`;
11527
11818
  }
11528
11819
  if (state?.syncedAt === void 0) {
11529
- return `${machine.name} (never synced; run llm-usage sync ${machine.name})`;
11820
+ return state?.lastError === void 0 ? `${machine.name} (never synced; run llm-usage sync ${machine.name})` : `${machine.name} (never synced)`;
11530
11821
  }
11531
11822
  const details = [`synced ${formatAge(now - state.syncedAt)}`];
11532
- if (state.lastError !== void 0 && (state.attemptedAt ?? 0) >= state.syncedAt) {
11533
- details.push("last sync failed");
11534
- }
11535
11823
  if (state.cliVersion && state.cliVersion !== localVersion) {
11536
11824
  details.push(
11537
11825
  `llm-usage-metrics ${state.cliVersion} there; shared sessions may count twice until both run the same version`
@@ -11552,16 +11840,35 @@ function formatRefreshFailure(outcome, now) {
11552
11840
  const { syncedAt } = outcome.state;
11553
11841
  if (syncedAt === void 0) {
11554
11842
  return {
11555
- text: `Could not sync ${outcome.name} (${outcome.error}); it has no usage cached yet.`,
11843
+ text: `Could not sync ${outcome.name} (${formatReason(outcome.error)}); it has no usage cached yet.`,
11556
11844
  stale: true
11557
11845
  };
11558
11846
  }
11559
11847
  const hint = outcome.error.endsWith(`(${MACHINE_EXPORT_TIMEOUT_MARK})`) ? ` Run llm-usage sync ${outcome.name} to wait for it.` : "";
11560
11848
  return {
11561
- text: `Could not sync ${outcome.name} (${outcome.error}); using its usage from ${formatAge(now - syncedAt)}.${hint}`,
11849
+ text: `Could not sync ${outcome.name} (${formatReason(outcome.error)}); using its usage from ${formatAge(now - syncedAt)}.${hint}`,
11562
11850
  stale: now - syncedAt >= STALE_CACHE_MS
11563
11851
  };
11564
11852
  }
11853
+ function formatStoredSyncFailure(machine, now) {
11854
+ const { state } = machine;
11855
+ if (!machine.enabled || state?.lastError === void 0) {
11856
+ return void 0;
11857
+ }
11858
+ if (state.syncedAt === void 0) {
11859
+ return {
11860
+ text: `${machine.name}: never synced (${formatReason(state.lastError)}). Run llm-usage sync ${machine.name} to retry.`,
11861
+ stale: true
11862
+ };
11863
+ }
11864
+ if ((state.attemptedAt ?? 0) < state.syncedAt) {
11865
+ return void 0;
11866
+ }
11867
+ return {
11868
+ text: `${machine.name}: last sync failed (${formatReason(state.lastError)}); using its usage from ${formatAge(now - state.syncedAt)}. Run llm-usage sync ${machine.name} to retry.`,
11869
+ stale: now - state.syncedAt >= STALE_CACHE_MS
11870
+ };
11871
+ }
11565
11872
 
11566
11873
  // src/machines/sync-machine.ts
11567
11874
  async function syncMachine(name, machine, options = {}) {
@@ -11592,7 +11899,7 @@ async function syncMachine(name, machine, options = {}) {
11592
11899
  const result = applyMachineExport(cache, bundle, now());
11593
11900
  return { name, ok: true, result, remoteWarnings, state: readMachineSyncState(cache) };
11594
11901
  } catch (error) {
11595
- const reason = getErrorReason(error);
11902
+ const reason = toTerminalLine(getErrorReason(error));
11596
11903
  let state = {};
11597
11904
  try {
11598
11905
  if (!options.signal?.aborted) {
@@ -31437,6 +31744,11 @@ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = g
31437
31744
  }
31438
31745
 
31439
31746
  // src/sources/source-adapter.ts
31747
+ function getSessionCopyingSourceIds(adapters) {
31748
+ return new Set(
31749
+ adapters.filter((adapter) => adapter.capabilities?.sessionsCopyEvents).map((adapter) => adapter.id)
31750
+ );
31751
+ }
31440
31752
  function getRepeatingSourceIds(adapters) {
31441
31753
  return adapters.filter((adapter) => adapter.capabilities?.eventsRepeatAcrossFiles).map((adapter) => adapter.id);
31442
31754
  }
@@ -31510,6 +31822,23 @@ function findUnmatchedFilterWarnings(input) {
31510
31822
  ...findUnmatchedModelWarnings(eventsInRange, input.modelFilter, scope)
31511
31823
  ];
31512
31824
  }
31825
+ function hasValue(value) {
31826
+ return Array.isArray(value) ? value.length > 0 : Boolean(value);
31827
+ }
31828
+ function hasCliFilters(options) {
31829
+ return [
31830
+ options.source,
31831
+ options.provider,
31832
+ options.model,
31833
+ options.machine,
31834
+ options.since,
31835
+ options.until,
31836
+ options.last,
31837
+ options.vsSince,
31838
+ options.vsUntil,
31839
+ options.id
31840
+ ].some(hasValue);
31841
+ }
31513
31842
 
31514
31843
  // src/cli/runtime-profile.ts
31515
31844
  var RUNTIME_PROFILE_ENV_VAR = "LLM_USAGE_PROFILE_RUNTIME";
@@ -31863,6 +32192,7 @@ async function buildUsageEventDataset(options, deps = {}) {
31863
32192
  return parseSelectedAdapters(adaptersToParse, parsingRuntimeConfig.maxParallelFileParsing, {
31864
32193
  eventStore: parseEventStoreRuntimeConfig,
31865
32194
  openedStore: openedEventStore,
32195
+ overrideSourceIds: cliDirectorySourceIds,
31866
32196
  since: configuredOptions.since,
31867
32197
  parseWorkers: {
31868
32198
  workerCount: parsingRuntimeConfig.parseWorkers,
@@ -31875,7 +32205,12 @@ async function buildUsageEventDataset(options, deps = {}) {
31875
32205
  const parseWarnings = eventStoreOpenWarning ? [eventStoreOpenWarning, ...warnings] : warnings;
31876
32206
  throwOnExplicitSourceFailures(sourceFailures, normalizedInputs.explicitSourceIds);
31877
32207
  let parseResultsForFiltering = successfulParseResults;
31878
- const ledger = eventStoreAvailable ? { path: eventStoreRuntimeConfig.path, parsedFiles: [...parsedFiles], historyFiles: [] } : void 0;
32208
+ const ledger = eventStoreAvailable ? {
32209
+ path: eventStoreRuntimeConfig.path,
32210
+ parsedFiles: [...parsedFiles],
32211
+ historyFiles: [],
32212
+ copiedEventCount: 0
32213
+ } : void 0;
31879
32214
  const historyWarnings = [];
31880
32215
  const historyNotes = [];
31881
32216
  const historyStore = openedEventStore;
@@ -31900,7 +32235,14 @@ async function buildUsageEventDataset(options, deps = {}) {
31900
32235
  discoveredFiles: discoveredFiles.filter(
31901
32236
  (file) => !failedFileKeys.has(`${file.source}\0${file.filePath}`)
31902
32237
  ),
31903
- presentFiles: failedFiles,
32238
+ // Files only ever found under a custom directory never departed from
32239
+ // their source's usual location, so a run there leaves them out.
32240
+ presentFiles: [
32241
+ ...failedFiles,
32242
+ ...readOverrideOnlyFiles(historyStore).filter(
32243
+ (file) => !cliDirectorySourceIds.has(file.source)
32244
+ )
32245
+ ],
31904
32246
  repeatingSources: getRepeatingSourceIds(adaptersToParse)
31905
32247
  },
31906
32248
  { unverifiable: "treat-as-departed" }
@@ -31961,6 +32303,13 @@ async function buildUsageEventDataset(options, deps = {}) {
31961
32303
  );
31962
32304
  parseResultsForFiltering = appendMachineEvents(parseResultsForFiltering, machineUsage.events);
31963
32305
  machineWarnings.push(...machineUsage.warnings);
32306
+ const refreshedNames = new Set(refreshOutcomes.map((outcome) => outcome.name));
32307
+ for (const machine of machineUsage.machines) {
32308
+ const failure = refreshedNames.has(machine.name) ? void 0 : formatStoredSyncFailure(machine, nowMs());
32309
+ if (failure) {
32310
+ (failure.stale ? machineWarnings : machineNotes).push(failure.text);
32311
+ }
32312
+ }
31964
32313
  machineNotes.push(
31965
32314
  formatMachinesNote(
31966
32315
  machineUsage.machines,
@@ -31969,6 +32318,21 @@ async function buildUsageEventDataset(options, deps = {}) {
31969
32318
  )
31970
32319
  );
31971
32320
  }
32321
+ const sessionCopyingSources = getSessionCopyingSourceIds(adaptersToParse);
32322
+ parseResultsForFiltering = measureRuntimeProfileStageSync(
32323
+ runtimeProfile,
32324
+ "usage.dataset.count_copies",
32325
+ () => parseResultsForFiltering.map((result) => {
32326
+ if (!sessionCopyingSources.has(result.source)) {
32327
+ return result;
32328
+ }
32329
+ const events = countCopiedSessionEventsOnce(result.events);
32330
+ if (ledger) {
32331
+ ledger.copiedEventCount += result.events.length - events.length;
32332
+ }
32333
+ return { ...result, events };
32334
+ })
32335
+ );
31972
32336
  const filteredEvents = measureRuntimeProfileStageSync(
31973
32337
  runtimeProfile,
31974
32338
  "usage.dataset.filter_events",
@@ -32042,6 +32406,22 @@ async function applyPricingToUsageEventDataset(dataset, deps = {}, pricingLoadMo
32042
32406
  )
32043
32407
  );
32044
32408
  }
32409
+ var MAX_NAMED_UNPRICED_MODELS = 5;
32410
+ function describeUnpricedEvents(events) {
32411
+ const unpricedEvents = events.filter((event) => event.costUsd === void 0);
32412
+ if (unpricedEvents.length === 0) {
32413
+ return void 0;
32414
+ }
32415
+ const models = [...new Set(unpricedEvents.map((event) => event.model ?? "unknown"))].sort();
32416
+ const eventCount = new Intl.NumberFormat("en-US").format(unpricedEvents.length);
32417
+ const eventLabel = unpricedEvents.length === 1 ? "event" : "events";
32418
+ const modelList = limitNamedModels(models, MAX_NAMED_UNPRICED_MODELS).join(", ");
32419
+ return `~ marks costs that leave out ${eventCount} ${eventLabel} with no known price (models: ${modelList})`;
32420
+ }
32421
+ function withUnpricedEventsNote(notes, events) {
32422
+ const note = describeUnpricedEvents(events);
32423
+ return note ? [...notes, note] : [...notes];
32424
+ }
32045
32425
 
32046
32426
  // src/cli/build-usage-data.ts
32047
32427
  var REPORT_PERIOD_NAMES = {
@@ -32054,14 +32434,14 @@ var DEFAULT_REPORT_WINDOWS = {
32054
32434
  weekly: { periods: 8, period: "week" }
32055
32435
  };
32056
32436
  var MAX_LAST_PERIODS = 1e4;
32057
- function parseLastOption(last) {
32437
+ function parseLastOption(last, flag = "--last") {
32058
32438
  if (last === void 0) {
32059
32439
  return void 0;
32060
32440
  }
32061
32441
  const normalized = last.trim();
32062
32442
  const periods = Number.parseInt(normalized, 10);
32063
32443
  if (!/^[1-9]\d*$/u.test(normalized) || periods > MAX_LAST_PERIODS) {
32064
- throw new Error(`--last must be a whole number from 1 to ${String(MAX_LAST_PERIODS)}`);
32444
+ throw new Error(`${flag} must be a whole number from 1 to ${String(MAX_LAST_PERIODS)}`);
32065
32445
  }
32066
32446
  return periods;
32067
32447
  }
@@ -32148,7 +32528,7 @@ async function buildUsageData(granularity, commandOptions, commandDeps = {}) {
32148
32528
  pricingOrigin,
32149
32529
  pricingWarning,
32150
32530
  warnings: dataset.warnings,
32151
- notes: dataset.notes,
32531
+ notes: options.cost === false ? dataset.notes : withUnpricedEventsNote(dataset.notes, pricedEvents),
32152
32532
  activeEnvOverrides: dataset.readEnvVarOverrides(),
32153
32533
  activeConfig: dataset.activeConfig,
32154
32534
  timezone: dataset.normalizedInputs.timezone,
@@ -32415,6 +32795,10 @@ function padLine(content, width) {
32415
32795
  function renderReportHeader(options) {
32416
32796
  const { title, useColor = true } = options;
32417
32797
  const boxWidth = getBoxWidth(title);
32798
+ const terminalWidth = options.terminalWidth ?? resolveTtyColumns(process.stdout);
32799
+ if (terminalWidth !== void 0 && boxWidth > terminalWidth) {
32800
+ return useColor ? pc2.bold(pc2.white(title)) : title;
32801
+ }
32418
32802
  const lines = [];
32419
32803
  const topBorder = drawBoxLine(boxWidth, "\u250C", "\u2500", "\u2510");
32420
32804
  lines.push(useColor ? pc2.gray(topBorder) : topBorder);
@@ -32424,6 +32808,9 @@ function renderReportHeader(options) {
32424
32808
  lines.push(useColor ? pc2.gray(bottomBorder) : bottomBorder);
32425
32809
  return lines.join("\n");
32426
32810
  }
32811
+ function formatNoUsageMessage(filtered) {
32812
+ return filtered ? "No usage data found for the selected filters." : "No usage data found.";
32813
+ }
32427
32814
 
32428
32815
  // src/render/terminal-table.ts
32429
32816
  import pc4 from "picocolors";
@@ -32901,6 +33288,75 @@ function computeColumnWidths(measureRows, options) {
32901
33288
  }
32902
33289
  return widths;
32903
33290
  }
33291
+ function measureBoxedTableWidth(widths) {
33292
+ return widths.reduce((total, width) => total + width + 3, 1);
33293
+ }
33294
+ function measureLinesWidth(cell) {
33295
+ return splitCellLines(cell).reduce((maxWidth, line) => Math.max(maxWidth, visibleWidth(line)), 0);
33296
+ }
33297
+ function joinCellLines(cell) {
33298
+ return splitCellLines(cell).join(" ");
33299
+ }
33300
+ function wrapAtNameBreaks(line, width) {
33301
+ const lines = [];
33302
+ for (const piece of line.split(/(?<=[\s/-])/u)) {
33303
+ const lastLine = lines.at(-1);
33304
+ if (lastLine !== void 0 && visibleWidth((lastLine + piece).trimEnd()) <= width) {
33305
+ lines[lines.length - 1] = lastLine + piece;
33306
+ } else {
33307
+ lines.push(piece);
33308
+ }
33309
+ }
33310
+ return lines.flatMap(
33311
+ (wrappedLine) => splitCellLines(wrapTableColumn([[wrappedLine.trimEnd()]], { columnIndex: 0, width })[0][0])
33312
+ );
33313
+ }
33314
+ function renderStackedRows(options) {
33315
+ const valueColumnIndexes = options.headerCells.map((_, columnIndex) => columnIndex).filter((columnIndex) => columnIndex >= options.titleColumnCount);
33316
+ const labelWidth = valueColumnIndexes.reduce(
33317
+ (maxWidth, columnIndex) => Math.max(maxWidth, visibleWidth(joinCellLines(options.measureHeaderCells[columnIndex]))),
33318
+ 0
33319
+ );
33320
+ const continuationIndent = " ".repeat(2 + labelWidth + 2);
33321
+ const availableValueWidth = Math.max(1, options.terminalWidth - continuationIndent.length);
33322
+ const valueWidth = Math.min(
33323
+ availableValueWidth,
33324
+ options.measureBodyRows.reduce(
33325
+ (maxWidth, row) => valueColumnIndexes.reduce(
33326
+ (rowMax, columnIndex) => Math.max(rowMax, measureLinesWidth(row[columnIndex])),
33327
+ maxWidth
33328
+ ),
33329
+ 0
33330
+ )
33331
+ );
33332
+ const records = options.bodyRows.map((row, rowIndex) => {
33333
+ const title = row.slice(0, options.titleColumnCount).map(joinCellLines).filter((cell) => cell.trim().length > 0).join(" \xB7 ");
33334
+ const valueLines = valueColumnIndexes.flatMap((columnIndex) => {
33335
+ const lines = measureLinesWidth(row[columnIndex]) > availableValueWidth ? splitCellLines(options.measureBodyRows[rowIndex][columnIndex]).flatMap(
33336
+ (line) => wrapAtNameBreaks(line, availableValueWidth)
33337
+ ) : splitCellLines(row[columnIndex]);
33338
+ if (lines.every((line) => line.trim().length === 0)) {
33339
+ return [];
33340
+ }
33341
+ const alignment2 = getColumnAlignment(
33342
+ columnIndex,
33343
+ options.multilineColumnIndex,
33344
+ options.leftAlignedColumnIndexes
33345
+ );
33346
+ const label = alignCellLine(
33347
+ joinCellLines(options.headerCells[columnIndex]),
33348
+ labelWidth,
33349
+ "left"
33350
+ );
33351
+ return lines.map((line, lineIndex) => {
33352
+ const value = alignCellLine(line, valueWidth, alignment2).trimEnd();
33353
+ return lineIndex === 0 ? ` ${label} ${value}` : `${continuationIndent}${value}`;
33354
+ });
33355
+ });
33356
+ return [title, ...valueLines].join("\n");
33357
+ });
33358
+ return records.join("\n\n");
33359
+ }
32904
33360
  function renderUnicodeTable(options) {
32905
33361
  const bodyColumnCount = getMaxRowColumnCount(options.bodyRows, options.headerCells.length);
32906
33362
  const measureColumnCount = getMaxRowColumnCount(
@@ -32932,6 +33388,20 @@ function renderUnicodeTable(options) {
32932
33388
  multilineColumnIndex: options.multilineColumnIndex,
32933
33389
  multilineColumnWidth: options.multilineColumnWidth
32934
33390
  });
33391
+ const normalizedBodyRows = renderableRows.map((row) => row.bodyRow);
33392
+ const normalizedRowMetas = renderableRows.map((row) => row.rowMeta);
33393
+ if (options.terminalWidth !== void 0 && measureBoxedTableWidth(widths) > options.terminalWidth) {
33394
+ return renderStackedRows({
33395
+ headerCells: normalizedHeaderRow,
33396
+ measureHeaderCells: normalizedMeasureHeaderRow,
33397
+ bodyRows: normalizedBodyRows,
33398
+ measureBodyRows: renderableRows.map((row) => row.measureBodyRow),
33399
+ titleColumnCount: options.stackedTitleColumnCount ?? options.multilineColumnIndex + 1,
33400
+ terminalWidth: options.terminalWidth,
33401
+ multilineColumnIndex: options.multilineColumnIndex,
33402
+ leftAlignedColumnIndexes: options.leftAlignedColumnIndexes
33403
+ });
33404
+ }
32935
33405
  const outputLines = [];
32936
33406
  outputLines.push(
32937
33407
  buildBorderLine(widths, {
@@ -32955,8 +33425,6 @@ function renderUnicodeTable(options) {
32955
33425
  right: "\u2524"
32956
33426
  })
32957
33427
  );
32958
- const normalizedBodyRows = renderableRows.map((row) => row.bodyRow);
32959
- const normalizedRowMetas = renderableRows.map((row) => row.rowMeta);
32960
33428
  normalizedBodyRows.forEach((row, rowIndex) => {
32961
33429
  outputLines.push(
32962
33430
  ...toRenderableRowLines(row, {
@@ -33254,7 +33722,8 @@ function renderPreparedTable(rows, prepared, options) {
33254
33722
  rowMetas: toRowMetas(rows),
33255
33723
  layout: options.tableLayout === "per_model_columns" ? "top_aligned" : "compact",
33256
33724
  multilineColumnIndex: prepared.showsModels ? columns.indexOf(modelsColumnIndex2) : columns.indexOf(sourceColumnIndex2),
33257
- multilineColumnWidth: prepared.showsModels ? options.modelsColumnWidth : void 0
33725
+ multilineColumnWidth: prepared.showsModels ? options.modelsColumnWidth : void 0,
33726
+ terminalWidth: options.stackedWidth
33258
33727
  });
33259
33728
  return { output, truncatedModelNames };
33260
33729
  }
@@ -33273,12 +33742,20 @@ function renderTerminalTableWithFit(rows, options = {}) {
33273
33742
  prepared = prepareFitStep(rows, tableLayout, step);
33274
33743
  }
33275
33744
  }
33745
+ const stacked = tableLayout === "compact" && terminalWidth !== void 0 && !fitsTerminal(prepared, terminalWidth);
33746
+ if (stacked) {
33747
+ const listStep = steps.find((step) => step.hiddenColumns.includes("models"));
33748
+ if (listStep) {
33749
+ prepared = prepareFitStep(rows, tableLayout, listStep);
33750
+ }
33751
+ }
33276
33752
  const modelsColumnWidth = resolveModelsColumnWidth(prepared, tableLayout, terminalWidth);
33277
33753
  const { output, truncatedModelNames } = renderPreparedTable(rows, prepared, {
33278
33754
  tableLayout,
33279
33755
  useColor,
33280
33756
  modelsColumnWidth,
33281
- packModels: terminalWidth !== void 0
33757
+ packModels: terminalWidth !== void 0,
33758
+ stackedWidth: stacked ? terminalWidth : void 0
33282
33759
  });
33283
33760
  if (hasExplicitTerminalWidth && terminalWidth !== void 0) {
33284
33761
  const renderedTableWidth = measureTableWidth(output);
@@ -33293,7 +33770,8 @@ function renderTerminalTableWithFit(rows, options = {}) {
33293
33770
  fit: {
33294
33771
  tokenFormat: prepared.step.tokenFormat,
33295
33772
  hiddenColumns: [...prepared.step.hiddenColumns],
33296
- truncatedModelNames
33773
+ truncatedModelNames,
33774
+ stacked
33297
33775
  }
33298
33776
  };
33299
33777
  }
@@ -33408,9 +33886,13 @@ function renderTerminalTable(options) {
33408
33886
  rowMetas: createRowMetas(options.bodyRows.length),
33409
33887
  layout: "compact",
33410
33888
  multilineColumnIndex: 0,
33411
- multilineColumnWidth: options.firstColumnWidth
33889
+ multilineColumnWidth: options.firstColumnWidth,
33890
+ terminalWidth: resolveTtyColumns(process.stdout)
33412
33891
  });
33413
33892
  }
33893
+ function hasNoUsage(compareData) {
33894
+ return compareData.current.totals.events === 0 && compareData.baseline.totals.events === 0;
33895
+ }
33414
33896
  function renderTerminalCompareReport(compareData, options) {
33415
33897
  const useColor = options.useColor ?? shouldUseColorByDefault();
33416
33898
  const lines = [];
@@ -33421,9 +33903,9 @@ function renderTerminalCompareReport(compareData, options) {
33421
33903
  })
33422
33904
  );
33423
33905
  lines.push("");
33424
- if (compareData.current.totals.events === 0 && compareData.baseline.totals.events === 0) {
33425
- lines.push("No usage data found for the selected filters.");
33426
- lines.push("");
33906
+ if (hasNoUsage(compareData)) {
33907
+ lines.push(formatNoUsageMessage(options.filtered ?? false));
33908
+ return lines.join("\n");
33427
33909
  }
33428
33910
  lines.push(
33429
33911
  renderTerminalTable({
@@ -34224,7 +34706,7 @@ async function buildCompareData(options, deps = {}) {
34224
34706
  pricingOrigin,
34225
34707
  pricingWarning,
34226
34708
  warnings: dataset.warnings,
34227
- notes: dataset.notes,
34709
+ notes: withUnpricedEventsNote(dataset.notes, [...currentEvents, ...baselineEvents]),
34228
34710
  activeEnvOverrides: dataset.readEnvVarOverrides(),
34229
34711
  activeConfig: dataset.activeConfig,
34230
34712
  timezone: dataset.normalizedInputs.timezone,
@@ -34883,7 +35365,7 @@ async function prepareCompareReport(options, deps = {}) {
34883
35365
  title: "Compare share card",
34884
35366
  render: (theme) => renderCompareShareSvg(compareData, theme)
34885
35367
  }) : void 0,
34886
- render: (compareData, format) => renderCompareReport(compareData, format)
35368
+ render: (compareData, format) => renderCompareReport(compareData, format, { filtered: hasCliFilters(options) })
34887
35369
  });
34888
35370
  }
34889
35371
  async function runCompareReport(options) {
@@ -35067,19 +35549,21 @@ function formatDecimal(value, options = {}) {
35067
35549
  const formatted = decimalFormatter.format(value);
35068
35550
  return options.approximate ? `~${formatted}` : formatted;
35069
35551
  }
35070
- function toEfficiencyTableCells(rows) {
35552
+ function toEfficiencyTableCells(rows, tokenFormat = "full") {
35553
+ const formatTokens = tokenFormat === "abbreviated" ? formatAbbreviatedTokenCount : formatInteger3;
35554
+ const formatTokenRate = (value) => value !== void 0 && tokenFormat === "abbreviated" ? formatAbbreviatedTokenCount(value) : formatDecimal(value);
35071
35555
  return rows.map((row) => [
35072
35556
  row.rowType === "period_source" ? ` ${row.source}` : row.periodKey,
35073
35557
  formatInteger3(row.rowType === "period_source" ? void 0 : row.commitCount),
35074
35558
  formatInteger3(row.rowType === "period_source" ? void 0 : row.linesAdded),
35075
35559
  formatInteger3(row.rowType === "period_source" ? void 0 : row.linesDeleted),
35076
35560
  formatInteger3(row.rowType === "period_source" ? void 0 : row.linesChanged),
35077
- formatInteger3(row.inputTokens),
35078
- formatInteger3(row.outputTokens),
35079
- formatInteger3(row.reasoningTokens),
35080
- formatInteger3(row.cacheReadTokens),
35081
- formatInteger3(row.cacheWriteTokens),
35082
- formatInteger3(row.totalTokens),
35561
+ formatTokens(row.inputTokens),
35562
+ formatTokens(row.outputTokens),
35563
+ formatTokens(row.reasoningTokens),
35564
+ formatTokens(row.cacheReadTokens),
35565
+ formatTokens(row.cacheWriteTokens),
35566
+ formatTokens(row.totalTokens),
35083
35567
  formatUsd4(row.costUsd, { approximate: row.costIncomplete }),
35084
35568
  formatUsdRate(row.rowType === "period_source" ? void 0 : row.usdPerCommit, {
35085
35569
  approximate: row.costIncomplete
@@ -35087,7 +35571,7 @@ function toEfficiencyTableCells(rows) {
35087
35571
  formatUsdRate(row.rowType === "period_source" ? void 0 : row.usdPer1kLinesChanged, {
35088
35572
  approximate: row.costIncomplete
35089
35573
  }),
35090
- formatDecimal(row.rowType === "period_source" ? void 0 : row.tokensPerCommit),
35574
+ formatTokenRate(row.rowType === "period_source" ? void 0 : row.tokensPerCommit),
35091
35575
  formatDecimal(row.rowType === "period_source" ? void 0 : row.commitsPerUsd, {
35092
35576
  approximate: row.costIncomplete
35093
35577
  })
@@ -35096,15 +35580,45 @@ function toEfficiencyTableCells(rows) {
35096
35580
 
35097
35581
  // src/render/render-efficiency-report.ts
35098
35582
  var periodColumnIndex2 = 0;
35099
- var minimumEfficiencyColumnWidth = 1;
35100
35583
  var commitsColumnIndex = 1;
35101
35584
  var linesAddedColumnIndex = 2;
35102
35585
  var linesDeletedColumnIndex = 3;
35103
35586
  var linesChangedColumnIndex = 4;
35587
+ var inputColumnIndex = 5;
35588
+ var outputColumnIndex = 6;
35589
+ var reasoningColumnIndex = 7;
35590
+ var cacheReadColumnIndex = 8;
35591
+ var cacheWriteColumnIndex = 9;
35104
35592
  var costColumnIndex = 11;
35105
35593
  var usdPerCommitColumnIndex = 12;
35106
35594
  var usdPer1kLinesChangedColumnIndex = 13;
35107
35595
  var commitsPerUsdColumnIndex = 15;
35596
+ var efficiencyFitSteps = [
35597
+ { tokenFormat: "full", hiddenColumnIndexes: [] },
35598
+ { tokenFormat: "abbreviated", hiddenColumnIndexes: [] },
35599
+ {
35600
+ tokenFormat: "abbreviated",
35601
+ hiddenColumnIndexes: [
35602
+ inputColumnIndex,
35603
+ outputColumnIndex,
35604
+ reasoningColumnIndex,
35605
+ cacheWriteColumnIndex
35606
+ ]
35607
+ },
35608
+ {
35609
+ tokenFormat: "abbreviated",
35610
+ hiddenColumnIndexes: [
35611
+ linesAddedColumnIndex,
35612
+ linesDeletedColumnIndex,
35613
+ inputColumnIndex,
35614
+ outputColumnIndex,
35615
+ reasoningColumnIndex,
35616
+ cacheReadColumnIndex,
35617
+ cacheWriteColumnIndex,
35618
+ commitsPerUsdColumnIndex
35619
+ ]
35620
+ }
35621
+ ];
35108
35622
  function getReportTitle(granularity) {
35109
35623
  switch (granularity) {
35110
35624
  case "daily":
@@ -35129,11 +35643,7 @@ function measureRenderedTableWidth(columnWidths) {
35129
35643
  return columnWidths.reduce((sum, width) => sum + width, 0) + columnWidths.length * 3 + 1;
35130
35644
  }
35131
35645
  function computeColumnWidths2(headerCells, bodyRows) {
35132
- const columnCount = Math.max(
35133
- headerCells.length,
35134
- ...bodyRows.map((row) => row.length),
35135
- efficiencyTableHeaders.length
35136
- );
35646
+ const columnCount = Math.max(headerCells.length, ...bodyRows.map((row) => row.length));
35137
35647
  const widths = Array.from({ length: columnCount }, () => 0);
35138
35648
  const measureRow = (row) => {
35139
35649
  for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
@@ -35148,71 +35658,69 @@ function computeColumnWidths2(headerCells, bodyRows) {
35148
35658
  }
35149
35659
  return widths;
35150
35660
  }
35151
- function resolveWrappedCells(headerCells, bodyRows, widths) {
35152
- let wrappedHeaderCells = [...headerCells];
35153
- let wrappedBodyRows = bodyRows.map((row) => [...row]);
35154
- for (let columnIndex = 0; columnIndex < widths.length; columnIndex += 1) {
35155
- const columnWidth = widths[columnIndex] ?? 0;
35156
- if (columnWidth <= 0) {
35157
- continue;
35661
+ function splitHeaderPieces(header) {
35662
+ return header.split(" ").flatMap((word) => word.split(/(?<=\/)/u));
35663
+ }
35664
+ function wrapHeader(header, width) {
35665
+ const lines = [];
35666
+ for (const piece of splitHeaderPieces(header)) {
35667
+ const lastLine = lines.at(-1);
35668
+ const separator = lastLine === void 0 || lastLine.endsWith("/") ? "" : " ";
35669
+ if (lastLine !== void 0 && visibleWidth(lastLine + separator + piece) <= width) {
35670
+ lines[lines.length - 1] = lastLine + separator + piece;
35671
+ } else {
35672
+ lines.push(piece);
35158
35673
  }
35159
- wrappedHeaderCells = wrapTableColumn([wrappedHeaderCells], {
35160
- columnIndex,
35161
- width: columnWidth
35162
- })[0] ?? [];
35163
- wrappedBodyRows = wrapTableColumn(wrappedBodyRows, {
35164
- columnIndex,
35165
- width: columnWidth
35166
- });
35167
35674
  }
35168
- return {
35169
- wrappedHeaderCells,
35170
- wrappedBodyRows
35171
- };
35675
+ return lines.join("\n");
35676
+ }
35677
+ function computeMinimumColumnWidths(headerCells, bodyRows) {
35678
+ const headerPieceWidths = headerCells.map(
35679
+ (header) => splitHeaderPieces(header).reduce(
35680
+ (maxWidth, piece) => Math.max(maxWidth, visibleWidth(piece)),
35681
+ 0
35682
+ )
35683
+ );
35684
+ return computeColumnWidths2([], bodyRows).map(
35685
+ (bodyWidth, columnIndex) => Math.max(bodyWidth, headerPieceWidths[columnIndex] ?? 0)
35686
+ );
35172
35687
  }
35173
35688
  function fitTableCellsToTerminal(headerCells, bodyRows) {
35174
35689
  const naturalWidths = computeColumnWidths2(headerCells, bodyRows);
35175
35690
  const terminalWidth = resolveTtyColumns(process.stdout);
35691
+ const natural = {
35692
+ headerCells: [...headerCells],
35693
+ bodyRows: bodyRows.map((row) => [...row]),
35694
+ widths: naturalWidths
35695
+ };
35176
35696
  if (terminalWidth === void 0 || measureRenderedTableWidth(naturalWidths) <= terminalWidth) {
35177
- return {
35178
- headerCells: [...headerCells],
35179
- bodyRows: bodyRows.map((row) => [...row]),
35180
- widths: naturalWidths
35181
- };
35697
+ return natural;
35698
+ }
35699
+ const minimumWidths = computeMinimumColumnWidths(headerCells, bodyRows);
35700
+ if (measureRenderedTableWidth(minimumWidths) > terminalWidth) {
35701
+ return { ...natural, stackedWidth: terminalWidth };
35182
35702
  }
35183
35703
  const constrainedWidths = [...naturalWidths];
35184
- let renderedTableWidth = measureRenderedTableWidth(constrainedWidths);
35185
- while (renderedTableWidth > terminalWidth && constrainedWidths.some((width) => width > minimumEfficiencyColumnWidth)) {
35186
- let widestIndex = -1;
35187
- let widestWidth = -1;
35188
- for (let columnIndex = 0; columnIndex < constrainedWidths.length; columnIndex += 1) {
35189
- const columnWidth = constrainedWidths[columnIndex];
35190
- if (columnWidth <= minimumEfficiencyColumnWidth || columnWidth <= widestWidth) {
35191
- continue;
35704
+ let overflowColumns = measureRenderedTableWidth(constrainedWidths) - terminalWidth;
35705
+ while (overflowColumns > 0) {
35706
+ let shrinkIndex = 0;
35707
+ for (let columnIndex = 1; columnIndex < constrainedWidths.length; columnIndex += 1) {
35708
+ if (constrainedWidths[columnIndex] - minimumWidths[columnIndex] > constrainedWidths[shrinkIndex] - minimumWidths[shrinkIndex]) {
35709
+ shrinkIndex = columnIndex;
35192
35710
  }
35193
- widestIndex = columnIndex;
35194
- widestWidth = columnWidth;
35195
35711
  }
35196
- if (widestIndex === -1) {
35197
- break;
35198
- }
35199
- const overflowColumns = renderedTableWidth - terminalWidth;
35200
- const maxReducibleWidth = widestWidth - minimumEfficiencyColumnWidth;
35201
- const reduction = Math.min(overflowColumns, maxReducibleWidth);
35202
- if (reduction <= 0) {
35203
- break;
35204
- }
35205
- constrainedWidths[widestIndex] -= reduction;
35206
- renderedTableWidth -= reduction;
35712
+ const reduction = Math.min(
35713
+ overflowColumns,
35714
+ constrainedWidths[shrinkIndex] - minimumWidths[shrinkIndex]
35715
+ );
35716
+ constrainedWidths[shrinkIndex] -= reduction;
35717
+ overflowColumns -= reduction;
35207
35718
  }
35208
- const { wrappedHeaderCells, wrappedBodyRows } = resolveWrappedCells(
35209
- headerCells,
35210
- bodyRows,
35211
- constrainedWidths
35212
- );
35213
35719
  return {
35214
- headerCells: wrappedHeaderCells,
35215
- bodyRows: wrappedBodyRows,
35720
+ headerCells: headerCells.map(
35721
+ (header, columnIndex) => wrapHeader(header, constrainedWidths[columnIndex])
35722
+ ),
35723
+ bodyRows: natural.bodyRows,
35216
35724
  widths: constrainedWidths
35217
35725
  };
35218
35726
  }
@@ -35282,12 +35790,50 @@ function styleEfficiencyTerminalRows(rows, bodyRows, options) {
35282
35790
  return styledCells;
35283
35791
  });
35284
35792
  }
35793
+ function fitEfficiencyStep(rows, step, options) {
35794
+ const visibleColumnIndexes = efficiencyTableHeaders.map((_, columnIndex) => columnIndex).filter((columnIndex) => !step.hiddenColumnIndexes.includes(columnIndex));
35795
+ const styledRows = styleEfficiencyTerminalRows(
35796
+ rows,
35797
+ toEfficiencyTableCells(rows, step.tokenFormat),
35798
+ options
35799
+ );
35800
+ return fitTableCellsToTerminal(
35801
+ selectColumns(Array.from(efficiencyTableHeaders), visibleColumnIndexes),
35802
+ styledRows.map((row) => selectColumns(row, visibleColumnIndexes))
35803
+ );
35804
+ }
35805
+ function describeEfficiencyFit(step, stacked) {
35806
+ if (stacked) {
35807
+ return "Listed rows instead of a table to fit the terminal. Widen the terminal to see the table.";
35808
+ }
35809
+ const changes = [];
35810
+ if (step.tokenFormat === "abbreviated") {
35811
+ changes.push("abbreviated token counts");
35812
+ }
35813
+ const hiddenHeaders = step.hiddenColumnIndexes.map(
35814
+ (columnIndex) => efficiencyTableHeaders[columnIndex]
35815
+ );
35816
+ if (hiddenHeaders.length > 0) {
35817
+ changes.push(`hid ${hiddenHeaders.slice(0, -1).join(", ")} and ${hiddenHeaders.at(-1) ?? ""}`);
35818
+ }
35819
+ return changes.length > 0 ? `Fitted the table to the terminal: ${changes.join(", ")}. Widen the terminal or use --json for full detail.` : void 0;
35820
+ }
35285
35821
  function renderTerminalEfficiencyTable(rows, options) {
35286
- const headerCells = Array.from(efficiencyTableHeaders);
35287
- const bodyRows = styleEfficiencyTerminalRows(rows, toEfficiencyTableCells(rows), options);
35288
35822
  const rowMetas = rows.map((row) => toTableRowMeta(row));
35289
- const fittedCells = fitTableCellsToTerminal(headerCells, bodyRows);
35290
- return renderUnicodeTable({
35823
+ let fittedStep = efficiencyFitSteps[0];
35824
+ let fittedCells = fitEfficiencyStep(rows, fittedStep, options);
35825
+ for (const step of efficiencyFitSteps.slice(1)) {
35826
+ if (fittedCells.stackedWidth === void 0) {
35827
+ break;
35828
+ }
35829
+ const candidate = fitEfficiencyStep(rows, step, options);
35830
+ if (candidate.stackedWidth === void 0) {
35831
+ fittedStep = step;
35832
+ fittedCells = candidate;
35833
+ }
35834
+ }
35835
+ const note = describeEfficiencyFit(fittedStep, fittedCells.stackedWidth !== void 0);
35836
+ const output = renderUnicodeTable({
35291
35837
  headerCells: fittedCells.headerCells,
35292
35838
  bodyRows: fittedCells.bodyRows,
35293
35839
  measureHeaderCells: fittedCells.headerCells,
@@ -35295,8 +35841,10 @@ function renderTerminalEfficiencyTable(rows, options) {
35295
35841
  rowMetas,
35296
35842
  layout: "compact",
35297
35843
  multilineColumnIndex: periodColumnIndex2,
35298
- multilineColumnWidth: fittedCells.widths[periodColumnIndex2] ?? efficiencyTableHeaders[periodColumnIndex2].length
35844
+ multilineColumnWidth: fittedCells.widths[periodColumnIndex2] ?? efficiencyTableHeaders[periodColumnIndex2].length,
35845
+ terminalWidth: fittedCells.stackedWidth
35299
35846
  });
35847
+ return { output, notes: note ? [note] : [] };
35300
35848
  }
35301
35849
  function renderMarkdownEfficiencyTable(rows) {
35302
35850
  const bodyRows = toEfficiencyTableCells(rows).map(
@@ -35317,19 +35865,22 @@ function renderTerminalEfficiencyReport(efficiencyData, options) {
35317
35865
  useColor
35318
35866
  })
35319
35867
  );
35320
- outputLines.push("");
35321
- outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows, { useColor }));
35322
- return outputLines.join("\n");
35868
+ const table = renderTerminalEfficiencyTable(efficiencyData.rows, { useColor });
35869
+ outputLines.push("", table.output);
35870
+ return { output: outputLines.join("\n"), notes: table.notes };
35323
35871
  }
35324
- function renderEfficiencyReport(efficiencyData, format, options) {
35872
+ function renderEfficiencyReportWithNotes(efficiencyData, format, options) {
35325
35873
  switch (format) {
35326
35874
  case "json":
35327
- return renderReportJson("efficiency", {
35328
- grouping: efficiencyData.grouping,
35329
- rows: efficiencyData.rows
35330
- });
35875
+ return {
35876
+ output: renderReportJson("efficiency", {
35877
+ grouping: efficiencyData.grouping,
35878
+ rows: efficiencyData.rows
35879
+ }),
35880
+ notes: []
35881
+ };
35331
35882
  case "markdown":
35332
- return renderMarkdownEfficiencyTable(efficiencyData.rows);
35883
+ return { output: renderMarkdownEfficiencyTable(efficiencyData.rows), notes: [] };
35333
35884
  case "terminal":
35334
35885
  return renderTerminalEfficiencyReport(efficiencyData, options);
35335
35886
  }
@@ -35581,12 +36132,22 @@ async function assertRepoDirReadable(repoDir) {
35581
36132
  throw new Error(`Repository path is not a directory: ${repoDir}`);
35582
36133
  }
35583
36134
  }
35584
- async function assertGitRepository(repoDir, runCommand) {
35585
- const gitRepoResult = await runCommand(repoDir, ["rev-parse", "--is-inside-work-tree"]);
35586
- if (gitRepoResult.exitCode === 0) {
35587
- return;
35588
- }
35589
- throw new Error(`Repository is not a git repository: ${repoDir}`);
36135
+ async function isGitRepository(repoDir, runCommand) {
36136
+ const gitRepoResult = await runCommand(repoDir, ["rev-parse", "--is-inside-work-tree"]).catch(
36137
+ (error) => {
36138
+ if (getNodeErrorCode2(error) === "ENOENT") {
36139
+ throw new Error("git was not found on PATH; efficiency needs git installed", {
36140
+ cause: error
36141
+ });
36142
+ }
36143
+ throw error;
36144
+ }
36145
+ );
36146
+ return gitRepoResult.exitCode === 0;
36147
+ }
36148
+ function notGitRepositoryMessage(repoDir, repoDirOption) {
36149
+ const hint = repoDirOption === void 0 ? "<path>" : "<path to a git repository>";
36150
+ return `${repoDir} is not a git repository; pass --repo-dir ${hint}`;
35590
36151
  }
35591
36152
  function isNoCommitHistoryFailure(result) {
35592
36153
  if (result.exitCode !== 128) {
@@ -35859,11 +36420,20 @@ async function collectGitOutcomes(options, deps = {}) {
35859
36420
  const includeMergeCommits = options.includeMergeCommits ?? false;
35860
36421
  const runCommand = deps.runGitCommand ?? runGitCommand;
35861
36422
  if (options.activeUsageDays?.size === 0) {
35862
- return createEmptyOutcomeCollection(repoDir, includeMergeCommits);
36423
+ const emptyCollection = createEmptyOutcomeCollection(repoDir, includeMergeCommits);
36424
+ if (!deps.runGitCommand) {
36425
+ await assertRepoDirReadable(repoDir);
36426
+ if (!await isGitRepository(repoDir, runCommand)) {
36427
+ emptyCollection.warning = notGitRepositoryMessage(repoDir, options.repoDir);
36428
+ }
36429
+ }
36430
+ return emptyCollection;
35863
36431
  }
35864
36432
  if (!deps.runGitCommand) {
35865
36433
  await assertRepoDirReadable(repoDir);
35866
- await assertGitRepository(repoDir, runCommand);
36434
+ if (!await isGitRepository(repoDir, runCommand)) {
36435
+ throw new Error(notGitRepositoryMessage(repoDir, options.repoDir));
36436
+ }
35867
36437
  }
35868
36438
  let authorEmail;
35869
36439
  try {
@@ -36174,8 +36744,8 @@ async function buildEfficiencyData(granularity, options, deps = {}) {
36174
36744
  sourceFailures: dataset.sourceFailures,
36175
36745
  pricingOrigin,
36176
36746
  pricingWarning,
36177
- warnings: dataset.warnings,
36178
- notes: dataset.notes,
36747
+ warnings: gitOutcomes.warning ? [...dataset.warnings, gitOutcomes.warning] : dataset.warnings,
36748
+ notes: withUnpricedEventsNote(dataset.notes, matchedEventsWithSignal),
36179
36749
  activeEnvOverrides: dataset.readEnvVarOverrides(),
36180
36750
  activeConfig: dataset.activeConfig,
36181
36751
  timezone: dataset.normalizedInputs.timezone,
@@ -36229,9 +36799,12 @@ async function prepareEfficiencyReport(granularity, options, deps = {}) {
36229
36799
  title: "Efficiency share card",
36230
36800
  render: (theme) => renderEfficiencyMonthlyShareSvg(efficiencyData, theme)
36231
36801
  }) : void 0,
36232
- render: (efficiencyData, format) => renderEfficiencyReport(efficiencyData, format, {
36233
- granularity
36234
- })
36802
+ render: (efficiencyData, format) => {
36803
+ const { output, notes } = renderEfficiencyReportWithNotes(efficiencyData, format, {
36804
+ granularity
36805
+ });
36806
+ return { output, hintsAfterOutput: notes };
36807
+ }
36235
36808
  });
36236
36809
  }
36237
36810
  function emitEfficiencyReportDiagnostics(diagnostics) {
@@ -36465,7 +37038,8 @@ function renderTerminalOptimizeReport(optimizeData, options) {
36465
37038
  rowMetas: optimizeData.rows.map((row) => toTableRowMeta2(row)),
36466
37039
  layout: "compact",
36467
37040
  multilineColumnIndex: 1,
36468
- multilineColumnWidth: candidateColumnWidth
37041
+ multilineColumnWidth: candidateColumnWidth,
37042
+ terminalWidth: resolveTtyColumns(process.stdout)
36469
37043
  })
36470
37044
  );
36471
37045
  return outputLines.join("\n");
@@ -36643,7 +37217,7 @@ function hasAnyUsageSignal(period) {
36643
37217
  }
36644
37218
  function parseCandidateModelsRaw(candidateModel) {
36645
37219
  if (!candidateModel || Array.isArray(candidateModel) && candidateModel.length === 0) {
36646
- throw new Error("At least one --candidate-model is required");
37220
+ throw new Error("At least one --candidate-model is required, e.g. --candidate-model gpt-5.5");
36647
37221
  }
36648
37222
  const normalizedCandidates = (Array.isArray(candidateModel) ? candidateModel : [candidateModel]).flatMap((candidate) => candidate.split(",")).map((candidate) => candidate.trim().toLowerCase()).filter((candidate) => candidate.length > 0);
36649
37223
  if (normalizedCandidates.length === 0) {
@@ -36692,9 +37266,7 @@ function createSyntheticEvent(period) {
36692
37266
  reasoningTokens: period.reasoningTokens,
36693
37267
  cacheReadTokens: period.cacheReadTokens,
36694
37268
  cacheWriteTokens: period.cacheWriteTokens,
36695
- // Report rows do not keep the one-hour share, so candidates price every write at the
36696
- // standard cache-write rate.
36697
- cacheWrite1hTokens: 0,
37269
+ cacheWrite1hTokens: period.cacheWrite1hTokens,
36698
37270
  totalTokens: period.totalTokens,
36699
37271
  costMode: "estimated",
36700
37272
  costUsd: void 0
@@ -36797,7 +37369,7 @@ function compareCandidateRank(left, right4) {
36797
37369
  }
36798
37370
  return compareByCodePoint(left.candidateModel, right4.candidateModel);
36799
37371
  }
36800
- function resolveBaselinePeriods(usageRows) {
37372
+ function resolveBaselinePeriods(usageRows, oneHourCacheWritesByPeriod) {
36801
37373
  const periodRows = /* @__PURE__ */ new Map();
36802
37374
  let grandTotalRow;
36803
37375
  for (const row of usageRows) {
@@ -36839,6 +37411,7 @@ function resolveBaselinePeriods(usageRows) {
36839
37411
  reasoningTokens: row.reasoningTokens,
36840
37412
  cacheReadTokens: row.cacheReadTokens,
36841
37413
  cacheWriteTokens: row.cacheWriteTokens,
37414
+ cacheWrite1hTokens: oneHourCacheWritesByPeriod.get(periodKey) ?? 0,
36842
37415
  totalTokens: row.totalTokens,
36843
37416
  baselineCostUsd: row.costUsd,
36844
37417
  baselineCostIncomplete: row.costIncomplete === true
@@ -36853,6 +37426,7 @@ function resolveBaselinePeriods(usageRows) {
36853
37426
  reasoningTokens: allRow.reasoningTokens,
36854
37427
  cacheReadTokens: allRow.cacheReadTokens,
36855
37428
  cacheWriteTokens: allRow.cacheWriteTokens,
37429
+ cacheWrite1hTokens: oneHourCacheWritesByPeriod.get("ALL") ?? 0,
36856
37430
  totalTokens: allRow.totalTokens,
36857
37431
  baselineCostUsd: allRow.costUsd,
36858
37432
  baselineCostIncomplete: allRow.costIncomplete === true
@@ -36865,6 +37439,7 @@ function resolveBaselinePeriods(usageRows) {
36865
37439
  reasoningTokens: 0,
36866
37440
  cacheReadTokens: 0,
36867
37441
  cacheWriteTokens: 0,
37442
+ cacheWrite1hTokens: 0,
36868
37443
  totalTokens: 0,
36869
37444
  baselineCostUsd: 0,
36870
37445
  baselineCostIncomplete: false
@@ -36880,7 +37455,7 @@ function buildWarning(periodKeys) {
36880
37455
  return `Baseline cost exists for zero-token periods (${sortedPeriodKeys.join(", ")}); savings were omitted.`;
36881
37456
  }
36882
37457
  function buildCounterfactualRows(input) {
36883
- const baselinePeriods = resolveBaselinePeriods(input.usageRows);
37458
+ const baselinePeriods = resolveBaselinePeriods(input.usageRows, input.oneHourCacheWritesByPeriod);
36884
37459
  const allPeriod = baselinePeriods.find((period) => period.periodKey === "ALL");
36885
37460
  if (!allPeriod) {
36886
37461
  throw new Error("Missing ALL baseline totals");
@@ -36923,7 +37498,7 @@ function buildCounterfactualRows(input) {
36923
37498
  }
36924
37499
 
36925
37500
  // src/cli/build-optimize-data.ts
36926
- function resolveOptimizeProvider(providers, providerFilter) {
37501
+ function resolveOptimizeProvider(providers, providerFilter, candidateModels) {
36927
37502
  const distinctProviders = collectCanonicalProviderRoots(providers);
36928
37503
  const normalizedProviderFilter = normalizeProviderFilter(providerFilter);
36929
37504
  if (distinctProviders.length > 1) {
@@ -36945,8 +37520,12 @@ function resolveOptimizeProvider(providers, providerFilter) {
36945
37520
  );
36946
37521
  }
36947
37522
  }
37523
+ const candidateProviders = inferCanonicalProviderRootsFromModels(candidateModels);
37524
+ const suggestedProvider = candidateProviders?.length === 1 && distinctProviders.includes(candidateProviders[0]) && distinctProviders.filter(
37525
+ (provider) => matchesCanonicalProviderFilter(provider, candidateProviders[0])
37526
+ ).length === 1 ? ` ${candidateProviders[0]}` : "";
36948
37527
  throw new Error(
36949
- `Optimize requires a single provider; found providers: ${distinctProviders.join(", ")}. Narrow with --provider.`
37528
+ `Optimize requires a single provider; found providers: ${distinctProviders.join(", ")}. Narrow with --provider${suggestedProvider}.`
36950
37529
  );
36951
37530
  }
36952
37531
  if (distinctProviders.length === 1) {
@@ -36968,7 +37547,11 @@ async function buildOptimizeData(granularity, options, deps = {}) {
36968
37547
  const provider = measureRuntimeProfileStageSync(
36969
37548
  deps.runtimeProfile,
36970
37549
  "optimize.resolve_provider",
36971
- () => resolveOptimizeProvider(detectedProviders, dataset.normalizedInputs.providerFilter)
37550
+ () => resolveOptimizeProvider(
37551
+ detectedProviders,
37552
+ dataset.normalizedInputs.providerFilter,
37553
+ candidateModels
37554
+ )
36972
37555
  );
36973
37556
  const { pricedEvents, pricingOrigin, pricingWarning, pricingSource } = await applyPricingToUsageEventDataset(dataset, deps, "force");
36974
37557
  const usageRows = measureRuntimeProfileStageSync(
@@ -36981,11 +37564,25 @@ async function buildOptimizeData(granularity, options, deps = {}) {
36981
37564
  includeModelBreakdown: false
36982
37565
  })
36983
37566
  );
37567
+ const oneHourCacheWritesByPeriod = /* @__PURE__ */ new Map();
37568
+ for (const event of pricedEvents) {
37569
+ if (event.cacheWrite1hTokens === 0) {
37570
+ continue;
37571
+ }
37572
+ const periodKey = getPeriodKey(event.timestamp, granularity, dataset.normalizedInputs.timezone);
37573
+ for (const key of [periodKey, "ALL"]) {
37574
+ oneHourCacheWritesByPeriod.set(
37575
+ key,
37576
+ (oneHourCacheWritesByPeriod.get(key) ?? 0) + event.cacheWrite1hTokens
37577
+ );
37578
+ }
37579
+ }
36984
37580
  const counterfactual = measureRuntimeProfileStageSync(
36985
37581
  deps.runtimeProfile,
36986
37582
  "optimize.counterfactual",
36987
37583
  () => buildCounterfactualRows({
36988
37584
  usageRows,
37585
+ oneHourCacheWritesByPeriod,
36989
37586
  provider,
36990
37587
  candidateModels,
36991
37588
  pricingSource,
@@ -36999,7 +37596,7 @@ async function buildOptimizeData(granularity, options, deps = {}) {
36999
37596
  pricingOrigin,
37000
37597
  pricingWarning,
37001
37598
  warnings: dataset.warnings,
37002
- notes: dataset.notes,
37599
+ notes: withUnpricedEventsNote(dataset.notes, pricedEvents),
37003
37600
  activeEnvOverrides: dataset.readEnvVarOverrides(),
37004
37601
  activeConfig: dataset.activeConfig,
37005
37602
  timezone: dataset.normalizedInputs.timezone,
@@ -37140,12 +37737,7 @@ function formatModels2(models) {
37140
37737
  if (models.length === 0) {
37141
37738
  return "-";
37142
37739
  }
37143
- if (models.length <= MAX_NAMED_MODELS) {
37144
- return models.join("\n");
37145
- }
37146
- return [...models.slice(0, MAX_NAMED_MODELS), `+${models.length - MAX_NAMED_MODELS} more`].join(
37147
- "\n"
37148
- );
37740
+ return limitNamedModels(models, MAX_NAMED_MODELS).join("\n");
37149
37741
  }
37150
37742
  function toSessionTableCells(row, options) {
37151
37743
  return [
@@ -37205,7 +37797,7 @@ function renderTerminalSessionReport(sessionData, options) {
37205
37797
  ""
37206
37798
  ];
37207
37799
  if (bodyRows.length === 0) {
37208
- outputLines.push("No usage data found for the selected filters.");
37800
+ outputLines.push(formatNoUsageMessage(options.filtered ?? false));
37209
37801
  return outputLines.join("\n");
37210
37802
  }
37211
37803
  const modelsColumnIndex3 = headers.length - 1;
@@ -37222,7 +37814,9 @@ function renderTerminalSessionReport(sessionData, options) {
37222
37814
  rowMetas: wrappedRows.map(() => toRowMeta()),
37223
37815
  layout: "top_aligned",
37224
37816
  multilineColumnIndex: modelsColumnIndex3,
37225
- multilineColumnWidth: MODELS_COLUMN_WIDTH
37817
+ multilineColumnWidth: MODELS_COLUMN_WIDTH,
37818
+ terminalWidth: resolveTtyColumns(process.stdout),
37819
+ stackedTitleColumnCount: 1
37226
37820
  })
37227
37821
  );
37228
37822
  return outputLines.join("\n");
@@ -37504,14 +38098,14 @@ async function buildSessionData(options, deps = {}) {
37504
38098
  since: options.since,
37505
38099
  until: options.until
37506
38100
  };
37507
- const diagnostics = buildUsageDiagnostics({
38101
+ const buildDiagnostics = (shownEvents) => buildUsageDiagnostics({
37508
38102
  adaptersToParse: dataset.adaptersToParse,
37509
38103
  successfulParseResults: dataset.successfulParseResults,
37510
38104
  sourceFailures: dataset.sourceFailures,
37511
38105
  pricingOrigin,
37512
38106
  pricingWarning,
37513
38107
  warnings: dataset.warnings,
37514
- notes: dataset.notes,
38108
+ notes: withUnpricedEventsNote(dataset.notes, shownEvents),
37515
38109
  activeEnvOverrides: dataset.readEnvVarOverrides(),
37516
38110
  activeConfig: dataset.activeConfig,
37517
38111
  timezone: dataset.normalizedInputs.timezone,
@@ -37524,11 +38118,14 @@ async function buildSessionData(options, deps = {}) {
37524
38118
  () => aggregateSessionsByRepo(pricedEvents, aggregateOptions)
37525
38119
  );
37526
38120
  const limited2 = limitRows(repoRows, limit, "repos");
38121
+ const shownRepos = new Set(limited2.rows.map((row) => row.repoRoot ?? ""));
37527
38122
  return {
37528
38123
  grouping: "repo",
37529
38124
  rows: limited2.rows,
37530
38125
  limitNote: limited2.limitNote,
37531
- diagnostics
38126
+ diagnostics: buildDiagnostics(
38127
+ pricedEvents.filter((event) => shownRepos.has(event.repoRoot ?? ""))
38128
+ )
37532
38129
  };
37533
38130
  }
37534
38131
  const sessionRows = measureRuntimeProfileStageSync(
@@ -37537,11 +38134,14 @@ async function buildSessionData(options, deps = {}) {
37537
38134
  () => aggregateSessions(pricedEvents, { ...aggregateOptions, ids: idFilters })
37538
38135
  );
37539
38136
  const limited = limitRows(sessionRows, limit, "sessions");
38137
+ const shownSessions = new Set(limited.rows.map((row) => getEventSessionKey(row)));
37540
38138
  return {
37541
38139
  grouping: "session",
37542
38140
  rows: limited.rows,
37543
38141
  limitNote: limited.limitNote,
37544
- diagnostics
38142
+ diagnostics: buildDiagnostics(
38143
+ pricedEvents.filter((event) => shownSessions.has(getEventSessionKey(event)))
38144
+ )
37545
38145
  };
37546
38146
  }
37547
38147
 
@@ -37559,7 +38159,8 @@ async function prepareSessionReport(options, deps = {}) {
37559
38159
  runtimeProfile: deps.runtimeProfile,
37560
38160
  render: (sessionData, format) => renderSessionReport(sessionData, format, {
37561
38161
  timezone: sessionData.diagnostics.timezone,
37562
- truncateSessionIds: !options.id?.length
38162
+ truncateSessionIds: !options.id?.length,
38163
+ filtered: hasCliFilters(options)
37563
38164
  })
37564
38165
  });
37565
38166
  }
@@ -37676,6 +38277,19 @@ function toTableRow(period) {
37676
38277
  function hasAnyUsage(summaryData) {
37677
38278
  return summaryData.periods.some((period) => period.totals.events > 0);
37678
38279
  }
38280
+ function toNoRecentUsageMessage(summaryData, filtered) {
38281
+ if (filtered) {
38282
+ return "No usage in the last 7 days or this month for the selected filters.";
38283
+ }
38284
+ const latestDay = summaryData.activity.days.filter((day) => day.totalTokens > 0).at(-1);
38285
+ if (latestDay) {
38286
+ return `No usage in the last 7 days or this month. Latest usage: ${latestDay.date}. Try \`llm-usage monthly --all\`.`;
38287
+ }
38288
+ if (summaryData.diagnostics.sessionStats.some((stat10) => stat10.filesFound > 0)) {
38289
+ return "No usage in the past year. Try `llm-usage monthly --all`.";
38290
+ }
38291
+ return "No usage found in the last 7 days or this month. Run `llm-usage doctor` to see which sources were found.";
38292
+ }
37679
38293
  function getTitle(summaryData) {
37680
38294
  const today = summaryData.periods[0]?.until ?? "";
37681
38295
  return `Usage summary for ${today} (${summaryData.timezone})`;
@@ -37803,12 +38417,16 @@ function renderTerminalSummaryReport(summaryData, options) {
37803
38417
  periodKey: "summary",
37804
38418
  rowKind: "detail"
37805
38419
  }));
37806
- const lines = [renderReportHeader({ title: getTitle(summaryData), useColor }), ""];
38420
+ const lines = [
38421
+ renderReportHeader({
38422
+ title: getTitle(summaryData),
38423
+ useColor,
38424
+ terminalWidth: options.terminalWidth
38425
+ }),
38426
+ ""
38427
+ ];
37807
38428
  if (!hasAnyUsage(summaryData)) {
37808
- lines.push(
37809
- "No usage found in the last 7 days or this month. Run `llm-usage doctor` to see which sources were found."
37810
- );
37811
- lines.push("");
38429
+ lines.push(toNoRecentUsageMessage(summaryData, options.filtered ?? false), "");
37812
38430
  }
37813
38431
  lines.push(
37814
38432
  renderUnicodeTable({
@@ -37820,7 +38438,8 @@ function renderTerminalSummaryReport(summaryData, options) {
37820
38438
  layout: "compact",
37821
38439
  multilineColumnIndex: 0,
37822
38440
  multilineColumnWidth: 13,
37823
- leftAlignedColumnIndexes: [3]
38441
+ leftAlignedColumnIndexes: [3],
38442
+ terminalWidth: options.terminalWidth ?? resolveTtyColumns(process.stdout)
37824
38443
  })
37825
38444
  );
37826
38445
  const monthNotes = toMonthNotes(summaryData);
@@ -38291,6 +38910,10 @@ async function buildSummaryData(options, deps = {}) {
38291
38910
  pricedEvents.filter((event) => isEventWithinWindow(event, monthToDateWindow, timezone)),
38292
38911
  pricingSource
38293
38912
  ) : void 0;
38913
+ const bestDay = activity.bestDay?.costUsd === void 0 ? void 0 : activity.bestDay.date;
38914
+ const shownCostEvents = pricedEvents.filter(
38915
+ (event) => windows.some((window) => isEventWithinWindow(event, window, timezone)) || getPeriodKey(event.timestamp, "daily", timezone) === bestDay
38916
+ );
38294
38917
  return {
38295
38918
  timezone,
38296
38919
  periods,
@@ -38308,7 +38931,7 @@ async function buildSummaryData(options, deps = {}) {
38308
38931
  pricingOrigin,
38309
38932
  pricingWarning,
38310
38933
  warnings: dataset.warnings,
38311
- notes: dataset.notes,
38934
+ notes: withUnpricedEventsNote(dataset.notes, shownCostEvents),
38312
38935
  activeEnvOverrides: dataset.readEnvVarOverrides(),
38313
38936
  activeConfig: dataset.activeConfig,
38314
38937
  timezone,
@@ -38333,7 +38956,7 @@ async function prepareSummaryReport(options, deps = {}) {
38333
38956
  title: "Activity share card",
38334
38957
  render: (theme) => renderSummaryShareSvg(summaryData, theme)
38335
38958
  }) : void 0,
38336
- render: (summaryData, format) => renderSummaryReport(summaryData, format),
38959
+ render: (summaryData, format) => renderSummaryReport(summaryData, format, { filtered: hasCliFilters(options) }),
38337
38960
  getHintsAfterOutput: (_summaryData, format) => format === "terminal" ? [SUMMARY_NEXT_STEPS_HINT] : []
38338
38961
  });
38339
38962
  }
@@ -38428,9 +39051,6 @@ function formatStatusline(summary, session) {
38428
39051
  return parts.join(SEPARATOR);
38429
39052
  }
38430
39053
  async function buildStatuslineSummary(options, deps) {
38431
- if (options.json) {
38432
- throw new Error("--json is not supported for statusline; use llm-usage summary --json");
38433
- }
38434
39054
  return buildSummaryData({ ...options, pricingOffline: true, sync: false }, deps);
38435
39055
  }
38436
39056
  async function runStatusline(options, deps = {}) {
@@ -38577,7 +39197,7 @@ function renderSummaryOnly(trendsData, options) {
38577
39197
  }
38578
39198
  if (trendsData.totalSeries.summary.observedDayCount === 0) {
38579
39199
  lines.push("No usage data found for the selected date range.");
38580
- lines.push("");
39200
+ return lines.join("\n");
38581
39201
  }
38582
39202
  if (shouldShowNoResolvedCostDataNote(trendsData)) {
38583
39203
  lines.push(
@@ -38736,8 +39356,9 @@ function renderTerminalTrendsReport(trendsData, options) {
38736
39356
  const terminalWidth = resolveTerminalWidth2(options.terminalWidth);
38737
39357
  const accent = trendsData.metric === "cost" ? pc8.green : pc8.cyan;
38738
39358
  const minimumChartWidth = 40;
38739
- if (terminalWidth !== void 0 && terminalWidth < minimumChartWidth) {
38740
- return renderSummaryOnly(trendsData, { useColor, narrowNote: true });
39359
+ const isEmpty = trendsData.totalSeries.summary.observedDayCount === 0;
39360
+ if (isEmpty || terminalWidth !== void 0 && terminalWidth < minimumChartWidth) {
39361
+ return renderSummaryOnly(trendsData, { useColor, narrowNote: !isEmpty });
38741
39362
  }
38742
39363
  const lines = [
38743
39364
  renderReportHeader({
@@ -38746,10 +39367,6 @@ function renderTerminalTrendsReport(trendsData, options) {
38746
39367
  }),
38747
39368
  ""
38748
39369
  ];
38749
- if (trendsData.totalSeries.summary.observedDayCount === 0) {
38750
- lines.push("No usage data found for the selected date range.");
38751
- lines.push("");
38752
- }
38753
39370
  if (shouldShowNoResolvedCostDataNote(trendsData)) {
38754
39371
  lines.push(
38755
39372
  "No resolved cost data for the selected range; use pricing or switch to --metric tokens."
@@ -39100,16 +39717,6 @@ function aggregateTrends(rows, options) {
39100
39717
  }
39101
39718
 
39102
39719
  // src/cli/build-trends-data.ts
39103
- function parseDaysOption(days) {
39104
- if (days === void 0) {
39105
- return void 0;
39106
- }
39107
- const trimmedDays = days.trim();
39108
- if (!/^[1-9]\d*$/u.test(trimmedDays)) {
39109
- throw new Error("--days must be a positive integer");
39110
- }
39111
- return Number.parseInt(trimmedDays, 10);
39112
- }
39113
39720
  function resolveMetric(metric) {
39114
39721
  if (metric === void 0 || metric.trim() === "") {
39115
39722
  return "cost";
@@ -39180,8 +39787,13 @@ function resolveOutputDateRange(options, today, days, observedDates) {
39180
39787
  };
39181
39788
  }
39182
39789
  function resolveTrendsOptions(options, timezone, now) {
39183
- if (options.days !== void 0 && (options.since || options.until)) {
39184
- throw new Error("--days cannot be combined with --since or --until");
39790
+ if (options.last !== void 0 && options.days !== void 0) {
39791
+ throw new Error("--days is an alias of --last; pass only --last");
39792
+ }
39793
+ const daysFlag = options.days === void 0 ? "--last" : "--days";
39794
+ const daysValue = options.last ?? options.days;
39795
+ if (daysValue !== void 0 && (options.since || options.until)) {
39796
+ throw new Error(`${daysFlag} cannot be combined with --since or --until`);
39185
39797
  }
39186
39798
  if (options.since) {
39187
39799
  validateDateInput(options.since, "--since");
@@ -39193,7 +39805,7 @@ function resolveTrendsOptions(options, timezone, now) {
39193
39805
  throw new Error("--since must be less than or equal to --until");
39194
39806
  }
39195
39807
  const metric = resolveMetric(options.metric);
39196
- const days = parseDaysOption(options.days);
39808
+ const days = parseLastOption(daysValue, daysFlag);
39197
39809
  const today = getCurrentLocalDateKey(timezone, now);
39198
39810
  return {
39199
39811
  days,
@@ -39300,6 +39912,10 @@ async function buildTrendsData(options, deps = {}) {
39300
39912
  sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
39301
39913
  })
39302
39914
  );
39915
+ const chartedEvents = resolved.metric === "cost" ? pricingResult.pricedEvents.filter((event) => {
39916
+ const day = getPeriodKey(event.timestamp, "daily", dataset.normalizedInputs.timezone);
39917
+ return day >= outputDateRange.from && day <= outputDateRange.to;
39918
+ }) : [];
39303
39919
  const diagnostics = buildUsageDiagnostics({
39304
39920
  adaptersToParse: dataset.adaptersToParse,
39305
39921
  successfulParseResults: dataset.successfulParseResults,
@@ -39307,7 +39923,7 @@ async function buildTrendsData(options, deps = {}) {
39307
39923
  pricingOrigin: pricingResult.pricingOrigin,
39308
39924
  pricingWarning: pricingResult.pricingWarning,
39309
39925
  warnings: dataset.warnings,
39310
- notes: dataset.notes,
39926
+ notes: withUnpricedEventsNote(dataset.notes, chartedEvents),
39311
39927
  activeEnvOverrides: dataset.readEnvVarOverrides(),
39312
39928
  activeConfig: dataset.activeConfig,
39313
39929
  timezone: dataset.normalizedInputs.timezone,
@@ -39460,7 +40076,8 @@ function describeTableFit(fit, compact, hideCost = false) {
39460
40076
  if (changes.length === 0) {
39461
40077
  return void 0;
39462
40078
  }
39463
- return `Fitted the table to the terminal: ${changes.join(", ")}. Widen the terminal or use --json for full detail.`;
40079
+ const layout = fit.stacked ? "Listed rows instead of a table to fit the terminal" : "Fitted the table to the terminal";
40080
+ return `${layout}: ${changes.join(", ")}. Widen the terminal or use --json for full detail.`;
39464
40081
  }
39465
40082
  function renderTerminalUsageReport(usageData, options) {
39466
40083
  const outputLines = [];
@@ -39469,12 +40086,13 @@ function renderTerminalUsageReport(usageData, options) {
39469
40086
  outputLines.push(
39470
40087
  renderReportHeader({
39471
40088
  title: getReportTitle4(options.granularity),
39472
- useColor
40089
+ useColor,
40090
+ terminalWidth: options.terminalWidth
39473
40091
  })
39474
40092
  );
39475
40093
  outputLines.push("");
39476
- if (usageData.rows.length === 0) {
39477
- outputLines.push("No usage data found for the selected filters.");
40094
+ if (usageData.rows.every((row) => row.rowType === "grand_total")) {
40095
+ outputLines.push(formatNoUsageMessage(options.filtered ?? false));
39478
40096
  return { output: outputLines.join("\n"), notes: [] };
39479
40097
  }
39480
40098
  const { output, fit } = renderTerminalTableWithFit(usageData.rows, {
@@ -39736,7 +40354,8 @@ async function prepareUsageReport(granularity, options, deps = {}) {
39736
40354
  granularity,
39737
40355
  tableLayout,
39738
40356
  compact: options.compact,
39739
- hideCost
40357
+ hideCost,
40358
+ filtered: hasCliFilters(options)
39740
40359
  });
39741
40360
  return { output, hintsAfterOutput: notes };
39742
40361
  },
@@ -40406,9 +41025,6 @@ async function runPruneReport(options, deps = {}) {
40406
41025
  var eventsExportFormats = ["jsonl", "csv"];
40407
41026
  var CSV_HEADER = "source,sessionId,timestamp,repoRoot,provider,model,inputTokens,outputTokens,reasoningTokens,cacheReadTokens,cacheWriteTokens,totalTokens,costUsd,costMode,machine";
40408
41027
  function resolveEventsFormat(options) {
40409
- if (options.json) {
40410
- throw new Error("--json is not supported for events; use --format jsonl");
40411
- }
40412
41028
  const format = options.format ?? "jsonl";
40413
41029
  const resolved = eventsExportFormats.find((candidate) => candidate === format);
40414
41030
  if (!resolved) {
@@ -40594,17 +41210,31 @@ function renderTopTable(headerCells, bodyRows) {
40594
41210
  rowMetas,
40595
41211
  layout: "compact",
40596
41212
  multilineColumnIndex: TOP_ITEM_NAME_COLUMN,
40597
- multilineColumnWidth: nameColumnWidth
41213
+ multilineColumnWidth: nameColumnWidth,
41214
+ terminalWidth: resolveTtyColumns(process.stdout)
40598
41215
  });
40599
41216
  }
41217
+ function toEmptyMessage(recap, filtered) {
41218
+ if (recap.eventCount > 0) {
41219
+ return void 0;
41220
+ }
41221
+ return filtered ? formatNoUsageMessage(true) : `No usage in ${recap.year}.`;
41222
+ }
40600
41223
  function renderWrappedTerminalReport(recap, options) {
40601
41224
  const useColor = options.useColor ?? shouldUseColorByDefault();
40602
41225
  const palette = options.palette ?? defaultTerminalStylePalette;
40603
41226
  const paint = (styler) => (text2) => useColor ? styler(text2) : text2;
40604
- return [
41227
+ const heading = [
40605
41228
  renderReportHeader({ title: `Wrapped ${recap.year}`, useColor }),
40606
41229
  paint(palette.dim)(`${recap.from} to ${recap.to} (${recap.timezone})`),
40607
- "",
41230
+ ""
41231
+ ];
41232
+ const emptyMessage = toEmptyMessage(recap, options.filtered ?? false);
41233
+ if (emptyMessage) {
41234
+ return [...heading, emptyMessage].join("\n");
41235
+ }
41236
+ return [
41237
+ ...heading,
40608
41238
  renderStatLines(recap, paint, palette),
40609
41239
  "",
40610
41240
  "Monthly activity",
@@ -41022,7 +41652,7 @@ async function buildWrappedData(options, deps = {}) {
41022
41652
  pricingOrigin,
41023
41653
  pricingWarning,
41024
41654
  warnings: dataset.warnings,
41025
- notes: dataset.notes,
41655
+ notes: withUnpricedEventsNote(dataset.notes, pricedEvents),
41026
41656
  activeEnvOverrides: dataset.readEnvVarOverrides(),
41027
41657
  activeConfig: dataset.activeConfig,
41028
41658
  timezone: dataset.normalizedInputs.timezone,
@@ -41051,7 +41681,7 @@ async function prepareWrappedReport(options, deps = {}) {
41051
41681
  title: `${wrappedData.recap.year} Wrapped share card`,
41052
41682
  render: (theme) => renderWrappedShareSvg(wrappedData.recap, theme)
41053
41683
  }) : void 0,
41054
- render: (wrappedData, format) => renderWrappedReport(wrappedData.recap, format)
41684
+ render: (wrappedData, format) => renderWrappedReport(wrappedData.recap, format, { filtered: hasCliFilters(options) })
41055
41685
  });
41056
41686
  }
41057
41687
  async function runWrappedReport(options) {
@@ -41064,102 +41694,144 @@ async function runWrappedReport(options) {
41064
41694
  import { Option } from "commander";
41065
41695
  var sharedOptionProfileConfig = {
41066
41696
  usage: {
41697
+ includeJson: true,
41698
+ includeQuiet: true,
41067
41699
  includeDateFilters: true,
41068
41700
  includeMarkdown: true,
41069
41701
  includePerModelColumns: true,
41070
41702
  includePricing: true,
41703
+ includePricingFetch: true,
41071
41704
  includeProviderModelFilters: true,
41072
41705
  includeHistory: true,
41706
+ includeSync: true,
41073
41707
  includeShare: true,
41074
41708
  includeTimezone: true
41075
41709
  },
41076
41710
  summary: {
41711
+ includeJson: true,
41712
+ includeQuiet: true,
41077
41713
  includeDateFilters: false,
41078
41714
  includeMarkdown: true,
41079
41715
  includePerModelColumns: false,
41080
41716
  includePricing: true,
41717
+ includePricingFetch: true,
41081
41718
  includeProviderModelFilters: true,
41082
41719
  includeHistory: true,
41720
+ includeSync: true,
41083
41721
  includeShare: true,
41084
41722
  includeTimezone: true
41085
41723
  },
41086
41724
  specialized: {
41725
+ includeJson: true,
41726
+ includeQuiet: true,
41087
41727
  includeDateFilters: true,
41088
41728
  includeMarkdown: true,
41089
41729
  includePerModelColumns: false,
41090
41730
  includePricing: true,
41731
+ includePricingFetch: true,
41091
41732
  includeProviderModelFilters: true,
41092
41733
  includeHistory: true,
41734
+ includeSync: true,
41093
41735
  includeShare: true,
41094
41736
  includeTimezone: true
41095
41737
  },
41096
41738
  compare: {
41739
+ includeJson: true,
41740
+ includeQuiet: true,
41097
41741
  includeDateFilters: true,
41098
41742
  includeMarkdown: true,
41099
41743
  includePerModelColumns: false,
41100
41744
  includePricing: true,
41745
+ includePricingFetch: true,
41101
41746
  includeProviderModelFilters: true,
41102
41747
  includeHistory: true,
41748
+ includeSync: true,
41103
41749
  includeShare: true,
41104
41750
  includeTimezone: true
41105
41751
  },
41106
41752
  trends: {
41753
+ includeJson: true,
41754
+ includeQuiet: true,
41107
41755
  includeDateFilters: true,
41108
41756
  includeMarkdown: true,
41109
41757
  includePerModelColumns: false,
41110
41758
  includePricing: true,
41759
+ includePricingFetch: true,
41111
41760
  includeProviderModelFilters: true,
41112
41761
  includeHistory: true,
41762
+ includeSync: true,
41113
41763
  includeShare: true,
41114
41764
  includeTimezone: true
41115
41765
  },
41116
41766
  session: {
41767
+ includeJson: true,
41768
+ includeQuiet: true,
41117
41769
  includeDateFilters: true,
41118
41770
  includeMarkdown: true,
41119
41771
  includePerModelColumns: false,
41120
41772
  includePricing: true,
41773
+ includePricingFetch: true,
41121
41774
  includeProviderModelFilters: true,
41122
41775
  includeHistory: true,
41776
+ includeSync: true,
41123
41777
  includeShare: false,
41124
41778
  includeTimezone: true
41125
41779
  },
41126
41780
  wrapped: {
41781
+ includeJson: true,
41782
+ includeQuiet: true,
41127
41783
  includeDateFilters: false,
41128
41784
  includeMarkdown: true,
41129
41785
  includePerModelColumns: false,
41130
41786
  includePricing: true,
41787
+ includePricingFetch: true,
41131
41788
  includeProviderModelFilters: true,
41132
41789
  includeHistory: true,
41790
+ includeSync: true,
41133
41791
  includeShare: true,
41134
41792
  includeTimezone: true
41135
41793
  },
41136
41794
  events: {
41795
+ includeJson: false,
41796
+ jsonAlternative: "use --format jsonl",
41797
+ includeQuiet: true,
41137
41798
  includeDateFilters: true,
41138
41799
  includeMarkdown: false,
41139
41800
  includePerModelColumns: false,
41140
41801
  includePricing: true,
41802
+ includePricingFetch: true,
41141
41803
  includeProviderModelFilters: true,
41142
41804
  includeHistory: true,
41805
+ includeSync: true,
41143
41806
  includeShare: false,
41144
41807
  includeTimezone: true
41145
41808
  },
41146
41809
  statusline: {
41810
+ includeJson: false,
41811
+ jsonAlternative: "use llm-usage summary --json",
41812
+ includeQuiet: false,
41147
41813
  includeDateFilters: false,
41148
41814
  includeMarkdown: false,
41149
41815
  includePerModelColumns: false,
41150
41816
  includePricing: true,
41817
+ includePricingFetch: false,
41151
41818
  includeProviderModelFilters: true,
41152
41819
  includeHistory: true,
41820
+ includeSync: false,
41153
41821
  includeShare: false,
41154
41822
  includeTimezone: true
41155
41823
  },
41156
41824
  doctor: {
41825
+ includeJson: true,
41826
+ includeQuiet: true,
41157
41827
  includeDateFilters: false,
41158
41828
  includeMarkdown: false,
41159
41829
  includePerModelColumns: false,
41160
41830
  includePricing: false,
41831
+ includePricingFetch: false,
41161
41832
  includeProviderModelFilters: false,
41162
41833
  includeHistory: false,
41834
+ includeSync: false,
41163
41835
  includeShare: false,
41164
41836
  includeTimezone: false
41165
41837
  }
@@ -41200,6 +41872,20 @@ function suggestHiddenOptionsOnTypo(command) {
41200
41872
  }
41201
41873
  };
41202
41874
  }
41875
+ function redirectUnsupportedJson(command, alternative) {
41876
+ if (!hasUnknownOptionHook(command)) {
41877
+ return;
41878
+ }
41879
+ const reportUnknownOption = command.unknownOption.bind(command);
41880
+ command.unknownOption = (flag) => {
41881
+ if (flag === "--json") {
41882
+ command.error(`error: --json is not supported for ${command.name()}; ${alternative}`, {
41883
+ code: "commander.unknownOption"
41884
+ });
41885
+ }
41886
+ reportUnknownOption(flag);
41887
+ };
41888
+ }
41203
41889
  function registerSourcePathOptions(command) {
41204
41890
  for (const overrideOption of getSourceOverrideOptions()) {
41205
41891
  const option = new Option(
@@ -41216,7 +41902,7 @@ function registerSourcePathOptions(command) {
41216
41902
  command.help();
41217
41903
  }).addHelpText(
41218
41904
  "after",
41219
- () => hasHiddenOptions(command) ? "\nPer-source path flags (--claude-dir, --codex-dir, --opencode-db, ...) are\nlisted by --help-all." : ""
41905
+ () => hasHiddenOptions(command) ? "\nPer-source path flags (--claude-dir, ...)\nare listed by --help-all." : ""
41220
41906
  );
41221
41907
  suggestHiddenOptionsOnTypo(command);
41222
41908
  }
@@ -41241,7 +41927,19 @@ function registerSharedReportOptions(command, profile) {
41241
41927
  "--source <name>",
41242
41928
  `Filter by source id (repeatable or comma-separated, supported sources ${supportedSourcesSummary})`,
41243
41929
  collectRepeatedOption
41244
- ).option("--json", "Render output as JSON").option("--quiet", "Suppress informational stderr output (warnings still print)").option(
41930
+ );
41931
+ if (profileConfig.includeJson) {
41932
+ configuredCommand.option("--json", "Render output as JSON");
41933
+ } else {
41934
+ redirectUnsupportedJson(configuredCommand, profileConfig.jsonAlternative);
41935
+ }
41936
+ if (profileConfig.includeQuiet) {
41937
+ configuredCommand.option(
41938
+ "--quiet",
41939
+ "Suppress informational stderr output (warnings still print)"
41940
+ );
41941
+ }
41942
+ configuredCommand.option(
41245
41943
  "--verbose",
41246
41944
  "Print detailed diagnostics on stderr (per-source counts, routine skips, config, env)"
41247
41945
  );
@@ -41265,10 +41963,20 @@ function registerSharedReportOptions(command, profile) {
41265
41963
  );
41266
41964
  }
41267
41965
  if (profileConfig.includePricing) {
41268
- configuredCommand.option("--pricing-url <url>", "Override LiteLLM pricing source URL").option(
41966
+ if (profileConfig.includePricingFetch) {
41967
+ configuredCommand.option("--pricing-url <url>", "Override LiteLLM pricing source URL");
41968
+ }
41969
+ configuredCommand.option(
41269
41970
  "--pricing-overrides <path>",
41270
41971
  "Path to a JSON file of per-model pricing overrides (takes precedence over LiteLLM)"
41271
- ).option("--pricing-offline", "Use cached LiteLLM pricing only (no network fetch)").option(
41972
+ );
41973
+ if (profileConfig.includePricingFetch) {
41974
+ configuredCommand.option(
41975
+ "--pricing-offline",
41976
+ "Use cached LiteLLM pricing only (no network fetch)"
41977
+ );
41978
+ }
41979
+ configuredCommand.option(
41272
41980
  "--ignore-pricing-failures",
41273
41981
  "Continue without estimated costs when pricing cannot be loaded"
41274
41982
  );
@@ -41276,12 +41984,18 @@ function registerSharedReportOptions(command, profile) {
41276
41984
  if (profileConfig.includeHistory) {
41277
41985
  configuredCommand.option(
41278
41986
  "--history",
41279
- "include usage from files that no longer exist on disk (the default; fails if the event store is unavailable)"
41280
- ).option("--no-history", "leave out usage from files that no longer exist on disk").option(
41987
+ "Include usage from files that no longer exist on disk (on by default; when passed, fails if the event store is unavailable)"
41988
+ ).option("--no-history", "Leave out usage from files that no longer exist on disk").option(
41281
41989
  "--machine <name>",
41282
41990
  "Count only these machines (repeatable or comma-separated; local is this one; default: all)",
41283
41991
  collectRepeatedOption
41284
- ).option("--no-sync", "use other machines' cached usage without refreshing it over ssh");
41992
+ );
41993
+ if (profileConfig.includeSync) {
41994
+ configuredCommand.option(
41995
+ "--no-sync",
41996
+ "Use other machines' cached usage without refreshing it over ssh"
41997
+ );
41998
+ }
41285
41999
  }
41286
42000
  if (profileConfig.includeMarkdown) {
41287
42001
  configuredCommand.option("--markdown", "Render output as markdown table");
@@ -41323,6 +42037,15 @@ function parseGranularityArgument(value) {
41323
42037
  `Invalid granularity: ${value}. Expected one of: ${GRANULARITY_ARGUMENT_VALUES.join(", ")}`
41324
42038
  );
41325
42039
  }
42040
+ function addGranularityArgument(command) {
42041
+ const reportMissingGranularity = (name) => command.error(
42042
+ `error: missing required argument '${name}' (choose ${GRANULARITY_ARGUMENT_VALUES.join(", ")})`,
42043
+ { code: "commander.missingArgument" }
42044
+ );
42045
+ return Object.assign(command, { missingArgument: reportMissingGranularity }).addArgument(
42046
+ new Argument("<granularity>", "Report period").choices(GRANULARITY_ARGUMENT_VALUES).argParser(parseGranularityArgument)
42047
+ );
42048
+ }
41326
42049
  var MAX_COMMAND_HELP_EXAMPLES = 4;
41327
42050
  function formatHelpExamples(commands) {
41328
42051
  return commands.length === 0 ? "" : ["", "Examples:", ...commands.map((command) => ` $ ${command}`)].join("\n");
@@ -41507,7 +42230,7 @@ var efficiencyReportDefinition = {
41507
42230
  ]
41508
42231
  },
41509
42232
  register(command) {
41510
- command.argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option("--repo-dir <path>", "Path to repository for Git outcome metrics").option("--include-merge-commits", "Include merge commits in Git outcome metrics").option("--by-source", "Include per-source usage rows under each period").action(
42233
+ addGranularityArgument(command).option("--repo-dir <path>", "Path to repository for Git outcome metrics").option("--include-merge-commits", "Include merge commits in Git outcome metrics").option("--by-source", "Include per-source usage rows under each period").action(
41511
42234
  (granularity, options) => runEfficiencyReport(granularity, options)
41512
42235
  );
41513
42236
  return command;
@@ -41557,7 +42280,7 @@ var optimizeReportDefinition = {
41557
42280
  ]
41558
42281
  },
41559
42282
  register(command) {
41560
- command.argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option(
42283
+ addGranularityArgument(command).option(
41561
42284
  "--candidate-model <name>",
41562
42285
  "Candidate model for counterfactual pricing (repeatable or comma-separated)",
41563
42286
  collectRepeatedOption
@@ -41581,7 +42304,7 @@ var trendsReportDefinition = {
41581
42304
  includeInCliReference: true
41582
42305
  },
41583
42306
  {
41584
- command: "llm-usage trends --metric tokens --days 7",
42307
+ command: "llm-usage trends --metric tokens --last 7",
41585
42308
  includeInCliReference: true
41586
42309
  },
41587
42310
  {
@@ -41592,9 +42315,9 @@ var trendsReportDefinition = {
41592
42315
  },
41593
42316
  register(command) {
41594
42317
  command.option(
41595
- "--days <n>",
41596
- "Trailing local calendar days to chart; defaults to 30 when no date flags are provided"
41597
- ).option("--metric <name>", "Trend metric: cost | tokens | active-hours", "cost").option(
42318
+ "--last <n>",
42319
+ "Chart the last N local calendar days, counting today; defaults to 30 when no date flags are provided"
42320
+ ).addOption(new Option2("--days <n>", "Alias of --last").hideHelp()).option("--metric <name>", "Trend metric: cost | tokens | active-hours", "cost").option(
41598
42321
  "--by-source",
41599
42322
  "Render one sparkline row per source instead of a single combined chart"
41600
42323
  ).action((options) => runTrendsReport(options));
@@ -42015,7 +42738,7 @@ async function runMachineExport(options, deps = {}) {
42015
42738
  knownFiles
42016
42739
  );
42017
42740
  const end = lines.at(-1);
42018
- const missingEventCount = dataset.filteredEvents.length - (end?.type === "end" ? end.eventCount : 0);
42741
+ const missingEventCount = dataset.filteredEvents.length + dataset.ledger.copiedEventCount - (end?.type === "end" ? end.eventCount : 0);
42019
42742
  if (missingEventCount > 0) {
42020
42743
  logger.warn(
42021
42744
  `machine export left out ${missingEventCount} event(s) that reports count but the event store does not hold`
@@ -44701,6 +45424,29 @@ function rejectUnknownCommand(program, args) {
44701
45424
  (Did you mean ${suggestion}?)` : "";
44702
45425
  program.error(`error: unknown command '${unknownCommand}'${hint}`);
44703
45426
  }
45427
+ var SIDE_BY_SIDE_MIN_DESCRIPTION_WIDTH = 40;
45428
+ var STACKED_DESCRIPTION_INDENT = " ";
45429
+ function formatHelpItem(term, termWidth, description, helper) {
45430
+ const helpWidth = this.helpWidth ?? 80;
45431
+ const sideBySideWidth = helpWidth - termWidth - 4;
45432
+ if (!description || sideBySideWidth >= SIDE_BY_SIDE_MIN_DESCRIPTION_WIDTH) {
45433
+ return Help.prototype.formatItem.call(this, term, termWidth, description, helper);
45434
+ }
45435
+ const wrappedDescription = helper.boxWrap(
45436
+ description,
45437
+ helpWidth - STACKED_DESCRIPTION_INDENT.length
45438
+ );
45439
+ return [
45440
+ ` ${term}`,
45441
+ ...wrappedDescription.split("\n").map((line) => `${STACKED_DESCRIPTION_INDENT}${line}`)
45442
+ ].join("\n");
45443
+ }
45444
+ function wrapHelpOnNarrowTerminals(command) {
45445
+ command.configureHelp({ minWidthToWrap: 20, formatItem: formatHelpItem });
45446
+ for (const subcommand of command.commands) {
45447
+ wrapHelpOnNarrowTerminals(subcommand);
45448
+ }
45449
+ }
44704
45450
  function createCli(options = {}) {
44705
45451
  const program = new Command6();
44706
45452
  program.name("llm-usage").description(createRootDescription()).version(options.version ?? "0.0.0").showHelpAfterError();
@@ -44736,6 +45482,7 @@ function createCli(options = {}) {
44736
45482
  setLogLevel("debug");
44737
45483
  }
44738
45484
  });
45485
+ wrapHelpOnNarrowTerminals(program);
44739
45486
  return program;
44740
45487
  }
44741
45488