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/README.md +23 -12
- package/dist/index.js +795 -196
- package/dist/index.js.map +1 -1
- package/package.json +44 -20
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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
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
|
|
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
|
-
["
|
|
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
|
-
|
|
1209
|
-
const
|
|
1210
|
-
|
|
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
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
-
|
|
1224
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
|
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[
|
|
1797
|
-
styledCells[
|
|
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:
|
|
1848
|
-
multilineColumnWidth: fittedCells.widths[
|
|
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
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
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:
|
|
2198
|
-
modelBreakdown:
|
|
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:
|
|
2215
|
-
modelBreakdown:
|
|
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:
|
|
2227
|
-
modelBreakdown:
|
|
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/
|
|
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 =
|
|
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
|
|
3494
|
-
return
|
|
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:
|
|
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
|
|
3627
|
-
import
|
|
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
|
|
4135
|
+
return path8.basename(filePath, ".settings.json");
|
|
3654
4136
|
}
|
|
3655
4137
|
function getSiblingJsonlPath(settingsPath) {
|
|
3656
|
-
return
|
|
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
|
|
3801
|
-
import
|
|
3802
|
-
var defaultGeminiDir =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3871
|
-
const projectDir =
|
|
3872
|
-
const projectIdentifier =
|
|
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) ??
|
|
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
|
|
4039
|
-
import
|
|
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) ??
|
|
4533
|
+
const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path10.join(homeDir, ".local", "share");
|
|
4052
4534
|
return [
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
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 =
|
|
4542
|
+
const appSupportDir = path10.join(homeDir, "Library", "Application Support");
|
|
4061
4543
|
return [
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
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 ?
|
|
4552
|
+
const roamingBase = normalizeEnvPath(env.APPDATA) ?? normalizeEnvPath(env.LOCALAPPDATA) ?? (userProfile ? path10.join(userProfile, "AppData", "Roaming") : void 0);
|
|
4071
4553
|
const roamingCandidates = roamingBase ? [
|
|
4072
|
-
|
|
4073
|
-
|
|
4554
|
+
path10.join(roamingBase, "opencode", "opencode.db"),
|
|
4555
|
+
path10.join(roamingBase, "opencode", "db.sqlite")
|
|
4074
4556
|
] : [];
|
|
4075
4557
|
return [
|
|
4076
4558
|
...roamingCandidates,
|
|
4077
|
-
|
|
4078
|
-
|
|
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 ??
|
|
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
|
|
4573
|
-
import
|
|
4574
|
-
var defaultSessionsDir3 =
|
|
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
|
|
4648
|
-
return
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
5876
|
+
const parsedPath = path12.parse(cacheFilePath);
|
|
5376
5877
|
const sourceShard = normalizeCacheShardSource(source);
|
|
5377
5878
|
if (parsedPath.ext.length > 0) {
|
|
5378
|
-
return
|
|
5879
|
+
return path12.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
|
|
5379
5880
|
}
|
|
5380
|
-
return
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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 {
|
|
7131
|
-
import
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
7147
|
-
args: ["
|
|
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:
|
|
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:
|
|
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 (
|
|
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) =>
|
|
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
|
|
8920
|
-
|
|
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
|
-
(
|
|
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
|