@blockrun/clawrouter 0.12.271 → 0.12.272

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -55996,7 +55996,7 @@ var require_websocket = __commonJS({
55996
55996
  var net3 = __require("net");
55997
55997
  var tls2 = __require("tls");
55998
55998
  var { randomBytes: randomBytes9, createHash: createHash4 } = __require("crypto");
55999
- var { Duplex, Readable: Readable2 } = __require("stream");
55999
+ var { Duplex, Readable: Readable3 } = __require("stream");
56000
56000
  var { URL: URL3 } = __require("url");
56001
56001
  var PerMessageDeflate2 = require_permessage_deflate();
56002
56002
  var Receiver2 = require_receiver();
@@ -60119,16 +60119,20 @@ function maskApiKey(key2) {
60119
60119
  async function readOptional2(path5) {
60120
60120
  try {
60121
60121
  return (await readTextFile(path5)).trim() || void 0;
60122
- } catch {
60123
- return void 0;
60122
+ } catch (error) {
60123
+ if (error.code === "ENOENT") return void 0;
60124
+ throw new Error(
60125
+ `Cannot read BlockRun API key file ${path5}; refusing to fall back to another account or wallet.`,
60126
+ { cause: error }
60127
+ );
60124
60128
  }
60125
60129
  }
60126
60130
  async function resolveApiKey() {
60127
60131
  const envKey = process["env"].BLOCKRUN_API_KEY?.trim();
60128
60132
  if (envKey) {
60129
60133
  if (isValidApiKey(envKey)) return { key: envKey, source: "env" };
60130
- console.warn(
60131
- `[ClawRouter] \u26A0 BLOCKRUN_API_KEY is set but does not look like a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
60134
+ throw new Error(
60135
+ `BLOCKRUN_API_KEY is malformed (expected brk_\u2026). Mint one at ${PORTAL_KEYS_URL}, or unset it to pay from the wallet. Refusing to fall back silently.`
60132
60136
  );
60133
60137
  }
60134
60138
  for (const [path5, source] of [
@@ -60138,8 +60142,8 @@ async function resolveApiKey() {
60138
60142
  const stored = await readOptional2(path5);
60139
60143
  if (stored === void 0) continue;
60140
60144
  if (isValidApiKey(stored)) return { key: stored, source };
60141
- console.warn(
60142
- `[ClawRouter] \u26A0 ${path5} does not contain a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
60145
+ throw new Error(
60146
+ `${path5} does not contain a BlockRun key (expected brk_\u2026). Run "clawrouter login" with a valid key, or "clawrouter logout" to return to wallet billing. Refusing to fall back silently: the next source could bill a different account or spend from a wallet.`
60143
60147
  );
60144
60148
  }
60145
60149
  return void 0;
@@ -60198,16 +60202,95 @@ function formatCreditBalance(b) {
60198
60202
  const mode = b.billingMode === "ungated" ? "no prepaid limit" : b.billingMode ?? "unknown";
60199
60203
  return `${spent ? `spent ${spent}` : "unknown"} \u2014 ${mode}`;
60200
60204
  }
60201
- function createApiKeyFetch(apiKey, baseFetch = fetch) {
60205
+ async function fetchUsagePage(apiKey, opts = {}) {
60206
+ const params = new URLSearchParams();
60207
+ if (opts.from) params.set("from", opts.from);
60208
+ if (opts.to) params.set("to", opts.to);
60209
+ if (opts.limit) params.set("limit", String(opts.limit));
60210
+ if (opts.cursor) params.set("cursor", opts.cursor);
60211
+ const query = params.toString();
60212
+ try {
60213
+ const res = await fetch(`${BLOCKRUN_API_KEY_API}/v1/usage${query ? `?${query}` : ""}`, {
60214
+ headers: { authorization: `Bearer ${apiKey}` },
60215
+ signal: AbortSignal.timeout(3e4)
60216
+ });
60217
+ if (!res.ok) return void 0;
60218
+ const body = await res.json();
60219
+ return {
60220
+ rows: (body.data ?? []).map((r2) => ({
60221
+ requestId: String(r2.request_id ?? ""),
60222
+ timestamp: String(r2.timestamp ?? ""),
60223
+ endpoint: String(r2.endpoint ?? ""),
60224
+ model: typeof r2.model === "string" ? r2.model : null,
60225
+ kind: String(r2.kind ?? ""),
60226
+ inputTokens: typeof r2.input_tokens === "number" ? r2.input_tokens : 0,
60227
+ outputTokens: typeof r2.output_tokens === "number" ? r2.output_tokens : 0,
60228
+ costUsd: typeof r2.cost_usd === "number" ? r2.cost_usd : null,
60229
+ costState: String(r2.cost_state ?? ""),
60230
+ status: typeof r2.status === "number" ? r2.status : 0
60231
+ })),
60232
+ nextCursor: body.next_cursor ?? null,
60233
+ unavailableDays: body.unavailable_days ?? []
60234
+ };
60235
+ } catch {
60236
+ return void 0;
60237
+ }
60238
+ }
60239
+ function normalizeApiKeyBase(raw) {
60240
+ const base4 = raw.replace(/\/+$/, "").replace(/\/v1$/, "");
60241
+ const url2 = new URL(base4);
60242
+ if (url2.username || url2.password || url2.search || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname))) {
60243
+ throw new Error(
60244
+ "BlockRun account API URL requires HTTPS (except localhost) and no credentials, query or fragment."
60245
+ );
60246
+ }
60247
+ return base4;
60248
+ }
60249
+ function createApiKeyFetch(apiKey, baseFetch = fetch, apiBase = BLOCKRUN_API_KEY_API) {
60250
+ const base4 = new URL(normalizeApiKeyBase(apiBase));
60202
60251
  return async (input, init2) => {
60203
- const headers = new Headers(init2?.headers);
60252
+ const request2 = input instanceof Request ? input : void 0;
60253
+ const url2 = new URL(request2?.url ?? String(input), `${base4}/`);
60254
+ if (url2.origin !== base4.origin || url2.username || url2.password) {
60255
+ throw new Error("Refusing to forward a BlockRun account key to another origin.");
60256
+ }
60257
+ if (base4.pathname === "/" && url2.pathname.startsWith("/api/v1/")) {
60258
+ url2.pathname = url2.pathname.slice(4);
60259
+ }
60260
+ const headers = new Headers(init2?.headers ?? request2?.headers);
60261
+ for (const name of [...headers.keys()]) {
60262
+ if (/payment/i.test(name) || name.toLowerCase() === "x-api-key") headers.delete(name);
60263
+ }
60204
60264
  headers.set("authorization", `Bearer ${apiKey}`);
60205
- headers.delete("x-payment");
60206
- headers.delete("x-api-key");
60207
- const response = await baseFetch(input, { ...init2, headers });
60265
+ const response = await baseFetch(request2 ? new Request(url2, request2) : url2.href, {
60266
+ ...init2,
60267
+ headers,
60268
+ redirect: "error"
60269
+ });
60208
60270
  return response.ok ? response : explainApiKeyFailure(response);
60209
60271
  };
60210
60272
  }
60273
+ async function pollApiKeyJob(response, payFetch, apiBase, signal, intervalMs = 2e3) {
60274
+ if (response.status !== 202) return response;
60275
+ const initial = await response.clone().json();
60276
+ if (!initial.poll_url) throw new Error("Async account response missing poll_url");
60277
+ const pollUrl = new URL(initial.poll_url, `${normalizeApiKeyBase(apiBase)}/`).href;
60278
+ const abort = AbortSignal.any([signal, AbortSignal.timeout(15 * 6e4)]);
60279
+ while (!abort.aborted) {
60280
+ const polled = await payFetch(pollUrl, { signal: abort });
60281
+ if ([502, 503, 504, 522, 524].includes(polled.status)) {
60282
+ await polled.body?.cancel();
60283
+ } else {
60284
+ if (!polled.ok) return polled;
60285
+ const data = await polled.clone().json();
60286
+ if (data.status === "completed") return polled;
60287
+ if (data.status === "failed") return polled;
60288
+ await polled.body?.cancel();
60289
+ }
60290
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
60291
+ }
60292
+ throw new Error("Async account job did not complete before the client aborted or timed out.");
60293
+ }
60211
60294
  async function explainApiKeyFailure(response) {
60212
60295
  const hint = HINTS[response.status];
60213
60296
  if (!hint) return response;
@@ -60241,7 +60324,7 @@ var init_api_key = __esm({
60241
60324
  PORTAL_URL = "https://user.blockrun.ai";
60242
60325
  PORTAL_KEYS_URL = `${PORTAL_URL}/dashboard/keys`;
60243
60326
  PORTAL_CREDITS_URL = `${PORTAL_URL}/dashboard/credits`;
60244
- BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "") || "https://api.blockrun.ai";
60327
+ BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "").replace(/\/v1$/, "") || "https://api.blockrun.ai";
60245
60328
  HINTS = {
60246
60329
  401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
60247
60330
  402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
@@ -69387,7 +69470,7 @@ var require_client_h2 = __commonJS({
69387
69470
  "node_modules/undici/lib/dispatcher/client-h2.js"(exports, module) {
69388
69471
  "use strict";
69389
69472
  var assert10 = __require("assert");
69390
- var { pipeline } = __require("stream");
69473
+ var { pipeline: pipeline2 } = __require("stream");
69391
69474
  var util5 = require_util();
69392
69475
  var {
69393
69476
  RequestContentLengthMismatchError,
@@ -70534,7 +70617,7 @@ var require_client_h2 = __commonJS({
70534
70617
  }
70535
70618
  function writeStream(abort, socket, expectsPayload, h2stream, body, client, request2, contentLength) {
70536
70619
  assert10(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined");
70537
- const pipe2 = pipeline(
70620
+ const pipe2 = pipeline2(
70538
70621
  body,
70539
70622
  h2stream,
70540
70623
  (err) => {
@@ -73689,7 +73772,7 @@ var require_readable = __commonJS({
73689
73772
  "use strict";
73690
73773
  var assert10 = __require("assert");
73691
73774
  var { addAbortListener } = __require("events");
73692
- var { Readable: Readable2 } = __require("stream");
73775
+ var { Readable: Readable3 } = __require("stream");
73693
73776
  var { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require_errors();
73694
73777
  var util5 = require_util();
73695
73778
  var { ReadableStreamFrom } = require_util();
@@ -73703,7 +73786,7 @@ var require_readable = __commonJS({
73703
73786
  var kBytesRead = /* @__PURE__ */ Symbol("kBytesRead");
73704
73787
  var noop2 = () => {
73705
73788
  };
73706
- var BodyReadable = class extends Readable2 {
73789
+ var BodyReadable = class extends Readable3 {
73707
73790
  /**
73708
73791
  * @param {object} opts
73709
73792
  * @param {(this: Readable, size: number) => void} opts.resume
@@ -74102,7 +74185,7 @@ var require_api_request = __commonJS({
74102
74185
  "use strict";
74103
74186
  var assert10 = __require("assert");
74104
74187
  var { AsyncResource } = __require("async_hooks");
74105
- var { Readable: Readable2 } = require_readable();
74188
+ var { Readable: Readable3 } = require_readable();
74106
74189
  var { InvalidArgumentError, RequestAbortedError } = require_errors();
74107
74190
  var util5 = require_util();
74108
74191
  function noop2() {
@@ -74188,7 +74271,7 @@ var require_api_request = __commonJS({
74188
74271
  const parsedHeaders = headers;
74189
74272
  const contentType = parsedHeaders?.["content-type"];
74190
74273
  const contentLength = parsedHeaders?.["content-length"];
74191
- const res = new Readable2({
74274
+ const res = new Readable3({
74192
74275
  resume: () => controller.resume(),
74193
74276
  abort: (reason) => controller.abort(reason),
74194
74277
  contentType,
@@ -74564,7 +74647,7 @@ var require_api_pipeline = __commonJS({
74564
74647
  "node_modules/undici/lib/api/api-pipeline.js"(exports, module) {
74565
74648
  "use strict";
74566
74649
  var {
74567
- Readable: Readable2,
74650
+ Readable: Readable3,
74568
74651
  Duplex,
74569
74652
  PassThrough
74570
74653
  } = __require("stream");
@@ -74581,7 +74664,7 @@ var require_api_pipeline = __commonJS({
74581
74664
  function noop2() {
74582
74665
  }
74583
74666
  var kResume = /* @__PURE__ */ Symbol("resume");
74584
- var PipelineRequest = class extends Readable2 {
74667
+ var PipelineRequest = class extends Readable3 {
74585
74668
  constructor() {
74586
74669
  super({ autoDestroy: true });
74587
74670
  this[kResume] = null;
@@ -74599,7 +74682,7 @@ var require_api_pipeline = __commonJS({
74599
74682
  callback(err);
74600
74683
  }
74601
74684
  };
74602
- var PipelineResponse = class extends Readable2 {
74685
+ var PipelineResponse = class extends Readable3 {
74603
74686
  constructor(resume2) {
74604
74687
  super({ autoDestroy: true });
74605
74688
  this[kResume] = resume2;
@@ -74753,7 +74836,7 @@ var require_api_pipeline = __commonJS({
74753
74836
  util5.destroy(ret, err);
74754
74837
  }
74755
74838
  };
74756
- function pipeline(opts, handler) {
74839
+ function pipeline2(opts, handler) {
74757
74840
  try {
74758
74841
  const pipelineHandler = new PipelineHandler(opts, handler);
74759
74842
  this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler);
@@ -74762,7 +74845,7 @@ var require_api_pipeline = __commonJS({
74762
74845
  return new PassThrough().destroy(err);
74763
74846
  }
74764
74847
  }
74765
- module.exports = pipeline;
74848
+ module.exports = pipeline2;
74766
74849
  }
74767
74850
  });
74768
74851
 
@@ -76398,7 +76481,7 @@ var require_snapshot_utils = __commonJS({
76398
76481
  var require_snapshot_recorder = __commonJS({
76399
76482
  "node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
76400
76483
  "use strict";
76401
- var { writeFile: writeFile4, readFile: readFile3, mkdir: mkdir6 } = __require("fs/promises");
76484
+ var { writeFile: writeFile4, readFile: readFile4, mkdir: mkdir6 } = __require("fs/promises");
76402
76485
  var { dirname: dirname4, resolve } = __require("path");
76403
76486
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
76404
76487
  var { InvalidArgumentError, UndiciError } = require_errors();
@@ -76615,7 +76698,7 @@ var require_snapshot_recorder = __commonJS({
76615
76698
  throw new InvalidArgumentError("Snapshot path is required");
76616
76699
  }
76617
76700
  try {
76618
- const data = await readFile3(resolve(path5), "utf8");
76701
+ const data = await readFile4(resolve(path5), "utf8");
76619
76702
  const parsed = JSON.parse(data);
76620
76703
  if (Array.isArray(parsed)) {
76621
76704
  this.#snapshots.clear();
@@ -79880,7 +79963,7 @@ var require_cache2 = __commonJS({
79880
79963
  "node_modules/undici/lib/interceptor/cache.js"(exports, module) {
79881
79964
  "use strict";
79882
79965
  var assert10 = __require("assert");
79883
- var { Readable: Readable2 } = __require("stream");
79966
+ var { Readable: Readable3 } = __require("stream");
79884
79967
  var util5 = require_util();
79885
79968
  var CacheHandler = require_cache_handler();
79886
79969
  var MemoryCacheStore = require_memory_cache_store();
@@ -80056,7 +80139,7 @@ var require_cache2 = __commonJS({
80056
80139
  return dispatch(opts, new CacheHandler(globalOpts, cacheKey2, handler));
80057
80140
  }
80058
80141
  function sendCachedValue(handler, opts, result, age, context, isStale2) {
80059
- const stream4 = util5.isStream(result.body) ? result.body : Readable2.from(result.body ?? []);
80142
+ const stream4 = util5.isStream(result.body) ? result.body : Readable3.from(result.body ?? []);
80060
80143
  assert10(!stream4.destroyed, "stream should not be destroyed");
80061
80144
  assert10(!stream4.readableDidRead, "stream should not be readableDidRead");
80062
80145
  const controller = {
@@ -80301,7 +80384,7 @@ var require_decompress = __commonJS({
80301
80384
  "node_modules/undici/lib/interceptor/decompress.js"(exports, module) {
80302
80385
  "use strict";
80303
80386
  var { createInflate, createGunzip, createBrotliDecompress, createZstdDecompress } = __require("zlib");
80304
- var { pipeline } = __require("stream");
80387
+ var { pipeline: pipeline2 } = __require("stream");
80305
80388
  var DecoratorHandler = require_decorator_handler();
80306
80389
  var supportedEncodings = {
80307
80390
  gzip: createGunzip,
@@ -80411,7 +80494,7 @@ var require_decompress = __commonJS({
80411
80494
  #setupMultipleDecompressors(controller) {
80412
80495
  const lastDecompressor = this.#decompressors[this.#decompressors.length - 1];
80413
80496
  this.#setupDecompressorEvents(lastDecompressor, controller);
80414
- pipeline(this.#decompressors, (err) => {
80497
+ pipeline2(this.#decompressors, (err) => {
80415
80498
  if (err) {
80416
80499
  super.onResponseError(controller, err);
80417
80500
  return;
@@ -83205,7 +83288,7 @@ var require_fetch = __commonJS({
83205
83288
  subresourceSet
83206
83289
  } = require_constants4();
83207
83290
  var EE = __require("events");
83208
- var { Readable: Readable2, pipeline, finished: finished2, isErrored, isReadable } = __require("stream");
83291
+ var { Readable: Readable3, pipeline: pipeline2, finished: finished2, isErrored, isReadable } = __require("stream");
83209
83292
  var { addAbortListener, bufferToLowerCasedHeaderName } = require_util();
83210
83293
  var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url();
83211
83294
  var { getGlobalDispatcher } = require_global2();
@@ -84181,7 +84264,7 @@ var require_fetch = __commonJS({
84181
84264
  const headersList = new HeadersList();
84182
84265
  appendHeadersListFromResponseHeaders(headersList, headers, rawHeaders);
84183
84266
  const location = headersList.get("location", true);
84184
- this.body = new Readable2({ read: () => controller.resume() });
84267
+ this.body = new Readable3({ read: () => controller.resume() });
84185
84268
  const willFollow = location && request2.redirect === "follow" && redirectStatusSet.has(status);
84186
84269
  const decoders = [];
84187
84270
  if (request2.method !== "HEAD" && request2.method !== "CONNECT" && !nullBodyStatus.includes(status) && !willFollow) {
@@ -84229,7 +84312,7 @@ var require_fetch = __commonJS({
84229
84312
  status,
84230
84313
  statusText,
84231
84314
  headersList,
84232
- body: decoders.length ? pipeline(this.body, ...decoders, (err) => {
84315
+ body: decoders.length ? pipeline2(this.body, ...decoders, (err) => {
84233
84316
  if (err) {
84234
84317
  this.onResponseError(controller, err);
84235
84318
  }
@@ -88041,7 +88124,7 @@ ${value}`;
88041
88124
  var require_eventsource = __commonJS({
88042
88125
  "node_modules/undici/lib/web/eventsource/eventsource.js"(exports, module) {
88043
88126
  "use strict";
88044
- var { pipeline } = __require("stream");
88127
+ var { pipeline: pipeline2 } = __require("stream");
88045
88128
  var { fetching } = require_fetch();
88046
88129
  var { webidl } = require_webidl();
88047
88130
  var { EventSourceStream } = require_eventsource_stream();
@@ -88191,7 +88274,7 @@ var require_eventsource = __commonJS({
88191
88274
  ));
88192
88275
  }
88193
88276
  });
88194
- pipeline(
88277
+ pipeline2(
88195
88278
  response.body.stream,
88196
88279
  eventSourceStream,
88197
88280
  (error) => {
@@ -90741,7 +90824,8 @@ var init_v1 = __esm({
90741
90824
  import { AsyncLocalStorage } from "async_hooks";
90742
90825
  import { createHmac } from "crypto";
90743
90826
  import { createServer } from "http";
90744
- import { finished } from "stream";
90827
+ import { finished, Readable } from "stream";
90828
+ import { pipeline } from "stream/promises";
90745
90829
  import { homedir as homedir8 } from "os";
90746
90830
  import { join as join11 } from "path";
90747
90831
  import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile2, stat as fsStat } from "fs/promises";
@@ -90750,10 +90834,10 @@ async function loadGatewayCatalog(apiBase, apiKey) {
90750
90834
  try {
90751
90835
  const controller = new AbortController();
90752
90836
  const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
90753
- const res = await fetch(`${apiBase}/v1/models`, {
90754
- signal: controller.signal,
90755
- ...apiKey ? { headers: { authorization: `Bearer ${apiKey}` } } : {}
90756
- });
90837
+ const res = await (apiKey ? createApiKeyFetch(apiKey, fetch, apiBase) : fetch)(
90838
+ `${apiBase}/v1/models`,
90839
+ { signal: controller.signal }
90840
+ );
90757
90841
  clearTimeout(timer2);
90758
90842
  if (!res.ok) return;
90759
90843
  const body = await res.json();
@@ -91499,6 +91583,39 @@ function blockrunRequestId(response) {
91499
91583
  }
91500
91584
  return void 0;
91501
91585
  }
91586
+ function gatewayRemainingCreditUsd(response) {
91587
+ const raw = response?.headers.get("x-blockrun-credit-remaining-usd");
91588
+ if (raw === null || raw === void 0) return void 0;
91589
+ const trimmed = raw.trim();
91590
+ if (trimmed === "") return void 0;
91591
+ const value = Number(trimmed);
91592
+ if (!Number.isFinite(value) || value < 0) return void 0;
91593
+ return value;
91594
+ }
91595
+ function settledMediaCostUsd(upstream, body, x402AmountUsd, estimate) {
91596
+ const header = gatewaySettledCostUsd(upstream);
91597
+ if (header !== void 0) return header;
91598
+ const price = body?.price?.amount;
91599
+ if (typeof price === "string" || typeof price === "number") {
91600
+ const parsed = Number(price);
91601
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
91602
+ }
91603
+ if (typeof x402AmountUsd === "number" && x402AmountUsd > 0) return x402AmountUsd;
91604
+ return estimate;
91605
+ }
91606
+ function noteRemainingCredit(response) {
91607
+ const remaining = gatewayRemainingCreditUsd(response);
91608
+ if (remaining === void 0) return;
91609
+ if (remaining > LOW_CREDIT_USD) {
91610
+ lowCreditWarned = false;
91611
+ return;
91612
+ }
91613
+ if (lowCreditWarned) return;
91614
+ lowCreditWarned = true;
91615
+ console.warn(
91616
+ remaining <= 0 ? `[ClawRouter] \u26A0 BlockRun account credit is exhausted \u2014 calls will fail with 402. Top up: ${PORTAL_CREDITS_URL}` : `[ClawRouter] \u26A0 BlockRun account credit low: $${remaining.toFixed(2)} remaining. Top up: ${PORTAL_CREDITS_URL}`
91617
+ );
91618
+ }
91502
91619
  function gatewaySettledCostUsd(response) {
91503
91620
  const raw = response?.headers.get("x-blockrun-cost-usd");
91504
91621
  if (raw === null || raw === void 0) return void 0;
@@ -91508,7 +91625,7 @@ function gatewaySettledCostUsd(response) {
91508
91625
  if (!Number.isFinite(value) || value < 0) return void 0;
91509
91626
  return value;
91510
91627
  }
91511
- async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
91628
+ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd, accountPassthrough = false, authMode = "wallet") {
91512
91629
  const startTime = Date.now();
91513
91630
  const upstreamUrl = `${apiBase}${req.url}`;
91514
91631
  const isBlockrunExa = req.url?.startsWith("/v1/exa/") ?? false;
@@ -91547,16 +91664,32 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
91547
91664
  return;
91548
91665
  responseHeaders[key2] = value;
91549
91666
  });
91667
+ if (accountPassthrough || authMode === "api-key") {
91668
+ responseHeaders["cache-control"] = "no-store";
91669
+ }
91550
91670
  res.writeHead(upstream.status, responseHeaders);
91551
- if (upstream.body) {
91552
- const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS);
91553
- for (const chunk of chunks) {
91554
- safeWrite(res, Buffer.from(chunk));
91671
+ if (accountPassthrough) {
91672
+ if (upstream.body) {
91673
+ await pipeline(
91674
+ Readable.fromWeb(upstream.body),
91675
+ res,
91676
+ { signal: clientAbort.signal }
91677
+ );
91678
+ } else {
91679
+ res.end();
91680
+ }
91681
+ } else {
91682
+ if (upstream.body) {
91683
+ const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS);
91684
+ for (const chunk of chunks) {
91685
+ safeWrite(res, Buffer.from(chunk));
91686
+ }
91555
91687
  }
91688
+ res.end();
91556
91689
  }
91557
- res.end();
91558
91690
  const latencyMs = Date.now() - startTime;
91559
91691
  console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
91692
+ noteRemainingCredit(upstream);
91560
91693
  const settledCostUsd = gatewaySettledCostUsd(upstream);
91561
91694
  const paidAmount = settledCostUsd ?? getActualPaymentUsd();
91562
91695
  const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
@@ -91660,7 +91793,8 @@ async function startProxy(options) {
91660
91793
  const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
91661
91794
  const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
91662
91795
  const paymentChain = options.paymentChain ?? await resolvePaymentChain();
91663
- const apiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
91796
+ const requestedApiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
91797
+ const apiBase = authMode === "api-key" ? normalizeApiKeyBase(requestedApiBase) : requestedApiBase;
91664
91798
  if (authMode === "api-key") {
91665
91799
  console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
91666
91800
  console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
@@ -91789,7 +91923,7 @@ async function startProxy(options) {
91789
91923
  if (store) store.amountUsd = amountUsd;
91790
91924
  console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
91791
91925
  });
91792
- const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
91926
+ const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey, fetch, apiBase) : createPayFetchWithPreAuth(fetch, x402, void 0, {
91793
91927
  skipPreAuth: paymentChain === "solana",
91794
91928
  // Per-request cost estimate so pre-auth is only reused when the cached
91795
91929
  // payment still covers the (possibly larger) request — BlockRun prices per
@@ -92293,9 +92427,15 @@ async function startProxy(options) {
92293
92427
  }
92294
92428
  }
92295
92429
  }
92296
- const imgActualCost = paymentStore.getStore()?.amountUsd ?? imgCost;
92430
+ const imgActualCost = settledMediaCostUsd(
92431
+ upstream,
92432
+ result,
92433
+ paymentStore.getStore()?.amountUsd,
92434
+ imgCost
92435
+ );
92297
92436
  logUsage({
92298
92437
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92438
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92299
92439
  model: imgModel,
92300
92440
  tier: "IMAGE",
92301
92441
  cost: imgActualCost,
@@ -92364,12 +92504,15 @@ async function startProxy(options) {
92364
92504
  return;
92365
92505
  }
92366
92506
  try {
92367
- const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
92507
+ let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
92368
92508
  method: "POST",
92369
92509
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
92370
92510
  body: reqBody,
92371
92511
  signal: clientAbort.signal
92372
92512
  });
92513
+ if (authMode === "api-key") {
92514
+ upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
92515
+ }
92373
92516
  const text = await upstream.text();
92374
92517
  if (!upstream.ok) {
92375
92518
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
@@ -92416,9 +92559,15 @@ async function startProxy(options) {
92416
92559
  }
92417
92560
  }
92418
92561
  }
92419
- const img2imgActualCost = paymentStore.getStore()?.amountUsd ?? img2imgCost;
92562
+ const img2imgActualCost = settledMediaCostUsd(
92563
+ upstream,
92564
+ result,
92565
+ paymentStore.getStore()?.amountUsd,
92566
+ img2imgCost
92567
+ );
92420
92568
  logUsage({
92421
92569
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92570
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92422
92571
  model: img2imgModel,
92423
92572
  tier: "IMAGE",
92424
92573
  cost: img2imgActualCost,
@@ -92458,12 +92607,15 @@ async function startProxy(options) {
92458
92607
  } catch {
92459
92608
  }
92460
92609
  try {
92461
- const upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
92610
+ let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
92462
92611
  method: "POST",
92463
92612
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
92464
92613
  body: reqBody,
92465
92614
  signal: clientAbort.signal
92466
92615
  });
92616
+ if (authMode === "api-key") {
92617
+ upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
92618
+ }
92467
92619
  const text = await upstream.text();
92468
92620
  if (!upstream.ok) {
92469
92621
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
@@ -92502,9 +92654,15 @@ async function startProxy(options) {
92502
92654
  }
92503
92655
  }
92504
92656
  }
92505
- const audioActualCost = paymentStore.getStore()?.amountUsd ?? 0.15;
92657
+ const audioActualCost = settledMediaCostUsd(
92658
+ upstream,
92659
+ result,
92660
+ paymentStore.getStore()?.amountUsd,
92661
+ 0.15
92662
+ );
92506
92663
  logUsage({
92507
92664
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92665
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92508
92666
  model: audioModel,
92509
92667
  tier: "AUDIO",
92510
92668
  cost: audioActualCost,
@@ -92662,7 +92820,12 @@ async function startProxy(options) {
92662
92820
  }
92663
92821
  }
92664
92822
  }
92665
- const videoActualCost = paymentStore.getStore()?.amountUsd ?? estimateVideoCost(videoModel, videoDuration, videoHasImageInput);
92823
+ const videoActualCost = settledMediaCostUsd(
92824
+ void 0,
92825
+ submitResult,
92826
+ paymentStore.getStore()?.amountUsd,
92827
+ estimateVideoCost(videoModel, videoDuration, videoHasImageInput)
92828
+ );
92666
92829
  logUsage({
92667
92830
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92668
92831
  model: videoModel,
@@ -92696,7 +92859,9 @@ async function startProxy(options) {
92696
92859
  res,
92697
92860
  apiBase,
92698
92861
  payFetch,
92699
- () => paymentStore.getStore()?.amountUsd ?? 0
92862
+ () => paymentStore.getStore()?.amountUsd ?? 0,
92863
+ false,
92864
+ authMode
92700
92865
  );
92701
92866
  } catch (err) {
92702
92867
  const error = err instanceof Error ? err : new Error(String(err));
@@ -92719,6 +92884,33 @@ async function startProxy(options) {
92719
92884
  res.end(JSON.stringify({ error: "Not found" }));
92720
92885
  return;
92721
92886
  }
92887
+ if (authMode === "api-key" && /^\/v1\//.test(req.url ?? "") && !/^\/v1\/(?:chat\/completions|messages)(?:\?|$)/.test(req.url ?? "")) {
92888
+ try {
92889
+ await proxyPaidApiRequest(
92890
+ req,
92891
+ res,
92892
+ apiBase,
92893
+ payFetch,
92894
+ () => paymentStore.getStore()?.amountUsd ?? 0,
92895
+ true,
92896
+ authMode
92897
+ );
92898
+ } catch (err) {
92899
+ const error = err instanceof Error ? err : new Error(String(err));
92900
+ options.onError?.(error);
92901
+ if (!res.headersSent) {
92902
+ res.writeHead(502, { "Content-Type": "application/json" });
92903
+ res.end(
92904
+ JSON.stringify({
92905
+ error: { message: `Account API error: ${error.message}`, type: "account_error" }
92906
+ })
92907
+ );
92908
+ } else if (!res.writableEnded) {
92909
+ res.end();
92910
+ }
92911
+ }
92912
+ return;
92913
+ }
92722
92914
  if (!req.url.includes("/chat/completions")) {
92723
92915
  res.writeHead(404, { "Content-Type": "application/json" });
92724
92916
  res.end(
@@ -93402,9 +93594,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
93402
93594
  responseText = lines.join("\n");
93403
93595
  }
93404
93596
  console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
93405
- const imagegenActualCost = paymentStore.getStore()?.amountUsd ?? estimateImageCost(imageModel, imageSize, 1);
93597
+ const imagegenActualCost = settledMediaCostUsd(
93598
+ imageResponse,
93599
+ imageResult,
93600
+ paymentStore.getStore()?.amountUsd,
93601
+ estimateImageCost(imageModel, imageSize, 1)
93602
+ );
93406
93603
  logUsage({
93407
93604
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93605
+ ...blockrunRequestId(imageResponse) ? { requestId: blockrunRequestId(imageResponse) } : {},
93408
93606
  model: imageModel,
93409
93607
  tier: "IMAGE",
93410
93608
  cost: imagegenActualCost,
@@ -93628,9 +93826,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
93628
93826
  responseText = lines.join("\n");
93629
93827
  }
93630
93828
  console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
93631
- const img2imgActualCost2 = paymentStore.getStore()?.amountUsd ?? estimateImageCost(img2imgModel, img2imgSize, 1);
93829
+ const img2imgActualCost2 = settledMediaCostUsd(
93830
+ img2imgResponse,
93831
+ img2imgResult,
93832
+ paymentStore.getStore()?.amountUsd,
93833
+ estimateImageCost(img2imgModel, img2imgSize, 1)
93834
+ );
93632
93835
  logUsage({
93633
93836
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93837
+ ...blockrunRequestId(img2imgResponse) ? { requestId: blockrunRequestId(img2imgResponse) } : {},
93634
93838
  model: img2imgModel,
93635
93839
  tier: "IMAGE",
93636
93840
  cost: img2imgActualCost2,
@@ -94686,6 +94890,7 @@ data: [DONE]
94686
94890
  }
94687
94891
  }
94688
94892
  upstreamRequestId = blockrunRequestId(upstream);
94893
+ noteRemainingCredit(upstream);
94689
94894
  if (!upstream) {
94690
94895
  const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
94691
94896
  const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
@@ -95226,7 +95431,7 @@ data: [DONE]
95226
95431
  });
95227
95432
  }
95228
95433
  }
95229
- var paymentStore, BLOCKRUN_API, BLOCKRUN_SOLANA_API, DESKTOP_SERVICE_TOKEN_FILE, IMAGE_DIR, AUDIO_DIR, VIDEO_DIR, AUTO_MODEL, ROUTING_PROFILES, FREE_MODELS, gatewayModelIds, FREE_MODEL, FREE_UPSTREAM_OVERRIDES, MAX_MESSAGES, CONTEXT_LIMIT_KB, HEARTBEAT_INTERVAL_MS, BALANCE_CHECK_TIMEOUT_MS, GATEWAY_CATALOG_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, PER_MODEL_TIMEOUT_MS, REASONING_MODEL_TIMEOUT_MS, REASONING_MODEL_IDS, MAX_FALLBACK_ATTEMPTS, HEALTH_CHECK_TIMEOUT_MS, RATE_LIMIT_COOLDOWN_MS, OVERLOAD_COOLDOWN_MS, PORT_RETRY_ATTEMPTS, PORT_RETRY_DELAY_MS, MODEL_BODY_READ_TIMEOUT_MS, ERROR_BODY_READ_TIMEOUT_MS, ClientDisconnectedError, rateLimitedModels, overloadedModels, perProviderErrors, BALANCE_CHECK_BUFFER, BALANCE_PREFLIGHT_OUTPUT_TOKEN_CAP, PROVIDER_ERROR_PATTERNS, DEGRADED_RESPONSE_PATTERNS, DEGRADED_LOOP_PATTERNS, VALID_ROLES, ROLE_MAPPINGS, VALID_TOOL_ID_PATTERN, KIMI_BLOCK_RE, KIMI_TOKEN_RE, THINKING_TAG_RE, THINKING_BLOCK_RE, BLOCKRUN_MODEL_BY_ID, IMAGE_PRICING, IMAGE_MODEL_IDS, IMAGE_MODEL_SIZES, IMAGE_MODEL_ALIASES, VIDEO_PRICING, PHONE_PRICING;
95434
+ var paymentStore, BLOCKRUN_API, BLOCKRUN_SOLANA_API, DESKTOP_SERVICE_TOKEN_FILE, IMAGE_DIR, AUDIO_DIR, VIDEO_DIR, AUTO_MODEL, ROUTING_PROFILES, FREE_MODELS, gatewayModelIds, FREE_MODEL, FREE_UPSTREAM_OVERRIDES, MAX_MESSAGES, CONTEXT_LIMIT_KB, HEARTBEAT_INTERVAL_MS, BALANCE_CHECK_TIMEOUT_MS, GATEWAY_CATALOG_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, PER_MODEL_TIMEOUT_MS, REASONING_MODEL_TIMEOUT_MS, REASONING_MODEL_IDS, MAX_FALLBACK_ATTEMPTS, HEALTH_CHECK_TIMEOUT_MS, RATE_LIMIT_COOLDOWN_MS, OVERLOAD_COOLDOWN_MS, PORT_RETRY_ATTEMPTS, PORT_RETRY_DELAY_MS, MODEL_BODY_READ_TIMEOUT_MS, ERROR_BODY_READ_TIMEOUT_MS, ClientDisconnectedError, rateLimitedModels, overloadedModels, perProviderErrors, BALANCE_CHECK_BUFFER, BALANCE_PREFLIGHT_OUTPUT_TOKEN_CAP, PROVIDER_ERROR_PATTERNS, DEGRADED_RESPONSE_PATTERNS, DEGRADED_LOOP_PATTERNS, VALID_ROLES, ROLE_MAPPINGS, VALID_TOOL_ID_PATTERN, KIMI_BLOCK_RE, KIMI_TOKEN_RE, THINKING_TAG_RE, THINKING_BLOCK_RE, BLOCKRUN_MODEL_BY_ID, IMAGE_PRICING, IMAGE_MODEL_IDS, IMAGE_MODEL_SIZES, IMAGE_MODEL_ALIASES, VIDEO_PRICING, PHONE_PRICING, LOW_CREDIT_USD, lowCreditWarned;
95230
95435
  var init_proxy = __esm({
95231
95436
  "src/proxy.ts"() {
95232
95437
  "use strict";
@@ -95488,6 +95693,8 @@ var init_proxy = __esm({
95488
95693
  "phone/numbers/release": 0,
95489
95694
  "voice/call": 0.54
95490
95695
  };
95696
+ LOW_CREDIT_USD = 1;
95697
+ lowCreditWarned = false;
95491
95698
  }
95492
95699
  });
95493
95700
 
@@ -96528,13 +96735,13 @@ ${USAGE}`
96528
96735
  ${USAGE}`);
96529
96736
  }
96530
96737
  if (raw === "clear") return { kind: "limit", window: target, usd: void 0 };
96531
- const usd = Number(raw);
96532
- if (!USD.test(raw) || !Number.isFinite(usd) || usd <= 0) {
96738
+ const usd2 = Number(raw);
96739
+ if (!USD.test(raw) || !Number.isFinite(usd2) || usd2 <= 0) {
96533
96740
  return fail(
96534
96741
  `Rejected amount "${raw}": must be a positive decimal USD value such as 0.10 or 5`
96535
96742
  );
96536
96743
  }
96537
- return { kind: "limit", window: target, usd };
96744
+ return { kind: "limit", window: target, usd: usd2 };
96538
96745
  }
96539
96746
  if (!isListAction(action)) return fail(`Unknown subcommand "${sub}"
96540
96747
  ${USAGE}`);
@@ -96679,6 +96886,194 @@ var init_policy = __esm({
96679
96886
  }
96680
96887
  });
96681
96888
 
96889
+ // src/reconcile.ts
96890
+ var reconcile_exports = {};
96891
+ __export(reconcile_exports, {
96892
+ formatReconcile: () => formatReconcile,
96893
+ joinRows: () => joinRows,
96894
+ loadGatewayRows: () => loadGatewayRows,
96895
+ loadLocalRows: () => loadLocalRows,
96896
+ reconcile: () => reconcile
96897
+ });
96898
+ import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
96899
+ import { join as join12 } from "path";
96900
+ import { homedir as homedir9 } from "os";
96901
+ async function loadLocalRows(since) {
96902
+ let files;
96903
+ try {
96904
+ files = (await readdir3(LOG_DIR3)).filter((f) => f.endsWith(".jsonl"));
96905
+ } catch {
96906
+ return { rows: [], unkeyed: 0 };
96907
+ }
96908
+ const rows = [];
96909
+ let unkeyed = 0;
96910
+ for (const file of files) {
96911
+ let text;
96912
+ try {
96913
+ text = await readFile3(join12(LOG_DIR3, file), "utf8");
96914
+ } catch {
96915
+ continue;
96916
+ }
96917
+ for (const line of text.split("\n")) {
96918
+ if (!line.trim()) continue;
96919
+ let entry;
96920
+ try {
96921
+ entry = JSON.parse(line);
96922
+ } catch {
96923
+ continue;
96924
+ }
96925
+ if (!entry.timestamp || new Date(entry.timestamp) < since) continue;
96926
+ if (!entry.requestId) {
96927
+ unkeyed++;
96928
+ continue;
96929
+ }
96930
+ rows.push({
96931
+ requestId: entry.requestId,
96932
+ model: entry.model,
96933
+ cost: typeof entry.cost === "number" ? entry.cost : 0,
96934
+ timestamp: entry.timestamp
96935
+ });
96936
+ }
96937
+ }
96938
+ return { rows, unkeyed };
96939
+ }
96940
+ async function loadGatewayRows(apiKey, from15, maxPages = 40) {
96941
+ const rows = [];
96942
+ const unavailableDays = /* @__PURE__ */ new Set();
96943
+ let cursor;
96944
+ for (let page = 0; page < maxPages; page++) {
96945
+ const result = await fetchUsagePage(apiKey, { from: from15, limit: 500, cursor });
96946
+ if (!result) return page === 0 ? void 0 : { rows, unavailableDays: [...unavailableDays] };
96947
+ rows.push(...result.rows);
96948
+ result.unavailableDays.forEach((d) => unavailableDays.add(d));
96949
+ if (!result.nextCursor) break;
96950
+ cursor = result.nextCursor;
96951
+ }
96952
+ return { rows, unavailableDays: [...unavailableDays] };
96953
+ }
96954
+ function joinRows(localRows, gatewayRows, unkeyed = 0, unavailableDays = []) {
96955
+ const localById = new Map(localRows.map((r2) => [r2.requestId, r2]));
96956
+ const seen = /* @__PURE__ */ new Set();
96957
+ const matched = [];
96958
+ const chargedNotRecorded = [];
96959
+ const pending = [];
96960
+ let gatewayTotalUsd = 0;
96961
+ for (const remote of gatewayRows) {
96962
+ if (remote.costState === "pending") {
96963
+ pending.push(remote);
96964
+ continue;
96965
+ }
96966
+ gatewayTotalUsd += remote.costUsd ?? 0;
96967
+ const local = localById.get(remote.requestId);
96968
+ if (!local) {
96969
+ chargedNotRecorded.push(remote);
96970
+ continue;
96971
+ }
96972
+ seen.add(remote.requestId);
96973
+ matched.push({ local, remote, deltaUsd: local.cost - (remote.costUsd ?? 0) });
96974
+ }
96975
+ const recordedNotCharged = localRows.filter((r2) => !seen.has(r2.requestId));
96976
+ const localTotalUsd = localRows.reduce((sum, r2) => sum + r2.cost, 0);
96977
+ return {
96978
+ matched,
96979
+ chargedNotRecorded,
96980
+ recordedNotCharged,
96981
+ pending,
96982
+ localTotalUsd,
96983
+ gatewayTotalUsd,
96984
+ unavailableDays,
96985
+ unkeyedLocalCount: unkeyed
96986
+ };
96987
+ }
96988
+ async function reconcile(apiKey, days) {
96989
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
96990
+ const [{ rows: localRows, unkeyed }, gateway] = await Promise.all([
96991
+ loadLocalRows(since),
96992
+ loadGatewayRows(apiKey, since.toISOString())
96993
+ ]);
96994
+ if (!gateway) throw new Error("Could not read the BlockRun usage ledger (GET /v1/usage).");
96995
+ return joinRows(localRows, gateway.rows, unkeyed, gateway.unavailableDays);
96996
+ }
96997
+ function formatReconcile(r2, days) {
96998
+ const out = [];
96999
+ const mismatched = r2.matched.filter((m) => Math.abs(m.deltaUsd) > CENT_EPSILON);
97000
+ out.push(`
97001
+ BlockRun reconciliation \u2014 last ${days} day${days === 1 ? "" : "s"}
97002
+ `);
97003
+ out.push(
97004
+ ` Gateway charged: ${usd(r2.gatewayTotalUsd)} (${r2.matched.length + r2.chargedNotRecorded.length} settled calls)`
97005
+ );
97006
+ out.push(` Journal recorded: ${usd(r2.localTotalUsd)}`);
97007
+ out.push(
97008
+ ` Matched: ${r2.matched.length}, of which ${mismatched.length} disagree on amount`
97009
+ );
97010
+ if (mismatched.length > 0) {
97011
+ out.push(`
97012
+ Amount mismatches (journal vs gateway):`);
97013
+ for (const m of mismatched.slice(0, 10)) {
97014
+ out.push(
97015
+ ` ${m.remote.requestId.slice(0, 8)} ${(m.local.model || m.remote.endpoint).padEnd(28)} journal ${usd(m.local.cost)} gateway ${usd(m.remote.costUsd ?? 0)}`
97016
+ );
97017
+ }
97018
+ if (mismatched.length > 10) out.push(` \u2026and ${mismatched.length - 10} more`);
97019
+ }
97020
+ if (r2.chargedNotRecorded.length > 0) {
97021
+ const total = r2.chargedNotRecorded.reduce((s3, x) => s3 + (x.costUsd ?? 0), 0);
97022
+ out.push(
97023
+ `
97024
+ \u26A0 Charged but NOT in this machine's journal: ${r2.chargedNotRecorded.length} call(s), ${usd(total)}`
97025
+ );
97026
+ out.push(
97027
+ ` Expected if the same key is used elsewhere \u2014 another machine, another BlockRun product,`
97028
+ );
97029
+ out.push(` or a call made before this proxy started recording request ids.`);
97030
+ for (const x of r2.chargedNotRecorded.slice(0, 10)) {
97031
+ out.push(
97032
+ ` ${x.requestId.slice(0, 8)} ${x.timestamp.slice(0, 19)} ${x.endpoint.padEnd(26)} ${usd(x.costUsd ?? 0)}`
97033
+ );
97034
+ }
97035
+ if (r2.chargedNotRecorded.length > 10) {
97036
+ out.push(` \u2026and ${r2.chargedNotRecorded.length - 10} more`);
97037
+ }
97038
+ }
97039
+ if (r2.recordedNotCharged.length > 0) {
97040
+ out.push(
97041
+ `
97042
+ Recorded locally with no settled ledger row: ${r2.recordedNotCharged.length} call(s)`
97043
+ );
97044
+ out.push(` Usually benign \u2014 free models, cache hits, and calls still pending pricing.`);
97045
+ }
97046
+ if (r2.pending.length > 0) {
97047
+ out.push(`
97048
+ Pending pricing (excluded from totals): ${r2.pending.length} call(s)`);
97049
+ out.push(` Usage recorded, charge not final. These can still be repriced.`);
97050
+ }
97051
+ if (r2.unkeyedLocalCount > 0) {
97052
+ out.push(`
97053
+ Journal entries with no request id: ${r2.unkeyedLocalCount} (not reconcilable)`);
97054
+ }
97055
+ if (r2.unavailableDays.length > 0) {
97056
+ out.push(`
97057
+ \u26A0 Gateway could not list these days: ${r2.unavailableDays.join(", ")}`);
97058
+ out.push(` Totals above are incomplete for that period.`);
97059
+ }
97060
+ out.push(`
97061
+ Note: the top-up fee (5.5% + $0.30) is charged at purchase, not per call,`);
97062
+ out.push(` so the sum above is less than your card was charged by exactly those fees.
97063
+ `);
97064
+ return out.join("\n");
97065
+ }
97066
+ var LOG_DIR3, CENT_EPSILON, usd;
97067
+ var init_reconcile = __esm({
97068
+ "src/reconcile.ts"() {
97069
+ "use strict";
97070
+ init_api_key();
97071
+ LOG_DIR3 = join12(homedir9(), ".openclaw", "blockrun", "logs");
97072
+ CENT_EPSILON = 1e-9;
97073
+ usd = (n) => Math.abs(n) < 0.01 ? `$${n.toFixed(6)}` : `$${n.toFixed(2)}`;
97074
+ }
97075
+ });
97076
+
96682
97077
  // src/provider.ts
96683
97078
  function setActiveProxy(proxy) {
96684
97079
  activeProxy = proxy;
@@ -112521,7 +112916,7 @@ var init_readBlob = __esm({
112521
112916
 
112522
112917
  // node_modules/axios/lib/helpers/formDataToStream.js
112523
112918
  import util2 from "util";
112524
- import { Readable } from "stream";
112919
+ import { Readable as Readable2 } from "stream";
112525
112920
  var BOUNDARY_ALPHABET, textEncoder, CRLF, CRLF_BYTES, CRLF_BYTES_COUNT, FormDataPart, formDataToStream, formDataToStream_default;
112526
112921
  var init_formDataToStream = __esm({
112527
112922
  "node_modules/axios/lib/helpers/formDataToStream.js"() {
@@ -112601,7 +112996,7 @@ var init_formDataToStream = __esm({
112601
112996
  computedHeaders["Content-Length"] = contentLength;
112602
112997
  }
112603
112998
  headersHandler && headersHandler(computedHeaders);
112604
- return Readable.from(
112999
+ return Readable2.from(
112605
113000
  (async function* () {
112606
113001
  for (const part of parts) {
112607
113002
  yield boundaryBytes;
@@ -117665,8 +118060,8 @@ var init_constants3 = __esm({
117665
118060
 
117666
118061
  // src/polymarket/wallet-adapter.ts
117667
118062
  import fs2 from "fs";
117668
- import { homedir as homedir9 } from "os";
117669
- import { join as join12 } from "path";
118063
+ import { homedir as homedir10 } from "os";
118064
+ import { join as join13 } from "path";
117670
118065
  function getOrCreateWalletKey() {
117671
118066
  const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
117672
118067
  if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
@@ -117714,7 +118109,7 @@ var init_wallet_adapter = __esm({
117714
118109
  init_esm();
117715
118110
  init_chains();
117716
118111
  init_constants3();
117717
- WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
118112
+ WALLET_FILE2 = join13(homedir10(), ".openclaw", "blockrun", "wallet.key");
117718
118113
  BASE_RPC_URLS = [
117719
118114
  "https://mainnet.base.org",
117720
118115
  "https://base.llamarpc.com",
@@ -118118,8 +118513,8 @@ var init_client5 = __esm({
118118
118513
  function resolveSpendControl(deps) {
118119
118514
  return deps?.spendControl ?? getSharedSpendControl();
118120
118515
  }
118121
- function usdToMicros(usd) {
118122
- return String(Math.ceil(usd * 1e6));
118516
+ function usdToMicros(usd2) {
118517
+ return String(Math.ceil(usd2 * 1e6));
118123
118518
  }
118124
118519
  async function signUnderSpendPolicy(deps, quote, action, sign4) {
118125
118520
  const control = resolveSpendControl(deps);
@@ -118148,13 +118543,13 @@ function getSessionLedger() {
118148
118543
  perAgent: Object.fromEntries(ledger.perAgent)
118149
118544
  };
118150
118545
  }
118151
- function reserveBet(usd, agentId) {
118152
- ledger.totalUsd += usd;
118153
- if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) + usd);
118546
+ function reserveBet(usd2, agentId) {
118547
+ ledger.totalUsd += usd2;
118548
+ if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) + usd2);
118154
118549
  }
118155
- function releaseBet(usd, agentId) {
118156
- ledger.totalUsd -= usd;
118157
- if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) - usd);
118550
+ function releaseBet(usd2, agentId) {
118551
+ ledger.totalUsd -= usd2;
118552
+ if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) - usd2);
118158
118553
  }
118159
118554
  function commitBet() {
118160
118555
  ledger.count += 1;
@@ -228116,9 +228511,9 @@ import {
228116
228511
  unlinkSync
228117
228512
  } from "fs";
228118
228513
  import { readFile as readFileAsync } from "fs/promises";
228119
- import { homedir as homedir14 } from "os";
228514
+ import { homedir as homedir15 } from "os";
228120
228515
  import { randomUUID } from "crypto";
228121
- import { join as join17 } from "path";
228516
+ import { join as join18 } from "path";
228122
228517
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
228123
228518
  const start = Date.now();
228124
228519
  while (Date.now() - start < timeoutMs) {
@@ -228160,8 +228555,8 @@ function isBlockrunWebSearchDisabled(config) {
228160
228555
  return cfg.tools?.web?.search?.enabled === false;
228161
228556
  }
228162
228557
  function injectModelsConfig(logger48, options = {}) {
228163
- const configDir = join17(homedir14(), ".openclaw");
228164
- const configPath = join17(configDir, "openclaw.json");
228558
+ const configDir = join18(homedir15(), ".openclaw");
228559
+ const configPath = join18(configDir, "openclaw.json");
228165
228560
  let config = {};
228166
228561
  let needsWrite = false;
228167
228562
  if (!existsSync4(configDir)) {
@@ -228382,7 +228777,7 @@ function injectModelsConfig(logger48, options = {}) {
228382
228777
  }
228383
228778
  function syncAgentModelCache(logger48, options = {}) {
228384
228779
  if (!isGatewayMode() && !options.forceWrite) return;
228385
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
228780
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
228386
228781
  if (!existsSync4(agentsDir)) return;
228387
228782
  let agentDirs;
228388
228783
  try {
@@ -228392,7 +228787,7 @@ function syncAgentModelCache(logger48, options = {}) {
228392
228787
  }
228393
228788
  const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
228394
228789
  for (const agent of agentDirs) {
228395
- const cachePath = join17(agentsDir, agent, "agent", "models.json");
228790
+ const cachePath = join18(agentsDir, agent, "agent", "models.json");
228396
228791
  if (!existsSync4(cachePath)) continue;
228397
228792
  try {
228398
228793
  const raw = readTextFileSync(cachePath).trim();
@@ -228421,7 +228816,7 @@ function syncAgentModelCache(logger48, options = {}) {
228421
228816
  }
228422
228817
  }
228423
228818
  function injectAuthProfile(logger48) {
228424
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
228819
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
228425
228820
  if (!existsSync4(agentsDir)) {
228426
228821
  try {
228427
228822
  mkdirSync3(agentsDir, { recursive: true });
@@ -228438,9 +228833,9 @@ function injectAuthProfile(logger48) {
228438
228833
  agents = ["main", ...agents];
228439
228834
  }
228440
228835
  for (const agentId of agents) {
228441
- const authDir = join17(agentsDir, agentId, "agent");
228442
- const authPath = join17(authDir, "auth-profiles.json");
228443
- const sqlitePath = join17(authDir, "openclaw-agent.sqlite");
228836
+ const authDir = join18(agentsDir, agentId, "agent");
228837
+ const authPath = join18(authDir, "auth-profiles.json");
228838
+ const sqlitePath = join18(authDir, "openclaw-agent.sqlite");
228444
228839
  if (existsSync4(sqlitePath)) {
228445
228840
  removeInjectedAuthPlaceholder(authPath, logger48, agentId);
228446
228841
  continue;
@@ -228588,12 +228983,12 @@ async function startProxyInBackground(api, startupGeneration) {
228588
228983
  proc.__clawrouterStartupPhase = "starting";
228589
228984
  }
228590
228985
  const pluginApiKey = api.pluginConfig?.apiKey;
228591
- const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
228592
- if (typeof pluginApiKey === "string" && !isValidApiKey(pluginApiKey)) {
228593
- api.logger.warn(
228594
- `pluginConfig.apiKey is set but invalid (expected brk_...) \u2014 ignoring it and looking elsewhere`
228986
+ if (typeof pluginApiKey === "string" && pluginApiKey.trim() && !isValidApiKey(pluginApiKey)) {
228987
+ throw new Error(
228988
+ `pluginConfig.apiKey is set but is not a BlockRun key (expected brk_\u2026). Fix it or remove it \u2014 refusing to fall back to another credential.`
228595
228989
  );
228596
228990
  }
228991
+ const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
228597
228992
  const apiKey = resolvedApiKey?.key;
228598
228993
  const configKey = api.pluginConfig?.walletKey;
228599
228994
  let wallet;
@@ -228943,7 +229338,7 @@ function buildImageGenerationProvider() {
228943
229338
  (result.data ?? []).map(async (img) => {
228944
229339
  const filename = img.url?.split("/images/").pop();
228945
229340
  if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
228946
- const filePath = join17(IMAGE_DIR2, filename);
229341
+ const filePath = join18(IMAGE_DIR2, filename);
228947
229342
  const buffer2 = await readFileAsync(filePath);
228948
229343
  const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
228949
229344
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
@@ -228995,7 +229390,7 @@ function buildMusicGenerationProvider() {
228995
229390
  (result.data ?? []).map(async (track) => {
228996
229391
  const filename = track.url?.split("/audio/").pop();
228997
229392
  if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
228998
- const filePath = join17(AUDIO_DIR2, filename);
229393
+ const filePath = join18(AUDIO_DIR2, filename);
228999
229394
  const buffer2 = await readFileAsync(filePath);
229000
229395
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
229001
229396
  const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
@@ -229071,7 +229466,7 @@ function buildVideoGenerationProvider() {
229071
229466
  (result.data ?? []).map(async (clip) => {
229072
229467
  const filename = clip.url?.split("/videos/").pop();
229073
229468
  if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
229074
- const filePath = join17(VIDEO_DIR2, filename);
229469
+ const filePath = join18(VIDEO_DIR2, filename);
229075
229470
  const buffer2 = await readFileAsync(filePath);
229076
229471
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
229077
229472
  const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
@@ -229436,9 +229831,9 @@ var init_index = __esm({
229436
229831
  init_partners();
229437
229832
  activeProxyHandle = null;
229438
229833
  pendingConfiguredStartupApi = null;
229439
- IMAGE_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "images");
229440
- AUDIO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "audio");
229441
- VIDEO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "videos");
229834
+ IMAGE_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "images");
229835
+ AUDIO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "audio");
229836
+ VIDEO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "videos");
229442
229837
  plugin = {
229443
229838
  // NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
229444
229839
  // 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
@@ -229780,7 +230175,18 @@ ${errText}`
229780
230175
  if (!isGatewayMode()) {
229781
230176
  if (shouldLogRegistration) {
229782
230177
  void (async () => {
229783
- const keyResolution = await resolveApiKey().catch(() => void 0);
230178
+ let keyResolution;
230179
+ try {
230180
+ keyResolution = await resolveApiKey();
230181
+ } catch (error) {
230182
+ api.logger.error(
230183
+ `BlockRun API key is configured but unusable: ${error instanceof Error ? error.message : String(error)}`
230184
+ );
230185
+ api.logger.error(
230186
+ `Not falling back to a wallet \u2014 fix the key, or run "clawrouter logout" to pay from the wallet deliberately.`
230187
+ );
230188
+ return;
230189
+ }
229784
230190
  if (keyResolution) {
229785
230191
  api.logger.info(
229786
230192
  `Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`
@@ -229863,7 +230269,7 @@ ${errText}`
229863
230269
  }
229864
230270
  resetProxyStartupState();
229865
230271
  try {
229866
- const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
230272
+ const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
229867
230273
  if (existsSync4(configPath)) {
229868
230274
  const config = JSON.parse(readTextFileSync(configPath));
229869
230275
  if (config.models?.providers?.blockrun) {
@@ -229905,11 +230311,11 @@ ${errText}`
229905
230311
  api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
229906
230312
  }
229907
230313
  try {
229908
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
230314
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
229909
230315
  if (existsSync4(agentsDir)) {
229910
230316
  for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
229911
230317
  if (!entry.isDirectory()) continue;
229912
- const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
230318
+ const authPath = join18(agentsDir, entry.name, "agent", "auth-profiles.json");
229913
230319
  if (!existsSync4(authPath)) continue;
229914
230320
  try {
229915
230321
  const store = JSON.parse(readTextFileSync(authPath));
@@ -230725,6 +231131,27 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
230725
231131
  process.exit(1);
230726
231132
  }
230727
231133
  }
231134
+ async function cmdReconcile(days) {
231135
+ const resolved = await resolveApiKey().catch((error) => {
231136
+ console.error(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
231137
+ process.exit(1);
231138
+ });
231139
+ if (!resolved) {
231140
+ console.error(`\u2717 Reconciliation needs a BlockRun API key \u2014 it compares against your account`);
231141
+ console.error(` ledger. Wallet spend settles on-chain instead; see "clawrouter stats".`);
231142
+ console.error(` Sign in at ${PORTAL_URL} and run: clawrouter login brk_live_...`);
231143
+ process.exit(1);
231144
+ }
231145
+ try {
231146
+ const { reconcile: reconcile2, formatReconcile: formatReconcile2 } = await Promise.resolve().then(() => (init_reconcile(), reconcile_exports));
231147
+ const result = await reconcile2(resolved.key, days);
231148
+ console.log(formatReconcile2(result, days));
231149
+ process.exitCode = result.chargedNotRecorded.length > 0 ? 2 : 0;
231150
+ } catch (error) {
231151
+ console.error(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
231152
+ process.exit(1);
231153
+ }
231154
+ }
230728
231155
  async function cmdStatus(port) {
230729
231156
  try {
230730
231157
  const data = await queryProxy("/health?full=true", port);
@@ -231002,8 +231429,8 @@ async function cmdSetup() {
231002
231429
  unlinkSync: unlinkSync2,
231003
231430
  writeFileSync: writeFileSync4
231004
231431
  } = await import("fs");
231005
- const { dirname: dirname4, join: join18 } = await import("path");
231006
- const { homedir: homedir15 } = await import("os");
231432
+ const { dirname: dirname4, join: join19 } = await import("path");
231433
+ const { homedir: homedir16 } = await import("os");
231007
231434
  const { injectModelsConfig: injectModelsConfig2, injectAuthProfile: injectAuthProfile2, syncAgentModelCache: syncAgentModelCache2, VISIBLE_OPENCLAW_MODELS: VISIBLE_OPENCLAW_MODELS2 } = await Promise.resolve().then(() => (init_index(), index_exports));
231008
231435
  const { BLOCKRUN_PLUGIN_ID: BLOCKRUN_PLUGIN_ID2, prepareBlockRunPluginConfig: prepareBlockRunPluginConfig2 } = await Promise.resolve().then(() => (init_openclaw_plugin_config(), openclaw_plugin_config_exports));
231009
231436
  console.log("\u{1F99E} ClawRouter setup\n");
@@ -231018,19 +231445,19 @@ async function cmdSetup() {
231018
231445
  } catch {
231019
231446
  }
231020
231447
  if (process.env.npm_config_prefix) {
231021
- candidates.push(join18(process.env.npm_config_prefix, "bin", "openclaw"));
231448
+ candidates.push(join19(process.env.npm_config_prefix, "bin", "openclaw"));
231022
231449
  }
231023
231450
  try {
231024
231451
  const npmRoot = execSync("npm root -g", {
231025
231452
  encoding: "utf8",
231026
231453
  stdio: ["ignore", "pipe", "ignore"]
231027
231454
  }).trim();
231028
- if (npmRoot) candidates.push(join18(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
231455
+ if (npmRoot) candidates.push(join19(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
231029
231456
  } catch {
231030
231457
  }
231031
231458
  candidates.push(
231032
- join18(homedir15(), ".npm-global", "bin", "openclaw"),
231033
- join18(homedir15(), ".local", "bin", "openclaw"),
231459
+ join19(homedir16(), ".npm-global", "bin", "openclaw"),
231460
+ join19(homedir16(), ".local", "bin", "openclaw"),
231034
231461
  "/usr/local/bin/openclaw"
231035
231462
  );
231036
231463
  return candidates.find((candidate) => existsSync5(candidate)) ?? "";
@@ -231042,7 +231469,7 @@ async function cmdSetup() {
231042
231469
  process.exit(1);
231043
231470
  }
231044
231471
  console.log(` \u2713 Found openclaw at ${openclawPath}`);
231045
- const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
231472
+ const configPath = join19(homedir16(), ".openclaw", "openclaw.json");
231046
231473
  const configInitiallyExisted = existsSync5(configPath);
231047
231474
  const configRollbackBackup = configInitiallyExisted ? `${configPath}.before-${BLOCKRUN_PLUGIN_ID2}.${Date.now()}` : void 0;
231048
231475
  if (configRollbackBackup) copyFileSync2(configPath, configRollbackBackup);
@@ -231050,23 +231477,23 @@ async function cmdSetup() {
231050
231477
  return configRollbackBackup;
231051
231478
  };
231052
231479
  let installAttempted = false;
231053
- const openclawRoot = join18(homedir15(), ".openclaw");
231480
+ const openclawRoot = join19(homedir16(), ".openclaw");
231054
231481
  const findManagedBlockRunInstalls = () => {
231055
231482
  const candidates = [
231056
- join18(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
231057
- join18(openclawRoot, "extensions", "clawrouter"),
231058
- join18(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
231483
+ join19(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
231484
+ join19(openclawRoot, "extensions", "clawrouter"),
231485
+ join19(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
231059
231486
  ];
231060
- const projectsDir = join18(openclawRoot, "npm", "projects");
231487
+ const projectsDir = join19(openclawRoot, "npm", "projects");
231061
231488
  try {
231062
231489
  for (const project of readdirSync2(projectsDir)) {
231063
- candidates.push(join18(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
231490
+ candidates.push(join19(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
231064
231491
  }
231065
231492
  } catch {
231066
231493
  }
231067
231494
  return [...new Set(candidates)].filter((candidate) => {
231068
231495
  try {
231069
- const metadata = JSON.parse(readFileSync4(join18(candidate, "package.json"), "utf8"));
231496
+ const metadata = JSON.parse(readFileSync4(join19(candidate, "package.json"), "utf8"));
231070
231497
  return metadata.name === "@blockrun/clawrouter";
231071
231498
  } catch {
231072
231499
  return false;
@@ -231074,7 +231501,7 @@ async function cmdSetup() {
231074
231501
  });
231075
231502
  };
231076
231503
  const initialPluginDirs = findManagedBlockRunInstalls();
231077
- const setupRollbackRoot = join18(
231504
+ const setupRollbackRoot = join19(
231078
231505
  openclawRoot,
231079
231506
  "blockrun",
231080
231507
  "setup-rollbacks",
@@ -231084,7 +231511,7 @@ async function cmdSetup() {
231084
231511
  if (initialPluginDirs.length > 0) {
231085
231512
  mkdirSync4(setupRollbackRoot, { recursive: true, mode: 448 });
231086
231513
  for (const [index2, pluginDir] of initialPluginDirs.entries()) {
231087
- const backup = join18(setupRollbackRoot, String(index2));
231514
+ const backup = join19(setupRollbackRoot, String(index2));
231088
231515
  cpSync(pluginDir, backup, { recursive: true });
231089
231516
  pluginRollbackBackups.set(pluginDir, backup);
231090
231517
  }
@@ -231129,8 +231556,8 @@ async function cmdSetup() {
231129
231556
  if (existsSync5(configPath)) {
231130
231557
  try {
231131
231558
  const config = JSON.parse(readFileSync4(configPath, "utf8"));
231132
- const legacyPackagePath = join18(
231133
- homedir15(),
231559
+ const legacyPackagePath = join19(
231560
+ homedir16(),
231134
231561
  ".openclaw",
231135
231562
  "extensions",
231136
231563
  "clawrouter",
@@ -231301,6 +231728,8 @@ function parseArgs(args) {
231301
231728
  queryModels: false,
231302
231729
  queryStats: false,
231303
231730
  queryStatsDays: 7,
231731
+ reconcile: false,
231732
+ reconcileDays: 7,
231304
231733
  queryCache: false,
231305
231734
  setup: false,
231306
231735
  share: false,
@@ -231335,6 +231764,12 @@ function parseArgs(args) {
231335
231764
  result.queryStatsDays = Math.min(parseInt(args[i + 2], 10) || 7, 30);
231336
231765
  i += 2;
231337
231766
  }
231767
+ } else if (arg === "reconcile") {
231768
+ result.reconcile = true;
231769
+ if (args[i + 1] === "--days" && args[i + 2]) {
231770
+ result.reconcileDays = Math.min(parseInt(args[i + 2], 10) || 7, 90);
231771
+ i += 2;
231772
+ }
231338
231773
  } else if (arg === "cache") {
231339
231774
  result.queryCache = true;
231340
231775
  } else if (arg === "doctor" || arg === "--doctor") {
@@ -231560,6 +231995,10 @@ async function main() {
231560
231995
  await cmdStats(queryPort, args.queryStatsDays);
231561
231996
  process.exit(0);
231562
231997
  }
231998
+ if (args.reconcile) {
231999
+ await cmdReconcile(args.reconcileDays);
232000
+ process.exit(0);
232001
+ }
231563
232002
  if (args.queryCache) {
231564
232003
  await cmdCache(queryPort);
231565
232004
  process.exit(0);