@blockrun/clawrouter 0.12.267 → 0.12.268

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/index.js CHANGED
@@ -40559,7 +40559,7 @@ var init_errors5 = __esm({
40559
40559
  });
40560
40560
 
40561
40561
  // src/balance.ts
40562
- var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor;
40562
+ var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor, ApiKeyBalanceMonitor;
40563
40563
  var init_balance = __esm({
40564
40564
  "src/balance.ts"() {
40565
40565
  "use strict";
@@ -40688,6 +40688,38 @@ var init_balance = __esm({
40688
40688
  };
40689
40689
  }
40690
40690
  };
40691
+ ApiKeyBalanceMonitor = class _ApiKeyBalanceMonitor {
40692
+ /** Large enough that every `balance >= estimatedCost` comparison passes. */
40693
+ static UNMETERED = BigInt(Number.MAX_SAFE_INTEGER);
40694
+ async checkBalance() {
40695
+ return {
40696
+ balance: _ApiKeyBalanceMonitor.UNMETERED,
40697
+ // Never a dollar figure: no caller may print this as if it were one.
40698
+ balanceUSD: "account credit",
40699
+ isLow: false,
40700
+ isEmpty: false,
40701
+ walletAddress: ""
40702
+ };
40703
+ }
40704
+ async checkSufficient() {
40705
+ return { sufficient: true, info: await this.checkBalance() };
40706
+ }
40707
+ /** No local cache to adjust — the server keeps the books. */
40708
+ deductEstimated() {
40709
+ }
40710
+ invalidate() {
40711
+ }
40712
+ async refresh() {
40713
+ return this.checkBalance();
40714
+ }
40715
+ formatUSDC(amountMicros) {
40716
+ return `$${(Number(amountMicros) / 1e6).toFixed(2)}`;
40717
+ }
40718
+ /** No wallet in this mode; callers render the account instead. */
40719
+ getWalletAddress() {
40720
+ return "";
40721
+ }
40722
+ };
40691
40723
  }
40692
40724
  });
40693
40725
 
@@ -59787,6 +59819,99 @@ var init_auth = __esm({
59787
59819
  }
59788
59820
  });
59789
59821
 
59822
+ // src/api-key.ts
59823
+ import { writeFile as writeFile2, mkdir as mkdir3, rm } from "fs/promises";
59824
+ import { join as join8 } from "path";
59825
+ import { homedir as homedir5 } from "os";
59826
+ function isValidApiKey(value) {
59827
+ if (typeof value !== "string") return false;
59828
+ return /^brk_[A-Za-z0-9_-]{8,}$/.test(value.trim());
59829
+ }
59830
+ function maskApiKey(key2) {
59831
+ const trimmed = key2.trim();
59832
+ if (trimmed.length <= 18) return `${trimmed.slice(0, 8)}\u2026`;
59833
+ return `${trimmed.slice(0, 14)}\u2026${trimmed.slice(-4)}`;
59834
+ }
59835
+ async function readOptional2(path5) {
59836
+ try {
59837
+ return (await readTextFile(path5)).trim() || void 0;
59838
+ } catch {
59839
+ return void 0;
59840
+ }
59841
+ }
59842
+ async function resolveApiKey() {
59843
+ const envKey = process["env"].BLOCKRUN_API_KEY?.trim();
59844
+ if (envKey) {
59845
+ if (isValidApiKey(envKey)) return { key: envKey, source: "env" };
59846
+ console.warn(
59847
+ `[ClawRouter] \u26A0 BLOCKRUN_API_KEY is set but does not look like a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
59848
+ );
59849
+ }
59850
+ for (const [path5, source] of [
59851
+ [CORE_API_KEY_FILE, "core"],
59852
+ [API_KEY_FILE, "saved"]
59853
+ ]) {
59854
+ const stored = await readOptional2(path5);
59855
+ if (stored === void 0) continue;
59856
+ if (isValidApiKey(stored)) return { key: stored, source };
59857
+ console.warn(
59858
+ `[ClawRouter] \u26A0 ${path5} does not contain a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
59859
+ );
59860
+ }
59861
+ return void 0;
59862
+ }
59863
+ function createApiKeyFetch(apiKey, baseFetch = fetch) {
59864
+ return async (input, init2) => {
59865
+ const headers = new Headers(init2?.headers);
59866
+ headers.set("authorization", `Bearer ${apiKey}`);
59867
+ headers.delete("x-payment");
59868
+ headers.delete("x-api-key");
59869
+ const response = await baseFetch(input, { ...init2, headers });
59870
+ return response.ok ? response : explainApiKeyFailure(response);
59871
+ };
59872
+ }
59873
+ async function explainApiKeyFailure(response) {
59874
+ const hint = HINTS[response.status];
59875
+ if (!hint) return response;
59876
+ if (!(response.headers.get("content-type") ?? "").includes("json")) return response;
59877
+ let parsed;
59878
+ try {
59879
+ parsed = await response.clone().json();
59880
+ } catch {
59881
+ return response;
59882
+ }
59883
+ if (!parsed?.error || typeof parsed.error.message !== "string") return response;
59884
+ if (response.status === 404 && !/unsupported endpoint/i.test(parsed.error.message)) {
59885
+ return response;
59886
+ }
59887
+ parsed.error.message = `${parsed.error.message} ${hint}`;
59888
+ const headers = new Headers(response.headers);
59889
+ headers.delete("content-length");
59890
+ return new Response(JSON.stringify(parsed), {
59891
+ status: response.status,
59892
+ statusText: response.statusText,
59893
+ headers
59894
+ });
59895
+ }
59896
+ var API_KEY_FILE, CORE_API_KEY_FILE, PORTAL_URL, PORTAL_KEYS_URL, PORTAL_CREDITS_URL, BLOCKRUN_API_KEY_API, HINTS;
59897
+ var init_api_key = __esm({
59898
+ "src/api-key.ts"() {
59899
+ "use strict";
59900
+ init_fs_read();
59901
+ API_KEY_FILE = join8(homedir5(), ".openclaw", "blockrun", "api-key");
59902
+ CORE_API_KEY_FILE = join8(homedir5(), ".blockrun", ".api-key");
59903
+ PORTAL_URL = "https://user.blockrun.ai";
59904
+ PORTAL_KEYS_URL = `${PORTAL_URL}/dashboard/keys`;
59905
+ PORTAL_CREDITS_URL = `${PORTAL_URL}/dashboard/credits`;
59906
+ BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "") || "https://api.blockrun.ai";
59907
+ HINTS = {
59908
+ 401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
59909
+ 402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
59910
+ 404: `api.blockrun.ai does not serve this endpoint yet \u2014 it currently carries chat and text completions. For media and partner APIs, run ClawRouter in wallet mode (unset BLOCKRUN_API_KEY and run "clawrouter logout").`
59911
+ };
59912
+ }
59913
+ });
59914
+
59790
59915
  // src/compression/types.ts
59791
59916
  var DEFAULT_COMPRESSION_CONFIG;
59792
59917
  var init_types2 = __esm({
@@ -60876,8 +61001,8 @@ var init_updater = __esm({
60876
61001
 
60877
61002
  // src/exclude-models.ts
60878
61003
  import { readFileSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync } from "fs";
60879
- import { join as join8, dirname as dirname2 } from "path";
60880
- import { homedir as homedir5 } from "os";
61004
+ import { join as join9, dirname as dirname2 } from "path";
61005
+ import { homedir as homedir6 } from "os";
60881
61006
  function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
60882
61007
  try {
60883
61008
  const mtimeMs = statSync(filePath).mtimeMs;
@@ -60926,7 +61051,7 @@ var init_exclude_models = __esm({
60926
61051
  "src/exclude-models.ts"() {
60927
61052
  "use strict";
60928
61053
  init_models();
60929
- DEFAULT_FILE_PATH = join8(homedir5(), ".openclaw", "blockrun", "exclude-models.json");
61054
+ DEFAULT_FILE_PATH = join9(homedir6(), ".openclaw", "blockrun", "exclude-models.json");
60930
61055
  loadCache = /* @__PURE__ */ new Map();
60931
61056
  }
60932
61057
  });
@@ -75935,7 +76060,7 @@ var require_snapshot_utils = __commonJS({
75935
76060
  var require_snapshot_recorder = __commonJS({
75936
76061
  "node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
75937
76062
  "use strict";
75938
- var { writeFile: writeFile3, readFile: readFile3, mkdir: mkdir5 } = __require("fs/promises");
76063
+ var { writeFile: writeFile4, readFile: readFile3, mkdir: mkdir6 } = __require("fs/promises");
75939
76064
  var { dirname: dirname4, resolve } = __require("path");
75940
76065
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
75941
76066
  var { InvalidArgumentError, UndiciError } = require_errors();
@@ -76182,12 +76307,12 @@ var require_snapshot_recorder = __commonJS({
76182
76307
  throw new InvalidArgumentError("Snapshot path is required");
76183
76308
  }
76184
76309
  const resolvedPath = resolve(path5);
76185
- await mkdir5(dirname4(resolvedPath), { recursive: true });
76310
+ await mkdir6(dirname4(resolvedPath), { recursive: true });
76186
76311
  const data = Array.from(this.#snapshots.entries()).map(([hash5, snapshot]) => ({
76187
76312
  hash: hash5,
76188
76313
  snapshot
76189
76314
  }));
76190
- await writeFile3(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
76315
+ await writeFile4(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
76191
76316
  }
76192
76317
  /**
76193
76318
  * Clears all recorded snapshots
@@ -88516,9 +88641,9 @@ var init_textual_tool_calls = __esm({
88516
88641
  });
88517
88642
 
88518
88643
  // src/response-store.ts
88519
- import { appendFile as appendFile2, mkdir as mkdir3, readFile, readdir as readdir2 } from "fs/promises";
88520
- import { homedir as homedir6 } from "os";
88521
- import { join as join9 } from "path";
88644
+ import { appendFile as appendFile2, mkdir as mkdir4, readFile, readdir as readdir2 } from "fs/promises";
88645
+ import { homedir as homedir7 } from "os";
88646
+ import { join as join10 } from "path";
88522
88647
  import { randomBytes as randomBytes7 } from "crypto";
88523
88648
  function isEnabled() {
88524
88649
  const v = process.env.BLOCKRUN_RESPONSE_STORE;
@@ -88526,12 +88651,12 @@ function isEnabled() {
88526
88651
  }
88527
88652
  async function ensureDir2() {
88528
88653
  if (dirReady2) return;
88529
- await mkdir3(STORE_DIR, { recursive: true });
88654
+ await mkdir4(STORE_DIR, { recursive: true });
88530
88655
  dirReady2 = true;
88531
88656
  }
88532
88657
  function dailyFile(date = /* @__PURE__ */ new Date()) {
88533
88658
  const iso = date.toISOString().slice(0, 10);
88534
- return join9(STORE_DIR, `responses-${iso}.jsonl`);
88659
+ return join10(STORE_DIR, `responses-${iso}.jsonl`);
88535
88660
  }
88536
88661
  function genId() {
88537
88662
  return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
@@ -88581,7 +88706,7 @@ async function listRecent(limit = 20, daysBack = 7) {
88581
88706
  d.setUTCDate(d.getUTCDate() - i);
88582
88707
  const iso = d.toISOString().slice(0, 10);
88583
88708
  const name = `responses-${iso}.jsonl`;
88584
- if (files.includes(name)) candidateFiles.push(join9(STORE_DIR, name));
88709
+ if (files.includes(name)) candidateFiles.push(join10(STORE_DIR, name));
88585
88710
  }
88586
88711
  const all3 = [];
88587
88712
  for (const f of candidateFiles) {
@@ -88610,7 +88735,7 @@ var STORE_DIR, dirReady2;
88610
88735
  var init_response_store = __esm({
88611
88736
  "src/response-store.ts"() {
88612
88737
  "use strict";
88613
- STORE_DIR = join9(homedir6(), ".openclaw", "blockrun", "responses");
88738
+ STORE_DIR = join10(homedir7(), ".openclaw", "blockrun", "responses");
88614
88739
  dirReady2 = false;
88615
88740
  }
88616
88741
  });
@@ -90261,15 +90386,18 @@ import { AsyncLocalStorage } from "async_hooks";
90261
90386
  import { createHmac } from "crypto";
90262
90387
  import { createServer } from "http";
90263
90388
  import { finished } from "stream";
90264
- import { homedir as homedir7 } from "os";
90265
- import { join as join10 } from "path";
90266
- import { mkdir as mkdir4, writeFile as writeFile2, readFile as readFile2, stat as fsStat } from "fs/promises";
90389
+ import { homedir as homedir8 } from "os";
90390
+ import { join as join11 } from "path";
90391
+ import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile2, stat as fsStat } from "fs/promises";
90267
90392
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
90268
- async function loadGatewayCatalog(apiBase) {
90393
+ async function loadGatewayCatalog(apiBase, apiKey) {
90269
90394
  try {
90270
90395
  const controller = new AbortController();
90271
90396
  const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
90272
- const res = await fetch(`${apiBase}/v1/models`, { signal: controller.signal });
90397
+ const res = await fetch(`${apiBase}/v1/models`, {
90398
+ signal: controller.signal,
90399
+ ...apiKey ? { headers: { authorization: `Bearer ${apiKey}` } } : {}
90400
+ });
90273
90401
  clearTimeout(timer2);
90274
90402
  if (!res.ok) return;
90275
90403
  const body = await res.json();
@@ -90568,8 +90696,9 @@ async function checkExistingProxy(port) {
90568
90696
  clearTimeout(timeoutId);
90569
90697
  if (response.ok) {
90570
90698
  const data = await response.json();
90571
- if (data.status === "ok" && data.wallet) {
90572
- return { wallet: data.wallet, paymentChain: data.paymentChain };
90699
+ const authMode = data.authMode === "api-key" ? "api-key" : "wallet";
90700
+ if (data.status === "ok" && (data.wallet || authMode === "api-key")) {
90701
+ return { wallet: data.wallet ?? "", paymentChain: data.paymentChain, authMode };
90573
90702
  }
90574
90703
  }
90575
90704
  return void 0;
@@ -91077,7 +91206,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
91077
91206
  });
91078
91207
  }
91079
91208
  function readImageFileAsDataUri(filePath) {
91080
- const resolved = filePath.startsWith("~/") ? join10(homedir7(), filePath.slice(2)) : filePath;
91209
+ const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
91081
91210
  if (!existsSync2(resolved)) {
91082
91211
  throw new Error(`Image file not found: ${resolved}`);
91083
91212
  }
@@ -91120,16 +91249,48 @@ async function uploadDataUriToHost(dataUri) {
91120
91249
  clearTimeout(uploadTimeout);
91121
91250
  }
91122
91251
  }
91252
+ function warnIfSpendLimitsUnenforced(spendControl) {
91253
+ let configured;
91254
+ try {
91255
+ const limits = (spendControl ?? new SpendControl()).getStatus().limits;
91256
+ configured = ["perRequest", "hourly", "daily", "session"].filter((window2) => typeof limits[window2] === "number").map((window2) => `${window2}=$${limits[window2].toFixed(2)}`);
91257
+ } catch {
91258
+ return;
91259
+ }
91260
+ if (configured.length === 0) return;
91261
+ console.warn(
91262
+ `[ClawRouter] \u26A0 Spend limits (${configured.join(", ")}) are NOT enforced in API-key mode \u2014 they gate x402 signing, and nothing is signed here.`
91263
+ );
91264
+ console.warn(
91265
+ `[ClawRouter] Your BlockRun account balance is the cap instead. Use maxCostPerRun for a per-session limit that still applies.`
91266
+ );
91267
+ }
91123
91268
  async function startProxy(options) {
91124
91269
  const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
91125
91270
  if (upstreamProxy) {
91126
91271
  console.log(`[ClawRouter] Upstream proxy: ${upstreamProxy}`);
91127
91272
  }
91128
- const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
91129
- const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
91273
+ const apiKey = options.apiKey?.trim() || void 0;
91274
+ if (apiKey && !isValidApiKey(apiKey)) {
91275
+ throw new Error(
91276
+ `BlockRun API key is malformed (expected it to start with "brk_"). Mint one at https://user.blockrun.ai/dashboard/keys`
91277
+ );
91278
+ }
91279
+ const authMode = apiKey ? "api-key" : "wallet";
91280
+ if (!apiKey && !options.wallet) {
91281
+ throw new Error(
91282
+ `startProxy needs a credential: pass either a wallet (x402) or an apiKey (brk_\u2026, from https://user.blockrun.ai/dashboard/keys).`
91283
+ );
91284
+ }
91285
+ const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
91286
+ const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
91130
91287
  const paymentChain = options.paymentChain ?? await resolvePaymentChain();
91131
- const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
91132
- if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
91288
+ const apiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
91289
+ if (authMode === "api-key") {
91290
+ console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
91291
+ console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
91292
+ warnIfSpendLimitsUnenforced(options.spendControl);
91293
+ } else if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
91133
91294
  console.warn(
91134
91295
  `[ClawRouter] \u26A0 Payment chain is Solana but no mnemonic found \u2014 falling back to Base (EVM).`
91135
91296
  );
@@ -91140,12 +91301,31 @@ async function startProxy(options) {
91140
91301
  } else if (paymentChain === "solana") {
91141
91302
  console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
91142
91303
  }
91143
- void loadGatewayCatalog(apiBase);
91304
+ void loadGatewayCatalog(apiBase, apiKey);
91144
91305
  const listenPort = options.port ?? getProxyPort();
91145
91306
  const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
91146
91307
  if (existingProxy) {
91147
- const account2 = privateKeyToAccount(walletKey);
91148
91308
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
91309
+ if (existingProxy.authMode !== authMode) {
91310
+ throw new Error(
91311
+ `Existing proxy on port ${listenPort} is authenticating with ${existingProxy.authMode === "api-key" ? "a BlockRun API key" : "a wallet"} but ${authMode === "api-key" ? "an API key" : "a wallet"} was requested. Stop the existing proxy first or use a different port.`
91312
+ );
91313
+ }
91314
+ if (authMode === "api-key") {
91315
+ options.onReady?.(listenPort);
91316
+ return {
91317
+ port: listenPort,
91318
+ baseUrl: baseUrl2,
91319
+ walletAddress: "",
91320
+ authMode,
91321
+ apiKeyLabel: maskApiKey(apiKey),
91322
+ balanceMonitor: new ApiKeyBalanceMonitor(),
91323
+ // No-op: we didn't start this proxy, so we shouldn't close it
91324
+ close: async () => {
91325
+ }
91326
+ };
91327
+ }
91328
+ const account2 = privateKeyToAccount(walletKey);
91149
91329
  if (existingProxy.wallet !== account2.address) {
91150
91330
  console.warn(
91151
91331
  `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
@@ -91184,20 +91364,23 @@ async function startProxy(options) {
91184
91364
  baseUrl: baseUrl2,
91185
91365
  walletAddress: existingProxy.wallet,
91186
91366
  solanaAddress: reuseSolanaAddress,
91367
+ authMode,
91187
91368
  balanceMonitor: balanceMonitor2,
91188
91369
  close: async () => {
91189
91370
  }
91190
91371
  };
91191
91372
  }
91192
- const account = privateKeyToAccount(walletKey);
91193
- const evmPublicClient = createPublicClient({ chain: base, transport: http() });
91194
- const evmSigner = toClientEvmSigner(account, evmPublicClient);
91195
- const x402 = new x402Client();
91196
- const spendControl = options.spendControl ?? new SpendControl();
91197
- registerSpendPolicyHook(x402, spendControl);
91198
- registerExactEvmScheme(x402, { signer: evmSigner });
91373
+ const account = walletKey ? privateKeyToAccount(walletKey) : void 0;
91374
+ const x402 = authMode === "wallet" ? new x402Client() : void 0;
91375
+ if (x402 && account) {
91376
+ const evmPublicClient = createPublicClient({ chain: base, transport: http() });
91377
+ const evmSigner = toClientEvmSigner(account, evmPublicClient);
91378
+ const spendControl = options.spendControl ?? new SpendControl();
91379
+ registerSpendPolicyHook(x402, spendControl);
91380
+ registerExactEvmScheme(x402, { signer: evmSigner });
91381
+ }
91199
91382
  let solanaAddress;
91200
- if (solanaPrivateKeyBytes) {
91383
+ if (x402 && solanaPrivateKeyBytes) {
91201
91384
  const { registerExactSvmScheme: registerExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
91202
91385
  const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
91203
91386
  const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
@@ -91205,12 +91388,12 @@ async function startProxy(options) {
91205
91388
  registerExactSvmScheme2(x402, { signer: solanaSigner });
91206
91389
  console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
91207
91390
  }
91208
- x402.onAfterPaymentCreation(async (context) => {
91391
+ x402?.onAfterPaymentCreation(async (context) => {
91209
91392
  if (!context.selectedRequirements.network.startsWith("eip155")) return;
91210
91393
  const payload = context.paymentPayload;
91211
91394
  payload.extensions = withBuilderCodeServiceCode(payload.extensions);
91212
91395
  });
91213
- x402.onAfterPaymentCreation(async (context) => {
91396
+ x402?.onAfterPaymentCreation(async (context) => {
91214
91397
  const network = context.selectedRequirements.network;
91215
91398
  const chain3 = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
91216
91399
  const amountMicros = parseInt(context.selectedRequirements.amount || "0", 10);
@@ -91219,7 +91402,7 @@ async function startProxy(options) {
91219
91402
  if (store) store.amountUsd = amountUsd;
91220
91403
  console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
91221
91404
  });
91222
- const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
91405
+ const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
91223
91406
  skipPreAuth: paymentChain === "solana",
91224
91407
  // Per-request cost estimate so pre-auth is only reused when the cached
91225
91408
  // payment still covers the (possibly larger) request — BlockRun prices per
@@ -91229,6 +91412,8 @@ async function startProxy(options) {
91229
91412
  let balanceMonitor;
91230
91413
  if (options._balanceMonitorOverride) {
91231
91414
  balanceMonitor = options._balanceMonitorOverride;
91415
+ } else if (authMode === "api-key") {
91416
+ balanceMonitor = new ApiKeyBalanceMonitor();
91232
91417
  } else if (paymentChain === "solana" && solanaAddress) {
91233
91418
  const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
91234
91419
  balanceMonitor = new SolanaBalanceMonitor2(solanaAddress);
@@ -91270,11 +91455,17 @@ async function startProxy(options) {
91270
91455
  const full = url2.searchParams.get("full") === "true";
91271
91456
  const response = {
91272
91457
  status: "ok",
91273
- wallet: account.address,
91274
- paymentChain
91458
+ authMode
91275
91459
  };
91276
- if (solanaAddress) {
91277
- response.solana = solanaAddress;
91460
+ if (authMode === "api-key") {
91461
+ response.apiKey = maskApiKey(apiKey);
91462
+ response.gateway = apiBase;
91463
+ } else {
91464
+ response.wallet = account.address;
91465
+ response.paymentChain = paymentChain;
91466
+ if (solanaAddress) {
91467
+ response.solana = solanaAddress;
91468
+ }
91278
91469
  }
91279
91470
  if (upstreamProxy) {
91280
91471
  response.upstreamProxy = upstreamProxy;
@@ -91290,9 +91481,15 @@ async function startProxy(options) {
91290
91481
  ).unref()
91291
91482
  )
91292
91483
  ]);
91293
- response.balance = balanceInfo.balanceUSD;
91294
- response.isLow = balanceInfo.isLow;
91295
- response.isEmpty = balanceInfo.isEmpty;
91484
+ if (authMode === "api-key") {
91485
+ response.balance = null;
91486
+ response.billing = "BlockRun account credit";
91487
+ response.topUpUrl = PORTAL_CREDITS_URL;
91488
+ } else {
91489
+ response.balance = balanceInfo.balanceUSD;
91490
+ response.isLow = balanceInfo.isLow;
91491
+ response.isEmpty = balanceInfo.isEmpty;
91492
+ }
91296
91493
  } catch {
91297
91494
  response.balanceError = "Could not fetch balance";
91298
91495
  }
@@ -91472,7 +91669,7 @@ async function startProxy(options) {
91472
91669
  res.end("Bad request");
91473
91670
  return;
91474
91671
  }
91475
- const filePath = join10(IMAGE_DIR, filename);
91672
+ const filePath = join11(IMAGE_DIR, filename);
91476
91673
  try {
91477
91674
  const s3 = await fsStat(filePath);
91478
91675
  if (!s3.isFile()) throw new Error("not a file");
@@ -91503,7 +91700,7 @@ async function startProxy(options) {
91503
91700
  res.end("Bad request");
91504
91701
  return;
91505
91702
  }
91506
- const filePath = join10(AUDIO_DIR, filename);
91703
+ const filePath = join11(AUDIO_DIR, filename);
91507
91704
  try {
91508
91705
  const s3 = await fsStat(filePath);
91509
91706
  if (!s3.isFile()) throw new Error("not a file");
@@ -91533,7 +91730,7 @@ async function startProxy(options) {
91533
91730
  res.end("Bad request");
91534
91731
  return;
91535
91732
  }
91536
- const filePath = join10(VIDEO_DIR, filename);
91733
+ const filePath = join11(VIDEO_DIR, filename);
91537
91734
  try {
91538
91735
  const s3 = await fsStat(filePath);
91539
91736
  if (!s3.isFile()) throw new Error("not a file");
@@ -91666,7 +91863,7 @@ async function startProxy(options) {
91666
91863
  }
91667
91864
  }
91668
91865
  if (result.data?.length) {
91669
- await mkdir4(IMAGE_DIR, { recursive: true });
91866
+ await mkdir5(IMAGE_DIR, { recursive: true });
91670
91867
  const port2 = server.address()?.port ?? 8402;
91671
91868
  for (const img of result.data) {
91672
91869
  const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
@@ -91674,7 +91871,7 @@ async function startProxy(options) {
91674
91871
  const [, mimeType, b64] = dataUriMatch;
91675
91872
  const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
91676
91873
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
91677
- await writeFile2(join10(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
91874
+ await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
91678
91875
  img.url = `http://localhost:${port2}/images/${filename}`;
91679
91876
  console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
91680
91877
  } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
@@ -91685,7 +91882,7 @@ async function startProxy(options) {
91685
91882
  const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
91686
91883
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
91687
91884
  const buf = Buffer.from(await imgResp.arrayBuffer());
91688
- await writeFile2(join10(IMAGE_DIR, filename), buf);
91885
+ await writeFile3(join11(IMAGE_DIR, filename), buf);
91689
91886
  img.url = `http://localhost:${port2}/images/${filename}`;
91690
91887
  console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
91691
91888
  }
@@ -91789,7 +91986,7 @@ async function startProxy(options) {
91789
91986
  return;
91790
91987
  }
91791
91988
  if (result.data?.length) {
91792
- await mkdir4(IMAGE_DIR, { recursive: true });
91989
+ await mkdir5(IMAGE_DIR, { recursive: true });
91793
91990
  const port2 = server.address()?.port ?? 8402;
91794
91991
  for (const img of result.data) {
91795
91992
  const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
@@ -91797,7 +91994,7 @@ async function startProxy(options) {
91797
91994
  const [, mimeType, b64] = dataUriMatch;
91798
91995
  const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
91799
91996
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
91800
- await writeFile2(join10(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
91997
+ await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
91801
91998
  img.url = `http://localhost:${port2}/images/${filename}`;
91802
91999
  console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
91803
92000
  } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
@@ -91808,7 +92005,7 @@ async function startProxy(options) {
91808
92005
  const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
91809
92006
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
91810
92007
  const buf = Buffer.from(await imgResp.arrayBuffer());
91811
- await writeFile2(join10(IMAGE_DIR, filename), buf);
92008
+ await writeFile3(join11(IMAGE_DIR, filename), buf);
91812
92009
  img.url = `http://localhost:${port2}/images/${filename}`;
91813
92010
  console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
91814
92011
  }
@@ -91883,7 +92080,7 @@ async function startProxy(options) {
91883
92080
  return;
91884
92081
  }
91885
92082
  if (result.data?.length) {
91886
- await mkdir4(AUDIO_DIR, { recursive: true });
92083
+ await mkdir5(AUDIO_DIR, { recursive: true });
91887
92084
  const port2 = server.address()?.port ?? 8402;
91888
92085
  for (const track of result.data) {
91889
92086
  if (track.url?.startsWith("https://") || track.url?.startsWith("http://")) {
@@ -91894,7 +92091,7 @@ async function startProxy(options) {
91894
92091
  const ext = contentType.includes("wav") ? "wav" : "mp3";
91895
92092
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
91896
92093
  const buf = Buffer.from(await audioResp.arrayBuffer());
91897
- await writeFile2(join10(AUDIO_DIR, filename), buf);
92094
+ await writeFile3(join11(AUDIO_DIR, filename), buf);
91898
92095
  track.url = `http://localhost:${port2}/audio/${filename}`;
91899
92096
  console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
91900
92097
  }
@@ -92043,7 +92240,7 @@ async function startProxy(options) {
92043
92240
  }
92044
92241
  }
92045
92242
  if (finalResult.data?.length) {
92046
- await mkdir4(VIDEO_DIR, { recursive: true });
92243
+ await mkdir5(VIDEO_DIR, { recursive: true });
92047
92244
  const port2 = server.address()?.port ?? 8402;
92048
92245
  for (const clip of finalResult.data) {
92049
92246
  if (clip.url?.startsWith("https://") || clip.url?.startsWith("http://")) {
@@ -92054,7 +92251,7 @@ async function startProxy(options) {
92054
92251
  const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
92055
92252
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
92056
92253
  const buf = Buffer.from(await videoResp.arrayBuffer());
92057
- await writeFile2(join10(VIDEO_DIR, filename), buf);
92254
+ await writeFile3(join11(VIDEO_DIR, filename), buf);
92058
92255
  clip.url = `http://localhost:${port2}/videos/${filename}`;
92059
92256
  console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
92060
92257
  }
@@ -92237,6 +92434,8 @@ async function startProxy(options) {
92237
92434
  port: listenPort,
92238
92435
  baseUrl: baseUrl2,
92239
92436
  walletAddress: error.wallet,
92437
+ authMode,
92438
+ ...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
92240
92439
  balanceMonitor,
92241
92440
  close: async () => {
92242
92441
  }
@@ -92287,8 +92486,10 @@ async function startProxy(options) {
92287
92486
  return {
92288
92487
  port,
92289
92488
  baseUrl,
92290
- walletAddress: account.address,
92489
+ walletAddress: account?.address ?? "",
92291
92490
  solanaAddress,
92491
+ authMode,
92492
+ ...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
92292
92493
  balanceMonitor,
92293
92494
  close: () => new Promise((res, rej) => {
92294
92495
  const timeout = setTimeout(() => {
@@ -94611,6 +94812,7 @@ var init_proxy = __esm({
94611
94812
  init_response_cache();
94612
94813
  init_balance();
94613
94814
  init_auth();
94815
+ init_api_key();
94614
94816
  init_spend_control();
94615
94817
  init_compression();
94616
94818
  init_version4();
@@ -94627,10 +94829,10 @@ var init_proxy = __esm({
94627
94829
  paymentStore = new AsyncLocalStorage();
94628
94830
  BLOCKRUN_API = "https://blockrun.ai/api";
94629
94831
  BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
94630
- DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join10(homedir7(), ".clawrouter-desktop", "service-token");
94631
- IMAGE_DIR = join10(homedir7(), ".openclaw", "blockrun", "images");
94632
- AUDIO_DIR = join10(homedir7(), ".openclaw", "blockrun", "audio");
94633
- VIDEO_DIR = join10(homedir7(), ".openclaw", "blockrun", "videos");
94832
+ DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join11(homedir8(), ".clawrouter-desktop", "service-token");
94833
+ IMAGE_DIR = join11(homedir8(), ".openclaw", "blockrun", "images");
94834
+ AUDIO_DIR = join11(homedir8(), ".openclaw", "blockrun", "audio");
94835
+ VIDEO_DIR = join11(homedir8(), ".openclaw", "blockrun", "videos");
94634
94836
  AUTO_MODEL = "blockrun/auto";
94635
94837
  ROUTING_PROFILES = /* @__PURE__ */ new Set([
94636
94838
  "blockrun/eco",
@@ -116822,8 +117024,8 @@ var init_constants3 = __esm({
116822
117024
 
116823
117025
  // src/polymarket/wallet-adapter.ts
116824
117026
  import fs2 from "fs";
116825
- import { homedir as homedir8 } from "os";
116826
- import { join as join11 } from "path";
117027
+ import { homedir as homedir9 } from "os";
117028
+ import { join as join12 } from "path";
116827
117029
  function getOrCreateWalletKey() {
116828
117030
  const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
116829
117031
  if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
@@ -116871,7 +117073,7 @@ var init_wallet_adapter = __esm({
116871
117073
  init_esm();
116872
117074
  init_chains();
116873
117075
  init_constants3();
116874
- WALLET_FILE2 = join11(homedir8(), ".openclaw", "blockrun", "wallet.key");
117076
+ WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
116875
117077
  BASE_RPC_URLS = [
116876
117078
  "https://mainnet.base.org",
116877
117079
  "https://base.llamarpc.com",
@@ -227387,9 +227589,9 @@ import {
227387
227589
  unlinkSync
227388
227590
  } from "fs";
227389
227591
  import { readFile as readFileAsync } from "fs/promises";
227390
- import { homedir as homedir13 } from "os";
227592
+ import { homedir as homedir14 } from "os";
227391
227593
  import { randomUUID } from "crypto";
227392
- import { join as join16 } from "path";
227594
+ import { join as join17 } from "path";
227393
227595
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
227394
227596
  const start = Date.now();
227395
227597
  while (Date.now() - start < timeoutMs) {
@@ -227431,8 +227633,8 @@ function isBlockrunWebSearchDisabled(config) {
227431
227633
  return cfg.tools?.web?.search?.enabled === false;
227432
227634
  }
227433
227635
  function injectModelsConfig(logger48, options = {}) {
227434
- const configDir = join16(homedir13(), ".openclaw");
227435
- const configPath = join16(configDir, "openclaw.json");
227636
+ const configDir = join17(homedir14(), ".openclaw");
227637
+ const configPath = join17(configDir, "openclaw.json");
227436
227638
  let config = {};
227437
227639
  let needsWrite = false;
227438
227640
  if (!existsSync3(configDir)) {
@@ -227653,7 +227855,7 @@ function injectModelsConfig(logger48, options = {}) {
227653
227855
  }
227654
227856
  function syncAgentModelCache(logger48, options = {}) {
227655
227857
  if (!isGatewayMode() && !options.forceWrite) return;
227656
- const agentsDir = join16(homedir13(), ".openclaw", "agents");
227858
+ const agentsDir = join17(homedir14(), ".openclaw", "agents");
227657
227859
  if (!existsSync3(agentsDir)) return;
227658
227860
  let agentDirs;
227659
227861
  try {
@@ -227663,7 +227865,7 @@ function syncAgentModelCache(logger48, options = {}) {
227663
227865
  }
227664
227866
  const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
227665
227867
  for (const agent of agentDirs) {
227666
- const cachePath = join16(agentsDir, agent, "agent", "models.json");
227868
+ const cachePath = join17(agentsDir, agent, "agent", "models.json");
227667
227869
  if (!existsSync3(cachePath)) continue;
227668
227870
  try {
227669
227871
  const raw = readTextFileSync(cachePath).trim();
@@ -227692,7 +227894,7 @@ function syncAgentModelCache(logger48, options = {}) {
227692
227894
  }
227693
227895
  }
227694
227896
  function injectAuthProfile(logger48) {
227695
- const agentsDir = join16(homedir13(), ".openclaw", "agents");
227897
+ const agentsDir = join17(homedir14(), ".openclaw", "agents");
227696
227898
  if (!existsSync3(agentsDir)) {
227697
227899
  try {
227698
227900
  mkdirSync3(agentsDir, { recursive: true });
@@ -227709,9 +227911,9 @@ function injectAuthProfile(logger48) {
227709
227911
  agents = ["main", ...agents];
227710
227912
  }
227711
227913
  for (const agentId of agents) {
227712
- const authDir = join16(agentsDir, agentId, "agent");
227713
- const authPath = join16(authDir, "auth-profiles.json");
227714
- const sqlitePath = join16(authDir, "openclaw-agent.sqlite");
227914
+ const authDir = join17(agentsDir, agentId, "agent");
227915
+ const authPath = join17(authDir, "auth-profiles.json");
227916
+ const sqlitePath = join17(authDir, "openclaw-agent.sqlite");
227715
227917
  if (existsSync3(sqlitePath)) {
227716
227918
  removeInjectedAuthPlaceholder(authPath, logger48, agentId);
227717
227919
  continue;
@@ -227858,9 +228060,21 @@ async function startProxyInBackground(api, startupGeneration) {
227858
228060
  if (startupGeneration !== void 0 && isProxyStartupCurrent(startupGeneration, proc)) {
227859
228061
  proc.__clawrouterStartupPhase = "starting";
227860
228062
  }
228063
+ const pluginApiKey = api.pluginConfig?.apiKey;
228064
+ const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
228065
+ if (typeof pluginApiKey === "string" && !isValidApiKey(pluginApiKey)) {
228066
+ api.logger.warn(
228067
+ `pluginConfig.apiKey is set but invalid (expected brk_...) \u2014 ignoring it and looking elsewhere`
228068
+ );
228069
+ }
228070
+ const apiKey = resolvedApiKey?.key;
227861
228071
  const configKey = api.pluginConfig?.walletKey;
227862
228072
  let wallet;
227863
- if (typeof configKey === "string" && /^0x[0-9a-fA-F]{64}$/.test(configKey)) {
228073
+ if (apiKey) {
228074
+ api.logger.info(
228075
+ `Using BlockRun API key ${maskApiKey(apiKey)} (from ${resolvedApiKey.source}) \u2014 billing account credit, no wallet`
228076
+ );
228077
+ } else if (typeof configKey === "string" && /^0x[0-9a-fA-F]{64}$/.test(configKey)) {
227864
228078
  const account = privateKeyToAccount(configKey);
227865
228079
  wallet = { key: configKey, address: account.address, source: "config" };
227866
228080
  } else {
@@ -227871,7 +228085,8 @@ async function startProxyInBackground(api, startupGeneration) {
227871
228085
  }
227872
228086
  wallet = await resolveOrGenerateWalletKey();
227873
228087
  }
227874
- if (wallet.source === "generated") {
228088
+ if (!wallet) {
228089
+ } else if (wallet.source === "generated") {
227875
228090
  api.logger.warn(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
227876
228091
  api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
227877
228092
  api.logger.warn(` Address : ${wallet.address}`);
@@ -227896,13 +228111,13 @@ async function startProxyInBackground(api, startupGeneration) {
227896
228111
  );
227897
228112
  }
227898
228113
  const proxy = await startProxy({
227899
- wallet,
228114
+ ...apiKey ? { apiKey } : { wallet },
227900
228115
  routingConfig,
227901
228116
  maxCostPerRunUsd,
227902
228117
  maxCostPerRunMode,
227903
228118
  spendControl: liveSpendControl = new SpendControl(),
227904
228119
  onReady: (port) => {
227905
- api.logger.info(`BlockRun x402 proxy listening on port ${port}`);
228120
+ api.logger.info(`BlockRun ${apiKey ? "API-key" : "x402"} proxy listening on port ${port}`);
227906
228121
  },
227907
228122
  onError: (error) => {
227908
228123
  api.logger.error(`BlockRun proxy error: ${error.message}`);
@@ -227948,6 +228163,10 @@ async function startProxyInBackground(api, startupGeneration) {
227948
228163
  }
227949
228164
  api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
227950
228165
  api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
228166
+ if (apiKey) {
228167
+ api.logger.info(`Billing: BlockRun account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
228168
+ return true;
228169
+ }
227951
228170
  const currentChain = await resolvePaymentChain();
227952
228171
  const displayAddress = currentChain === "solana" && proxy.solanaAddress ? proxy.solanaAddress : wallet.address;
227953
228172
  const network = currentChain === "solana" ? "Solana" : "Base";
@@ -228191,7 +228410,7 @@ function buildImageGenerationProvider() {
228191
228410
  (result.data ?? []).map(async (img) => {
228192
228411
  const filename = img.url?.split("/images/").pop();
228193
228412
  if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
228194
- const filePath = join16(IMAGE_DIR2, filename);
228413
+ const filePath = join17(IMAGE_DIR2, filename);
228195
228414
  const buffer2 = await readFileAsync(filePath);
228196
228415
  const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
228197
228416
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
@@ -228243,7 +228462,7 @@ function buildMusicGenerationProvider() {
228243
228462
  (result.data ?? []).map(async (track) => {
228244
228463
  const filename = track.url?.split("/audio/").pop();
228245
228464
  if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
228246
- const filePath = join16(AUDIO_DIR2, filename);
228465
+ const filePath = join17(AUDIO_DIR2, filename);
228247
228466
  const buffer2 = await readFileAsync(filePath);
228248
228467
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
228249
228468
  const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
@@ -228319,7 +228538,7 @@ function buildVideoGenerationProvider() {
228319
228538
  (result.data ?? []).map(async (clip) => {
228320
228539
  const filename = clip.url?.split("/videos/").pop();
228321
228540
  if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
228322
- const filePath = join16(VIDEO_DIR2, filename);
228541
+ const filePath = join17(VIDEO_DIR2, filename);
228323
228542
  const buffer2 = await readFileAsync(filePath);
228324
228543
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
228325
228544
  const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
@@ -228372,6 +228591,22 @@ function createWalletCommand(api) {
228372
228591
  requireAuth: true,
228373
228592
  handler: async (ctx) => {
228374
228593
  const subcommand = ctx.args?.trim().toLowerCase() || "status";
228594
+ const activeApiKey = (await resolveApiKey())?.key;
228595
+ if (activeApiKey) {
228596
+ return {
228597
+ text: [
228598
+ "**BlockRun account** (API key)",
228599
+ "",
228600
+ `**Key:** \`${maskApiKey(activeApiKey)}\``,
228601
+ "**Billing:** account credit \u2014 no wallet, no payment chain, no gas.",
228602
+ "",
228603
+ `**Add credit:** ${PORTAL_CREDITS_URL}`,
228604
+ `**Usage & keys:** ${PORTAL_KEYS_URL}`,
228605
+ "",
228606
+ "To pay with a USDC wallet over x402 instead, run `clawrouter logout` and restart."
228607
+ ].join("\n")
228608
+ };
228609
+ }
228375
228610
  let wallet;
228376
228611
  try {
228377
228612
  wallet = subcommand === "status" || subcommand === "export" ? await resolveExistingWalletKey() : await resolveOrGenerateWalletKey();
@@ -228628,6 +228863,7 @@ var init_index = __esm({
228628
228863
  "src/index.ts"() {
228629
228864
  init_provider();
228630
228865
  init_proxy();
228866
+ init_api_key();
228631
228867
  init_web_search_provider();
228632
228868
  init_auth();
228633
228869
  init_balance();
@@ -228667,9 +228903,9 @@ var init_index = __esm({
228667
228903
  activeProxyHandle = null;
228668
228904
  liveSpendControl = null;
228669
228905
  pendingConfiguredStartupApi = null;
228670
- IMAGE_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "images");
228671
- AUDIO_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "audio");
228672
- VIDEO_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "videos");
228906
+ IMAGE_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "images");
228907
+ AUDIO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "audio");
228908
+ VIDEO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "videos");
228673
228909
  plugin = {
228674
228910
  // NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
228675
228911
  // 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
@@ -229086,7 +229322,7 @@ ${errText}`
229086
229322
  }
229087
229323
  resetProxyStartupState();
229088
229324
  try {
229089
- const configPath = join16(homedir13(), ".openclaw", "openclaw.json");
229325
+ const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
229090
229326
  if (existsSync3(configPath)) {
229091
229327
  const config = JSON.parse(readTextFileSync(configPath));
229092
229328
  if (config.models?.providers?.blockrun) {
@@ -229128,11 +229364,11 @@ ${errText}`
229128
229364
  api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
229129
229365
  }
229130
229366
  try {
229131
- const agentsDir = join16(homedir13(), ".openclaw", "agents");
229367
+ const agentsDir = join17(homedir14(), ".openclaw", "agents");
229132
229368
  if (existsSync3(agentsDir)) {
229133
229369
  for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
229134
229370
  if (!entry.isDirectory()) continue;
229135
- const authPath = join16(agentsDir, entry.name, "agent", "auth-profiles.json");
229371
+ const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
229136
229372
  if (!existsSync3(authPath)) continue;
229137
229373
  try {
229138
229374
  const store = JSON.parse(readTextFileSync(authPath));