llm-usage-metrics 0.6.0 → 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 +15 -12
- package/dist/index.js +337 -90
- package/dist/index.js.map +1 -1
- package/package.json +29 -11
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) {
|
|
@@ -1076,8 +1077,14 @@ function escapeBareAutolinks(value) {
|
|
|
1076
1077
|
(_, prefix, email) => `${prefix}${email.replace("@", "\\@")}`
|
|
1077
1078
|
);
|
|
1078
1079
|
}
|
|
1080
|
+
function escapeHtmlText(value) {
|
|
1081
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
1082
|
+
}
|
|
1079
1083
|
function escapeMarkdownText(value) {
|
|
1080
|
-
const escapedMarkdownText = value
|
|
1084
|
+
const escapedMarkdownText = escapeHtmlText(value).replace(
|
|
1085
|
+
markdownSpecialCharacterPattern,
|
|
1086
|
+
"\\$&"
|
|
1087
|
+
);
|
|
1081
1088
|
return escapeBareAutolinks(escapedMarkdownText);
|
|
1082
1089
|
}
|
|
1083
1090
|
function toMarkdownSafeCell(value) {
|
|
@@ -1270,7 +1277,8 @@ var sourceStylePolicies = /* @__PURE__ */ new Map([
|
|
|
1270
1277
|
["codex", (palette) => palette.magenta],
|
|
1271
1278
|
["gemini", (palette) => palette.yellow],
|
|
1272
1279
|
["droid", (palette) => palette.green],
|
|
1273
|
-
["opencode", (palette) => palette.blue]
|
|
1280
|
+
["opencode", (palette) => palette.blue],
|
|
1281
|
+
["claude", (palette) => palette.cyan]
|
|
1274
1282
|
]);
|
|
1275
1283
|
function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
|
|
1276
1284
|
const stylePolicy = sourceStylePolicies.get(source);
|
|
@@ -3431,7 +3439,7 @@ function formatEnvVarOverrides(overrides) {
|
|
|
3431
3439
|
return lines;
|
|
3432
3440
|
}
|
|
3433
3441
|
|
|
3434
|
-
// src/sources/
|
|
3442
|
+
// src/sources/claude/claude-source-adapter.ts
|
|
3435
3443
|
import os2 from "os";
|
|
3436
3444
|
import path6 from "path";
|
|
3437
3445
|
|
|
@@ -3566,11 +3574,6 @@ async function discoverFiles(rootDir, options) {
|
|
|
3566
3574
|
return toCanonicalFiles(files, resolvedOptions);
|
|
3567
3575
|
}
|
|
3568
3576
|
|
|
3569
|
-
// src/utils/discover-jsonl-files.ts
|
|
3570
|
-
async function discoverJsonlFiles(rootDir) {
|
|
3571
|
-
return discoverFiles(rootDir, { extension: ".jsonl" });
|
|
3572
|
-
}
|
|
3573
|
-
|
|
3574
3577
|
// src/utils/fs-helpers.ts
|
|
3575
3578
|
import { access as access2, constants as constants2, stat as stat3 } from "fs/promises";
|
|
3576
3579
|
async function pathExists(filePath) {
|
|
@@ -3652,6 +3655,22 @@ async function* readJsonlObjects(filePath, options = {}) {
|
|
|
3652
3655
|
}
|
|
3653
3656
|
}
|
|
3654
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
|
+
|
|
3655
3674
|
// src/sources/parsing-utils.ts
|
|
3656
3675
|
var MIN_PLAUSIBLE_UNIX_SECONDS_ABS = 1e8;
|
|
3657
3676
|
var UNIX_SECONDS_ABS_CUTOFF = 1e10;
|
|
@@ -3704,8 +3723,180 @@ function normalizeTimestampCandidate(candidate) {
|
|
|
3704
3723
|
return date.toISOString();
|
|
3705
3724
|
}
|
|
3706
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
|
+
|
|
3707
3889
|
// src/sources/codex/codex-source-adapter.ts
|
|
3708
|
-
|
|
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
|
+
|
|
3898
|
+
// src/sources/codex/codex-source-adapter.ts
|
|
3899
|
+
var defaultSessionsDir = path7.join(os3.homedir(), ".codex", "sessions");
|
|
3709
3900
|
var LEGACY_CODEX_MODEL_FALLBACK = "legacy-codex-unknown";
|
|
3710
3901
|
var SESSION_META_LINE_PATTERN = /"type"\s*:\s*"session_meta"/u;
|
|
3711
3902
|
var TURN_CONTEXT_LINE_PATTERN = /"type"\s*:\s*"turn_context"/u;
|
|
@@ -3799,8 +3990,8 @@ function createLastUsageOnlyKey(timestamp, usage) {
|
|
|
3799
3990
|
usage.totalTokens
|
|
3800
3991
|
].join(":");
|
|
3801
3992
|
}
|
|
3802
|
-
function
|
|
3803
|
-
return
|
|
3993
|
+
function getFallbackSessionId2(filePath) {
|
|
3994
|
+
return path7.basename(filePath, ".jsonl");
|
|
3804
3995
|
}
|
|
3805
3996
|
function resolveRepoRootFromPayload(payload) {
|
|
3806
3997
|
if (!payload) {
|
|
@@ -3840,7 +4031,7 @@ var CodexSourceAdapter = class {
|
|
|
3840
4031
|
async parseFile(filePath) {
|
|
3841
4032
|
const events = [];
|
|
3842
4033
|
const state = {
|
|
3843
|
-
sessionId:
|
|
4034
|
+
sessionId: getFallbackSessionId2(filePath),
|
|
3844
4035
|
provider: "openai"
|
|
3845
4036
|
};
|
|
3846
4037
|
for await (const line of readJsonlObjects(filePath, {
|
|
@@ -3932,37 +4123,19 @@ var CodexSourceAdapter = class {
|
|
|
3932
4123
|
|
|
3933
4124
|
// src/sources/droid/droid-source-adapter.ts
|
|
3934
4125
|
import { readFile as readFile2 } from "fs/promises";
|
|
3935
|
-
import
|
|
3936
|
-
import
|
|
3937
|
-
|
|
3938
|
-
// src/sources/parse-diagnostics.ts
|
|
3939
|
-
function incrementSkippedReason(reasons, reason) {
|
|
3940
|
-
const current = reasons.get(reason) ?? 0;
|
|
3941
|
-
reasons.set(reason, current + 1);
|
|
3942
|
-
}
|
|
3943
|
-
function toSkippedRowReasonStats(reasons) {
|
|
3944
|
-
return [...reasons.entries()].map(([reason, count]) => ({ reason, count })).sort((left, right) => compareByCodePoint(left.reason, right.reason));
|
|
3945
|
-
}
|
|
3946
|
-
function toParseDiagnostics(events, skippedRows, skippedRowReasons) {
|
|
3947
|
-
return {
|
|
3948
|
-
events,
|
|
3949
|
-
skippedRows,
|
|
3950
|
-
skippedRowReasons: toSkippedRowReasonStats(skippedRowReasons)
|
|
3951
|
-
};
|
|
3952
|
-
}
|
|
3953
|
-
|
|
3954
|
-
// src/sources/droid/droid-source-adapter.ts
|
|
3955
|
-
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");
|
|
3956
4129
|
var DROID_SESSION_START_LINE_PATTERN = /"type"\s*:\s*"session_start"/u;
|
|
3957
4130
|
var DROID_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
|
|
3958
4131
|
function shouldParseDroidJsonlLine(lineText) {
|
|
3959
4132
|
return DROID_SESSION_START_LINE_PATTERN.test(lineText) || DROID_MESSAGE_LINE_PATTERN.test(lineText);
|
|
3960
4133
|
}
|
|
3961
4134
|
function getSettingsSessionId(filePath) {
|
|
3962
|
-
return
|
|
4135
|
+
return path8.basename(filePath, ".settings.json");
|
|
3963
4136
|
}
|
|
3964
4137
|
function getSiblingJsonlPath(settingsPath) {
|
|
3965
|
-
return
|
|
4138
|
+
return path8.join(path8.dirname(settingsPath), `${getSettingsSessionId(settingsPath)}.jsonl`);
|
|
3966
4139
|
}
|
|
3967
4140
|
function isSessionStartRecord(line) {
|
|
3968
4141
|
return asTrimmedText(line.type) === "session_start";
|
|
@@ -4106,11 +4279,11 @@ var DroidSourceAdapter = class {
|
|
|
4106
4279
|
|
|
4107
4280
|
// src/sources/gemini/gemini-source-adapter.ts
|
|
4108
4281
|
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
4109
|
-
import
|
|
4110
|
-
import
|
|
4111
|
-
var defaultGeminiDir =
|
|
4282
|
+
import os5 from "os";
|
|
4283
|
+
import path9 from "path";
|
|
4284
|
+
var defaultGeminiDir = path9.join(os5.homedir(), ".gemini");
|
|
4112
4285
|
function getProjectsJsonPath(geminiDir) {
|
|
4113
|
-
return
|
|
4286
|
+
return path9.join(geminiDir, "projects.json");
|
|
4114
4287
|
}
|
|
4115
4288
|
function parseProjectsJson(data) {
|
|
4116
4289
|
const mapping = /* @__PURE__ */ new Map();
|
|
@@ -4145,7 +4318,7 @@ async function loadProjectsJson(geminiDir) {
|
|
|
4145
4318
|
}
|
|
4146
4319
|
}
|
|
4147
4320
|
async function discoverSessionFiles(geminiDir) {
|
|
4148
|
-
const tmpDir =
|
|
4321
|
+
const tmpDir = path9.join(geminiDir, "tmp");
|
|
4149
4322
|
let projectEntries;
|
|
4150
4323
|
try {
|
|
4151
4324
|
projectEntries = await readdir2(tmpDir, { withFileTypes: true, encoding: "utf8" });
|
|
@@ -4162,7 +4335,7 @@ async function discoverSessionFiles(geminiDir) {
|
|
|
4162
4335
|
if (!projectEntry.isDirectory() && !projectEntry.isSymbolicLink()) {
|
|
4163
4336
|
continue;
|
|
4164
4337
|
}
|
|
4165
|
-
const chatsDir =
|
|
4338
|
+
const chatsDir = path9.join(tmpDir, projectEntry.name, "chats");
|
|
4166
4339
|
const discoveredChatFiles = await discoverFiles(chatsDir, { extension: ".json" });
|
|
4167
4340
|
allSessionFiles.push(...discoveredChatFiles);
|
|
4168
4341
|
}
|
|
@@ -4176,9 +4349,9 @@ function resolveRepoRoot(filePath, sessionData, projectMapping) {
|
|
|
4176
4349
|
return mappedRoot;
|
|
4177
4350
|
}
|
|
4178
4351
|
}
|
|
4179
|
-
const chatsDir =
|
|
4180
|
-
const projectDir =
|
|
4181
|
-
const projectIdentifier =
|
|
4352
|
+
const chatsDir = path9.dirname(filePath);
|
|
4353
|
+
const projectDir = path9.dirname(chatsDir);
|
|
4354
|
+
const projectIdentifier = path9.basename(projectDir);
|
|
4182
4355
|
return projectMapping.get(projectIdentifier);
|
|
4183
4356
|
}
|
|
4184
4357
|
function toFiniteNumber(value) {
|
|
@@ -4287,7 +4460,7 @@ var GeminiSourceAdapter = class {
|
|
|
4287
4460
|
incrementSkippedReason(skippedRowReasons, "invalid_session_data");
|
|
4288
4461
|
return toParseDiagnostics(events, skippedRows, skippedRowReasons);
|
|
4289
4462
|
}
|
|
4290
|
-
const sessionId = asTrimmedText(sessionDataRecord.sessionId) ??
|
|
4463
|
+
const sessionId = asTrimmedText(sessionDataRecord.sessionId) ?? path9.basename(filePath, ".json");
|
|
4291
4464
|
const projectMapping = await this.getProjectMappingForParse();
|
|
4292
4465
|
const repoRoot = resolveRepoRoot(filePath, sessionDataRecord, projectMapping);
|
|
4293
4466
|
if (!Array.isArray(sessionDataRecord.messages)) {
|
|
@@ -4344,8 +4517,8 @@ var GeminiSourceAdapter = class {
|
|
|
4344
4517
|
};
|
|
4345
4518
|
|
|
4346
4519
|
// src/sources/opencode/opencode-db-path-resolver.ts
|
|
4347
|
-
import
|
|
4348
|
-
import
|
|
4520
|
+
import os6 from "os";
|
|
4521
|
+
import path10 from "path";
|
|
4349
4522
|
function deduplicate(paths) {
|
|
4350
4523
|
return [...new Set(paths)];
|
|
4351
4524
|
}
|
|
@@ -4357,39 +4530,39 @@ function normalizeEnvPath(value) {
|
|
|
4357
4530
|
return normalized || void 0;
|
|
4358
4531
|
}
|
|
4359
4532
|
function getLinuxLikeCandidates(homeDir, env) {
|
|
4360
|
-
const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ??
|
|
4533
|
+
const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path10.join(homeDir, ".local", "share");
|
|
4361
4534
|
return [
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
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")
|
|
4366
4539
|
];
|
|
4367
4540
|
}
|
|
4368
4541
|
function getMacOsCandidates(homeDir) {
|
|
4369
|
-
const appSupportDir =
|
|
4542
|
+
const appSupportDir = path10.join(homeDir, "Library", "Application Support");
|
|
4370
4543
|
return [
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
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")
|
|
4375
4548
|
];
|
|
4376
4549
|
}
|
|
4377
4550
|
function getWindowsCandidates(homeDir, env) {
|
|
4378
4551
|
const userProfile = normalizeEnvPath(env.USERPROFILE);
|
|
4379
|
-
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);
|
|
4380
4553
|
const roamingCandidates = roamingBase ? [
|
|
4381
|
-
|
|
4382
|
-
|
|
4554
|
+
path10.join(roamingBase, "opencode", "opencode.db"),
|
|
4555
|
+
path10.join(roamingBase, "opencode", "db.sqlite")
|
|
4383
4556
|
] : [];
|
|
4384
4557
|
return [
|
|
4385
4558
|
...roamingCandidates,
|
|
4386
|
-
|
|
4387
|
-
|
|
4559
|
+
path10.join(homeDir, ".opencode", "opencode.db"),
|
|
4560
|
+
path10.join(homeDir, ".opencode", "db.sqlite")
|
|
4388
4561
|
];
|
|
4389
4562
|
}
|
|
4390
4563
|
function getDefaultOpenCodeDbPathCandidates(options = {}) {
|
|
4391
4564
|
const platform = options.platform ?? process.platform;
|
|
4392
|
-
const homeDir = options.homeDir ??
|
|
4565
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
4393
4566
|
const env = options.env ?? process.env;
|
|
4394
4567
|
switch (platform) {
|
|
4395
4568
|
case "win32":
|
|
@@ -4878,9 +5051,9 @@ var OpenCodeSourceAdapter = class {
|
|
|
4878
5051
|
};
|
|
4879
5052
|
|
|
4880
5053
|
// src/sources/pi/pi-source-adapter.ts
|
|
4881
|
-
import
|
|
4882
|
-
import
|
|
4883
|
-
var defaultSessionsDir3 =
|
|
5054
|
+
import os7 from "os";
|
|
5055
|
+
import path11 from "path";
|
|
5056
|
+
var defaultSessionsDir3 = path11.join(os7.homedir(), ".pi", "agent", "sessions");
|
|
4884
5057
|
var PI_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
|
|
4885
5058
|
var PI_SESSION_LINE_PATTERN = /"type"\s*:\s*"session"/u;
|
|
4886
5059
|
var PI_MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
|
|
@@ -4953,8 +5126,8 @@ function extractUsage(line, message) {
|
|
|
4953
5126
|
}
|
|
4954
5127
|
return extractUsageFromRecord(messageUsage);
|
|
4955
5128
|
}
|
|
4956
|
-
function
|
|
4957
|
-
return
|
|
5129
|
+
function getFallbackSessionId3(filePath) {
|
|
5130
|
+
return path11.basename(filePath, ".jsonl");
|
|
4958
5131
|
}
|
|
4959
5132
|
function resolveRepoRootFromRecord(record) {
|
|
4960
5133
|
if (!record) {
|
|
@@ -4986,7 +5159,7 @@ var PiSourceAdapter = class {
|
|
|
4986
5159
|
}
|
|
4987
5160
|
async parseFile(filePath) {
|
|
4988
5161
|
const events = [];
|
|
4989
|
-
const state = { sessionId:
|
|
5162
|
+
const state = { sessionId: getFallbackSessionId3(filePath) };
|
|
4990
5163
|
for await (const line of readJsonlObjects(filePath, {
|
|
4991
5164
|
shouldParseLine: shouldParsePiJsonlLine
|
|
4992
5165
|
})) {
|
|
@@ -5125,6 +5298,21 @@ var sourceRegistrations = [
|
|
|
5125
5298
|
create: (options) => new OpenCodeSourceAdapter({
|
|
5126
5299
|
dbPath: options.opencodeDb
|
|
5127
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
|
+
}
|
|
5128
5316
|
}
|
|
5129
5317
|
];
|
|
5130
5318
|
var sourceDirUnsupportedFlags = new Map(
|
|
@@ -5201,6 +5389,7 @@ function createDefaultAdapters(options) {
|
|
|
5201
5389
|
validateDirectoryOverride("--codex-dir", options.codexDir);
|
|
5202
5390
|
validateDirectoryOverride("--gemini-dir", options.geminiDir);
|
|
5203
5391
|
validateDirectoryOverride("--droid-dir", options.droidDir);
|
|
5392
|
+
validateDirectoryOverride("--claude-dir", options.claudeDir);
|
|
5204
5393
|
const sourceDirectoryOverrides = parseSourceDirectoryOverrides(options.sourceDir);
|
|
5205
5394
|
validateSourceDirectoryOverrideIds(sourceDirectoryOverrides);
|
|
5206
5395
|
return sourceRegistrations.map((source) => source.create(options, sourceDirectoryOverrides));
|
|
@@ -5316,6 +5505,9 @@ function resolveExplicitSourceIds(options, sourceFilter) {
|
|
|
5316
5505
|
if (options.droidDir) {
|
|
5317
5506
|
explicitSourceIds.add("droid");
|
|
5318
5507
|
}
|
|
5508
|
+
if (options.claudeDir) {
|
|
5509
|
+
explicitSourceIds.add("claude");
|
|
5510
|
+
}
|
|
5319
5511
|
if (options.opencodeDb) {
|
|
5320
5512
|
explicitSourceIds.add("opencode");
|
|
5321
5513
|
}
|
|
@@ -5444,7 +5636,7 @@ function normalizeSkippedRowReasons(value) {
|
|
|
5444
5636
|
|
|
5445
5637
|
// src/cli/parse-file-cache.ts
|
|
5446
5638
|
import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat4, writeFile as writeFile2 } from "fs/promises";
|
|
5447
|
-
import
|
|
5639
|
+
import path12 from "path";
|
|
5448
5640
|
var PARSE_FILE_CACHE_VERSION = 5;
|
|
5449
5641
|
var CACHE_KEY_SEPARATOR = "\0";
|
|
5450
5642
|
function createCacheKey(source, filePath) {
|
|
@@ -5671,7 +5863,7 @@ function normalizeCacheEntry(value) {
|
|
|
5671
5863
|
};
|
|
5672
5864
|
}
|
|
5673
5865
|
function getDefaultParseFileCachePath() {
|
|
5674
|
-
return
|
|
5866
|
+
return path12.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
|
|
5675
5867
|
}
|
|
5676
5868
|
function normalizeCacheShardSource(source) {
|
|
5677
5869
|
const normalizedSource = normalizeCacheSource(source);
|
|
@@ -5681,12 +5873,12 @@ function normalizeCacheShardSource(source) {
|
|
|
5681
5873
|
return normalizedSource.replace(/[^a-z0-9._-]/gu, "_");
|
|
5682
5874
|
}
|
|
5683
5875
|
function getSourceShardedParseFileCachePath(cacheFilePath, source) {
|
|
5684
|
-
const parsedPath =
|
|
5876
|
+
const parsedPath = path12.parse(cacheFilePath);
|
|
5685
5877
|
const sourceShard = normalizeCacheShardSource(source);
|
|
5686
5878
|
if (parsedPath.ext.length > 0) {
|
|
5687
|
-
return
|
|
5879
|
+
return path12.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
|
|
5688
5880
|
}
|
|
5689
|
-
return
|
|
5881
|
+
return path12.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
|
|
5690
5882
|
}
|
|
5691
5883
|
var ParseFileCache = class _ParseFileCache {
|
|
5692
5884
|
constructor(cacheFilePath, limits, now) {
|
|
@@ -5694,6 +5886,9 @@ var ParseFileCache = class _ParseFileCache {
|
|
|
5694
5886
|
this.limits = limits;
|
|
5695
5887
|
this.now = now;
|
|
5696
5888
|
}
|
|
5889
|
+
cacheFilePath;
|
|
5890
|
+
limits;
|
|
5891
|
+
now;
|
|
5697
5892
|
entriesByKey = /* @__PURE__ */ new Map();
|
|
5698
5893
|
dirty = false;
|
|
5699
5894
|
static async load(options) {
|
|
@@ -5780,7 +5975,7 @@ var ParseFileCache = class _ParseFileCache {
|
|
|
5780
5975
|
keptEntries.length = bestCount;
|
|
5781
5976
|
payloadText = bestPayloadText;
|
|
5782
5977
|
}
|
|
5783
|
-
await mkdir2(
|
|
5978
|
+
await mkdir2(path12.dirname(this.cacheFilePath), { recursive: true });
|
|
5784
5979
|
const temporaryPath = `${this.cacheFilePath}.${process.pid}.${this.now()}.tmp`;
|
|
5785
5980
|
try {
|
|
5786
5981
|
await writeFile2(temporaryPath, payloadText, "utf8");
|
|
@@ -6269,7 +6464,7 @@ function applyPricingToEvents(events, pricingSource) {
|
|
|
6269
6464
|
|
|
6270
6465
|
// src/pricing/litellm-pricing-fetcher.ts
|
|
6271
6466
|
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
6272
|
-
import
|
|
6467
|
+
import path13 from "path";
|
|
6273
6468
|
|
|
6274
6469
|
// src/pricing/litellm-model-map.json
|
|
6275
6470
|
var litellm_model_map_default = {
|
|
@@ -6453,7 +6648,7 @@ function normalizeLitellmPricingPayload(payload) {
|
|
|
6453
6648
|
return normalizedPricing;
|
|
6454
6649
|
}
|
|
6455
6650
|
function getDefaultLiteLLMPricingCachePath() {
|
|
6456
|
-
return
|
|
6651
|
+
return path13.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
|
|
6457
6652
|
}
|
|
6458
6653
|
function stripProviderPrefix(model) {
|
|
6459
6654
|
const slashIndex = model.lastIndexOf("/");
|
|
@@ -6847,7 +7042,7 @@ var LiteLLMPricingFetcher = class {
|
|
|
6847
7042
|
};
|
|
6848
7043
|
}
|
|
6849
7044
|
async writeCache() {
|
|
6850
|
-
const directoryPath =
|
|
7045
|
+
const directoryPath = path13.dirname(this.cacheFilePath);
|
|
6851
7046
|
await mkdir3(directoryPath, { recursive: true });
|
|
6852
7047
|
const payload = {
|
|
6853
7048
|
fetchedAt: this.now(),
|
|
@@ -6958,6 +7153,7 @@ var RuntimeProfileCollector = class {
|
|
|
6958
7153
|
constructor(now = () => performance.now()) {
|
|
6959
7154
|
this.now = now;
|
|
6960
7155
|
}
|
|
7156
|
+
now;
|
|
6961
7157
|
sourceSelection;
|
|
6962
7158
|
sourceStats = /* @__PURE__ */ new Map();
|
|
6963
7159
|
stageDurations = /* @__PURE__ */ new Map();
|
|
@@ -7251,6 +7447,9 @@ function resolveScopeNote(options) {
|
|
|
7251
7447
|
if (hasActiveTextOption(options.droidDir)) {
|
|
7252
7448
|
activeFilters.push("--droid-dir");
|
|
7253
7449
|
}
|
|
7450
|
+
if (hasActiveTextOption(options.claudeDir)) {
|
|
7451
|
+
activeFilters.push("--claude-dir");
|
|
7452
|
+
}
|
|
7254
7453
|
if (hasActiveTextOption(options.opencodeDb)) {
|
|
7255
7454
|
activeFilters.push("--opencode-db");
|
|
7256
7455
|
}
|
|
@@ -7436,10 +7635,11 @@ function emitEnvVarOverrides(activeEnvOverrides, diagnosticsLogger) {
|
|
|
7436
7635
|
|
|
7437
7636
|
// src/cli/share-artifact.ts
|
|
7438
7637
|
import { spawn as spawn4 } from "child_process";
|
|
7439
|
-
import {
|
|
7440
|
-
import
|
|
7638
|
+
import { constants as constants3 } from "fs";
|
|
7639
|
+
import { access as access3, writeFile as writeFile4 } from "fs/promises";
|
|
7640
|
+
import path14 from "path";
|
|
7441
7641
|
async function writeShareSvgFile(fileName, svgContent) {
|
|
7442
|
-
const outputPath =
|
|
7642
|
+
const outputPath = path14.resolve(process.cwd(), fileName);
|
|
7443
7643
|
await writeFile4(outputPath, svgContent, "utf8");
|
|
7444
7644
|
return outputPath;
|
|
7445
7645
|
}
|
|
@@ -7449,21 +7649,68 @@ function stringifyError(error) {
|
|
|
7449
7649
|
}
|
|
7450
7650
|
return String(error);
|
|
7451
7651
|
}
|
|
7452
|
-
|
|
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) {
|
|
7453
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
|
+
}
|
|
7454
7689
|
return {
|
|
7455
|
-
command:
|
|
7456
|
-
args: ["
|
|
7690
|
+
command: resolvedPath2,
|
|
7691
|
+
args: ["shell32.dll,ShellExec_RunDLL", filePath]
|
|
7457
7692
|
};
|
|
7458
7693
|
}
|
|
7459
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
|
+
}
|
|
7460
7701
|
return {
|
|
7461
|
-
command:
|
|
7702
|
+
command: resolvedPath2,
|
|
7462
7703
|
args: [filePath]
|
|
7463
7704
|
};
|
|
7464
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
|
+
}
|
|
7465
7712
|
return {
|
|
7466
|
-
command:
|
|
7713
|
+
command: resolvedPath,
|
|
7467
7714
|
args: [filePath]
|
|
7468
7715
|
};
|
|
7469
7716
|
}
|
|
@@ -7494,7 +7741,7 @@ async function spawnDetached(command, args) {
|
|
|
7494
7741
|
async function openShareSvgFile(filePath, deps = {}) {
|
|
7495
7742
|
const platform = deps.platform ?? process.platform;
|
|
7496
7743
|
const runDetached = deps.spawnDetached ?? spawnDetached;
|
|
7497
|
-
const { command, args } = resolveOpenCommand(filePath, platform);
|
|
7744
|
+
const { command, args } = await resolveOpenCommand(filePath, platform);
|
|
7498
7745
|
await runDetached(command, args);
|
|
7499
7746
|
}
|
|
7500
7747
|
async function writeAndOpenShareSvgFile(fileName, svgContent, deps = {}) {
|
|
@@ -7782,7 +8029,7 @@ function resolveTerminalContextLines(optimizeData, options) {
|
|
|
7782
8029
|
const bestRow = rowsWithSavings.length > 0 ? rowsWithSavings.reduce(
|
|
7783
8030
|
(best, current) => (current.savingsUsd ?? Number.NEGATIVE_INFINITY) > (best.savingsUsd ?? Number.NEGATIVE_INFINITY) ? current : best
|
|
7784
8031
|
) : void 0;
|
|
7785
|
-
if (
|
|
8032
|
+
if (bestRow?.savingsUsd === void 0) {
|
|
7786
8033
|
lines.push("ALL best candidate: unavailable (missing baseline or candidate pricing)");
|
|
7787
8034
|
} else if (bestRow.savingsUsd > 0) {
|
|
7788
8035
|
lines.push(
|
|
@@ -9654,7 +9901,7 @@ function registerSharedReportOptions(command, profile) {
|
|
|
9654
9901
|
const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
|
|
9655
9902
|
const supportedSourcesSummary = `(${supportedSourceIds.length}): ${allowedSourcesLabel}`;
|
|
9656
9903
|
const profileConfig = sharedOptionProfileConfig[profile];
|
|
9657
|
-
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(
|
|
9658
9905
|
"--source-dir <source-id=path>",
|
|
9659
9906
|
"Override source directory for directory-backed sources (repeatable)",
|
|
9660
9907
|
collectRepeatedOption
|