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 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 **OpenCode** with zero configuration required.
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 OpenCode session data
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/pi/pi-source-adapter.ts
5009
+ // src/sources/openclaw/openclaw-source-adapter.ts
5010
5010
  import os7 from "os";
5011
5011
  import path11 from "path";
5012
- var defaultSessionsDir3 = path11.join(os7.homedir(), ".pi", "agent", "sessions");
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 resolveTimestamp2(line, message, state) {
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 extractUsageFromRecord(usage) {
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 toFiniteNumber2 = (value) => {
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 = toFiniteNumber2(value);
5304
+ const parsed = toFiniteNumber3(value);
5065
5305
  return parsed !== void 0 && parsed > 0;
5066
5306
  });
5067
- const explicitCost = toFiniteNumber2(extracted.costUsd);
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 extractUsage(line, message) {
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 = extractUsageFromRecord(lineUsage);
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 extractUsageFromRecord(messageUsage);
5323
+ return extractUsageFromRecord2(messageUsage);
5084
5324
  }
5085
- function getFallbackSessionId3(filePath) {
5086
- return path11.basename(filePath, ".jsonl");
5325
+ function getFallbackSessionId4(filePath) {
5326
+ return path12.basename(filePath, ".jsonl");
5087
5327
  }
5088
- function resolveRepoRootFromRecord(record) {
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: getFallbackSessionId3(filePath) };
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 = resolveRepoRootFromRecord(line) ?? 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 = resolveRepoRootFromRecord(line) ?? 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 = extractUsage(line, message);
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 = resolveTimestamp2(line, message, state);
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 = resolveRepoRootFromRecord(line) ?? resolveRepoRootFromRecord(message) ?? state.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 path12 from "path";
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 path12.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
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 = path12.parse(cacheFilePath);
6087
+ const parsedPath = path13.parse(cacheFilePath);
5833
6088
  const sourceShard = normalizeCacheShardSource(source);
5834
6089
  if (parsedPath.ext.length > 0) {
5835
- return path12.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
6090
+ return path13.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
5836
6091
  }
5837
- return path12.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
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(path12.dirname(this.cacheFilePath), { recursive: true });
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 path13 from "path";
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 path13.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
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 = path13.dirname(this.cacheFilePath);
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 path14 from "path";
7955
+ import path15 from "path";
7701
7956
  async function writeShareSvgFile(fileName, svgContent) {
7702
- const outputPath = path14.resolve(process.cwd(), fileName);
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(path14.delimiter).filter(Boolean);
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 = path14.join(dir, fallbackName);
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
  },