llm-usage-metrics 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -694,7 +694,8 @@ var knownSourceColors = {
694
694
  codex: "#22c55e",
695
695
  gemini: "#eab308",
696
696
  droid: "#3b82f6",
697
- opencode: "#a855f7"
697
+ opencode: "#a855f7",
698
+ claude: "#d97757"
698
699
  };
699
700
  var fallbackColors = ["#f97316", "#06b6d4", "#ef4444", "#84cc16", "#f43f5e"];
700
701
  function getSourceColor(source, index) {
@@ -889,9 +890,6 @@ ${noData}
889
890
  import { markdownTable } from "markdown-table";
890
891
  import pc4 from "picocolors";
891
892
 
892
- // src/render/report-header.ts
893
- import pc from "picocolors";
894
-
895
893
  // src/render/table-text-layout.ts
896
894
  var ansiEscapePattern = new RegExp(String.raw`\u001B\[[0-9;]*m`, "gu");
897
895
  var combiningMarkPattern = /\p{Mark}/u;
@@ -1065,7 +1063,36 @@ function wrapTableColumn(rows, options) {
1065
1063
  });
1066
1064
  }
1067
1065
 
1066
+ // src/render/markdown-safe-cell.ts
1067
+ var markdownSpecialCharacterPattern = /[\\`*_~[\]()!|]/gu;
1068
+ var bareUrlPattern = /\bhttps?:\/\/[^\s<]+|\bwww\.[^\s<]+/giu;
1069
+ var bareEmailPattern = /(^|[^\w.+-])([\w.+-]+@[\w.-]+\.[a-z]{2,})(?=$|[^\w.-])/giu;
1070
+ function escapeBareAutolinks(value) {
1071
+ const withoutBareUrls = value.replace(
1072
+ bareUrlPattern,
1073
+ (match) => match.startsWith("www.") ? match.replace("www.", "www\\.") : match.replace("://", "\\://")
1074
+ );
1075
+ return withoutBareUrls.replace(
1076
+ bareEmailPattern,
1077
+ (_, prefix, email) => `${prefix}${email.replace("@", "\\@")}`
1078
+ );
1079
+ }
1080
+ function escapeHtmlText(value) {
1081
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1082
+ }
1083
+ function escapeMarkdownText(value) {
1084
+ const escapedMarkdownText = escapeHtmlText(value).replace(
1085
+ markdownSpecialCharacterPattern,
1086
+ "\\$&"
1087
+ );
1088
+ return escapeBareAutolinks(escapedMarkdownText);
1089
+ }
1090
+ function toMarkdownSafeCell(value) {
1091
+ return splitCellLines(value).map((line) => escapeMarkdownText(line)).join("<br>");
1092
+ }
1093
+
1068
1094
  // src/render/report-header.ts
1095
+ import pc from "picocolors";
1069
1096
  function getBoxWidth(content) {
1070
1097
  return visibleWidth(content) + 4;
1071
1098
  }
@@ -1188,13 +1215,70 @@ var defaultTerminalStylePalette = {
1188
1215
  dim: pc2.dim
1189
1216
  };
1190
1217
  var passthroughStyler = (text) => text;
1218
+ var periodColumnIndex = 0;
1219
+ var sourceColumnIndex = 1;
1220
+ var modelsColumnIndex = 2;
1221
+ var totalColumnIndex = 8;
1222
+ var packedModelStartPattern = /(^| {2,})(?=• )/gu;
1191
1223
  function styleCellLines(cell, styler) {
1192
- return cell.split("\n").map((line) => line.length === 0 ? "" : styler(line)).join("\n");
1224
+ return splitCellLines(cell).map((line) => line.length === 0 ? "" : styler(line)).join("\n");
1225
+ }
1226
+ function styleModelsCell(cell, palette, options = {}) {
1227
+ const emphasizePrimaryWhenSingleLine = options.emphasizePrimaryWhenSingleLine ?? false;
1228
+ const primaryStyler = options.primaryStyler ?? palette.bold;
1229
+ const secondaryStyler = options.secondaryStyler ?? passthroughStyler;
1230
+ const totalStyler = options.totalStyler ?? ((text) => palette.bold(palette.green(text)));
1231
+ const lines = splitCellLines(cell);
1232
+ const modelEntryCount = lines.reduce((count, line) => {
1233
+ if (line === "\u03A3 TOTAL") {
1234
+ return count;
1235
+ }
1236
+ const segmentStarts = [...line.matchAll(packedModelStartPattern)];
1237
+ return count + segmentStarts.length;
1238
+ }, 0);
1239
+ const shouldEmphasizePrimary = emphasizePrimaryWhenSingleLine || modelEntryCount > 1;
1240
+ let currentStyler = shouldEmphasizePrimary ? primaryStyler : passthroughStyler;
1241
+ let modelLineCount = 0;
1242
+ return lines.map((line) => {
1243
+ if (line.length === 0) {
1244
+ return "";
1245
+ }
1246
+ if (line === "\u03A3 TOTAL") {
1247
+ currentStyler = totalStyler;
1248
+ return totalStyler(line);
1249
+ }
1250
+ const segmentStarts = [...line.matchAll(packedModelStartPattern)].map((match) => match.index);
1251
+ if (segmentStarts.length === 0) {
1252
+ return currentStyler(line);
1253
+ }
1254
+ const prefix = segmentStarts[0] > 0 ? currentStyler(line.slice(0, segmentStarts[0])) : "";
1255
+ let nextContinuationStyler = currentStyler;
1256
+ const renderedSegments = segmentStarts.map((segmentStart, segmentIndex) => {
1257
+ const segmentStyler = modelLineCount === 0 && shouldEmphasizePrimary ? primaryStyler : secondaryStyler;
1258
+ modelLineCount += 1;
1259
+ nextContinuationStyler = segmentStyler;
1260
+ const segmentEnd = segmentStarts[segmentIndex + 1] ?? line.length;
1261
+ return segmentStyler(line.slice(segmentStart, segmentEnd));
1262
+ }).join("");
1263
+ currentStyler = nextContinuationStyler;
1264
+ return prefix + renderedSegments;
1265
+ }).join("\n");
1266
+ }
1267
+ function styleCellAtIndex(cells, index, styler) {
1268
+ if (index < 0 || index >= cells.length) {
1269
+ return cells;
1270
+ }
1271
+ const styledCells = [...cells];
1272
+ styledCells[index] = styleCellLines(styledCells[index], styler);
1273
+ return styledCells;
1193
1274
  }
1194
1275
  var sourceStylePolicies = /* @__PURE__ */ new Map([
1195
1276
  ["pi", (palette) => palette.cyan],
1196
1277
  ["codex", (palette) => palette.magenta],
1197
- ["opencode", (palette) => palette.blue]
1278
+ ["gemini", (palette) => palette.yellow],
1279
+ ["droid", (palette) => palette.green],
1280
+ ["opencode", (palette) => palette.blue],
1281
+ ["claude", (palette) => palette.cyan]
1198
1282
  ]);
1199
1283
  function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
1200
1284
  const stylePolicy = sourceStylePolicies.get(source);
@@ -1203,28 +1287,98 @@ function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
1203
1287
  }
1204
1288
  return stylePolicy(palette);
1205
1289
  }
1290
+ function getTrailingColumnIndex(cells) {
1291
+ return cells.length === 0 ? void 0 : cells.length - 1;
1292
+ }
1206
1293
  var rowTypeStylePolicies = {
1207
1294
  period_source: (cells, palette) => {
1208
- const styledCells = [...cells];
1209
- const costColumnIndex2 = styledCells.length - 1;
1210
- styledCells[costColumnIndex2] = styleCellLines(styledCells[costColumnIndex2], palette.yellow);
1295
+ let styledCells = [...cells];
1296
+ const trailingColumnIndex = getTrailingColumnIndex(styledCells);
1297
+ if (modelsColumnIndex < styledCells.length) {
1298
+ styledCells[modelsColumnIndex] = styleModelsCell(styledCells[modelsColumnIndex], palette, {
1299
+ secondaryStyler: palette.dim
1300
+ });
1301
+ }
1302
+ styledCells = styleCellAtIndex(styledCells, totalColumnIndex, palette.green);
1303
+ if (trailingColumnIndex !== void 0) {
1304
+ styledCells = styleCellAtIndex(
1305
+ styledCells,
1306
+ trailingColumnIndex,
1307
+ (line) => palette.bold(palette.yellow(line))
1308
+ );
1309
+ }
1310
+ return styledCells;
1311
+ },
1312
+ period_combined: (cells, palette) => {
1313
+ let styledCells = [...cells];
1314
+ const trailingColumnIndex = getTrailingColumnIndex(styledCells);
1315
+ styledCells = styleCellAtIndex(styledCells, periodColumnIndex, palette.white);
1316
+ styledCells = styleCellAtIndex(
1317
+ styledCells,
1318
+ sourceColumnIndex,
1319
+ (line) => palette.bold(palette.yellow(line))
1320
+ );
1321
+ if (modelsColumnIndex < styledCells.length) {
1322
+ styledCells[modelsColumnIndex] = styleModelsCell(styledCells[modelsColumnIndex], palette, {
1323
+ secondaryStyler: palette.dim
1324
+ });
1325
+ }
1326
+ styledCells = styleCellAtIndex(
1327
+ styledCells,
1328
+ totalColumnIndex,
1329
+ (line) => palette.bold(palette.green(line))
1330
+ );
1331
+ if (trailingColumnIndex !== void 0) {
1332
+ styledCells = styleCellAtIndex(
1333
+ styledCells,
1334
+ trailingColumnIndex,
1335
+ (line) => palette.bold(palette.yellow(line))
1336
+ );
1337
+ }
1211
1338
  return styledCells;
1212
1339
  },
1213
- period_combined: (cells, palette) => cells.map((cell, cellIndex) => {
1214
- if (cellIndex === 1) {
1215
- return styleCellLines(cell, (line) => palette.bold(palette.yellow(line)));
1340
+ grand_total: (cells, palette) => {
1341
+ const styledCells = cells.map((cell, cellIndex) => {
1342
+ if (cellIndex === modelsColumnIndex) {
1343
+ return cell;
1344
+ }
1345
+ return styleCellLines(cell, palette.bold);
1346
+ });
1347
+ if (periodColumnIndex < styledCells.length) {
1348
+ styledCells[periodColumnIndex] = styleCellLines(
1349
+ cells[periodColumnIndex],
1350
+ (line) => palette.bold(palette.white(line))
1351
+ );
1352
+ }
1353
+ if (sourceColumnIndex < styledCells.length) {
1354
+ styledCells[sourceColumnIndex] = styleCellLines(
1355
+ cells[sourceColumnIndex],
1356
+ (line) => palette.bold(palette.green(line))
1357
+ );
1216
1358
  }
1217
- return styleCellLines(cell, palette.dim);
1218
- }),
1219
- grand_total: (cells, palette) => cells.map((cell, cellIndex) => {
1220
- if (cellIndex === 0) {
1221
- return styleCellLines(cell, (line) => palette.bold(palette.white(line)));
1359
+ if (modelsColumnIndex < styledCells.length) {
1360
+ styledCells[modelsColumnIndex] = styleModelsCell(styledCells[modelsColumnIndex], palette, {
1361
+ emphasizePrimaryWhenSingleLine: true,
1362
+ primaryStyler: (line) => palette.bold(palette.white(line)),
1363
+ secondaryStyler: palette.dim,
1364
+ totalStyler: (line) => palette.bold(palette.green(line))
1365
+ });
1366
+ }
1367
+ if (totalColumnIndex < styledCells.length) {
1368
+ styledCells[totalColumnIndex] = styleCellLines(
1369
+ cells[totalColumnIndex],
1370
+ (line) => palette.bold(palette.green(line))
1371
+ );
1222
1372
  }
1223
- if (cellIndex === 1) {
1224
- return styleCellLines(cell, (line) => palette.bold(palette.green(line)));
1373
+ const trailingColumnIndex = getTrailingColumnIndex(styledCells);
1374
+ if (trailingColumnIndex !== void 0) {
1375
+ styledCells[trailingColumnIndex] = styleCellLines(
1376
+ cells[trailingColumnIndex],
1377
+ (line) => palette.bold(palette.yellow(line))
1378
+ );
1225
1379
  }
1226
- return styleCellLines(cell, palette.bold);
1227
- })
1380
+ return styledCells;
1381
+ }
1228
1382
  };
1229
1383
  function applyRowTypeStyle(rowType, cells, palette = defaultTerminalStylePalette) {
1230
1384
  return rowTypeStylePolicies[rowType](cells, palette);
@@ -1244,8 +1398,9 @@ function colorizeUsageBodyRows(bodyRows, rows, options) {
1244
1398
  }
1245
1399
  const palette = options.palette ?? defaultTerminalStylePalette;
1246
1400
  return rows.map((row, index) => {
1401
+ const bodyRow = bodyRows[index] ?? [];
1247
1402
  const sourceStyler = row.rowType === "period_source" ? resolveSourceStyler(String(row.source), palette) : passthroughStyler;
1248
- const baseStyledCells = applyBaseCellStyle(bodyRows[index], palette, sourceStyler);
1403
+ const baseStyledCells = row.rowType === "period_source" ? applyBaseCellStyle(bodyRow, palette, sourceStyler) : [...bodyRow];
1249
1404
  return applyRowTypeStyle(row.rowType, baseStyledCells, palette);
1250
1405
  });
1251
1406
  }
@@ -1348,7 +1503,7 @@ function getColumnAlignment(columnIndex, multilineColumnIndex) {
1348
1503
  return "right";
1349
1504
  }
1350
1505
  function getVerticalAlignment(columnIndex, layout, multilineColumnIndex) {
1351
- if (columnIndex === multilineColumnIndex) {
1506
+ if (columnIndex <= multilineColumnIndex) {
1352
1507
  return "top";
1353
1508
  }
1354
1509
  return layout === "top_aligned" ? "top" : "middle";
@@ -1406,7 +1561,7 @@ function shouldDrawBodySeparator(index, rowMetas) {
1406
1561
  }
1407
1562
  const previousRow = rowMetas[index];
1408
1563
  const nextRow = rowMetas[index + 1];
1409
- return previousRow.rowKind === "combined" || nextRow.rowKind === "total" || previousRow.periodKey !== nextRow.periodKey;
1564
+ return previousRow.rowKind === "detail" && (nextRow.rowKind === "detail" || nextRow.rowKind === "combined") && previousRow.periodKey === nextRow.periodKey || previousRow.rowKind === "combined" || nextRow.rowKind === "total" || previousRow.periodKey !== nextRow.periodKey;
1410
1565
  }
1411
1566
  function getRowKindWeight(rowKind) {
1412
1567
  switch (rowKind) {
@@ -1566,9 +1721,10 @@ function renderUnicodeTable(options) {
1566
1721
  }
1567
1722
 
1568
1723
  // src/render/terminal-table.ts
1569
- var modelsColumnIndex = 2;
1724
+ var modelsColumnIndex2 = 2;
1570
1725
  var defaultModelsColumnWidth = 32;
1571
1726
  var minimumModelsColumnWidth = 12;
1727
+ var compactModelsColumnGap = 2;
1572
1728
  function shouldUseColorByDefault() {
1573
1729
  if (process.env.NO_COLOR !== void 0) {
1574
1730
  return false;
@@ -1584,7 +1740,28 @@ function colorizeHeader(useColor) {
1584
1740
  if (!useColor) {
1585
1741
  return headerCells;
1586
1742
  }
1587
- return headerCells.map((header) => pc3.bold(pc3.white(header)));
1743
+ return headerCells.map((header, index) => {
1744
+ switch (index) {
1745
+ case 1:
1746
+ return pc3.bold(pc3.cyan(header));
1747
+ case 2:
1748
+ return pc3.bold(pc3.magenta(header));
1749
+ case 3:
1750
+ case 6:
1751
+ return pc3.bold(pc3.blue(header));
1752
+ case 4:
1753
+ case 7:
1754
+ return pc3.bold(pc3.cyan(header));
1755
+ case 5:
1756
+ return pc3.bold(pc3.magenta(header));
1757
+ case 8:
1758
+ return pc3.bold(pc3.green(header));
1759
+ case 9:
1760
+ return pc3.bold(pc3.yellow(header));
1761
+ default:
1762
+ return pc3.bold(pc3.white(header));
1763
+ }
1764
+ });
1588
1765
  }
1589
1766
  function isValidTerminalWidth(width) {
1590
1767
  return typeof width === "number" && Number.isFinite(width) && width > 0;
@@ -1598,12 +1775,119 @@ function resolveTerminalWidth(override) {
1598
1775
  function measureTableWidth(tableOutput) {
1599
1776
  return tableOutput.trimEnd().split("\n").reduce((maxWidth, line) => Math.max(maxWidth, visibleWidth(line)), 0);
1600
1777
  }
1601
- function renderTableWithModelsWidth(rows, tableLayout, useColor, modelsColumnWidth) {
1602
- const uncoloredBodyRows = toUsageTableCells(rows, { layout: tableLayout });
1603
- const wrappedBodyRows = wrapTableColumn(uncoloredBodyRows, {
1604
- columnIndex: modelsColumnIndex,
1778
+ function padVisibleEnd(value, width) {
1779
+ return `${value}${" ".repeat(Math.max(0, width - visibleWidth(value)))}`;
1780
+ }
1781
+ function formatCompactModelsCell(value, width) {
1782
+ const modelLines = splitCellLines(value);
1783
+ if (modelLines.length < 2) {
1784
+ return value;
1785
+ }
1786
+ const longestLineWidth = modelLines.reduce(
1787
+ (maxWidth, line) => Math.max(maxWidth, visibleWidth(line)),
1788
+ 0
1789
+ );
1790
+ const maxColumnCount = Math.floor(
1791
+ (width + compactModelsColumnGap) / (longestLineWidth + compactModelsColumnGap)
1792
+ );
1793
+ if (maxColumnCount <= 1) {
1794
+ return value;
1795
+ }
1796
+ const columnCount = Math.min(modelLines.length, maxColumnCount);
1797
+ const rowCount = Math.ceil(modelLines.length / columnCount);
1798
+ const compactLines = [];
1799
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
1800
+ const rowStart = rowIndex * columnCount;
1801
+ const cells = modelLines.slice(rowStart, rowStart + columnCount);
1802
+ compactLines.push(
1803
+ cells.map(
1804
+ (cell, columnIndex) => columnIndex === cells.length - 1 ? cell : padVisibleEnd(cell, longestLineWidth)
1805
+ ).join(" ".repeat(compactModelsColumnGap))
1806
+ );
1807
+ }
1808
+ return compactLines.join("\n");
1809
+ }
1810
+ function layoutModelsColumn(bodyRows, tableLayout, modelsColumnWidth) {
1811
+ if (tableLayout !== "compact" || modelsColumnWidth <= defaultModelsColumnWidth) {
1812
+ return bodyRows.map((row) => [...row]);
1813
+ }
1814
+ return bodyRows.map((row) => {
1815
+ const nextRow = [...row];
1816
+ nextRow[modelsColumnIndex2] = formatCompactModelsCell(
1817
+ nextRow[modelsColumnIndex2],
1818
+ modelsColumnWidth
1819
+ );
1820
+ return nextRow;
1821
+ });
1822
+ }
1823
+ function prepareWrappedBodyRows(bodyRows, tableLayout, modelsColumnWidth) {
1824
+ const laidOutRows = layoutModelsColumn(bodyRows, tableLayout, modelsColumnWidth);
1825
+ return wrapTableColumn(laidOutRows, {
1826
+ columnIndex: modelsColumnIndex2,
1605
1827
  width: modelsColumnWidth
1606
1828
  });
1829
+ }
1830
+ function measureCompactBodyHeight(bodyRows, modelsColumnWidth) {
1831
+ return prepareWrappedBodyRows(bodyRows, "compact", modelsColumnWidth).reduce(
1832
+ (totalHeight, row) => totalHeight + splitCellLines(row[modelsColumnIndex2] ?? "").length,
1833
+ 0
1834
+ );
1835
+ }
1836
+ function resolveExpandedModelsColumnWidth(bodyRows, tableLayout, currentWidth, maximumWidth) {
1837
+ if (maximumWidth <= currentWidth) {
1838
+ return currentWidth;
1839
+ }
1840
+ if (tableLayout === "per_model_columns") {
1841
+ const longestModelLineWidth = bodyRows.reduce((maxWidth, row) => {
1842
+ const cellMaxWidth = splitCellLines(row[modelsColumnIndex2] ?? "").reduce(
1843
+ (lineMaxWidth, line) => Math.max(lineMaxWidth, visibleWidth(line)),
1844
+ 0
1845
+ );
1846
+ return Math.max(maxWidth, cellMaxWidth);
1847
+ }, currentWidth);
1848
+ const preferredWidth = Math.max(
1849
+ currentWidth,
1850
+ defaultModelsColumnWidth + 16,
1851
+ longestModelLineWidth
1852
+ );
1853
+ return Math.min(maximumWidth, preferredWidth);
1854
+ }
1855
+ const candidateWidths = /* @__PURE__ */ new Set([currentWidth]);
1856
+ for (const row of bodyRows) {
1857
+ const modelLines = splitCellLines(row[modelsColumnIndex2] ?? "");
1858
+ const longestLineWidth = modelLines.reduce(
1859
+ (maxLineWidth, line) => Math.max(maxLineWidth, visibleWidth(line)),
1860
+ 0
1861
+ );
1862
+ if (longestLineWidth > currentWidth && longestLineWidth <= maximumWidth) {
1863
+ candidateWidths.add(longestLineWidth);
1864
+ }
1865
+ if (modelLines.length < 2) {
1866
+ continue;
1867
+ }
1868
+ for (let columnCount = 2; columnCount <= modelLines.length; columnCount += 1) {
1869
+ const candidateWidth = columnCount * longestLineWidth + (columnCount - 1) * compactModelsColumnGap;
1870
+ if (candidateWidth > maximumWidth) {
1871
+ break;
1872
+ }
1873
+ if (candidateWidth > currentWidth) {
1874
+ candidateWidths.add(candidateWidth);
1875
+ }
1876
+ }
1877
+ }
1878
+ let bestWidth = currentWidth;
1879
+ let bestHeight = measureCompactBodyHeight(bodyRows, currentWidth);
1880
+ for (const candidateWidth of Array.from(candidateWidths).sort((left, right) => left - right)) {
1881
+ const candidateHeight = measureCompactBodyHeight(bodyRows, candidateWidth);
1882
+ if (candidateHeight < bestHeight) {
1883
+ bestHeight = candidateHeight;
1884
+ bestWidth = candidateWidth;
1885
+ }
1886
+ }
1887
+ return bestWidth;
1888
+ }
1889
+ function renderTableWithModelsWidth(rows, uncoloredBodyRows, tableLayout, useColor, modelsColumnWidth) {
1890
+ const wrappedBodyRows = prepareWrappedBodyRows(uncoloredBodyRows, tableLayout, modelsColumnWidth);
1607
1891
  const bodyRows = colorizeUsageBodyRows(wrappedBodyRows, rows, { useColor });
1608
1892
  const rowMetas = rows.map((row) => ({
1609
1893
  periodKey: row.periodKey,
@@ -1617,7 +1901,7 @@ function renderTableWithModelsWidth(rows, tableLayout, useColor, modelsColumnWid
1617
1901
  measureBodyRows: wrappedBodyRows,
1618
1902
  rowMetas,
1619
1903
  layout: tableLayout === "per_model_columns" ? "top_aligned" : "compact",
1620
- multilineColumnIndex: modelsColumnIndex,
1904
+ multilineColumnIndex: modelsColumnIndex2,
1621
1905
  multilineColumnWidth: modelsColumnWidth
1622
1906
  });
1623
1907
  }
@@ -1626,8 +1910,15 @@ function renderTerminalTable(rows, options = {}) {
1626
1910
  const tableLayout = options.tableLayout ?? "compact";
1627
1911
  const hasExplicitTerminalWidth = isValidTerminalWidth(options.terminalWidth);
1628
1912
  const terminalWidth = resolveTerminalWidth(options.terminalWidth);
1913
+ const uncoloredBodyRows = toUsageTableCells(rows, { layout: tableLayout });
1629
1914
  let modelsColumnWidth = defaultModelsColumnWidth;
1630
- let renderedTable = renderTableWithModelsWidth(rows, tableLayout, useColor, modelsColumnWidth);
1915
+ let renderedTable = renderTableWithModelsWidth(
1916
+ rows,
1917
+ uncoloredBodyRows,
1918
+ tableLayout,
1919
+ useColor,
1920
+ modelsColumnWidth
1921
+ );
1631
1922
  if (terminalWidth !== void 0) {
1632
1923
  let renderedTableWidth = measureTableWidth(renderedTable);
1633
1924
  while (renderedTableWidth > terminalWidth && modelsColumnWidth > minimumModelsColumnWidth) {
@@ -1640,9 +1931,35 @@ function renderTerminalTable(rows, options = {}) {
1640
1931
  break;
1641
1932
  }
1642
1933
  modelsColumnWidth = nextModelsColumnWidth;
1643
- renderedTable = renderTableWithModelsWidth(rows, tableLayout, useColor, modelsColumnWidth);
1934
+ renderedTable = renderTableWithModelsWidth(
1935
+ rows,
1936
+ uncoloredBodyRows,
1937
+ tableLayout,
1938
+ useColor,
1939
+ modelsColumnWidth
1940
+ );
1644
1941
  renderedTableWidth = measureTableWidth(renderedTable);
1645
1942
  }
1943
+ if (renderedTableWidth < terminalWidth) {
1944
+ const maximumWidth = modelsColumnWidth + (terminalWidth - renderedTableWidth);
1945
+ const expandedWidth = resolveExpandedModelsColumnWidth(
1946
+ uncoloredBodyRows,
1947
+ tableLayout,
1948
+ modelsColumnWidth,
1949
+ maximumWidth
1950
+ );
1951
+ if (expandedWidth > modelsColumnWidth) {
1952
+ modelsColumnWidth = expandedWidth;
1953
+ renderedTable = renderTableWithModelsWidth(
1954
+ rows,
1955
+ uncoloredBodyRows,
1956
+ tableLayout,
1957
+ useColor,
1958
+ modelsColumnWidth
1959
+ );
1960
+ renderedTableWidth = measureTableWidth(renderedTable);
1961
+ }
1962
+ }
1646
1963
  if (hasExplicitTerminalWidth && renderedTableWidth > terminalWidth) {
1647
1964
  throw new Error(
1648
1965
  `Configured terminal width (${terminalWidth}) is too narrow for table rendering (minimum ${renderedTableWidth}).`
@@ -1653,7 +1970,7 @@ function renderTerminalTable(rows, options = {}) {
1653
1970
  }
1654
1971
 
1655
1972
  // src/render/render-efficiency-report.ts
1656
- var periodColumnIndex = 0;
1973
+ var periodColumnIndex2 = 0;
1657
1974
  var minimumEfficiencyColumnWidth = 1;
1658
1975
  var commitsColumnIndex = 1;
1659
1976
  var linesAddedColumnIndex = 2;
@@ -1793,8 +2110,8 @@ function styleEfficiencyTerminalRows(rows, bodyRows, options) {
1793
2110
  }
1794
2111
  const row = rows[rowIndex];
1795
2112
  const styledCells = [...cells];
1796
- const periodCell = styledCells[periodColumnIndex];
1797
- styledCells[periodColumnIndex] = row.rowType === "grand_total" ? pc4.bold(pc4.cyan(periodCell)) : pc4.bold(periodCell);
2113
+ const periodCell = styledCells[periodColumnIndex2];
2114
+ styledCells[periodColumnIndex2] = row.rowType === "grand_total" ? pc4.bold(pc4.cyan(periodCell)) : pc4.bold(periodCell);
1798
2115
  styledCells[commitsColumnIndex] = pc4.bold(styledCells[commitsColumnIndex]);
1799
2116
  styledCells[linesAddedColumnIndex] = styleDeltaCell(
1800
2117
  row.linesAdded,
@@ -1844,13 +2161,10 @@ function renderTerminalEfficiencyTable(rows, options) {
1844
2161
  measureBodyRows: fittedCells.bodyRows,
1845
2162
  rowMetas,
1846
2163
  layout: "compact",
1847
- multilineColumnIndex: periodColumnIndex,
1848
- multilineColumnWidth: fittedCells.widths[periodColumnIndex] ?? efficiencyTableHeaders[periodColumnIndex].length
2164
+ multilineColumnIndex: periodColumnIndex2,
2165
+ multilineColumnWidth: fittedCells.widths[periodColumnIndex2] ?? efficiencyTableHeaders[periodColumnIndex2].length
1849
2166
  });
1850
2167
  }
1851
- function toMarkdownSafeCell(value) {
1852
- return value.replace(/\r?\n/gu, "<br>");
1853
- }
1854
2168
  function renderMarkdownEfficiencyTable(rows) {
1855
2169
  const bodyRows = toEfficiencyTableCells(rows).map(
1856
2170
  (row) => row.map((cell) => toMarkdownSafeCell(cell))
@@ -1908,52 +2222,6 @@ var logger = {
1908
2222
  }
1909
2223
  };
1910
2224
 
1911
- // src/domain/normalization.ts
1912
- function normalizeNonNegativeInteger(value) {
1913
- if (value === null || value === void 0) {
1914
- return 0;
1915
- }
1916
- const parsed = typeof value === "number" ? value : Number(value);
1917
- if (!Number.isFinite(parsed)) {
1918
- return 0;
1919
- }
1920
- return Math.max(0, Math.trunc(parsed));
1921
- }
1922
- function normalizeUsdCost(value) {
1923
- if (value === null || value === void 0) {
1924
- return void 0;
1925
- }
1926
- if (typeof value === "string" && value.trim() === "") {
1927
- return void 0;
1928
- }
1929
- const parsed = typeof value === "number" ? value : Number(value);
1930
- if (!Number.isFinite(parsed)) {
1931
- return void 0;
1932
- }
1933
- return Math.max(0, parsed);
1934
- }
1935
- function normalizeTimestamp(value) {
1936
- const date = value instanceof Date ? value : new Date(value);
1937
- if (Number.isNaN(date.getTime())) {
1938
- throw new Error(`Invalid timestamp: ${String(value)}`);
1939
- }
1940
- return date.toISOString();
1941
- }
1942
- function normalizeModelList(models) {
1943
- const deduplicated = /* @__PURE__ */ new Set();
1944
- for (const model of models) {
1945
- if (!model) {
1946
- continue;
1947
- }
1948
- const normalized = model.trim();
1949
- if (!normalized) {
1950
- continue;
1951
- }
1952
- deduplicated.add(normalized);
1953
- }
1954
- return [...deduplicated].sort(compareByCodePoint);
1955
- }
1956
-
1957
2225
  // src/utils/time-buckets.ts
1958
2226
  var formatterCache = /* @__PURE__ */ new Map();
1959
2227
  function getDateFormatter(timezone) {
@@ -2138,15 +2406,29 @@ function mergeModelTotals(targetModelTotals, sourceModelTotals) {
2138
2406
  targetModelTotals.set(model, targetTotals);
2139
2407
  }
2140
2408
  }
2141
- function toModelUsageBreakdown(modelTotals) {
2142
- const sortedModels = normalizeModelList(modelTotals.keys());
2143
- return sortedModels.map((model) => {
2144
- const totals = modelTotals.get(model) ?? createEmptyTotals();
2145
- return {
2146
- model,
2147
- ...totals
2148
- };
2149
- });
2409
+ function compareModelUsageBreakdown(left, right) {
2410
+ if (left.totalTokens !== right.totalTokens) {
2411
+ return right.totalTokens - left.totalTokens;
2412
+ }
2413
+ const leftCost = left.costUsd ?? Number.NEGATIVE_INFINITY;
2414
+ const rightCost = right.costUsd ?? Number.NEGATIVE_INFINITY;
2415
+ if (leftCost !== rightCost) {
2416
+ return rightCost - leftCost;
2417
+ }
2418
+ if (left.inputTokens !== right.inputTokens) {
2419
+ return right.inputTokens - left.inputTokens;
2420
+ }
2421
+ return compareByCodePoint(left.model, right.model);
2422
+ }
2423
+ function toRankedModelUsage(modelTotals) {
2424
+ const modelBreakdown = [...modelTotals.entries()].map(([model, totals]) => ({
2425
+ model,
2426
+ ...totals
2427
+ })).sort(compareModelUsageBreakdown);
2428
+ return {
2429
+ models: modelBreakdown.map((modelUsage) => modelUsage.model),
2430
+ modelBreakdown
2431
+ };
2150
2432
  }
2151
2433
  function sourceSortComparator(left, right, sourceWeightMap) {
2152
2434
  const leftWeight = sourceWeightMap.get(left) ?? Number.MAX_SAFE_INTEGER;
@@ -2190,12 +2472,13 @@ function aggregateUsage(events, options) {
2190
2472
  if (!accumulator) {
2191
2473
  continue;
2192
2474
  }
2475
+ const rankedModelUsage = includeModelBreakdown && accumulator.modelTotals ? toRankedModelUsage(accumulator.modelTotals) : { models: [], modelBreakdown: [] };
2193
2476
  const sourceRow = {
2194
2477
  rowType: "period_source",
2195
2478
  periodKey,
2196
2479
  source,
2197
- models: includeModelBreakdown && accumulator.modelTotals ? normalizeModelList(accumulator.modelTotals.keys()) : [],
2198
- modelBreakdown: includeModelBreakdown && accumulator.modelTotals ? toModelUsageBreakdown(accumulator.modelTotals) : [],
2480
+ models: rankedModelUsage.models,
2481
+ modelBreakdown: rankedModelUsage.modelBreakdown,
2199
2482
  ...accumulator.totals
2200
2483
  };
2201
2484
  rows.push(sourceRow);
@@ -2207,24 +2490,26 @@ function aggregateUsage(events, options) {
2207
2490
  }
2208
2491
  }
2209
2492
  if (sortedSources.length > 1) {
2493
+ const rankedCombinedModels = includeModelBreakdown ? toRankedModelUsage(periodCombinedModelTotals) : { models: [], modelBreakdown: [] };
2210
2494
  const combinedRow = {
2211
2495
  rowType: "period_combined",
2212
2496
  periodKey,
2213
2497
  source: "combined",
2214
- models: includeModelBreakdown ? normalizeModelList(periodCombinedModelTotals.keys()) : [],
2215
- modelBreakdown: includeModelBreakdown ? toModelUsageBreakdown(periodCombinedModelTotals) : [],
2498
+ models: rankedCombinedModels.models,
2499
+ modelBreakdown: rankedCombinedModels.modelBreakdown,
2216
2500
  ...periodCombinedTotals
2217
2501
  };
2218
2502
  rows.push(combinedRow);
2219
2503
  }
2220
2504
  }
2221
2505
  const finalizedGrandTotals = events.length === 0 && grandTotals.costUsd === void 0 && grandTotals.costIncomplete !== true ? { ...grandTotals, costUsd: 0 } : grandTotals;
2506
+ const rankedGrandModels = includeModelBreakdown ? toRankedModelUsage(grandModelTotals) : { models: [], modelBreakdown: [] };
2222
2507
  const grandTotalRow = {
2223
2508
  rowType: "grand_total",
2224
2509
  periodKey: "ALL",
2225
2510
  source: "combined",
2226
- models: includeModelBreakdown ? normalizeModelList(grandModelTotals.keys()) : [],
2227
- modelBreakdown: includeModelBreakdown ? toModelUsageBreakdown(grandModelTotals) : [],
2511
+ models: rankedGrandModels.models,
2512
+ modelBreakdown: rankedGrandModels.modelBreakdown,
2228
2513
  ...finalizedGrandTotals
2229
2514
  };
2230
2515
  rows.push(grandTotalRow);
@@ -2858,6 +3143,38 @@ async function attributeUsageEventsToRepo(events, repoDir, resolveRepoRoot3 = re
2858
3143
  };
2859
3144
  }
2860
3145
 
3146
+ // src/domain/normalization.ts
3147
+ function normalizeNonNegativeInteger(value) {
3148
+ if (value === null || value === void 0) {
3149
+ return 0;
3150
+ }
3151
+ const parsed = typeof value === "number" ? value : Number(value);
3152
+ if (!Number.isFinite(parsed)) {
3153
+ return 0;
3154
+ }
3155
+ return Math.max(0, Math.trunc(parsed));
3156
+ }
3157
+ function normalizeUsdCost(value) {
3158
+ if (value === null || value === void 0) {
3159
+ return void 0;
3160
+ }
3161
+ if (typeof value === "string" && value.trim() === "") {
3162
+ return void 0;
3163
+ }
3164
+ const parsed = typeof value === "number" ? value : Number(value);
3165
+ if (!Number.isFinite(parsed)) {
3166
+ return void 0;
3167
+ }
3168
+ return Math.max(0, parsed);
3169
+ }
3170
+ function normalizeTimestamp(value) {
3171
+ const date = value instanceof Date ? value : new Date(value);
3172
+ if (Number.isNaN(date.getTime())) {
3173
+ throw new Error(`Invalid timestamp: ${String(value)}`);
3174
+ }
3175
+ return date.toISOString();
3176
+ }
3177
+
2861
3178
  // src/domain/provider-normalization.ts
2862
3179
  var billingProviderAliases = /* @__PURE__ */ new Map([
2863
3180
  ["openai-codex", "openai"],
@@ -3122,7 +3439,7 @@ function formatEnvVarOverrides(overrides) {
3122
3439
  return lines;
3123
3440
  }
3124
3441
 
3125
- // src/sources/codex/codex-source-adapter.ts
3442
+ // src/sources/claude/claude-source-adapter.ts
3126
3443
  import os2 from "os";
3127
3444
  import path6 from "path";
3128
3445
 
@@ -3257,11 +3574,6 @@ async function discoverFiles(rootDir, options) {
3257
3574
  return toCanonicalFiles(files, resolvedOptions);
3258
3575
  }
3259
3576
 
3260
- // src/utils/discover-jsonl-files.ts
3261
- async function discoverJsonlFiles(rootDir) {
3262
- return discoverFiles(rootDir, { extension: ".jsonl" });
3263
- }
3264
-
3265
3577
  // src/utils/fs-helpers.ts
3266
3578
  import { access as access2, constants as constants2, stat as stat3 } from "fs/promises";
3267
3579
  async function pathExists(filePath) {
@@ -3343,6 +3655,22 @@ async function* readJsonlObjects(filePath, options = {}) {
3343
3655
  }
3344
3656
  }
3345
3657
 
3658
+ // src/sources/parse-diagnostics.ts
3659
+ function incrementSkippedReason(reasons, reason) {
3660
+ const current = reasons.get(reason) ?? 0;
3661
+ reasons.set(reason, current + 1);
3662
+ }
3663
+ function toSkippedRowReasonStats(reasons) {
3664
+ return [...reasons.entries()].map(([reason, count]) => ({ reason, count })).sort((left, right) => compareByCodePoint(left.reason, right.reason));
3665
+ }
3666
+ function toParseDiagnostics(events, skippedRows, skippedRowReasons) {
3667
+ return {
3668
+ events,
3669
+ skippedRows,
3670
+ skippedRowReasons: toSkippedRowReasonStats(skippedRowReasons)
3671
+ };
3672
+ }
3673
+
3346
3674
  // src/sources/parsing-utils.ts
3347
3675
  var MIN_PLAUSIBLE_UNIX_SECONDS_ABS = 1e8;
3348
3676
  var UNIX_SECONDS_ABS_CUTOFF = 1e10;
@@ -3395,8 +3723,180 @@ function normalizeTimestampCandidate(candidate) {
3395
3723
  return date.toISOString();
3396
3724
  }
3397
3725
 
3726
+ // src/sources/claude/claude-source-adapter.ts
3727
+ var defaultClaudeProjectsDir = path6.join(os2.homedir(), ".claude", "projects");
3728
+ var CLAUDE_ASSISTANT_LINE_PATTERN = /"type"\s*:\s*"assistant"/u;
3729
+ var CLAUDE_USAGE_LINE_PATTERN = /"usage"\s*:/u;
3730
+ function shouldParseClaudeJsonlLine(lineText) {
3731
+ return CLAUDE_ASSISTANT_LINE_PATTERN.test(lineText) && CLAUDE_USAGE_LINE_PATTERN.test(lineText);
3732
+ }
3733
+ function getFallbackSessionId(filePath) {
3734
+ return path6.basename(filePath, ".jsonl");
3735
+ }
3736
+ function resolveProvider(message, model) {
3737
+ const explicitProvider = asTrimmedText(message.provider);
3738
+ if (explicitProvider) {
3739
+ return explicitProvider;
3740
+ }
3741
+ if (model?.toLowerCase().startsWith("claude-")) {
3742
+ return "anthropic";
3743
+ }
3744
+ return void 0;
3745
+ }
3746
+ function parseUsage(usage) {
3747
+ const inputTokens = normalizeNonNegativeInteger(toNumberLike(usage.input_tokens));
3748
+ const outputTokens = normalizeNonNegativeInteger(toNumberLike(usage.output_tokens));
3749
+ const cacheReadTokens = normalizeNonNegativeInteger(toNumberLike(usage.cache_read_input_tokens));
3750
+ const cacheWriteTokens = normalizeNonNegativeInteger(
3751
+ toNumberLike(usage.cache_creation_input_tokens)
3752
+ );
3753
+ const totalTokens = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
3754
+ if (totalTokens === 0) {
3755
+ return void 0;
3756
+ }
3757
+ return {
3758
+ inputTokens,
3759
+ outputTokens,
3760
+ reasoningTokens: 0,
3761
+ cacheReadTokens,
3762
+ cacheWriteTokens,
3763
+ totalTokens
3764
+ };
3765
+ }
3766
+ function createDedupKey(filePath, line, message) {
3767
+ const messageId = asTrimmedText(message.id);
3768
+ if (messageId) {
3769
+ return `${filePath}\0${messageId}`;
3770
+ }
3771
+ const uuid = asTrimmedText(line.uuid);
3772
+ return uuid ? `${filePath}\0${uuid}` : void 0;
3773
+ }
3774
+ function comparePendingEvents(left, right) {
3775
+ if (left.timestamp !== right.timestamp) {
3776
+ return compareByCodePoint(left.timestamp, right.timestamp);
3777
+ }
3778
+ return left.sequence - right.sequence;
3779
+ }
3780
+ var ClaudeSourceAdapter = class {
3781
+ id = "claude";
3782
+ projectsDir;
3783
+ requireProjectsDir;
3784
+ constructor(options = {}) {
3785
+ this.projectsDir = options.projectsDir ?? defaultClaudeProjectsDir;
3786
+ this.requireProjectsDir = options.requireProjectsDir ?? false;
3787
+ }
3788
+ getNormalizedProjectsDir() {
3789
+ if (isBlankText(this.projectsDir)) {
3790
+ throw new Error("Claude projects directory must be a non-empty path");
3791
+ }
3792
+ return this.projectsDir.trim();
3793
+ }
3794
+ async discoverFiles() {
3795
+ const normalizedProjectsDir = this.getNormalizedProjectsDir();
3796
+ if (this.requireProjectsDir && !await pathReadable(normalizedProjectsDir)) {
3797
+ throw new Error(
3798
+ `Claude projects directory is missing or unreadable: ${normalizedProjectsDir}`
3799
+ );
3800
+ }
3801
+ if (this.requireProjectsDir && !await pathIsDirectory(normalizedProjectsDir)) {
3802
+ throw new Error(`Claude projects directory is not a directory: ${normalizedProjectsDir}`);
3803
+ }
3804
+ return discoverFiles(normalizedProjectsDir, { extension: ".jsonl" });
3805
+ }
3806
+ async parseFile(filePath) {
3807
+ const { events } = await this.parseFileWithDiagnostics(filePath);
3808
+ return events;
3809
+ }
3810
+ async parseFileWithDiagnostics(filePath) {
3811
+ const eventsByDedupKey = /* @__PURE__ */ new Map();
3812
+ let skippedRows = 0;
3813
+ let sequence = 0;
3814
+ const skippedRowReasons = /* @__PURE__ */ new Map();
3815
+ for await (const line of readJsonlObjects(filePath, {
3816
+ shouldParseLine: shouldParseClaudeJsonlLine
3817
+ })) {
3818
+ if (asTrimmedText(line.type) !== "assistant") {
3819
+ continue;
3820
+ }
3821
+ const message = asRecord(line.message);
3822
+ if (!message || asTrimmedText(message.role) !== "assistant") {
3823
+ skippedRows++;
3824
+ incrementSkippedReason(skippedRowReasons, "invalid_assistant_message");
3825
+ continue;
3826
+ }
3827
+ const model = asTrimmedText(message.model);
3828
+ if (model === "<synthetic>") {
3829
+ skippedRows++;
3830
+ incrementSkippedReason(skippedRowReasons, "synthetic_message");
3831
+ continue;
3832
+ }
3833
+ const usage = parseUsage(asRecord(message.usage) ?? {});
3834
+ if (!usage) {
3835
+ skippedRows++;
3836
+ incrementSkippedReason(skippedRowReasons, "no_token_usage");
3837
+ continue;
3838
+ }
3839
+ const timestamp = normalizeTimestampCandidate(line.timestamp);
3840
+ if (!timestamp) {
3841
+ skippedRows++;
3842
+ incrementSkippedReason(skippedRowReasons, "invalid_timestamp");
3843
+ continue;
3844
+ }
3845
+ const dedupKey = createDedupKey(filePath, line, message);
3846
+ if (!dedupKey) {
3847
+ skippedRows++;
3848
+ incrementSkippedReason(skippedRowReasons, "missing_message_id");
3849
+ continue;
3850
+ }
3851
+ const sessionId = asTrimmedText(line.sessionId) ?? getFallbackSessionId(filePath);
3852
+ const repoRoot = asTrimmedText(line.cwd);
3853
+ const provider = resolveProvider(message, model);
3854
+ sequence += 1;
3855
+ eventsByDedupKey.set(dedupKey, {
3856
+ sessionId,
3857
+ timestamp,
3858
+ repoRoot,
3859
+ provider,
3860
+ model,
3861
+ usage,
3862
+ sequence
3863
+ });
3864
+ }
3865
+ const events = [];
3866
+ for (const pendingEvent of [...eventsByDedupKey.values()].sort(comparePendingEvents)) {
3867
+ try {
3868
+ events.push(
3869
+ createUsageEvent({
3870
+ source: this.id,
3871
+ sessionId: pendingEvent.sessionId,
3872
+ timestamp: pendingEvent.timestamp,
3873
+ repoRoot: pendingEvent.repoRoot,
3874
+ provider: pendingEvent.provider,
3875
+ model: pendingEvent.model,
3876
+ ...pendingEvent.usage,
3877
+ costMode: "estimated"
3878
+ })
3879
+ );
3880
+ } catch {
3881
+ skippedRows++;
3882
+ incrementSkippedReason(skippedRowReasons, "event_creation_failed");
3883
+ }
3884
+ }
3885
+ return toParseDiagnostics(events, skippedRows, skippedRowReasons);
3886
+ }
3887
+ };
3888
+
3889
+ // src/sources/codex/codex-source-adapter.ts
3890
+ import os3 from "os";
3891
+ import path7 from "path";
3892
+
3893
+ // src/utils/discover-jsonl-files.ts
3894
+ async function discoverJsonlFiles(rootDir) {
3895
+ return discoverFiles(rootDir, { extension: ".jsonl" });
3896
+ }
3897
+
3398
3898
  // src/sources/codex/codex-source-adapter.ts
3399
- var defaultSessionsDir = path6.join(os2.homedir(), ".codex", "sessions");
3899
+ var defaultSessionsDir = path7.join(os3.homedir(), ".codex", "sessions");
3400
3900
  var LEGACY_CODEX_MODEL_FALLBACK = "legacy-codex-unknown";
3401
3901
  var SESSION_META_LINE_PATTERN = /"type"\s*:\s*"session_meta"/u;
3402
3902
  var TURN_CONTEXT_LINE_PATTERN = /"type"\s*:\s*"turn_context"/u;
@@ -3490,8 +3990,8 @@ function createLastUsageOnlyKey(timestamp, usage) {
3490
3990
  usage.totalTokens
3491
3991
  ].join(":");
3492
3992
  }
3493
- function getFallbackSessionId(filePath) {
3494
- return path6.basename(filePath, ".jsonl");
3993
+ function getFallbackSessionId2(filePath) {
3994
+ return path7.basename(filePath, ".jsonl");
3495
3995
  }
3496
3996
  function resolveRepoRootFromPayload(payload) {
3497
3997
  if (!payload) {
@@ -3531,7 +4031,7 @@ var CodexSourceAdapter = class {
3531
4031
  async parseFile(filePath) {
3532
4032
  const events = [];
3533
4033
  const state = {
3534
- sessionId: getFallbackSessionId(filePath),
4034
+ sessionId: getFallbackSessionId2(filePath),
3535
4035
  provider: "openai"
3536
4036
  };
3537
4037
  for await (const line of readJsonlObjects(filePath, {
@@ -3623,37 +4123,19 @@ var CodexSourceAdapter = class {
3623
4123
 
3624
4124
  // src/sources/droid/droid-source-adapter.ts
3625
4125
  import { readFile as readFile2 } from "fs/promises";
3626
- import os3 from "os";
3627
- import path7 from "path";
3628
-
3629
- // src/sources/parse-diagnostics.ts
3630
- function incrementSkippedReason(reasons, reason) {
3631
- const current = reasons.get(reason) ?? 0;
3632
- reasons.set(reason, current + 1);
3633
- }
3634
- function toSkippedRowReasonStats(reasons) {
3635
- return [...reasons.entries()].map(([reason, count]) => ({ reason, count })).sort((left, right) => compareByCodePoint(left.reason, right.reason));
3636
- }
3637
- function toParseDiagnostics(events, skippedRows, skippedRowReasons) {
3638
- return {
3639
- events,
3640
- skippedRows,
3641
- skippedRowReasons: toSkippedRowReasonStats(skippedRowReasons)
3642
- };
3643
- }
3644
-
3645
- // src/sources/droid/droid-source-adapter.ts
3646
- var defaultSessionsDir2 = path7.join(os3.homedir(), ".factory", "sessions");
4126
+ import os4 from "os";
4127
+ import path8 from "path";
4128
+ var defaultSessionsDir2 = path8.join(os4.homedir(), ".factory", "sessions");
3647
4129
  var DROID_SESSION_START_LINE_PATTERN = /"type"\s*:\s*"session_start"/u;
3648
4130
  var DROID_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
3649
4131
  function shouldParseDroidJsonlLine(lineText) {
3650
4132
  return DROID_SESSION_START_LINE_PATTERN.test(lineText) || DROID_MESSAGE_LINE_PATTERN.test(lineText);
3651
4133
  }
3652
4134
  function getSettingsSessionId(filePath) {
3653
- return path7.basename(filePath, ".settings.json");
4135
+ return path8.basename(filePath, ".settings.json");
3654
4136
  }
3655
4137
  function getSiblingJsonlPath(settingsPath) {
3656
- return path7.join(path7.dirname(settingsPath), `${getSettingsSessionId(settingsPath)}.jsonl`);
4138
+ return path8.join(path8.dirname(settingsPath), `${getSettingsSessionId(settingsPath)}.jsonl`);
3657
4139
  }
3658
4140
  function isSessionStartRecord(line) {
3659
4141
  return asTrimmedText(line.type) === "session_start";
@@ -3797,11 +4279,11 @@ var DroidSourceAdapter = class {
3797
4279
 
3798
4280
  // src/sources/gemini/gemini-source-adapter.ts
3799
4281
  import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
3800
- import os4 from "os";
3801
- import path8 from "path";
3802
- var defaultGeminiDir = path8.join(os4.homedir(), ".gemini");
4282
+ import os5 from "os";
4283
+ import path9 from "path";
4284
+ var defaultGeminiDir = path9.join(os5.homedir(), ".gemini");
3803
4285
  function getProjectsJsonPath(geminiDir) {
3804
- return path8.join(geminiDir, "projects.json");
4286
+ return path9.join(geminiDir, "projects.json");
3805
4287
  }
3806
4288
  function parseProjectsJson(data) {
3807
4289
  const mapping = /* @__PURE__ */ new Map();
@@ -3836,7 +4318,7 @@ async function loadProjectsJson(geminiDir) {
3836
4318
  }
3837
4319
  }
3838
4320
  async function discoverSessionFiles(geminiDir) {
3839
- const tmpDir = path8.join(geminiDir, "tmp");
4321
+ const tmpDir = path9.join(geminiDir, "tmp");
3840
4322
  let projectEntries;
3841
4323
  try {
3842
4324
  projectEntries = await readdir2(tmpDir, { withFileTypes: true, encoding: "utf8" });
@@ -3853,7 +4335,7 @@ async function discoverSessionFiles(geminiDir) {
3853
4335
  if (!projectEntry.isDirectory() && !projectEntry.isSymbolicLink()) {
3854
4336
  continue;
3855
4337
  }
3856
- const chatsDir = path8.join(tmpDir, projectEntry.name, "chats");
4338
+ const chatsDir = path9.join(tmpDir, projectEntry.name, "chats");
3857
4339
  const discoveredChatFiles = await discoverFiles(chatsDir, { extension: ".json" });
3858
4340
  allSessionFiles.push(...discoveredChatFiles);
3859
4341
  }
@@ -3867,9 +4349,9 @@ function resolveRepoRoot(filePath, sessionData, projectMapping) {
3867
4349
  return mappedRoot;
3868
4350
  }
3869
4351
  }
3870
- const chatsDir = path8.dirname(filePath);
3871
- const projectDir = path8.dirname(chatsDir);
3872
- const projectIdentifier = path8.basename(projectDir);
4352
+ const chatsDir = path9.dirname(filePath);
4353
+ const projectDir = path9.dirname(chatsDir);
4354
+ const projectIdentifier = path9.basename(projectDir);
3873
4355
  return projectMapping.get(projectIdentifier);
3874
4356
  }
3875
4357
  function toFiniteNumber(value) {
@@ -3978,7 +4460,7 @@ var GeminiSourceAdapter = class {
3978
4460
  incrementSkippedReason(skippedRowReasons, "invalid_session_data");
3979
4461
  return toParseDiagnostics(events, skippedRows, skippedRowReasons);
3980
4462
  }
3981
- const sessionId = asTrimmedText(sessionDataRecord.sessionId) ?? path8.basename(filePath, ".json");
4463
+ const sessionId = asTrimmedText(sessionDataRecord.sessionId) ?? path9.basename(filePath, ".json");
3982
4464
  const projectMapping = await this.getProjectMappingForParse();
3983
4465
  const repoRoot = resolveRepoRoot(filePath, sessionDataRecord, projectMapping);
3984
4466
  if (!Array.isArray(sessionDataRecord.messages)) {
@@ -4035,8 +4517,8 @@ var GeminiSourceAdapter = class {
4035
4517
  };
4036
4518
 
4037
4519
  // src/sources/opencode/opencode-db-path-resolver.ts
4038
- import os5 from "os";
4039
- import path9 from "path";
4520
+ import os6 from "os";
4521
+ import path10 from "path";
4040
4522
  function deduplicate(paths) {
4041
4523
  return [...new Set(paths)];
4042
4524
  }
@@ -4048,39 +4530,39 @@ function normalizeEnvPath(value) {
4048
4530
  return normalized || void 0;
4049
4531
  }
4050
4532
  function getLinuxLikeCandidates(homeDir, env) {
4051
- const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path9.join(homeDir, ".local", "share");
4533
+ const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path10.join(homeDir, ".local", "share");
4052
4534
  return [
4053
- path9.join(xdgDataHome, "opencode", "opencode.db"),
4054
- path9.join(xdgDataHome, "opencode", "db.sqlite"),
4055
- path9.join(homeDir, ".opencode", "opencode.db"),
4056
- path9.join(homeDir, ".opencode", "db.sqlite")
4535
+ path10.join(xdgDataHome, "opencode", "opencode.db"),
4536
+ path10.join(xdgDataHome, "opencode", "db.sqlite"),
4537
+ path10.join(homeDir, ".opencode", "opencode.db"),
4538
+ path10.join(homeDir, ".opencode", "db.sqlite")
4057
4539
  ];
4058
4540
  }
4059
4541
  function getMacOsCandidates(homeDir) {
4060
- const appSupportDir = path9.join(homeDir, "Library", "Application Support");
4542
+ const appSupportDir = path10.join(homeDir, "Library", "Application Support");
4061
4543
  return [
4062
- path9.join(appSupportDir, "opencode", "opencode.db"),
4063
- path9.join(appSupportDir, "opencode", "db.sqlite"),
4064
- path9.join(homeDir, ".opencode", "opencode.db"),
4065
- path9.join(homeDir, ".opencode", "db.sqlite")
4544
+ path10.join(appSupportDir, "opencode", "opencode.db"),
4545
+ path10.join(appSupportDir, "opencode", "db.sqlite"),
4546
+ path10.join(homeDir, ".opencode", "opencode.db"),
4547
+ path10.join(homeDir, ".opencode", "db.sqlite")
4066
4548
  ];
4067
4549
  }
4068
4550
  function getWindowsCandidates(homeDir, env) {
4069
4551
  const userProfile = normalizeEnvPath(env.USERPROFILE);
4070
- const roamingBase = normalizeEnvPath(env.APPDATA) ?? normalizeEnvPath(env.LOCALAPPDATA) ?? (userProfile ? path9.join(userProfile, "AppData", "Roaming") : void 0);
4552
+ const roamingBase = normalizeEnvPath(env.APPDATA) ?? normalizeEnvPath(env.LOCALAPPDATA) ?? (userProfile ? path10.join(userProfile, "AppData", "Roaming") : void 0);
4071
4553
  const roamingCandidates = roamingBase ? [
4072
- path9.join(roamingBase, "opencode", "opencode.db"),
4073
- path9.join(roamingBase, "opencode", "db.sqlite")
4554
+ path10.join(roamingBase, "opencode", "opencode.db"),
4555
+ path10.join(roamingBase, "opencode", "db.sqlite")
4074
4556
  ] : [];
4075
4557
  return [
4076
4558
  ...roamingCandidates,
4077
- path9.join(homeDir, ".opencode", "opencode.db"),
4078
- path9.join(homeDir, ".opencode", "db.sqlite")
4559
+ path10.join(homeDir, ".opencode", "opencode.db"),
4560
+ path10.join(homeDir, ".opencode", "db.sqlite")
4079
4561
  ];
4080
4562
  }
4081
4563
  function getDefaultOpenCodeDbPathCandidates(options = {}) {
4082
4564
  const platform = options.platform ?? process.platform;
4083
- const homeDir = options.homeDir ?? os5.homedir();
4565
+ const homeDir = options.homeDir ?? os6.homedir();
4084
4566
  const env = options.env ?? process.env;
4085
4567
  switch (platform) {
4086
4568
  case "win32":
@@ -4569,9 +5051,9 @@ var OpenCodeSourceAdapter = class {
4569
5051
  };
4570
5052
 
4571
5053
  // src/sources/pi/pi-source-adapter.ts
4572
- import os6 from "os";
4573
- import path10 from "path";
4574
- var defaultSessionsDir3 = path10.join(os6.homedir(), ".pi", "agent", "sessions");
5054
+ import os7 from "os";
5055
+ import path11 from "path";
5056
+ var defaultSessionsDir3 = path11.join(os7.homedir(), ".pi", "agent", "sessions");
4575
5057
  var PI_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
4576
5058
  var PI_SESSION_LINE_PATTERN = /"type"\s*:\s*"session"/u;
4577
5059
  var PI_MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
@@ -4644,8 +5126,8 @@ function extractUsage(line, message) {
4644
5126
  }
4645
5127
  return extractUsageFromRecord(messageUsage);
4646
5128
  }
4647
- function getFallbackSessionId2(filePath) {
4648
- return path10.basename(filePath, ".jsonl");
5129
+ function getFallbackSessionId3(filePath) {
5130
+ return path11.basename(filePath, ".jsonl");
4649
5131
  }
4650
5132
  function resolveRepoRootFromRecord(record) {
4651
5133
  if (!record) {
@@ -4677,7 +5159,7 @@ var PiSourceAdapter = class {
4677
5159
  }
4678
5160
  async parseFile(filePath) {
4679
5161
  const events = [];
4680
- const state = { sessionId: getFallbackSessionId2(filePath) };
5162
+ const state = { sessionId: getFallbackSessionId3(filePath) };
4681
5163
  for await (const line of readJsonlObjects(filePath, {
4682
5164
  shouldParseLine: shouldParsePiJsonlLine
4683
5165
  })) {
@@ -4816,6 +5298,21 @@ var sourceRegistrations = [
4816
5298
  create: (options) => new OpenCodeSourceAdapter({
4817
5299
  dbPath: options.opencodeDb
4818
5300
  })
5301
+ },
5302
+ {
5303
+ id: "claude",
5304
+ sourceDirOverride: { kind: "directory" },
5305
+ create: (options, sourceDirectoryOverrides) => {
5306
+ const directoryConfig = resolveDirectoryConfig(
5307
+ "claude",
5308
+ options.claudeDir,
5309
+ sourceDirectoryOverrides
5310
+ );
5311
+ return new ClaudeSourceAdapter({
5312
+ projectsDir: directoryConfig.path,
5313
+ requireProjectsDir: directoryConfig.requireExistingPath
5314
+ });
5315
+ }
4819
5316
  }
4820
5317
  ];
4821
5318
  var sourceDirUnsupportedFlags = new Map(
@@ -4892,6 +5389,7 @@ function createDefaultAdapters(options) {
4892
5389
  validateDirectoryOverride("--codex-dir", options.codexDir);
4893
5390
  validateDirectoryOverride("--gemini-dir", options.geminiDir);
4894
5391
  validateDirectoryOverride("--droid-dir", options.droidDir);
5392
+ validateDirectoryOverride("--claude-dir", options.claudeDir);
4895
5393
  const sourceDirectoryOverrides = parseSourceDirectoryOverrides(options.sourceDir);
4896
5394
  validateSourceDirectoryOverrideIds(sourceDirectoryOverrides);
4897
5395
  return sourceRegistrations.map((source) => source.create(options, sourceDirectoryOverrides));
@@ -5007,6 +5505,9 @@ function resolveExplicitSourceIds(options, sourceFilter) {
5007
5505
  if (options.droidDir) {
5008
5506
  explicitSourceIds.add("droid");
5009
5507
  }
5508
+ if (options.claudeDir) {
5509
+ explicitSourceIds.add("claude");
5510
+ }
5010
5511
  if (options.opencodeDb) {
5011
5512
  explicitSourceIds.add("opencode");
5012
5513
  }
@@ -5135,7 +5636,7 @@ function normalizeSkippedRowReasons(value) {
5135
5636
 
5136
5637
  // src/cli/parse-file-cache.ts
5137
5638
  import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat4, writeFile as writeFile2 } from "fs/promises";
5138
- import path11 from "path";
5639
+ import path12 from "path";
5139
5640
  var PARSE_FILE_CACHE_VERSION = 5;
5140
5641
  var CACHE_KEY_SEPARATOR = "\0";
5141
5642
  function createCacheKey(source, filePath) {
@@ -5362,7 +5863,7 @@ function normalizeCacheEntry(value) {
5362
5863
  };
5363
5864
  }
5364
5865
  function getDefaultParseFileCachePath() {
5365
- return path11.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
5866
+ return path12.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
5366
5867
  }
5367
5868
  function normalizeCacheShardSource(source) {
5368
5869
  const normalizedSource = normalizeCacheSource(source);
@@ -5372,12 +5873,12 @@ function normalizeCacheShardSource(source) {
5372
5873
  return normalizedSource.replace(/[^a-z0-9._-]/gu, "_");
5373
5874
  }
5374
5875
  function getSourceShardedParseFileCachePath(cacheFilePath, source) {
5375
- const parsedPath = path11.parse(cacheFilePath);
5876
+ const parsedPath = path12.parse(cacheFilePath);
5376
5877
  const sourceShard = normalizeCacheShardSource(source);
5377
5878
  if (parsedPath.ext.length > 0) {
5378
- return path11.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
5879
+ return path12.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
5379
5880
  }
5380
- return path11.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
5881
+ return path12.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
5381
5882
  }
5382
5883
  var ParseFileCache = class _ParseFileCache {
5383
5884
  constructor(cacheFilePath, limits, now) {
@@ -5385,6 +5886,9 @@ var ParseFileCache = class _ParseFileCache {
5385
5886
  this.limits = limits;
5386
5887
  this.now = now;
5387
5888
  }
5889
+ cacheFilePath;
5890
+ limits;
5891
+ now;
5388
5892
  entriesByKey = /* @__PURE__ */ new Map();
5389
5893
  dirty = false;
5390
5894
  static async load(options) {
@@ -5471,7 +5975,7 @@ var ParseFileCache = class _ParseFileCache {
5471
5975
  keptEntries.length = bestCount;
5472
5976
  payloadText = bestPayloadText;
5473
5977
  }
5474
- await mkdir2(path11.dirname(this.cacheFilePath), { recursive: true });
5978
+ await mkdir2(path12.dirname(this.cacheFilePath), { recursive: true });
5475
5979
  const temporaryPath = `${this.cacheFilePath}.${process.pid}.${this.now()}.tmp`;
5476
5980
  try {
5477
5981
  await writeFile2(temporaryPath, payloadText, "utf8");
@@ -5960,7 +6464,7 @@ function applyPricingToEvents(events, pricingSource) {
5960
6464
 
5961
6465
  // src/pricing/litellm-pricing-fetcher.ts
5962
6466
  import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
5963
- import path12 from "path";
6467
+ import path13 from "path";
5964
6468
 
5965
6469
  // src/pricing/litellm-model-map.json
5966
6470
  var litellm_model_map_default = {
@@ -6144,7 +6648,7 @@ function normalizeLitellmPricingPayload(payload) {
6144
6648
  return normalizedPricing;
6145
6649
  }
6146
6650
  function getDefaultLiteLLMPricingCachePath() {
6147
- return path12.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
6651
+ return path13.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
6148
6652
  }
6149
6653
  function stripProviderPrefix(model) {
6150
6654
  const slashIndex = model.lastIndexOf("/");
@@ -6538,7 +7042,7 @@ var LiteLLMPricingFetcher = class {
6538
7042
  };
6539
7043
  }
6540
7044
  async writeCache() {
6541
- const directoryPath = path12.dirname(this.cacheFilePath);
7045
+ const directoryPath = path13.dirname(this.cacheFilePath);
6542
7046
  await mkdir3(directoryPath, { recursive: true });
6543
7047
  const payload = {
6544
7048
  fetchedAt: this.now(),
@@ -6649,6 +7153,7 @@ var RuntimeProfileCollector = class {
6649
7153
  constructor(now = () => performance.now()) {
6650
7154
  this.now = now;
6651
7155
  }
7156
+ now;
6652
7157
  sourceSelection;
6653
7158
  sourceStats = /* @__PURE__ */ new Map();
6654
7159
  stageDurations = /* @__PURE__ */ new Map();
@@ -6942,6 +7447,9 @@ function resolveScopeNote(options) {
6942
7447
  if (hasActiveTextOption(options.droidDir)) {
6943
7448
  activeFilters.push("--droid-dir");
6944
7449
  }
7450
+ if (hasActiveTextOption(options.claudeDir)) {
7451
+ activeFilters.push("--claude-dir");
7452
+ }
6945
7453
  if (hasActiveTextOption(options.opencodeDb)) {
6946
7454
  activeFilters.push("--opencode-db");
6947
7455
  }
@@ -7127,10 +7635,11 @@ function emitEnvVarOverrides(activeEnvOverrides, diagnosticsLogger) {
7127
7635
 
7128
7636
  // src/cli/share-artifact.ts
7129
7637
  import { spawn as spawn4 } from "child_process";
7130
- import { writeFile as writeFile4 } from "fs/promises";
7131
- import path13 from "path";
7638
+ import { constants as constants3 } from "fs";
7639
+ import { access as access3, writeFile as writeFile4 } from "fs/promises";
7640
+ import path14 from "path";
7132
7641
  async function writeShareSvgFile(fileName, svgContent) {
7133
- const outputPath = path13.resolve(process.cwd(), fileName);
7642
+ const outputPath = path14.resolve(process.cwd(), fileName);
7134
7643
  await writeFile4(outputPath, svgContent, "utf8");
7135
7644
  return outputPath;
7136
7645
  }
@@ -7140,21 +7649,68 @@ function stringifyError(error) {
7140
7649
  }
7141
7650
  return String(error);
7142
7651
  }
7143
- function resolveOpenCommand(filePath, platform) {
7652
+ var WINDOWS_OPEN_COMMAND = "C:\\Windows\\System32\\rundll32.exe";
7653
+ var DARWIN_OPEN_COMMAND = "/usr/bin/open";
7654
+ var UNIX_OPEN_COMMAND = "/usr/bin/xdg-open";
7655
+ var WINDOWS_FALLBACK_COMMANDS = ["rundll32.exe"];
7656
+ var DARWIN_FALLBACK_COMMANDS = ["open"];
7657
+ var UNIX_FALLBACK_COMMANDS = ["xdg-open"];
7658
+ async function fileExists(filePath) {
7659
+ try {
7660
+ await access3(filePath, constants3.X_OK);
7661
+ return true;
7662
+ } catch {
7663
+ return false;
7664
+ }
7665
+ }
7666
+ async function resolveBinaryPath(primaryPath, fallbackNames) {
7667
+ if (await fileExists(primaryPath)) {
7668
+ return primaryPath;
7669
+ }
7670
+ const pathDirs = (process.env.PATH ?? "").split(path14.delimiter).filter(Boolean);
7671
+ for (const fallbackName of fallbackNames) {
7672
+ for (const dir of pathDirs) {
7673
+ const candidate = path14.join(dir, fallbackName);
7674
+ if (await fileExists(candidate)) {
7675
+ return candidate;
7676
+ }
7677
+ }
7678
+ }
7679
+ return void 0;
7680
+ }
7681
+ async function resolveOpenCommand(filePath, platform) {
7144
7682
  if (platform === "win32") {
7683
+ const resolvedPath2 = await resolveBinaryPath(WINDOWS_OPEN_COMMAND, WINDOWS_FALLBACK_COMMANDS);
7684
+ if (!resolvedPath2) {
7685
+ throw new Error(
7686
+ "Could not find rundll32.exe. Please ensure Windows System32 is accessible or rundll32.exe is available on PATH."
7687
+ );
7688
+ }
7145
7689
  return {
7146
- command: "cmd",
7147
- args: ["/c", "start", "", filePath]
7690
+ command: resolvedPath2,
7691
+ args: ["shell32.dll,ShellExec_RunDLL", filePath]
7148
7692
  };
7149
7693
  }
7150
7694
  if (platform === "darwin") {
7695
+ const resolvedPath2 = await resolveBinaryPath(DARWIN_OPEN_COMMAND, DARWIN_FALLBACK_COMMANDS);
7696
+ if (!resolvedPath2) {
7697
+ throw new Error(
7698
+ "Could not find open command. Please ensure macOS is properly configured or open is available on PATH."
7699
+ );
7700
+ }
7151
7701
  return {
7152
- command: "open",
7702
+ command: resolvedPath2,
7153
7703
  args: [filePath]
7154
7704
  };
7155
7705
  }
7706
+ const resolvedPath = await resolveBinaryPath(UNIX_OPEN_COMMAND, UNIX_FALLBACK_COMMANDS);
7707
+ if (!resolvedPath) {
7708
+ throw new Error(
7709
+ "Could not find xdg-open. Please install xdg-utils or ensure it is in your PATH."
7710
+ );
7711
+ }
7156
7712
  return {
7157
- command: "xdg-open",
7713
+ command: resolvedPath,
7158
7714
  args: [filePath]
7159
7715
  };
7160
7716
  }
@@ -7185,7 +7741,7 @@ async function spawnDetached(command, args) {
7185
7741
  async function openShareSvgFile(filePath, deps = {}) {
7186
7742
  const platform = deps.platform ?? process.platform;
7187
7743
  const runDetached = deps.spawnDetached ?? spawnDetached;
7188
- const { command, args } = resolveOpenCommand(filePath, platform);
7744
+ const { command, args } = await resolveOpenCommand(filePath, platform);
7189
7745
  await runDetached(command, args);
7190
7746
  }
7191
7747
  async function writeAndOpenShareSvgFile(fileName, svgContent, deps = {}) {
@@ -7473,7 +8029,7 @@ function resolveTerminalContextLines(optimizeData, options) {
7473
8029
  const bestRow = rowsWithSavings.length > 0 ? rowsWithSavings.reduce(
7474
8030
  (best, current) => (current.savingsUsd ?? Number.NEGATIVE_INFINITY) > (best.savingsUsd ?? Number.NEGATIVE_INFINITY) ? current : best
7475
8031
  ) : void 0;
7476
- if (!bestRow || bestRow.savingsUsd === void 0) {
8032
+ if (bestRow?.savingsUsd === void 0) {
7477
8033
  lines.push("ALL best candidate: unavailable (missing baseline or candidate pricing)");
7478
8034
  } else if (bestRow.savingsUsd > 0) {
7479
8035
  lines.push(
@@ -7529,9 +8085,6 @@ function toTableCells(optimizeData, options) {
7529
8085
  return options.includeNotesColumn ? [...candidateCells, styleNotesCell(row.notes, notesCell, options.useColor)] : candidateCells;
7530
8086
  });
7531
8087
  }
7532
- function toMarkdownSafeCell2(value) {
7533
- return value.replace(/\r?\n/gu, "<br>");
7534
- }
7535
8088
  function toTableRowMeta2(row) {
7536
8089
  return {
7537
8090
  periodKey: row.periodKey,
@@ -7587,7 +8140,7 @@ function renderMarkdownOptimizeReport(optimizeData) {
7587
8140
  const bodyRows = toTableCells(optimizeData, {
7588
8141
  useColor: false,
7589
8142
  includeNotesColumn
7590
- }).map((row) => row.map((cell) => toMarkdownSafeCell2(cell)));
8143
+ }).map((row) => row.map((cell) => toMarkdownSafeCell(cell)));
7591
8144
  const tableRows = [headerCells, ...bodyRows];
7592
8145
  const alignment2 = headerCells.map((_, index) => index <= 1 ? "l" : "r");
7593
8146
  return markdownTable2(tableRows, { align: alignment2 });
@@ -8916,13 +9469,59 @@ async function buildUsageData(granularity, options, deps = {}) {
8916
9469
  // src/render/markdown-table.ts
8917
9470
  import { markdownTable as markdownTable3 } from "markdown-table";
8918
9471
  var alignment = ["l", "l", "l", "r", "r", "r", "r", "r", "r", "r"];
8919
- function toMarkdownSafeCell3(value) {
8920
- return value.replace(/\r?\n/gu, "<br>");
9472
+ function boldMarkdownText(value) {
9473
+ const safeValue = toMarkdownSafeCell(value);
9474
+ return safeValue.length === 0 ? safeValue : `**${safeValue}**`;
9475
+ }
9476
+ function emphasizeMarkdownModelsCell(value) {
9477
+ const lines = splitCellLines(value);
9478
+ return lines.map((line, index) => {
9479
+ if (line.length === 0) {
9480
+ return "";
9481
+ }
9482
+ if (line === "\u03A3 TOTAL") {
9483
+ return boldMarkdownText(line);
9484
+ }
9485
+ if (index === 0 && lines.length > 1) {
9486
+ return boldMarkdownText(line);
9487
+ }
9488
+ return toMarkdownSafeCell(line);
9489
+ }).join("<br>");
9490
+ }
9491
+ function emphasizeMarkdownSummaryMetricCell(value) {
9492
+ const lines = splitCellLines(value);
9493
+ if (lines.length <= 1) {
9494
+ return boldMarkdownText(value);
9495
+ }
9496
+ return lines.map(
9497
+ (line, index) => index === lines.length - 1 ? boldMarkdownText(line) : toMarkdownSafeCell(line)
9498
+ ).join("<br>");
9499
+ }
9500
+ function emphasizeMarkdownRow(row, cells) {
9501
+ const styledCells = cells.map((cell) => toMarkdownSafeCell(cell));
9502
+ if (styledCells.length > 2) {
9503
+ styledCells[2] = emphasizeMarkdownModelsCell(cells[2]);
9504
+ }
9505
+ if (row.rowType !== "period_source") {
9506
+ if (styledCells.length > 1) {
9507
+ styledCells[1] = boldMarkdownText(cells[1]);
9508
+ }
9509
+ if (styledCells.length > 8) {
9510
+ styledCells[8] = emphasizeMarkdownSummaryMetricCell(cells[8]);
9511
+ }
9512
+ if (styledCells.length > 9) {
9513
+ styledCells[9] = emphasizeMarkdownSummaryMetricCell(cells[9]);
9514
+ }
9515
+ }
9516
+ if (row.rowType === "grand_total" && styledCells.length > 0) {
9517
+ styledCells[0] = boldMarkdownText(cells[0]);
9518
+ }
9519
+ return styledCells;
8921
9520
  }
8922
9521
  function renderMarkdownTable(rows, options = {}) {
8923
9522
  const tableLayout = options.tableLayout ?? "compact";
8924
9523
  const bodyRows = toUsageTableCells(rows, { layout: tableLayout }).map(
8925
- (row) => row.map((cell) => toMarkdownSafeCell3(cell))
9524
+ (cells, index) => emphasizeMarkdownRow(rows[index], cells)
8926
9525
  );
8927
9526
  const tableRows = [Array.from(usageTableHeaders), ...bodyRows];
8928
9527
  return markdownTable3(tableRows, {
@@ -9302,7 +9901,7 @@ function registerSharedReportOptions(command, profile) {
9302
9901
  const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
9303
9902
  const supportedSourcesSummary = `(${supportedSourceIds.length}): ${allowedSourcesLabel}`;
9304
9903
  const profileConfig = sharedOptionProfileConfig[profile];
9305
- const configuredCommand = command.option("--pi-dir <path>", "Path to .pi sessions directory").option("--codex-dir <path>", "Path to .codex sessions directory").option("--gemini-dir <path>", "Path to .gemini directory").option("--droid-dir <path>", "Path to Droid sessions directory").option("--opencode-db <path>", "Path to OpenCode SQLite DB").option(
9904
+ const configuredCommand = command.option("--pi-dir <path>", "Path to .pi sessions directory").option("--codex-dir <path>", "Path to .codex sessions directory").option("--gemini-dir <path>", "Path to .gemini directory").option("--droid-dir <path>", "Path to Droid sessions directory").option("--claude-dir <path>", "Path to Claude projects directory").option("--opencode-db <path>", "Path to OpenCode SQLite DB").option(
9306
9905
  "--source-dir <source-id=path>",
9307
9906
  "Override source directory for directory-backed sources (repeatable)",
9308
9907
  collectRepeatedOption