llm-usage-metrics 0.7.1 → 0.7.2
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 +7 -5
- package/dist/index.js +288 -33
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
Aggregate token usage and costs from your local coding agent sessions. Supports **pi**, **codex**, **Gemini CLI**, **Droid CLI**, and **
|
|
24
|
+
Aggregate token usage and costs from your local coding agent sessions. Supports **pi**, **codex**, **Gemini CLI**, **Droid CLI**, **OpenCode**, **OpenClaw**, and **Claude Code** with zero configuration required.
|
|
25
25
|
|
|
26
26
|
## ✨ Features
|
|
27
27
|
|
|
28
|
-
- **Zero-Config Discovery** — Automatically finds `.pi`, `.codex`, `.gemini`, `.factory`, and
|
|
28
|
+
- **Zero-Config Discovery** — Automatically finds `.pi`, `.codex`, `.gemini`, `.factory`, OpenCode, OpenClaw, and Claude session data
|
|
29
29
|
- **LiteLLM Pricing** — Real-time pricing sync with offline caching support
|
|
30
30
|
- **Flexible Reports** — Daily, weekly, and monthly aggregations
|
|
31
31
|
- **Efficiency Reports** — Correlate cost/tokens with repository commit outcomes
|
|
@@ -62,6 +62,7 @@ llm-usage daily
|
|
|
62
62
|
| **Gemini CLI** | `~/.gemini/tmp/*/chats/*.json` | Automatic |
|
|
63
63
|
| **Droid CLI** | `~/.factory/sessions/**/*.settings.json` | Automatic |
|
|
64
64
|
| **OpenCode** | `~/.opencode/opencode.db` | Auto or explicit `--opencode-db` |
|
|
65
|
+
| **OpenClaw** | `~/.openclaw/agents/**/*.jsonl` | Automatic |
|
|
65
66
|
| **Claude Code** | `~/.claude/projects/**/*.jsonl` | Automatic |
|
|
66
67
|
|
|
67
68
|
OpenCode source support requires Node.js 24+ runtime with built-in `node:sqlite`.
|
|
@@ -155,6 +156,7 @@ llm-usage efficiency monthly --repo-dir /path/to/repo --source codex
|
|
|
155
156
|
llm-usage efficiency monthly --repo-dir /path/to/repo --source gemini
|
|
156
157
|
llm-usage efficiency monthly --repo-dir /path/to/repo --source droid
|
|
157
158
|
llm-usage efficiency monthly --repo-dir /path/to/repo --source opencode
|
|
159
|
+
llm-usage efficiency monthly --repo-dir /path/to/repo --source openclaw
|
|
158
160
|
llm-usage efficiency monthly --repo-dir /path/to/repo --source claude
|
|
159
161
|
```
|
|
160
162
|
|
|
@@ -179,7 +181,7 @@ llm-usage optimize monthly --provider openai --candidate-model gpt-4.1 --candida
|
|
|
179
181
|
|
|
180
182
|
```bash
|
|
181
183
|
# By source
|
|
182
|
-
llm-usage monthly --source pi,codex,gemini,droid,claude
|
|
184
|
+
llm-usage monthly --source pi,codex,gemini,droid,openclaw,claude
|
|
183
185
|
|
|
184
186
|
# By provider
|
|
185
187
|
llm-usage monthly --provider openai
|
|
@@ -191,13 +193,13 @@ llm-usage monthly --model claude
|
|
|
191
193
|
llm-usage monthly --source opencode --provider openai --model gpt-4.1
|
|
192
194
|
```
|
|
193
195
|
|
|
194
|
-
Use `--source` to scope where events came from (`pi`, `codex`, `gemini`, `droid`, `opencode`, `claude`), and `--provider` to scope the billing entity behind those events.
|
|
196
|
+
Use `--source` to scope where events came from (`pi`, `codex`, `gemini`, `droid`, `opencode`, `openclaw`, `claude`), and `--provider` to scope the billing entity behind those events.
|
|
195
197
|
|
|
196
198
|
### Custom Paths
|
|
197
199
|
|
|
198
200
|
```bash
|
|
199
201
|
# Custom directories
|
|
200
|
-
llm-usage daily --source-dir pi=/path/to/pi --source-dir codex=/path/to/codex --source-dir gemini=/path/to/.gemini --source-dir droid=/path/to/.factory/sessions --source-dir claude=/path/to/.claude/projects
|
|
202
|
+
llm-usage daily --source-dir pi=/path/to/pi --source-dir codex=/path/to/codex --source-dir gemini=/path/to/.gemini --source-dir droid=/path/to/.factory/sessions --source-dir openclaw=/path/to/.openclaw/agents --source-dir claude=/path/to/.claude/projects
|
|
201
203
|
|
|
202
204
|
# Explicit Gemini/Droid/Claude/OpenCode paths
|
|
203
205
|
llm-usage daily --gemini-dir /path/to/.gemini
|
package/dist/index.js
CHANGED
|
@@ -5006,17 +5006,257 @@ var OpenCodeSourceAdapter = class {
|
|
|
5006
5006
|
}
|
|
5007
5007
|
};
|
|
5008
5008
|
|
|
5009
|
-
// src/sources/
|
|
5009
|
+
// src/sources/openclaw/openclaw-source-adapter.ts
|
|
5010
5010
|
import os7 from "os";
|
|
5011
5011
|
import path11 from "path";
|
|
5012
|
-
var
|
|
5012
|
+
var defaultAgentsDir = path11.join(os7.homedir(), ".openclaw", "agents");
|
|
5013
|
+
var SESSION_LINE_PATTERN = /"type"\s*:\s*"session"/u;
|
|
5014
|
+
var MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
|
|
5015
|
+
var MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
|
|
5016
|
+
var USAGE_LINE_PATTERN = /"usage"\s*:/u;
|
|
5017
|
+
var MODEL_LINE_PATTERN = /"model"\s*:/u;
|
|
5018
|
+
var PROVIDER_LINE_PATTERN = /"provider"\s*:/u;
|
|
5019
|
+
function shouldParseOpenClawJsonlLine(lineText) {
|
|
5020
|
+
return SESSION_LINE_PATTERN.test(lineText) || MESSAGE_LINE_PATTERN.test(lineText) || MODEL_CHANGE_LINE_PATTERN.test(lineText) || USAGE_LINE_PATTERN.test(lineText) || MODEL_LINE_PATTERN.test(lineText) || PROVIDER_LINE_PATTERN.test(lineText);
|
|
5021
|
+
}
|
|
5022
|
+
function getFallbackSessionId3(filePath) {
|
|
5023
|
+
return path11.basename(filePath, ".jsonl");
|
|
5024
|
+
}
|
|
5025
|
+
function resolveRepoRootFromRecord(record) {
|
|
5026
|
+
if (!record) {
|
|
5027
|
+
return void 0;
|
|
5028
|
+
}
|
|
5029
|
+
const pathRecord = asRecord(record.path);
|
|
5030
|
+
return asTrimmedText(record.cwd) ?? asTrimmedText(pathRecord?.root) ?? asTrimmedText(pathRecord?.cwd) ?? asTrimmedText(record.repo_root) ?? asTrimmedText(record.repoRoot) ?? asTrimmedText(record.project_root) ?? asTrimmedText(record.projectRoot) ?? asTrimmedText(record.workspace);
|
|
5031
|
+
}
|
|
5032
|
+
function firstText(...values) {
|
|
5033
|
+
for (const value of values) {
|
|
5034
|
+
const normalized = asTrimmedText(value);
|
|
5035
|
+
if (normalized) {
|
|
5036
|
+
return normalized;
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
return void 0;
|
|
5040
|
+
}
|
|
5041
|
+
function isAssistantMessage(line, message) {
|
|
5042
|
+
return firstText(line.role, message.role)?.toLowerCase() === "assistant";
|
|
5043
|
+
}
|
|
5044
|
+
function isDeliveryMirror(provider, model, message) {
|
|
5045
|
+
const messageKind = firstText(message.kind, message.source, message.provenance);
|
|
5046
|
+
return provider?.toLowerCase() === "openclaw" || model?.toLowerCase() === "delivery-mirror" || messageKind?.toLowerCase() === "delivery-mirror";
|
|
5047
|
+
}
|
|
5048
|
+
function toFiniteNumber2(value) {
|
|
5049
|
+
if (value === null || value === void 0) {
|
|
5050
|
+
return void 0;
|
|
5051
|
+
}
|
|
5052
|
+
if (typeof value === "string" && value.trim().length === 0) {
|
|
5053
|
+
return void 0;
|
|
5054
|
+
}
|
|
5055
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
5056
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
5057
|
+
}
|
|
5058
|
+
function extractCostUsd(usage) {
|
|
5059
|
+
const cost = asRecord(usage.cost);
|
|
5060
|
+
return toNumberLike(usage.costUsd) ?? toNumberLike(usage.cost_usd) ?? toNumberLike(usage.turnUsd) ?? toNumberLike(usage.turn_usd) ?? toNumberLike(usage.usd) ?? toNumberLike(usage.cost) ?? toNumberLike(cost?.total) ?? toNumberLike(cost?.usd) ?? toNumberLike(cost?.turnUsd) ?? toNumberLike(cost?.turn_usd);
|
|
5061
|
+
}
|
|
5062
|
+
function extractUsageFromRecord(usage) {
|
|
5063
|
+
const extracted = {
|
|
5064
|
+
inputTokens: toNumberLike(usage.input) ?? toNumberLike(usage.inputTokens) ?? toNumberLike(usage.input_tokens) ?? toNumberLike(usage.prompt_tokens),
|
|
5065
|
+
outputTokens: toNumberLike(usage.output) ?? toNumberLike(usage.outputTokens) ?? toNumberLike(usage.output_tokens) ?? toNumberLike(usage.completion_tokens),
|
|
5066
|
+
reasoningTokens: toNumberLike(usage.reasoning) ?? toNumberLike(usage.reasoningTokens) ?? toNumberLike(usage.reasoning_tokens) ?? toNumberLike(usage.reasoningOutput) ?? toNumberLike(usage.reasoning_output_tokens),
|
|
5067
|
+
cacheReadTokens: toNumberLike(usage.cacheRead) ?? toNumberLike(usage.cache_read) ?? toNumberLike(usage.cacheReadTokens) ?? toNumberLike(usage.cache_read_tokens) ?? toNumberLike(usage.cached) ?? toNumberLike(usage.cached_input_tokens),
|
|
5068
|
+
cacheWriteTokens: toNumberLike(usage.cacheWrite) ?? toNumberLike(usage.cache_write) ?? toNumberLike(usage.cacheWriteTokens) ?? toNumberLike(usage.cache_write_tokens),
|
|
5069
|
+
totalTokens: toNumberLike(usage.total) ?? toNumberLike(usage.totalTokens) ?? toNumberLike(usage.total_tokens),
|
|
5070
|
+
costUsd: extractCostUsd(usage)
|
|
5071
|
+
};
|
|
5072
|
+
const usageCandidates = [
|
|
5073
|
+
extracted.inputTokens,
|
|
5074
|
+
extracted.outputTokens,
|
|
5075
|
+
extracted.reasoningTokens,
|
|
5076
|
+
extracted.cacheReadTokens,
|
|
5077
|
+
extracted.cacheWriteTokens,
|
|
5078
|
+
extracted.totalTokens
|
|
5079
|
+
];
|
|
5080
|
+
const hasPositiveUsageSignal = usageCandidates.some((value) => {
|
|
5081
|
+
const parsed = toFiniteNumber2(value);
|
|
5082
|
+
return parsed !== void 0 && parsed > 0;
|
|
5083
|
+
});
|
|
5084
|
+
const explicitCost = toFiniteNumber2(extracted.costUsd);
|
|
5085
|
+
const hasPositiveCostSignal = explicitCost !== void 0 && explicitCost > 0;
|
|
5086
|
+
return hasPositiveUsageSignal || hasPositiveCostSignal ? extracted : void 0;
|
|
5087
|
+
}
|
|
5088
|
+
function mergeUsageExtracts(primary, fallback) {
|
|
5089
|
+
if (!primary) {
|
|
5090
|
+
return fallback;
|
|
5091
|
+
}
|
|
5092
|
+
if (!fallback) {
|
|
5093
|
+
return primary;
|
|
5094
|
+
}
|
|
5095
|
+
return {
|
|
5096
|
+
inputTokens: primary.inputTokens ?? fallback.inputTokens,
|
|
5097
|
+
outputTokens: primary.outputTokens ?? fallback.outputTokens,
|
|
5098
|
+
reasoningTokens: primary.reasoningTokens ?? fallback.reasoningTokens,
|
|
5099
|
+
cacheReadTokens: primary.cacheReadTokens ?? fallback.cacheReadTokens,
|
|
5100
|
+
cacheWriteTokens: primary.cacheWriteTokens ?? fallback.cacheWriteTokens,
|
|
5101
|
+
totalTokens: primary.totalTokens ?? fallback.totalTokens,
|
|
5102
|
+
costUsd: primary.costUsd ?? fallback.costUsd
|
|
5103
|
+
};
|
|
5104
|
+
}
|
|
5105
|
+
function extractUsage(line, message) {
|
|
5106
|
+
const lineUsage = asRecord(line.usage);
|
|
5107
|
+
const messageUsage = asRecord(message.usage);
|
|
5108
|
+
const extractedLineUsage = lineUsage ? extractUsageFromRecord(lineUsage) : void 0;
|
|
5109
|
+
const extractedMessageUsage = messageUsage ? extractUsageFromRecord(messageUsage) : void 0;
|
|
5110
|
+
return mergeUsageExtracts(extractedLineUsage, extractedMessageUsage);
|
|
5111
|
+
}
|
|
5112
|
+
function resolveTimestamp2(line, message, state, fallbackTimestamp) {
|
|
5113
|
+
const candidates = [line.timestamp, message.timestamp, fallbackTimestamp, state.sessionTimestamp];
|
|
5114
|
+
for (const candidate of candidates) {
|
|
5115
|
+
const normalizedTimestamp = normalizeTimestampCandidate(candidate);
|
|
5116
|
+
if (normalizedTimestamp) {
|
|
5117
|
+
return normalizedTimestamp;
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
return void 0;
|
|
5121
|
+
}
|
|
5122
|
+
function updateRuntimeStateFromRecord(state, record, nested) {
|
|
5123
|
+
state.provider = firstText(
|
|
5124
|
+
record.provider,
|
|
5125
|
+
record.modelProvider,
|
|
5126
|
+
record.model_provider,
|
|
5127
|
+
nested?.provider,
|
|
5128
|
+
nested?.modelProvider,
|
|
5129
|
+
nested?.model_provider
|
|
5130
|
+
) ?? state.provider;
|
|
5131
|
+
state.model = firstText(
|
|
5132
|
+
record.model,
|
|
5133
|
+
record.modelId,
|
|
5134
|
+
record.model_id,
|
|
5135
|
+
nested?.model,
|
|
5136
|
+
nested?.modelId,
|
|
5137
|
+
nested?.model_id
|
|
5138
|
+
) ?? state.model;
|
|
5139
|
+
state.repoRoot = resolveRepoRootFromRecord(record) ?? resolveRepoRootFromRecord(nested) ?? state.repoRoot;
|
|
5140
|
+
}
|
|
5141
|
+
var OpenClawSourceAdapter = class {
|
|
5142
|
+
id = "openclaw";
|
|
5143
|
+
agentsDir;
|
|
5144
|
+
requireAgentsDir;
|
|
5145
|
+
constructor(options = {}) {
|
|
5146
|
+
this.agentsDir = options.agentsDir ?? defaultAgentsDir;
|
|
5147
|
+
this.requireAgentsDir = options.requireAgentsDir ?? false;
|
|
5148
|
+
}
|
|
5149
|
+
async discoverFiles() {
|
|
5150
|
+
if (isBlankText(this.agentsDir)) {
|
|
5151
|
+
throw new Error("OpenClaw agents directory must be a non-empty path");
|
|
5152
|
+
}
|
|
5153
|
+
const normalizedAgentsDir = this.agentsDir.trim();
|
|
5154
|
+
if (this.requireAgentsDir) {
|
|
5155
|
+
const agentsDirStats = await pathStat(normalizedAgentsDir);
|
|
5156
|
+
if (!agentsDirStats) {
|
|
5157
|
+
throw new Error(
|
|
5158
|
+
`OpenClaw agents directory is missing or unreadable: ${normalizedAgentsDir}`
|
|
5159
|
+
);
|
|
5160
|
+
}
|
|
5161
|
+
if (!agentsDirStats.isDirectory()) {
|
|
5162
|
+
throw new Error(`OpenClaw agents directory is not a directory: ${normalizedAgentsDir}`);
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
return discoverJsonlFiles(normalizedAgentsDir);
|
|
5166
|
+
}
|
|
5167
|
+
async parseFile(filePath) {
|
|
5168
|
+
return (await this.parseFileWithDiagnostics(filePath)).events;
|
|
5169
|
+
}
|
|
5170
|
+
async parseFileWithDiagnostics(filePath) {
|
|
5171
|
+
const events = [];
|
|
5172
|
+
let skippedRows = 0;
|
|
5173
|
+
const skippedRowReasons = /* @__PURE__ */ new Map();
|
|
5174
|
+
const fileStats = await pathStat(filePath);
|
|
5175
|
+
const fallbackTimestamp = fileStats?.mtime.toISOString();
|
|
5176
|
+
const state = {
|
|
5177
|
+
sessionId: getFallbackSessionId3(filePath)
|
|
5178
|
+
};
|
|
5179
|
+
for await (const line of readJsonlObjects(filePath, {
|
|
5180
|
+
shouldParseLine: shouldParseOpenClawJsonlLine
|
|
5181
|
+
})) {
|
|
5182
|
+
const message = asRecord(line.message) ?? line;
|
|
5183
|
+
if (line.type === "session") {
|
|
5184
|
+
state.sessionId = firstText(line.id, line.sessionId, line.session_id) ?? state.sessionId;
|
|
5185
|
+
state.sessionTimestamp = normalizeTimestampCandidate(line.timestamp) ?? state.sessionTimestamp;
|
|
5186
|
+
updateRuntimeStateFromRecord(state, line, message);
|
|
5187
|
+
continue;
|
|
5188
|
+
}
|
|
5189
|
+
if (line.type === "model_change") {
|
|
5190
|
+
updateRuntimeStateFromRecord(state, line, message);
|
|
5191
|
+
continue;
|
|
5192
|
+
}
|
|
5193
|
+
if (line.type !== "message" || !isAssistantMessage(line, message)) {
|
|
5194
|
+
updateRuntimeStateFromRecord(state, line, message);
|
|
5195
|
+
continue;
|
|
5196
|
+
}
|
|
5197
|
+
const rowProvider = firstText(
|
|
5198
|
+
line.provider,
|
|
5199
|
+
message.provider,
|
|
5200
|
+
line.modelProvider,
|
|
5201
|
+
message.modelProvider,
|
|
5202
|
+
line.model_provider,
|
|
5203
|
+
message.model_provider
|
|
5204
|
+
);
|
|
5205
|
+
const rowModel = firstText(
|
|
5206
|
+
line.model,
|
|
5207
|
+
message.model,
|
|
5208
|
+
line.modelId,
|
|
5209
|
+
message.modelId,
|
|
5210
|
+
line.model_id,
|
|
5211
|
+
message.model_id
|
|
5212
|
+
);
|
|
5213
|
+
if (isDeliveryMirror(rowProvider, rowModel, message)) {
|
|
5214
|
+
continue;
|
|
5215
|
+
}
|
|
5216
|
+
updateRuntimeStateFromRecord(state, line, message);
|
|
5217
|
+
const provider = rowProvider ?? state.provider;
|
|
5218
|
+
const model = rowModel ?? state.model;
|
|
5219
|
+
const usage = extractUsage(line, message);
|
|
5220
|
+
if (!usage) {
|
|
5221
|
+
continue;
|
|
5222
|
+
}
|
|
5223
|
+
const timestamp = resolveTimestamp2(line, message, state, fallbackTimestamp);
|
|
5224
|
+
if (!timestamp) {
|
|
5225
|
+
continue;
|
|
5226
|
+
}
|
|
5227
|
+
try {
|
|
5228
|
+
events.push(
|
|
5229
|
+
createUsageEvent({
|
|
5230
|
+
source: this.id,
|
|
5231
|
+
sessionId: state.sessionId,
|
|
5232
|
+
timestamp,
|
|
5233
|
+
repoRoot: state.repoRoot,
|
|
5234
|
+
provider,
|
|
5235
|
+
model,
|
|
5236
|
+
...usage
|
|
5237
|
+
})
|
|
5238
|
+
);
|
|
5239
|
+
} catch {
|
|
5240
|
+
skippedRows += 1;
|
|
5241
|
+
incrementSkippedReason(skippedRowReasons, "invalid_usage_event");
|
|
5242
|
+
continue;
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
return toParseDiagnostics(events, skippedRows, skippedRowReasons);
|
|
5246
|
+
}
|
|
5247
|
+
};
|
|
5248
|
+
|
|
5249
|
+
// src/sources/pi/pi-source-adapter.ts
|
|
5250
|
+
import os8 from "os";
|
|
5251
|
+
import path12 from "path";
|
|
5252
|
+
var defaultSessionsDir3 = path12.join(os8.homedir(), ".pi", "agent", "sessions");
|
|
5013
5253
|
var PI_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
|
|
5014
5254
|
var PI_SESSION_LINE_PATTERN = /"type"\s*:\s*"session"/u;
|
|
5015
5255
|
var PI_MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
|
|
5016
5256
|
function shouldParsePiJsonlLine(lineText) {
|
|
5017
5257
|
return PI_MESSAGE_LINE_PATTERN.test(lineText) || PI_SESSION_LINE_PATTERN.test(lineText) || PI_MODEL_CHANGE_LINE_PATTERN.test(lineText);
|
|
5018
5258
|
}
|
|
5019
|
-
function
|
|
5259
|
+
function resolveTimestamp3(line, message, state) {
|
|
5020
5260
|
const candidates = [line.timestamp, message?.timestamp, state.sessionTimestamp];
|
|
5021
5261
|
for (const candidate of candidates) {
|
|
5022
5262
|
const normalizedTimestamp = normalizeTimestampCandidate(candidate);
|
|
@@ -5026,7 +5266,7 @@ function resolveTimestamp2(line, message, state) {
|
|
|
5026
5266
|
}
|
|
5027
5267
|
return void 0;
|
|
5028
5268
|
}
|
|
5029
|
-
function
|
|
5269
|
+
function extractUsageFromRecord2(usage) {
|
|
5030
5270
|
const cost = asRecord(usage.cost);
|
|
5031
5271
|
const extracted = {
|
|
5032
5272
|
inputTokens: toNumberLike(usage.input),
|
|
@@ -5039,7 +5279,7 @@ function extractUsageFromRecord(usage) {
|
|
|
5039
5279
|
totalTokens: toNumberLike(usage.totalTokens),
|
|
5040
5280
|
costUsd: toNumberLike(cost?.total)
|
|
5041
5281
|
};
|
|
5042
|
-
const
|
|
5282
|
+
const toFiniteNumber3 = (value) => {
|
|
5043
5283
|
if (value === null || value === void 0) {
|
|
5044
5284
|
return void 0;
|
|
5045
5285
|
}
|
|
@@ -5061,18 +5301,18 @@ function extractUsageFromRecord(usage) {
|
|
|
5061
5301
|
extracted.totalTokens
|
|
5062
5302
|
];
|
|
5063
5303
|
const hasPositiveUsageSignal = usageCandidates.some((value) => {
|
|
5064
|
-
const parsed =
|
|
5304
|
+
const parsed = toFiniteNumber3(value);
|
|
5065
5305
|
return parsed !== void 0 && parsed > 0;
|
|
5066
5306
|
});
|
|
5067
|
-
const explicitCost =
|
|
5307
|
+
const explicitCost = toFiniteNumber3(extracted.costUsd);
|
|
5068
5308
|
const hasPositiveCostSignal = explicitCost !== void 0 && explicitCost > 0;
|
|
5069
5309
|
return hasPositiveUsageSignal || hasPositiveCostSignal ? extracted : void 0;
|
|
5070
5310
|
}
|
|
5071
|
-
function
|
|
5311
|
+
function extractUsage2(line, message) {
|
|
5072
5312
|
const lineUsage = asRecord(line.usage);
|
|
5073
5313
|
const messageUsage = asRecord(message?.usage);
|
|
5074
5314
|
if (lineUsage) {
|
|
5075
|
-
const extractedLineUsage =
|
|
5315
|
+
const extractedLineUsage = extractUsageFromRecord2(lineUsage);
|
|
5076
5316
|
if (extractedLineUsage) {
|
|
5077
5317
|
return extractedLineUsage;
|
|
5078
5318
|
}
|
|
@@ -5080,12 +5320,12 @@ function extractUsage(line, message) {
|
|
|
5080
5320
|
if (!messageUsage) {
|
|
5081
5321
|
return void 0;
|
|
5082
5322
|
}
|
|
5083
|
-
return
|
|
5323
|
+
return extractUsageFromRecord2(messageUsage);
|
|
5084
5324
|
}
|
|
5085
|
-
function
|
|
5086
|
-
return
|
|
5325
|
+
function getFallbackSessionId4(filePath) {
|
|
5326
|
+
return path12.basename(filePath, ".jsonl");
|
|
5087
5327
|
}
|
|
5088
|
-
function
|
|
5328
|
+
function resolveRepoRootFromRecord2(record) {
|
|
5089
5329
|
if (!record) {
|
|
5090
5330
|
return void 0;
|
|
5091
5331
|
}
|
|
@@ -5115,37 +5355,37 @@ var PiSourceAdapter = class {
|
|
|
5115
5355
|
}
|
|
5116
5356
|
async parseFile(filePath) {
|
|
5117
5357
|
const events = [];
|
|
5118
|
-
const state = { sessionId:
|
|
5358
|
+
const state = { sessionId: getFallbackSessionId4(filePath) };
|
|
5119
5359
|
for await (const line of readJsonlObjects(filePath, {
|
|
5120
5360
|
shouldParseLine: shouldParsePiJsonlLine
|
|
5121
5361
|
})) {
|
|
5122
5362
|
if (line.type === "session") {
|
|
5123
5363
|
state.sessionId = asTrimmedText(line.id) ?? state.sessionId;
|
|
5124
5364
|
state.sessionTimestamp = asTrimmedText(line.timestamp) ?? state.sessionTimestamp;
|
|
5125
|
-
state.repoRoot =
|
|
5365
|
+
state.repoRoot = resolveRepoRootFromRecord2(line) ?? state.repoRoot;
|
|
5126
5366
|
continue;
|
|
5127
5367
|
}
|
|
5128
5368
|
if (line.type === "model_change") {
|
|
5129
5369
|
state.provider = asTrimmedText(line.provider) ?? state.provider;
|
|
5130
5370
|
state.model = asTrimmedText(line.modelId) ?? asTrimmedText(line.model) ?? state.model;
|
|
5131
|
-
state.repoRoot =
|
|
5371
|
+
state.repoRoot = resolveRepoRootFromRecord2(line) ?? state.repoRoot;
|
|
5132
5372
|
continue;
|
|
5133
5373
|
}
|
|
5134
5374
|
if (line.type !== "message") {
|
|
5135
5375
|
continue;
|
|
5136
5376
|
}
|
|
5137
5377
|
const message = asRecord(line.message);
|
|
5138
|
-
const usage =
|
|
5378
|
+
const usage = extractUsage2(line, message);
|
|
5139
5379
|
if (!usage) {
|
|
5140
5380
|
continue;
|
|
5141
5381
|
}
|
|
5142
5382
|
const provider = asTrimmedText(line.provider) ?? asTrimmedText(message?.provider) ?? state.provider;
|
|
5143
|
-
const timestamp =
|
|
5383
|
+
const timestamp = resolveTimestamp3(line, message, state);
|
|
5144
5384
|
if (!timestamp || !state.sessionId) {
|
|
5145
5385
|
continue;
|
|
5146
5386
|
}
|
|
5147
5387
|
const model = asTrimmedText(line.model) ?? asTrimmedText(line.modelId) ?? asTrimmedText(message?.model) ?? state.model;
|
|
5148
|
-
const repoRoot =
|
|
5388
|
+
const repoRoot = resolveRepoRootFromRecord2(line) ?? resolveRepoRootFromRecord2(message) ?? state.repoRoot;
|
|
5149
5389
|
try {
|
|
5150
5390
|
events.push(
|
|
5151
5391
|
createUsageEvent({
|
|
@@ -5255,6 +5495,21 @@ var sourceRegistrations = [
|
|
|
5255
5495
|
dbPath: options.opencodeDb
|
|
5256
5496
|
})
|
|
5257
5497
|
},
|
|
5498
|
+
{
|
|
5499
|
+
id: "openclaw",
|
|
5500
|
+
sourceDirOverride: { kind: "directory" },
|
|
5501
|
+
create: (_options, sourceDirectoryOverrides) => {
|
|
5502
|
+
const directoryConfig = resolveDirectoryConfig(
|
|
5503
|
+
"openclaw",
|
|
5504
|
+
void 0,
|
|
5505
|
+
sourceDirectoryOverrides
|
|
5506
|
+
);
|
|
5507
|
+
return new OpenClawSourceAdapter({
|
|
5508
|
+
agentsDir: directoryConfig.path,
|
|
5509
|
+
requireAgentsDir: directoryConfig.requireExistingPath
|
|
5510
|
+
});
|
|
5511
|
+
}
|
|
5512
|
+
},
|
|
5258
5513
|
{
|
|
5259
5514
|
id: "claude",
|
|
5260
5515
|
sourceDirOverride: { kind: "directory" },
|
|
@@ -5592,7 +5847,7 @@ function normalizeSkippedRowReasons(value) {
|
|
|
5592
5847
|
|
|
5593
5848
|
// src/cli/parse-file-cache.ts
|
|
5594
5849
|
import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat4, writeFile as writeFile2 } from "fs/promises";
|
|
5595
|
-
import
|
|
5850
|
+
import path13 from "path";
|
|
5596
5851
|
var PARSE_FILE_CACHE_VERSION = 5;
|
|
5597
5852
|
var CACHE_KEY_SEPARATOR = "\0";
|
|
5598
5853
|
function createCacheKey(source, filePath) {
|
|
@@ -5819,7 +6074,7 @@ function normalizeCacheEntry(value) {
|
|
|
5819
6074
|
};
|
|
5820
6075
|
}
|
|
5821
6076
|
function getDefaultParseFileCachePath() {
|
|
5822
|
-
return
|
|
6077
|
+
return path13.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
|
|
5823
6078
|
}
|
|
5824
6079
|
function normalizeCacheShardSource(source) {
|
|
5825
6080
|
const normalizedSource = normalizeCacheSource(source);
|
|
@@ -5829,12 +6084,12 @@ function normalizeCacheShardSource(source) {
|
|
|
5829
6084
|
return normalizedSource.replace(/[^a-z0-9._-]/gu, "_");
|
|
5830
6085
|
}
|
|
5831
6086
|
function getSourceShardedParseFileCachePath(cacheFilePath, source) {
|
|
5832
|
-
const parsedPath =
|
|
6087
|
+
const parsedPath = path13.parse(cacheFilePath);
|
|
5833
6088
|
const sourceShard = normalizeCacheShardSource(source);
|
|
5834
6089
|
if (parsedPath.ext.length > 0) {
|
|
5835
|
-
return
|
|
6090
|
+
return path13.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
|
|
5836
6091
|
}
|
|
5837
|
-
return
|
|
6092
|
+
return path13.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
|
|
5838
6093
|
}
|
|
5839
6094
|
var ParseFileCache = class _ParseFileCache {
|
|
5840
6095
|
constructor(cacheFilePath, limits, now) {
|
|
@@ -5931,7 +6186,7 @@ var ParseFileCache = class _ParseFileCache {
|
|
|
5931
6186
|
keptEntries.length = bestCount;
|
|
5932
6187
|
payloadText = bestPayloadText;
|
|
5933
6188
|
}
|
|
5934
|
-
await mkdir2(
|
|
6189
|
+
await mkdir2(path13.dirname(this.cacheFilePath), { recursive: true });
|
|
5935
6190
|
const temporaryPath = `${this.cacheFilePath}.${process.pid}.${this.now()}.tmp`;
|
|
5936
6191
|
try {
|
|
5937
6192
|
await writeFile2(temporaryPath, payloadText, "utf8");
|
|
@@ -6503,7 +6758,7 @@ var PricingOverrideSource = class {
|
|
|
6503
6758
|
|
|
6504
6759
|
// src/pricing/litellm-pricing-fetcher.ts
|
|
6505
6760
|
import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
|
|
6506
|
-
import
|
|
6761
|
+
import path14 from "path";
|
|
6507
6762
|
|
|
6508
6763
|
// src/pricing/litellm-model-map.json
|
|
6509
6764
|
var litellm_model_map_default = {
|
|
@@ -6687,7 +6942,7 @@ function normalizeLitellmPricingPayload(payload) {
|
|
|
6687
6942
|
return normalizedPricing;
|
|
6688
6943
|
}
|
|
6689
6944
|
function getDefaultLiteLLMPricingCachePath() {
|
|
6690
|
-
return
|
|
6945
|
+
return path14.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
|
|
6691
6946
|
}
|
|
6692
6947
|
function stripProviderPrefix(model) {
|
|
6693
6948
|
const slashIndex = model.lastIndexOf("/");
|
|
@@ -7081,7 +7336,7 @@ var LiteLLMPricingFetcher = class {
|
|
|
7081
7336
|
};
|
|
7082
7337
|
}
|
|
7083
7338
|
async writeCache() {
|
|
7084
|
-
const directoryPath =
|
|
7339
|
+
const directoryPath = path14.dirname(this.cacheFilePath);
|
|
7085
7340
|
await mkdir3(directoryPath, { recursive: true });
|
|
7086
7341
|
const payload = {
|
|
7087
7342
|
fetchedAt: this.now(),
|
|
@@ -7697,9 +7952,9 @@ function emitEnvVarOverrides(activeEnvOverrides, diagnosticsLogger) {
|
|
|
7697
7952
|
import { spawn as spawn3 } from "child_process";
|
|
7698
7953
|
import { constants as constants3 } from "fs";
|
|
7699
7954
|
import { access as access3, writeFile as writeFile4 } from "fs/promises";
|
|
7700
|
-
import
|
|
7955
|
+
import path15 from "path";
|
|
7701
7956
|
async function writeShareSvgFile(fileName, svgContent) {
|
|
7702
|
-
const outputPath =
|
|
7957
|
+
const outputPath = path15.resolve(process.cwd(), fileName);
|
|
7703
7958
|
await writeFile4(outputPath, svgContent, "utf8");
|
|
7704
7959
|
return outputPath;
|
|
7705
7960
|
}
|
|
@@ -7727,10 +7982,10 @@ async function resolveBinaryPath(primaryPath, fallbackNames) {
|
|
|
7727
7982
|
if (await fileExists(primaryPath)) {
|
|
7728
7983
|
return primaryPath;
|
|
7729
7984
|
}
|
|
7730
|
-
const pathDirs = (process.env.PATH ?? "").split(
|
|
7985
|
+
const pathDirs = (process.env.PATH ?? "").split(path15.delimiter).filter(Boolean);
|
|
7731
7986
|
for (const fallbackName of fallbackNames) {
|
|
7732
7987
|
for (const dir of pathDirs) {
|
|
7733
|
-
const candidate =
|
|
7988
|
+
const candidate = path15.join(dir, fallbackName);
|
|
7734
7989
|
if (await fileExists(candidate)) {
|
|
7735
7990
|
return candidate;
|
|
7736
7991
|
}
|
|
@@ -10031,7 +10286,7 @@ function createUsageReportDefinition(granularity) {
|
|
|
10031
10286
|
includeInRootHelp: true
|
|
10032
10287
|
},
|
|
10033
10288
|
{
|
|
10034
|
-
command: "llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions",
|
|
10289
|
+
command: "llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions --source-dir openclaw=/tmp/openclaw-agents",
|
|
10035
10290
|
includeInRootHelp: true,
|
|
10036
10291
|
includeInCliReference: true
|
|
10037
10292
|
},
|