@blockrun/clawrouter 0.12.271 → 0.12.273

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();
@@ -91054,7 +91138,12 @@ async function checkExistingProxy(port) {
91054
91138
  const data = await response.json();
91055
91139
  const authMode = data.authMode === "api-key" ? "api-key" : "wallet";
91056
91140
  if (data.status === "ok" && (data.wallet || authMode === "api-key")) {
91057
- return { wallet: data.wallet ?? "", paymentChain: data.paymentChain, authMode };
91141
+ return {
91142
+ wallet: data.wallet ?? "",
91143
+ paymentChain: data.paymentChain,
91144
+ authMode,
91145
+ apiKeyLabel: typeof data.apiKey === "string" ? data.apiKey : void 0
91146
+ };
91058
91147
  }
91059
91148
  }
91060
91149
  return void 0;
@@ -91499,6 +91588,39 @@ function blockrunRequestId(response) {
91499
91588
  }
91500
91589
  return void 0;
91501
91590
  }
91591
+ function gatewayRemainingCreditUsd(response) {
91592
+ const raw = response?.headers.get("x-blockrun-credit-remaining-usd");
91593
+ if (raw === null || raw === void 0) return void 0;
91594
+ const trimmed = raw.trim();
91595
+ if (trimmed === "") return void 0;
91596
+ const value = Number(trimmed);
91597
+ if (!Number.isFinite(value) || value < 0) return void 0;
91598
+ return value;
91599
+ }
91600
+ function settledMediaCostUsd(upstream, body, x402AmountUsd, estimate) {
91601
+ const header = gatewaySettledCostUsd(upstream);
91602
+ if (header !== void 0) return header;
91603
+ const price = body?.price?.amount;
91604
+ if (typeof price === "string" || typeof price === "number") {
91605
+ const parsed = Number(price);
91606
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
91607
+ }
91608
+ if (typeof x402AmountUsd === "number" && x402AmountUsd > 0) return x402AmountUsd;
91609
+ return estimate;
91610
+ }
91611
+ function noteRemainingCredit(response) {
91612
+ const remaining = gatewayRemainingCreditUsd(response);
91613
+ if (remaining === void 0) return;
91614
+ if (remaining > LOW_CREDIT_USD) {
91615
+ lowCreditWarned = false;
91616
+ return;
91617
+ }
91618
+ if (lowCreditWarned) return;
91619
+ lowCreditWarned = true;
91620
+ console.warn(
91621
+ 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}`
91622
+ );
91623
+ }
91502
91624
  function gatewaySettledCostUsd(response) {
91503
91625
  const raw = response?.headers.get("x-blockrun-cost-usd");
91504
91626
  if (raw === null || raw === void 0) return void 0;
@@ -91508,7 +91630,7 @@ function gatewaySettledCostUsd(response) {
91508
91630
  if (!Number.isFinite(value) || value < 0) return void 0;
91509
91631
  return value;
91510
91632
  }
91511
- async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
91633
+ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd, accountPassthrough = false, authMode = "wallet") {
91512
91634
  const startTime = Date.now();
91513
91635
  const upstreamUrl = `${apiBase}${req.url}`;
91514
91636
  const isBlockrunExa = req.url?.startsWith("/v1/exa/") ?? false;
@@ -91547,16 +91669,32 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
91547
91669
  return;
91548
91670
  responseHeaders[key2] = value;
91549
91671
  });
91672
+ if (accountPassthrough || authMode === "api-key") {
91673
+ responseHeaders["cache-control"] = "no-store";
91674
+ }
91550
91675
  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));
91676
+ if (accountPassthrough) {
91677
+ if (upstream.body) {
91678
+ await pipeline(
91679
+ Readable.fromWeb(upstream.body),
91680
+ res,
91681
+ { signal: clientAbort.signal }
91682
+ );
91683
+ } else {
91684
+ res.end();
91555
91685
  }
91686
+ } else {
91687
+ if (upstream.body) {
91688
+ const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS);
91689
+ for (const chunk of chunks) {
91690
+ safeWrite(res, Buffer.from(chunk));
91691
+ }
91692
+ }
91693
+ res.end();
91556
91694
  }
91557
- res.end();
91558
91695
  const latencyMs = Date.now() - startTime;
91559
91696
  console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
91697
+ noteRemainingCredit(upstream);
91560
91698
  const settledCostUsd = gatewaySettledCostUsd(upstream);
91561
91699
  const paidAmount = settledCostUsd ?? getActualPaymentUsd();
91562
91700
  const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
@@ -91660,7 +91798,8 @@ async function startProxy(options) {
91660
91798
  const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
91661
91799
  const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
91662
91800
  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);
91801
+ const requestedApiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
91802
+ const apiBase = authMode === "api-key" ? normalizeApiKeyBase(requestedApiBase) : requestedApiBase;
91664
91803
  if (authMode === "api-key") {
91665
91804
  console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
91666
91805
  console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
@@ -91687,13 +91826,20 @@ async function startProxy(options) {
91687
91826
  );
91688
91827
  }
91689
91828
  if (authMode === "api-key") {
91829
+ const runningLabel = existingProxy.apiKeyLabel;
91830
+ const ourLabel = maskApiKey(apiKey);
91831
+ if (runningLabel !== ourLabel) {
91832
+ throw new Error(
91833
+ `Existing proxy on port ${listenPort} is billing ${runningLabel ?? "an unidentified BlockRun account"}, but this process is configured for ${ourLabel}. Reusing it would charge the other account. Stop that proxy first, or use a different port.`
91834
+ );
91835
+ }
91690
91836
  options.onReady?.(listenPort);
91691
91837
  return {
91692
91838
  port: listenPort,
91693
91839
  baseUrl: baseUrl2,
91694
91840
  walletAddress: "",
91695
91841
  authMode,
91696
- apiKeyLabel: maskApiKey(apiKey),
91842
+ apiKeyLabel: ourLabel,
91697
91843
  balanceMonitor: new ApiKeyBalanceMonitor(),
91698
91844
  // No-op: we didn't start this proxy, so we shouldn't close it
91699
91845
  close: async () => {
@@ -91789,7 +91935,7 @@ async function startProxy(options) {
91789
91935
  if (store) store.amountUsd = amountUsd;
91790
91936
  console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
91791
91937
  });
91792
- const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
91938
+ const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey, fetch, apiBase) : createPayFetchWithPreAuth(fetch, x402, void 0, {
91793
91939
  skipPreAuth: paymentChain === "solana",
91794
91940
  // Per-request cost estimate so pre-auth is only reused when the cached
91795
91941
  // payment still covers the (possibly larger) request — BlockRun prices per
@@ -92293,9 +92439,15 @@ async function startProxy(options) {
92293
92439
  }
92294
92440
  }
92295
92441
  }
92296
- const imgActualCost = paymentStore.getStore()?.amountUsd ?? imgCost;
92442
+ const imgActualCost = settledMediaCostUsd(
92443
+ upstream,
92444
+ result,
92445
+ paymentStore.getStore()?.amountUsd,
92446
+ imgCost
92447
+ );
92297
92448
  logUsage({
92298
92449
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92450
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92299
92451
  model: imgModel,
92300
92452
  tier: "IMAGE",
92301
92453
  cost: imgActualCost,
@@ -92364,12 +92516,15 @@ async function startProxy(options) {
92364
92516
  return;
92365
92517
  }
92366
92518
  try {
92367
- const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
92519
+ let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
92368
92520
  method: "POST",
92369
92521
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
92370
92522
  body: reqBody,
92371
92523
  signal: clientAbort.signal
92372
92524
  });
92525
+ if (authMode === "api-key") {
92526
+ upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
92527
+ }
92373
92528
  const text = await upstream.text();
92374
92529
  if (!upstream.ok) {
92375
92530
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
@@ -92416,9 +92571,15 @@ async function startProxy(options) {
92416
92571
  }
92417
92572
  }
92418
92573
  }
92419
- const img2imgActualCost = paymentStore.getStore()?.amountUsd ?? img2imgCost;
92574
+ const img2imgActualCost = settledMediaCostUsd(
92575
+ upstream,
92576
+ result,
92577
+ paymentStore.getStore()?.amountUsd,
92578
+ img2imgCost
92579
+ );
92420
92580
  logUsage({
92421
92581
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92582
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92422
92583
  model: img2imgModel,
92423
92584
  tier: "IMAGE",
92424
92585
  cost: img2imgActualCost,
@@ -92458,12 +92619,15 @@ async function startProxy(options) {
92458
92619
  } catch {
92459
92620
  }
92460
92621
  try {
92461
- const upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
92622
+ let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
92462
92623
  method: "POST",
92463
92624
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
92464
92625
  body: reqBody,
92465
92626
  signal: clientAbort.signal
92466
92627
  });
92628
+ if (authMode === "api-key") {
92629
+ upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
92630
+ }
92467
92631
  const text = await upstream.text();
92468
92632
  if (!upstream.ok) {
92469
92633
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
@@ -92502,9 +92666,15 @@ async function startProxy(options) {
92502
92666
  }
92503
92667
  }
92504
92668
  }
92505
- const audioActualCost = paymentStore.getStore()?.amountUsd ?? 0.15;
92669
+ const audioActualCost = settledMediaCostUsd(
92670
+ upstream,
92671
+ result,
92672
+ paymentStore.getStore()?.amountUsd,
92673
+ 0.15
92674
+ );
92506
92675
  logUsage({
92507
92676
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92677
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92508
92678
  model: audioModel,
92509
92679
  tier: "AUDIO",
92510
92680
  cost: audioActualCost,
@@ -92662,7 +92832,12 @@ async function startProxy(options) {
92662
92832
  }
92663
92833
  }
92664
92834
  }
92665
- const videoActualCost = paymentStore.getStore()?.amountUsd ?? estimateVideoCost(videoModel, videoDuration, videoHasImageInput);
92835
+ const videoActualCost = settledMediaCostUsd(
92836
+ void 0,
92837
+ submitResult,
92838
+ paymentStore.getStore()?.amountUsd,
92839
+ estimateVideoCost(videoModel, videoDuration, videoHasImageInput)
92840
+ );
92666
92841
  logUsage({
92667
92842
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92668
92843
  model: videoModel,
@@ -92696,7 +92871,9 @@ async function startProxy(options) {
92696
92871
  res,
92697
92872
  apiBase,
92698
92873
  payFetch,
92699
- () => paymentStore.getStore()?.amountUsd ?? 0
92874
+ () => paymentStore.getStore()?.amountUsd ?? 0,
92875
+ false,
92876
+ authMode
92700
92877
  );
92701
92878
  } catch (err) {
92702
92879
  const error = err instanceof Error ? err : new Error(String(err));
@@ -92719,6 +92896,33 @@ async function startProxy(options) {
92719
92896
  res.end(JSON.stringify({ error: "Not found" }));
92720
92897
  return;
92721
92898
  }
92899
+ if (authMode === "api-key" && /^\/v1\//.test(req.url ?? "") && !/^\/v1\/(?:chat\/completions|messages)(?:\?|$)/.test(req.url ?? "")) {
92900
+ try {
92901
+ await proxyPaidApiRequest(
92902
+ req,
92903
+ res,
92904
+ apiBase,
92905
+ payFetch,
92906
+ () => paymentStore.getStore()?.amountUsd ?? 0,
92907
+ true,
92908
+ authMode
92909
+ );
92910
+ } catch (err) {
92911
+ const error = err instanceof Error ? err : new Error(String(err));
92912
+ options.onError?.(error);
92913
+ if (!res.headersSent) {
92914
+ res.writeHead(502, { "Content-Type": "application/json" });
92915
+ res.end(
92916
+ JSON.stringify({
92917
+ error: { message: `Account API error: ${error.message}`, type: "account_error" }
92918
+ })
92919
+ );
92920
+ } else if (!res.writableEnded) {
92921
+ res.end();
92922
+ }
92923
+ }
92924
+ return;
92925
+ }
92722
92926
  if (!req.url.includes("/chat/completions")) {
92723
92927
  res.writeHead(404, { "Content-Type": "application/json" });
92724
92928
  res.end(
@@ -93402,9 +93606,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
93402
93606
  responseText = lines.join("\n");
93403
93607
  }
93404
93608
  console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
93405
- const imagegenActualCost = paymentStore.getStore()?.amountUsd ?? estimateImageCost(imageModel, imageSize, 1);
93609
+ const imagegenActualCost = settledMediaCostUsd(
93610
+ imageResponse,
93611
+ imageResult,
93612
+ paymentStore.getStore()?.amountUsd,
93613
+ estimateImageCost(imageModel, imageSize, 1)
93614
+ );
93406
93615
  logUsage({
93407
93616
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93617
+ ...blockrunRequestId(imageResponse) ? { requestId: blockrunRequestId(imageResponse) } : {},
93408
93618
  model: imageModel,
93409
93619
  tier: "IMAGE",
93410
93620
  cost: imagegenActualCost,
@@ -93628,9 +93838,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
93628
93838
  responseText = lines.join("\n");
93629
93839
  }
93630
93840
  console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
93631
- const img2imgActualCost2 = paymentStore.getStore()?.amountUsd ?? estimateImageCost(img2imgModel, img2imgSize, 1);
93841
+ const img2imgActualCost2 = settledMediaCostUsd(
93842
+ img2imgResponse,
93843
+ img2imgResult,
93844
+ paymentStore.getStore()?.amountUsd,
93845
+ estimateImageCost(img2imgModel, img2imgSize, 1)
93846
+ );
93632
93847
  logUsage({
93633
93848
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93849
+ ...blockrunRequestId(img2imgResponse) ? { requestId: blockrunRequestId(img2imgResponse) } : {},
93634
93850
  model: img2imgModel,
93635
93851
  tier: "IMAGE",
93636
93852
  cost: img2imgActualCost2,
@@ -94686,6 +94902,7 @@ data: [DONE]
94686
94902
  }
94687
94903
  }
94688
94904
  upstreamRequestId = blockrunRequestId(upstream);
94905
+ noteRemainingCredit(upstream);
94689
94906
  if (!upstream) {
94690
94907
  const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
94691
94908
  const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
@@ -95226,7 +95443,7 @@ data: [DONE]
95226
95443
  });
95227
95444
  }
95228
95445
  }
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;
95446
+ 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
95447
  var init_proxy = __esm({
95231
95448
  "src/proxy.ts"() {
95232
95449
  "use strict";
@@ -95488,6 +95705,8 @@ var init_proxy = __esm({
95488
95705
  "phone/numbers/release": 0,
95489
95706
  "voice/call": 0.54
95490
95707
  };
95708
+ LOW_CREDIT_USD = 1;
95709
+ lowCreditWarned = false;
95491
95710
  }
95492
95711
  });
95493
95712
 
@@ -96528,13 +96747,13 @@ ${USAGE}`
96528
96747
  ${USAGE}`);
96529
96748
  }
96530
96749
  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) {
96750
+ const usd2 = Number(raw);
96751
+ if (!USD.test(raw) || !Number.isFinite(usd2) || usd2 <= 0) {
96533
96752
  return fail(
96534
96753
  `Rejected amount "${raw}": must be a positive decimal USD value such as 0.10 or 5`
96535
96754
  );
96536
96755
  }
96537
- return { kind: "limit", window: target, usd };
96756
+ return { kind: "limit", window: target, usd: usd2 };
96538
96757
  }
96539
96758
  if (!isListAction(action)) return fail(`Unknown subcommand "${sub}"
96540
96759
  ${USAGE}`);
@@ -96679,6 +96898,194 @@ var init_policy = __esm({
96679
96898
  }
96680
96899
  });
96681
96900
 
96901
+ // src/reconcile.ts
96902
+ var reconcile_exports = {};
96903
+ __export(reconcile_exports, {
96904
+ formatReconcile: () => formatReconcile,
96905
+ joinRows: () => joinRows,
96906
+ loadGatewayRows: () => loadGatewayRows,
96907
+ loadLocalRows: () => loadLocalRows,
96908
+ reconcile: () => reconcile
96909
+ });
96910
+ import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
96911
+ import { join as join12 } from "path";
96912
+ import { homedir as homedir9 } from "os";
96913
+ async function loadLocalRows(since) {
96914
+ let files;
96915
+ try {
96916
+ files = (await readdir3(LOG_DIR3)).filter((f) => f.endsWith(".jsonl"));
96917
+ } catch {
96918
+ return { rows: [], unkeyed: 0 };
96919
+ }
96920
+ const rows = [];
96921
+ let unkeyed = 0;
96922
+ for (const file of files) {
96923
+ let text;
96924
+ try {
96925
+ text = await readFile3(join12(LOG_DIR3, file), "utf8");
96926
+ } catch {
96927
+ continue;
96928
+ }
96929
+ for (const line of text.split("\n")) {
96930
+ if (!line.trim()) continue;
96931
+ let entry;
96932
+ try {
96933
+ entry = JSON.parse(line);
96934
+ } catch {
96935
+ continue;
96936
+ }
96937
+ if (!entry.timestamp || new Date(entry.timestamp) < since) continue;
96938
+ if (!entry.requestId) {
96939
+ unkeyed++;
96940
+ continue;
96941
+ }
96942
+ rows.push({
96943
+ requestId: entry.requestId,
96944
+ model: entry.model,
96945
+ cost: typeof entry.cost === "number" ? entry.cost : 0,
96946
+ timestamp: entry.timestamp
96947
+ });
96948
+ }
96949
+ }
96950
+ return { rows, unkeyed };
96951
+ }
96952
+ async function loadGatewayRows(apiKey, from15, maxPages = 40) {
96953
+ const rows = [];
96954
+ const unavailableDays = /* @__PURE__ */ new Set();
96955
+ let cursor;
96956
+ for (let page = 0; page < maxPages; page++) {
96957
+ const result = await fetchUsagePage(apiKey, { from: from15, limit: 500, cursor });
96958
+ if (!result) return page === 0 ? void 0 : { rows, unavailableDays: [...unavailableDays] };
96959
+ rows.push(...result.rows);
96960
+ result.unavailableDays.forEach((d) => unavailableDays.add(d));
96961
+ if (!result.nextCursor) break;
96962
+ cursor = result.nextCursor;
96963
+ }
96964
+ return { rows, unavailableDays: [...unavailableDays] };
96965
+ }
96966
+ function joinRows(localRows, gatewayRows, unkeyed = 0, unavailableDays = []) {
96967
+ const localById = new Map(localRows.map((r2) => [r2.requestId, r2]));
96968
+ const seen = /* @__PURE__ */ new Set();
96969
+ const matched = [];
96970
+ const chargedNotRecorded = [];
96971
+ const pending = [];
96972
+ let gatewayTotalUsd = 0;
96973
+ for (const remote of gatewayRows) {
96974
+ if (remote.costState === "pending") {
96975
+ pending.push(remote);
96976
+ continue;
96977
+ }
96978
+ gatewayTotalUsd += remote.costUsd ?? 0;
96979
+ const local = localById.get(remote.requestId);
96980
+ if (!local) {
96981
+ chargedNotRecorded.push(remote);
96982
+ continue;
96983
+ }
96984
+ seen.add(remote.requestId);
96985
+ matched.push({ local, remote, deltaUsd: local.cost - (remote.costUsd ?? 0) });
96986
+ }
96987
+ const recordedNotCharged = localRows.filter((r2) => !seen.has(r2.requestId));
96988
+ const localTotalUsd = localRows.reduce((sum, r2) => sum + r2.cost, 0);
96989
+ return {
96990
+ matched,
96991
+ chargedNotRecorded,
96992
+ recordedNotCharged,
96993
+ pending,
96994
+ localTotalUsd,
96995
+ gatewayTotalUsd,
96996
+ unavailableDays,
96997
+ unkeyedLocalCount: unkeyed
96998
+ };
96999
+ }
97000
+ async function reconcile(apiKey, days) {
97001
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
97002
+ const [{ rows: localRows, unkeyed }, gateway] = await Promise.all([
97003
+ loadLocalRows(since),
97004
+ loadGatewayRows(apiKey, since.toISOString())
97005
+ ]);
97006
+ if (!gateway) throw new Error("Could not read the BlockRun usage ledger (GET /v1/usage).");
97007
+ return joinRows(localRows, gateway.rows, unkeyed, gateway.unavailableDays);
97008
+ }
97009
+ function formatReconcile(r2, days) {
97010
+ const out = [];
97011
+ const mismatched = r2.matched.filter((m) => Math.abs(m.deltaUsd) > CENT_EPSILON);
97012
+ out.push(`
97013
+ BlockRun reconciliation \u2014 last ${days} day${days === 1 ? "" : "s"}
97014
+ `);
97015
+ out.push(
97016
+ ` Gateway charged: ${usd(r2.gatewayTotalUsd)} (${r2.matched.length + r2.chargedNotRecorded.length} settled calls)`
97017
+ );
97018
+ out.push(` Journal recorded: ${usd(r2.localTotalUsd)}`);
97019
+ out.push(
97020
+ ` Matched: ${r2.matched.length}, of which ${mismatched.length} disagree on amount`
97021
+ );
97022
+ if (mismatched.length > 0) {
97023
+ out.push(`
97024
+ Amount mismatches (journal vs gateway):`);
97025
+ for (const m of mismatched.slice(0, 10)) {
97026
+ out.push(
97027
+ ` ${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)}`
97028
+ );
97029
+ }
97030
+ if (mismatched.length > 10) out.push(` \u2026and ${mismatched.length - 10} more`);
97031
+ }
97032
+ if (r2.chargedNotRecorded.length > 0) {
97033
+ const total = r2.chargedNotRecorded.reduce((s3, x) => s3 + (x.costUsd ?? 0), 0);
97034
+ out.push(
97035
+ `
97036
+ \u26A0 Charged but NOT in this machine's journal: ${r2.chargedNotRecorded.length} call(s), ${usd(total)}`
97037
+ );
97038
+ out.push(
97039
+ ` Expected if the same key is used elsewhere \u2014 another machine, another BlockRun product,`
97040
+ );
97041
+ out.push(` or a call made before this proxy started recording request ids.`);
97042
+ for (const x of r2.chargedNotRecorded.slice(0, 10)) {
97043
+ out.push(
97044
+ ` ${x.requestId.slice(0, 8)} ${x.timestamp.slice(0, 19)} ${x.endpoint.padEnd(26)} ${usd(x.costUsd ?? 0)}`
97045
+ );
97046
+ }
97047
+ if (r2.chargedNotRecorded.length > 10) {
97048
+ out.push(` \u2026and ${r2.chargedNotRecorded.length - 10} more`);
97049
+ }
97050
+ }
97051
+ if (r2.recordedNotCharged.length > 0) {
97052
+ out.push(
97053
+ `
97054
+ Recorded locally with no settled ledger row: ${r2.recordedNotCharged.length} call(s)`
97055
+ );
97056
+ out.push(` Usually benign \u2014 free models, cache hits, and calls still pending pricing.`);
97057
+ }
97058
+ if (r2.pending.length > 0) {
97059
+ out.push(`
97060
+ Pending pricing (excluded from totals): ${r2.pending.length} call(s)`);
97061
+ out.push(` Usage recorded, charge not final. These can still be repriced.`);
97062
+ }
97063
+ if (r2.unkeyedLocalCount > 0) {
97064
+ out.push(`
97065
+ Journal entries with no request id: ${r2.unkeyedLocalCount} (not reconcilable)`);
97066
+ }
97067
+ if (r2.unavailableDays.length > 0) {
97068
+ out.push(`
97069
+ \u26A0 Gateway could not list these days: ${r2.unavailableDays.join(", ")}`);
97070
+ out.push(` Totals above are incomplete for that period.`);
97071
+ }
97072
+ out.push(`
97073
+ Note: the top-up fee (5.5% + $0.30) is charged at purchase, not per call,`);
97074
+ out.push(` so the sum above is less than your card was charged by exactly those fees.
97075
+ `);
97076
+ return out.join("\n");
97077
+ }
97078
+ var LOG_DIR3, CENT_EPSILON, usd;
97079
+ var init_reconcile = __esm({
97080
+ "src/reconcile.ts"() {
97081
+ "use strict";
97082
+ init_api_key();
97083
+ LOG_DIR3 = join12(homedir9(), ".openclaw", "blockrun", "logs");
97084
+ CENT_EPSILON = 1e-9;
97085
+ usd = (n) => Math.abs(n) < 0.01 ? `$${n.toFixed(6)}` : `$${n.toFixed(2)}`;
97086
+ }
97087
+ });
97088
+
96682
97089
  // src/provider.ts
96683
97090
  function setActiveProxy(proxy) {
96684
97091
  activeProxy = proxy;
@@ -112521,7 +112928,7 @@ var init_readBlob = __esm({
112521
112928
 
112522
112929
  // node_modules/axios/lib/helpers/formDataToStream.js
112523
112930
  import util2 from "util";
112524
- import { Readable } from "stream";
112931
+ import { Readable as Readable2 } from "stream";
112525
112932
  var BOUNDARY_ALPHABET, textEncoder, CRLF, CRLF_BYTES, CRLF_BYTES_COUNT, FormDataPart, formDataToStream, formDataToStream_default;
112526
112933
  var init_formDataToStream = __esm({
112527
112934
  "node_modules/axios/lib/helpers/formDataToStream.js"() {
@@ -112601,7 +113008,7 @@ var init_formDataToStream = __esm({
112601
113008
  computedHeaders["Content-Length"] = contentLength;
112602
113009
  }
112603
113010
  headersHandler && headersHandler(computedHeaders);
112604
- return Readable.from(
113011
+ return Readable2.from(
112605
113012
  (async function* () {
112606
113013
  for (const part of parts) {
112607
113014
  yield boundaryBytes;
@@ -117665,8 +118072,8 @@ var init_constants3 = __esm({
117665
118072
 
117666
118073
  // src/polymarket/wallet-adapter.ts
117667
118074
  import fs2 from "fs";
117668
- import { homedir as homedir9 } from "os";
117669
- import { join as join12 } from "path";
118075
+ import { homedir as homedir10 } from "os";
118076
+ import { join as join13 } from "path";
117670
118077
  function getOrCreateWalletKey() {
117671
118078
  const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
117672
118079
  if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
@@ -117714,7 +118121,7 @@ var init_wallet_adapter = __esm({
117714
118121
  init_esm();
117715
118122
  init_chains();
117716
118123
  init_constants3();
117717
- WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
118124
+ WALLET_FILE2 = join13(homedir10(), ".openclaw", "blockrun", "wallet.key");
117718
118125
  BASE_RPC_URLS = [
117719
118126
  "https://mainnet.base.org",
117720
118127
  "https://base.llamarpc.com",
@@ -118118,8 +118525,8 @@ var init_client5 = __esm({
118118
118525
  function resolveSpendControl(deps) {
118119
118526
  return deps?.spendControl ?? getSharedSpendControl();
118120
118527
  }
118121
- function usdToMicros(usd) {
118122
- return String(Math.ceil(usd * 1e6));
118528
+ function usdToMicros(usd2) {
118529
+ return String(Math.ceil(usd2 * 1e6));
118123
118530
  }
118124
118531
  async function signUnderSpendPolicy(deps, quote, action, sign4) {
118125
118532
  const control = resolveSpendControl(deps);
@@ -118148,13 +118555,13 @@ function getSessionLedger() {
118148
118555
  perAgent: Object.fromEntries(ledger.perAgent)
118149
118556
  };
118150
118557
  }
118151
- function reserveBet(usd, agentId) {
118152
- ledger.totalUsd += usd;
118153
- if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) + usd);
118558
+ function reserveBet(usd2, agentId) {
118559
+ ledger.totalUsd += usd2;
118560
+ if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) + usd2);
118154
118561
  }
118155
- function releaseBet(usd, agentId) {
118156
- ledger.totalUsd -= usd;
118157
- if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) - usd);
118562
+ function releaseBet(usd2, agentId) {
118563
+ ledger.totalUsd -= usd2;
118564
+ if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) - usd2);
118158
118565
  }
118159
118566
  function commitBet() {
118160
118567
  ledger.count += 1;
@@ -228116,9 +228523,9 @@ import {
228116
228523
  unlinkSync
228117
228524
  } from "fs";
228118
228525
  import { readFile as readFileAsync } from "fs/promises";
228119
- import { homedir as homedir14 } from "os";
228526
+ import { homedir as homedir15 } from "os";
228120
228527
  import { randomUUID } from "crypto";
228121
- import { join as join17 } from "path";
228528
+ import { join as join18 } from "path";
228122
228529
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
228123
228530
  const start = Date.now();
228124
228531
  while (Date.now() - start < timeoutMs) {
@@ -228160,8 +228567,8 @@ function isBlockrunWebSearchDisabled(config) {
228160
228567
  return cfg.tools?.web?.search?.enabled === false;
228161
228568
  }
228162
228569
  function injectModelsConfig(logger48, options = {}) {
228163
- const configDir = join17(homedir14(), ".openclaw");
228164
- const configPath = join17(configDir, "openclaw.json");
228570
+ const configDir = join18(homedir15(), ".openclaw");
228571
+ const configPath = join18(configDir, "openclaw.json");
228165
228572
  let config = {};
228166
228573
  let needsWrite = false;
228167
228574
  if (!existsSync4(configDir)) {
@@ -228382,7 +228789,7 @@ function injectModelsConfig(logger48, options = {}) {
228382
228789
  }
228383
228790
  function syncAgentModelCache(logger48, options = {}) {
228384
228791
  if (!isGatewayMode() && !options.forceWrite) return;
228385
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
228792
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
228386
228793
  if (!existsSync4(agentsDir)) return;
228387
228794
  let agentDirs;
228388
228795
  try {
@@ -228392,7 +228799,7 @@ function syncAgentModelCache(logger48, options = {}) {
228392
228799
  }
228393
228800
  const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
228394
228801
  for (const agent of agentDirs) {
228395
- const cachePath = join17(agentsDir, agent, "agent", "models.json");
228802
+ const cachePath = join18(agentsDir, agent, "agent", "models.json");
228396
228803
  if (!existsSync4(cachePath)) continue;
228397
228804
  try {
228398
228805
  const raw = readTextFileSync(cachePath).trim();
@@ -228421,7 +228828,7 @@ function syncAgentModelCache(logger48, options = {}) {
228421
228828
  }
228422
228829
  }
228423
228830
  function injectAuthProfile(logger48) {
228424
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
228831
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
228425
228832
  if (!existsSync4(agentsDir)) {
228426
228833
  try {
228427
228834
  mkdirSync3(agentsDir, { recursive: true });
@@ -228438,9 +228845,9 @@ function injectAuthProfile(logger48) {
228438
228845
  agents = ["main", ...agents];
228439
228846
  }
228440
228847
  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");
228848
+ const authDir = join18(agentsDir, agentId, "agent");
228849
+ const authPath = join18(authDir, "auth-profiles.json");
228850
+ const sqlitePath = join18(authDir, "openclaw-agent.sqlite");
228444
228851
  if (existsSync4(sqlitePath)) {
228445
228852
  removeInjectedAuthPlaceholder(authPath, logger48, agentId);
228446
228853
  continue;
@@ -228588,12 +228995,12 @@ async function startProxyInBackground(api, startupGeneration) {
228588
228995
  proc.__clawrouterStartupPhase = "starting";
228589
228996
  }
228590
228997
  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`
228998
+ if (typeof pluginApiKey === "string" && pluginApiKey.trim() && !isValidApiKey(pluginApiKey)) {
228999
+ throw new Error(
229000
+ `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
229001
  );
228596
229002
  }
229003
+ const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
228597
229004
  const apiKey = resolvedApiKey?.key;
228598
229005
  const configKey = api.pluginConfig?.walletKey;
228599
229006
  let wallet;
@@ -228943,7 +229350,7 @@ function buildImageGenerationProvider() {
228943
229350
  (result.data ?? []).map(async (img) => {
228944
229351
  const filename = img.url?.split("/images/").pop();
228945
229352
  if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
228946
- const filePath = join17(IMAGE_DIR2, filename);
229353
+ const filePath = join18(IMAGE_DIR2, filename);
228947
229354
  const buffer2 = await readFileAsync(filePath);
228948
229355
  const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
228949
229356
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
@@ -228995,7 +229402,7 @@ function buildMusicGenerationProvider() {
228995
229402
  (result.data ?? []).map(async (track) => {
228996
229403
  const filename = track.url?.split("/audio/").pop();
228997
229404
  if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
228998
- const filePath = join17(AUDIO_DIR2, filename);
229405
+ const filePath = join18(AUDIO_DIR2, filename);
228999
229406
  const buffer2 = await readFileAsync(filePath);
229000
229407
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
229001
229408
  const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
@@ -229071,7 +229478,7 @@ function buildVideoGenerationProvider() {
229071
229478
  (result.data ?? []).map(async (clip) => {
229072
229479
  const filename = clip.url?.split("/videos/").pop();
229073
229480
  if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
229074
- const filePath = join17(VIDEO_DIR2, filename);
229481
+ const filePath = join18(VIDEO_DIR2, filename);
229075
229482
  const buffer2 = await readFileAsync(filePath);
229076
229483
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
229077
229484
  const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
@@ -229436,9 +229843,9 @@ var init_index = __esm({
229436
229843
  init_partners();
229437
229844
  activeProxyHandle = null;
229438
229845
  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");
229846
+ IMAGE_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "images");
229847
+ AUDIO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "audio");
229848
+ VIDEO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "videos");
229442
229849
  plugin = {
229443
229850
  // NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
229444
229851
  // 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
@@ -229780,7 +230187,18 @@ ${errText}`
229780
230187
  if (!isGatewayMode()) {
229781
230188
  if (shouldLogRegistration) {
229782
230189
  void (async () => {
229783
- const keyResolution = await resolveApiKey().catch(() => void 0);
230190
+ let keyResolution;
230191
+ try {
230192
+ keyResolution = await resolveApiKey();
230193
+ } catch (error) {
230194
+ api.logger.error(
230195
+ `BlockRun API key is configured but unusable: ${error instanceof Error ? error.message : String(error)}`
230196
+ );
230197
+ api.logger.error(
230198
+ `Not falling back to a wallet \u2014 fix the key, or run "clawrouter logout" to pay from the wallet deliberately.`
230199
+ );
230200
+ return;
230201
+ }
229784
230202
  if (keyResolution) {
229785
230203
  api.logger.info(
229786
230204
  `Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`
@@ -229863,7 +230281,7 @@ ${errText}`
229863
230281
  }
229864
230282
  resetProxyStartupState();
229865
230283
  try {
229866
- const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
230284
+ const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
229867
230285
  if (existsSync4(configPath)) {
229868
230286
  const config = JSON.parse(readTextFileSync(configPath));
229869
230287
  if (config.models?.providers?.blockrun) {
@@ -229905,11 +230323,11 @@ ${errText}`
229905
230323
  api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
229906
230324
  }
229907
230325
  try {
229908
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
230326
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
229909
230327
  if (existsSync4(agentsDir)) {
229910
230328
  for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
229911
230329
  if (!entry.isDirectory()) continue;
229912
- const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
230330
+ const authPath = join18(agentsDir, entry.name, "agent", "auth-profiles.json");
229913
230331
  if (!existsSync4(authPath)) continue;
229914
230332
  try {
229915
230333
  const store = JSON.parse(readTextFileSync(authPath));
@@ -230725,6 +231143,27 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
230725
231143
  process.exit(1);
230726
231144
  }
230727
231145
  }
231146
+ async function cmdReconcile(days) {
231147
+ const resolved = await resolveApiKey().catch((error) => {
231148
+ console.error(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
231149
+ process.exit(1);
231150
+ });
231151
+ if (!resolved) {
231152
+ console.error(`\u2717 Reconciliation needs a BlockRun API key \u2014 it compares against your account`);
231153
+ console.error(` ledger. Wallet spend settles on-chain instead; see "clawrouter stats".`);
231154
+ console.error(` Sign in at ${PORTAL_URL} and run: clawrouter login brk_live_...`);
231155
+ process.exit(1);
231156
+ }
231157
+ try {
231158
+ const { reconcile: reconcile2, formatReconcile: formatReconcile2 } = await Promise.resolve().then(() => (init_reconcile(), reconcile_exports));
231159
+ const result = await reconcile2(resolved.key, days);
231160
+ console.log(formatReconcile2(result, days));
231161
+ process.exitCode = result.chargedNotRecorded.length > 0 ? 2 : 0;
231162
+ } catch (error) {
231163
+ console.error(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
231164
+ process.exit(1);
231165
+ }
231166
+ }
230728
231167
  async function cmdStatus(port) {
230729
231168
  try {
230730
231169
  const data = await queryProxy("/health?full=true", port);
@@ -231002,8 +231441,8 @@ async function cmdSetup() {
231002
231441
  unlinkSync: unlinkSync2,
231003
231442
  writeFileSync: writeFileSync4
231004
231443
  } = await import("fs");
231005
- const { dirname: dirname4, join: join18 } = await import("path");
231006
- const { homedir: homedir15 } = await import("os");
231444
+ const { dirname: dirname4, join: join19 } = await import("path");
231445
+ const { homedir: homedir16 } = await import("os");
231007
231446
  const { injectModelsConfig: injectModelsConfig2, injectAuthProfile: injectAuthProfile2, syncAgentModelCache: syncAgentModelCache2, VISIBLE_OPENCLAW_MODELS: VISIBLE_OPENCLAW_MODELS2 } = await Promise.resolve().then(() => (init_index(), index_exports));
231008
231447
  const { BLOCKRUN_PLUGIN_ID: BLOCKRUN_PLUGIN_ID2, prepareBlockRunPluginConfig: prepareBlockRunPluginConfig2 } = await Promise.resolve().then(() => (init_openclaw_plugin_config(), openclaw_plugin_config_exports));
231009
231448
  console.log("\u{1F99E} ClawRouter setup\n");
@@ -231018,19 +231457,19 @@ async function cmdSetup() {
231018
231457
  } catch {
231019
231458
  }
231020
231459
  if (process.env.npm_config_prefix) {
231021
- candidates.push(join18(process.env.npm_config_prefix, "bin", "openclaw"));
231460
+ candidates.push(join19(process.env.npm_config_prefix, "bin", "openclaw"));
231022
231461
  }
231023
231462
  try {
231024
231463
  const npmRoot = execSync("npm root -g", {
231025
231464
  encoding: "utf8",
231026
231465
  stdio: ["ignore", "pipe", "ignore"]
231027
231466
  }).trim();
231028
- if (npmRoot) candidates.push(join18(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
231467
+ if (npmRoot) candidates.push(join19(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
231029
231468
  } catch {
231030
231469
  }
231031
231470
  candidates.push(
231032
- join18(homedir15(), ".npm-global", "bin", "openclaw"),
231033
- join18(homedir15(), ".local", "bin", "openclaw"),
231471
+ join19(homedir16(), ".npm-global", "bin", "openclaw"),
231472
+ join19(homedir16(), ".local", "bin", "openclaw"),
231034
231473
  "/usr/local/bin/openclaw"
231035
231474
  );
231036
231475
  return candidates.find((candidate) => existsSync5(candidate)) ?? "";
@@ -231042,7 +231481,7 @@ async function cmdSetup() {
231042
231481
  process.exit(1);
231043
231482
  }
231044
231483
  console.log(` \u2713 Found openclaw at ${openclawPath}`);
231045
- const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
231484
+ const configPath = join19(homedir16(), ".openclaw", "openclaw.json");
231046
231485
  const configInitiallyExisted = existsSync5(configPath);
231047
231486
  const configRollbackBackup = configInitiallyExisted ? `${configPath}.before-${BLOCKRUN_PLUGIN_ID2}.${Date.now()}` : void 0;
231048
231487
  if (configRollbackBackup) copyFileSync2(configPath, configRollbackBackup);
@@ -231050,23 +231489,23 @@ async function cmdSetup() {
231050
231489
  return configRollbackBackup;
231051
231490
  };
231052
231491
  let installAttempted = false;
231053
- const openclawRoot = join18(homedir15(), ".openclaw");
231492
+ const openclawRoot = join19(homedir16(), ".openclaw");
231054
231493
  const findManagedBlockRunInstalls = () => {
231055
231494
  const candidates = [
231056
- join18(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
231057
- join18(openclawRoot, "extensions", "clawrouter"),
231058
- join18(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
231495
+ join19(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
231496
+ join19(openclawRoot, "extensions", "clawrouter"),
231497
+ join19(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
231059
231498
  ];
231060
- const projectsDir = join18(openclawRoot, "npm", "projects");
231499
+ const projectsDir = join19(openclawRoot, "npm", "projects");
231061
231500
  try {
231062
231501
  for (const project of readdirSync2(projectsDir)) {
231063
- candidates.push(join18(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
231502
+ candidates.push(join19(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
231064
231503
  }
231065
231504
  } catch {
231066
231505
  }
231067
231506
  return [...new Set(candidates)].filter((candidate) => {
231068
231507
  try {
231069
- const metadata = JSON.parse(readFileSync4(join18(candidate, "package.json"), "utf8"));
231508
+ const metadata = JSON.parse(readFileSync4(join19(candidate, "package.json"), "utf8"));
231070
231509
  return metadata.name === "@blockrun/clawrouter";
231071
231510
  } catch {
231072
231511
  return false;
@@ -231074,7 +231513,7 @@ async function cmdSetup() {
231074
231513
  });
231075
231514
  };
231076
231515
  const initialPluginDirs = findManagedBlockRunInstalls();
231077
- const setupRollbackRoot = join18(
231516
+ const setupRollbackRoot = join19(
231078
231517
  openclawRoot,
231079
231518
  "blockrun",
231080
231519
  "setup-rollbacks",
@@ -231084,7 +231523,7 @@ async function cmdSetup() {
231084
231523
  if (initialPluginDirs.length > 0) {
231085
231524
  mkdirSync4(setupRollbackRoot, { recursive: true, mode: 448 });
231086
231525
  for (const [index2, pluginDir] of initialPluginDirs.entries()) {
231087
- const backup = join18(setupRollbackRoot, String(index2));
231526
+ const backup = join19(setupRollbackRoot, String(index2));
231088
231527
  cpSync(pluginDir, backup, { recursive: true });
231089
231528
  pluginRollbackBackups.set(pluginDir, backup);
231090
231529
  }
@@ -231129,8 +231568,8 @@ async function cmdSetup() {
231129
231568
  if (existsSync5(configPath)) {
231130
231569
  try {
231131
231570
  const config = JSON.parse(readFileSync4(configPath, "utf8"));
231132
- const legacyPackagePath = join18(
231133
- homedir15(),
231571
+ const legacyPackagePath = join19(
231572
+ homedir16(),
231134
231573
  ".openclaw",
231135
231574
  "extensions",
231136
231575
  "clawrouter",
@@ -231301,6 +231740,8 @@ function parseArgs(args) {
231301
231740
  queryModels: false,
231302
231741
  queryStats: false,
231303
231742
  queryStatsDays: 7,
231743
+ reconcile: false,
231744
+ reconcileDays: 7,
231304
231745
  queryCache: false,
231305
231746
  setup: false,
231306
231747
  share: false,
@@ -231335,6 +231776,12 @@ function parseArgs(args) {
231335
231776
  result.queryStatsDays = Math.min(parseInt(args[i + 2], 10) || 7, 30);
231336
231777
  i += 2;
231337
231778
  }
231779
+ } else if (arg === "reconcile") {
231780
+ result.reconcile = true;
231781
+ if (args[i + 1] === "--days" && args[i + 2]) {
231782
+ result.reconcileDays = Math.min(parseInt(args[i + 2], 10) || 7, 90);
231783
+ i += 2;
231784
+ }
231338
231785
  } else if (arg === "cache") {
231339
231786
  result.queryCache = true;
231340
231787
  } else if (arg === "doctor" || arg === "--doctor") {
@@ -231560,6 +232007,10 @@ async function main() {
231560
232007
  await cmdStats(queryPort, args.queryStatsDays);
231561
232008
  process.exit(0);
231562
232009
  }
232010
+ if (args.reconcile) {
232011
+ await cmdReconcile(args.reconcileDays);
232012
+ process.exit(0);
232013
+ }
231563
232014
  if (args.queryCache) {
231564
232015
  await cmdCache(queryPort);
231565
232016
  process.exit(0);