@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/README.md +147 -21
- package/dist/cli.js +664 -157
- package/dist/index.d.ts +58 -11
- package/dist/index.js +326 -90
- package/docs/configuration.md +102 -9
- package/openclaw.plugin.json +9 -0
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -40691,7 +40691,7 @@ var init_errors5 = __esm({
|
|
|
40691
40691
|
});
|
|
40692
40692
|
|
|
40693
40693
|
// src/balance.ts
|
|
40694
|
-
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor;
|
|
40694
|
+
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor, ApiKeyBalanceMonitor;
|
|
40695
40695
|
var init_balance = __esm({
|
|
40696
40696
|
"src/balance.ts"() {
|
|
40697
40697
|
"use strict";
|
|
@@ -40820,6 +40820,38 @@ var init_balance = __esm({
|
|
|
40820
40820
|
};
|
|
40821
40821
|
}
|
|
40822
40822
|
};
|
|
40823
|
+
ApiKeyBalanceMonitor = class _ApiKeyBalanceMonitor {
|
|
40824
|
+
/** Large enough that every `balance >= estimatedCost` comparison passes. */
|
|
40825
|
+
static UNMETERED = BigInt(Number.MAX_SAFE_INTEGER);
|
|
40826
|
+
async checkBalance() {
|
|
40827
|
+
return {
|
|
40828
|
+
balance: _ApiKeyBalanceMonitor.UNMETERED,
|
|
40829
|
+
// Never a dollar figure: no caller may print this as if it were one.
|
|
40830
|
+
balanceUSD: "account credit",
|
|
40831
|
+
isLow: false,
|
|
40832
|
+
isEmpty: false,
|
|
40833
|
+
walletAddress: ""
|
|
40834
|
+
};
|
|
40835
|
+
}
|
|
40836
|
+
async checkSufficient() {
|
|
40837
|
+
return { sufficient: true, info: await this.checkBalance() };
|
|
40838
|
+
}
|
|
40839
|
+
/** No local cache to adjust — the server keeps the books. */
|
|
40840
|
+
deductEstimated() {
|
|
40841
|
+
}
|
|
40842
|
+
invalidate() {
|
|
40843
|
+
}
|
|
40844
|
+
async refresh() {
|
|
40845
|
+
return this.checkBalance();
|
|
40846
|
+
}
|
|
40847
|
+
formatUSDC(amountMicros) {
|
|
40848
|
+
return `$${(Number(amountMicros) / 1e6).toFixed(2)}`;
|
|
40849
|
+
}
|
|
40850
|
+
/** No wallet in this mode; callers render the account instead. */
|
|
40851
|
+
getWalletAddress() {
|
|
40852
|
+
return "";
|
|
40853
|
+
}
|
|
40854
|
+
};
|
|
40823
40855
|
}
|
|
40824
40856
|
});
|
|
40825
40857
|
|
|
@@ -59980,6 +60012,119 @@ var init_auth = __esm({
|
|
|
59980
60012
|
}
|
|
59981
60013
|
});
|
|
59982
60014
|
|
|
60015
|
+
// src/api-key.ts
|
|
60016
|
+
import { writeFile as writeFile2, mkdir as mkdir3, rm } from "fs/promises";
|
|
60017
|
+
import { join as join8 } from "path";
|
|
60018
|
+
import { homedir as homedir5 } from "os";
|
|
60019
|
+
function isValidApiKey(value) {
|
|
60020
|
+
if (typeof value !== "string") return false;
|
|
60021
|
+
return /^brk_[A-Za-z0-9_-]{8,}$/.test(value.trim());
|
|
60022
|
+
}
|
|
60023
|
+
function maskApiKey(key2) {
|
|
60024
|
+
const trimmed = key2.trim();
|
|
60025
|
+
if (trimmed.length <= 18) return `${trimmed.slice(0, 8)}\u2026`;
|
|
60026
|
+
return `${trimmed.slice(0, 14)}\u2026${trimmed.slice(-4)}`;
|
|
60027
|
+
}
|
|
60028
|
+
async function readOptional2(path5) {
|
|
60029
|
+
try {
|
|
60030
|
+
return (await readTextFile(path5)).trim() || void 0;
|
|
60031
|
+
} catch {
|
|
60032
|
+
return void 0;
|
|
60033
|
+
}
|
|
60034
|
+
}
|
|
60035
|
+
async function resolveApiKey() {
|
|
60036
|
+
const envKey = process["env"].BLOCKRUN_API_KEY?.trim();
|
|
60037
|
+
if (envKey) {
|
|
60038
|
+
if (isValidApiKey(envKey)) return { key: envKey, source: "env" };
|
|
60039
|
+
console.warn(
|
|
60040
|
+
`[ClawRouter] \u26A0 BLOCKRUN_API_KEY is set but does not look like a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
|
|
60041
|
+
);
|
|
60042
|
+
}
|
|
60043
|
+
for (const [path5, source] of [
|
|
60044
|
+
[CORE_API_KEY_FILE, "core"],
|
|
60045
|
+
[API_KEY_FILE, "saved"]
|
|
60046
|
+
]) {
|
|
60047
|
+
const stored = await readOptional2(path5);
|
|
60048
|
+
if (stored === void 0) continue;
|
|
60049
|
+
if (isValidApiKey(stored)) return { key: stored, source };
|
|
60050
|
+
console.warn(
|
|
60051
|
+
`[ClawRouter] \u26A0 ${path5} does not contain a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
|
|
60052
|
+
);
|
|
60053
|
+
}
|
|
60054
|
+
return void 0;
|
|
60055
|
+
}
|
|
60056
|
+
async function saveApiKey(key2) {
|
|
60057
|
+
const trimmed = key2.trim();
|
|
60058
|
+
if (!isValidApiKey(trimmed)) {
|
|
60059
|
+
throw new Error(
|
|
60060
|
+
`Not a BlockRun API key: expected it to start with "brk_". Mint one at ${PORTAL_KEYS_URL}`
|
|
60061
|
+
);
|
|
60062
|
+
}
|
|
60063
|
+
await mkdir3(join8(homedir5(), ".blockrun"), { recursive: true });
|
|
60064
|
+
await writeFile2(CORE_API_KEY_FILE, trimmed + "\n", { mode: 384 });
|
|
60065
|
+
return CORE_API_KEY_FILE;
|
|
60066
|
+
}
|
|
60067
|
+
async function clearApiKey() {
|
|
60068
|
+
const removed = [];
|
|
60069
|
+
for (const path5 of [CORE_API_KEY_FILE, API_KEY_FILE]) {
|
|
60070
|
+
if (await readOptional2(path5) === void 0) continue;
|
|
60071
|
+
await rm(path5, { force: true });
|
|
60072
|
+
removed.push(path5);
|
|
60073
|
+
}
|
|
60074
|
+
return { removed, envStillSet: Boolean(process["env"].BLOCKRUN_API_KEY?.trim()) };
|
|
60075
|
+
}
|
|
60076
|
+
function createApiKeyFetch(apiKey, baseFetch = fetch) {
|
|
60077
|
+
return async (input, init2) => {
|
|
60078
|
+
const headers = new Headers(init2?.headers);
|
|
60079
|
+
headers.set("authorization", `Bearer ${apiKey}`);
|
|
60080
|
+
headers.delete("x-payment");
|
|
60081
|
+
headers.delete("x-api-key");
|
|
60082
|
+
const response = await baseFetch(input, { ...init2, headers });
|
|
60083
|
+
return response.ok ? response : explainApiKeyFailure(response);
|
|
60084
|
+
};
|
|
60085
|
+
}
|
|
60086
|
+
async function explainApiKeyFailure(response) {
|
|
60087
|
+
const hint = HINTS[response.status];
|
|
60088
|
+
if (!hint) return response;
|
|
60089
|
+
if (!(response.headers.get("content-type") ?? "").includes("json")) return response;
|
|
60090
|
+
let parsed;
|
|
60091
|
+
try {
|
|
60092
|
+
parsed = await response.clone().json();
|
|
60093
|
+
} catch {
|
|
60094
|
+
return response;
|
|
60095
|
+
}
|
|
60096
|
+
if (!parsed?.error || typeof parsed.error.message !== "string") return response;
|
|
60097
|
+
if (response.status === 404 && !/unsupported endpoint/i.test(parsed.error.message)) {
|
|
60098
|
+
return response;
|
|
60099
|
+
}
|
|
60100
|
+
parsed.error.message = `${parsed.error.message} ${hint}`;
|
|
60101
|
+
const headers = new Headers(response.headers);
|
|
60102
|
+
headers.delete("content-length");
|
|
60103
|
+
return new Response(JSON.stringify(parsed), {
|
|
60104
|
+
status: response.status,
|
|
60105
|
+
statusText: response.statusText,
|
|
60106
|
+
headers
|
|
60107
|
+
});
|
|
60108
|
+
}
|
|
60109
|
+
var API_KEY_FILE, CORE_API_KEY_FILE, PORTAL_URL, PORTAL_KEYS_URL, PORTAL_CREDITS_URL, BLOCKRUN_API_KEY_API, HINTS;
|
|
60110
|
+
var init_api_key = __esm({
|
|
60111
|
+
"src/api-key.ts"() {
|
|
60112
|
+
"use strict";
|
|
60113
|
+
init_fs_read();
|
|
60114
|
+
API_KEY_FILE = join8(homedir5(), ".openclaw", "blockrun", "api-key");
|
|
60115
|
+
CORE_API_KEY_FILE = join8(homedir5(), ".blockrun", ".api-key");
|
|
60116
|
+
PORTAL_URL = "https://user.blockrun.ai";
|
|
60117
|
+
PORTAL_KEYS_URL = `${PORTAL_URL}/dashboard/keys`;
|
|
60118
|
+
PORTAL_CREDITS_URL = `${PORTAL_URL}/dashboard/credits`;
|
|
60119
|
+
BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "") || "https://api.blockrun.ai";
|
|
60120
|
+
HINTS = {
|
|
60121
|
+
401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
|
|
60122
|
+
402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
|
|
60123
|
+
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").`
|
|
60124
|
+
};
|
|
60125
|
+
}
|
|
60126
|
+
});
|
|
60127
|
+
|
|
59983
60128
|
// src/compression/types.ts
|
|
59984
60129
|
var DEFAULT_COMPRESSION_CONFIG;
|
|
59985
60130
|
var init_types2 = __esm({
|
|
@@ -61069,8 +61214,8 @@ var init_updater = __esm({
|
|
|
61069
61214
|
|
|
61070
61215
|
// src/exclude-models.ts
|
|
61071
61216
|
import { readFileSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
61072
|
-
import { join as
|
|
61073
|
-
import { homedir as
|
|
61217
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
61218
|
+
import { homedir as homedir6 } from "os";
|
|
61074
61219
|
function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
|
|
61075
61220
|
try {
|
|
61076
61221
|
const mtimeMs = statSync(filePath).mtimeMs;
|
|
@@ -61119,7 +61264,7 @@ var init_exclude_models = __esm({
|
|
|
61119
61264
|
"src/exclude-models.ts"() {
|
|
61120
61265
|
"use strict";
|
|
61121
61266
|
init_models();
|
|
61122
|
-
DEFAULT_FILE_PATH =
|
|
61267
|
+
DEFAULT_FILE_PATH = join9(homedir6(), ".openclaw", "blockrun", "exclude-models.json");
|
|
61123
61268
|
loadCache = /* @__PURE__ */ new Map();
|
|
61124
61269
|
}
|
|
61125
61270
|
});
|
|
@@ -76128,7 +76273,7 @@ var require_snapshot_utils = __commonJS({
|
|
|
76128
76273
|
var require_snapshot_recorder = __commonJS({
|
|
76129
76274
|
"node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
76130
76275
|
"use strict";
|
|
76131
|
-
var { writeFile:
|
|
76276
|
+
var { writeFile: writeFile4, readFile: readFile3, mkdir: mkdir6 } = __require("fs/promises");
|
|
76132
76277
|
var { dirname: dirname4, resolve } = __require("path");
|
|
76133
76278
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
|
|
76134
76279
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
@@ -76375,12 +76520,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
76375
76520
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
76376
76521
|
}
|
|
76377
76522
|
const resolvedPath = resolve(path5);
|
|
76378
|
-
await
|
|
76523
|
+
await mkdir6(dirname4(resolvedPath), { recursive: true });
|
|
76379
76524
|
const data = Array.from(this.#snapshots.entries()).map(([hash5, snapshot]) => ({
|
|
76380
76525
|
hash: hash5,
|
|
76381
76526
|
snapshot
|
|
76382
76527
|
}));
|
|
76383
|
-
await
|
|
76528
|
+
await writeFile4(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
76384
76529
|
}
|
|
76385
76530
|
/**
|
|
76386
76531
|
* Clears all recorded snapshots
|
|
@@ -88709,9 +88854,9 @@ var init_textual_tool_calls = __esm({
|
|
|
88709
88854
|
});
|
|
88710
88855
|
|
|
88711
88856
|
// src/response-store.ts
|
|
88712
|
-
import { appendFile as appendFile2, mkdir as
|
|
88713
|
-
import { homedir as
|
|
88714
|
-
import { join as
|
|
88857
|
+
import { appendFile as appendFile2, mkdir as mkdir4, readFile, readdir as readdir2 } from "fs/promises";
|
|
88858
|
+
import { homedir as homedir7 } from "os";
|
|
88859
|
+
import { join as join10 } from "path";
|
|
88715
88860
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
88716
88861
|
function isEnabled() {
|
|
88717
88862
|
const v = process.env.BLOCKRUN_RESPONSE_STORE;
|
|
@@ -88719,12 +88864,12 @@ function isEnabled() {
|
|
|
88719
88864
|
}
|
|
88720
88865
|
async function ensureDir2() {
|
|
88721
88866
|
if (dirReady2) return;
|
|
88722
|
-
await
|
|
88867
|
+
await mkdir4(STORE_DIR, { recursive: true });
|
|
88723
88868
|
dirReady2 = true;
|
|
88724
88869
|
}
|
|
88725
88870
|
function dailyFile(date = /* @__PURE__ */ new Date()) {
|
|
88726
88871
|
const iso = date.toISOString().slice(0, 10);
|
|
88727
|
-
return
|
|
88872
|
+
return join10(STORE_DIR, `responses-${iso}.jsonl`);
|
|
88728
88873
|
}
|
|
88729
88874
|
function genId() {
|
|
88730
88875
|
return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
|
|
@@ -88774,7 +88919,7 @@ async function listRecent(limit = 20, daysBack = 7) {
|
|
|
88774
88919
|
d.setUTCDate(d.getUTCDate() - i);
|
|
88775
88920
|
const iso = d.toISOString().slice(0, 10);
|
|
88776
88921
|
const name = `responses-${iso}.jsonl`;
|
|
88777
|
-
if (files.includes(name)) candidateFiles.push(
|
|
88922
|
+
if (files.includes(name)) candidateFiles.push(join10(STORE_DIR, name));
|
|
88778
88923
|
}
|
|
88779
88924
|
const all3 = [];
|
|
88780
88925
|
for (const f of candidateFiles) {
|
|
@@ -88803,7 +88948,7 @@ var STORE_DIR, dirReady2;
|
|
|
88803
88948
|
var init_response_store = __esm({
|
|
88804
88949
|
"src/response-store.ts"() {
|
|
88805
88950
|
"use strict";
|
|
88806
|
-
STORE_DIR =
|
|
88951
|
+
STORE_DIR = join10(homedir7(), ".openclaw", "blockrun", "responses");
|
|
88807
88952
|
dirReady2 = false;
|
|
88808
88953
|
}
|
|
88809
88954
|
});
|
|
@@ -90454,15 +90599,18 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
90454
90599
|
import { createHmac } from "crypto";
|
|
90455
90600
|
import { createServer } from "http";
|
|
90456
90601
|
import { finished } from "stream";
|
|
90457
|
-
import { homedir as
|
|
90458
|
-
import { join as
|
|
90459
|
-
import { mkdir as
|
|
90602
|
+
import { homedir as homedir8 } from "os";
|
|
90603
|
+
import { join as join11 } from "path";
|
|
90604
|
+
import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile2, stat as fsStat } from "fs/promises";
|
|
90460
90605
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
90461
|
-
async function loadGatewayCatalog(apiBase) {
|
|
90606
|
+
async function loadGatewayCatalog(apiBase, apiKey) {
|
|
90462
90607
|
try {
|
|
90463
90608
|
const controller = new AbortController();
|
|
90464
90609
|
const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
|
|
90465
|
-
const res = await fetch(`${apiBase}/v1/models`, {
|
|
90610
|
+
const res = await fetch(`${apiBase}/v1/models`, {
|
|
90611
|
+
signal: controller.signal,
|
|
90612
|
+
...apiKey ? { headers: { authorization: `Bearer ${apiKey}` } } : {}
|
|
90613
|
+
});
|
|
90466
90614
|
clearTimeout(timer2);
|
|
90467
90615
|
if (!res.ok) return;
|
|
90468
90616
|
const body = await res.json();
|
|
@@ -90761,8 +90909,9 @@ async function checkExistingProxy(port) {
|
|
|
90761
90909
|
clearTimeout(timeoutId);
|
|
90762
90910
|
if (response.ok) {
|
|
90763
90911
|
const data = await response.json();
|
|
90764
|
-
|
|
90765
|
-
|
|
90912
|
+
const authMode = data.authMode === "api-key" ? "api-key" : "wallet";
|
|
90913
|
+
if (data.status === "ok" && (data.wallet || authMode === "api-key")) {
|
|
90914
|
+
return { wallet: data.wallet ?? "", paymentChain: data.paymentChain, authMode };
|
|
90766
90915
|
}
|
|
90767
90916
|
}
|
|
90768
90917
|
return void 0;
|
|
@@ -91270,7 +91419,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91270
91419
|
});
|
|
91271
91420
|
}
|
|
91272
91421
|
function readImageFileAsDataUri(filePath) {
|
|
91273
|
-
const resolved = filePath.startsWith("~/") ?
|
|
91422
|
+
const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
|
|
91274
91423
|
if (!existsSync2(resolved)) {
|
|
91275
91424
|
throw new Error(`Image file not found: ${resolved}`);
|
|
91276
91425
|
}
|
|
@@ -91313,16 +91462,48 @@ async function uploadDataUriToHost(dataUri) {
|
|
|
91313
91462
|
clearTimeout(uploadTimeout);
|
|
91314
91463
|
}
|
|
91315
91464
|
}
|
|
91465
|
+
function warnIfSpendLimitsUnenforced(spendControl) {
|
|
91466
|
+
let configured;
|
|
91467
|
+
try {
|
|
91468
|
+
const limits = (spendControl ?? new SpendControl()).getStatus().limits;
|
|
91469
|
+
configured = ["perRequest", "hourly", "daily", "session"].filter((window2) => typeof limits[window2] === "number").map((window2) => `${window2}=$${limits[window2].toFixed(2)}`);
|
|
91470
|
+
} catch {
|
|
91471
|
+
return;
|
|
91472
|
+
}
|
|
91473
|
+
if (configured.length === 0) return;
|
|
91474
|
+
console.warn(
|
|
91475
|
+
`[ClawRouter] \u26A0 Spend limits (${configured.join(", ")}) are NOT enforced in API-key mode \u2014 they gate x402 signing, and nothing is signed here.`
|
|
91476
|
+
);
|
|
91477
|
+
console.warn(
|
|
91478
|
+
`[ClawRouter] Your BlockRun account balance is the cap instead. Use maxCostPerRun for a per-session limit that still applies.`
|
|
91479
|
+
);
|
|
91480
|
+
}
|
|
91316
91481
|
async function startProxy(options) {
|
|
91317
91482
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91318
91483
|
if (upstreamProxy) {
|
|
91319
91484
|
console.log(`[ClawRouter] Upstream proxy: ${upstreamProxy}`);
|
|
91320
91485
|
}
|
|
91321
|
-
const
|
|
91322
|
-
|
|
91486
|
+
const apiKey = options.apiKey?.trim() || void 0;
|
|
91487
|
+
if (apiKey && !isValidApiKey(apiKey)) {
|
|
91488
|
+
throw new Error(
|
|
91489
|
+
`BlockRun API key is malformed (expected it to start with "brk_"). Mint one at https://user.blockrun.ai/dashboard/keys`
|
|
91490
|
+
);
|
|
91491
|
+
}
|
|
91492
|
+
const authMode = apiKey ? "api-key" : "wallet";
|
|
91493
|
+
if (!apiKey && !options.wallet) {
|
|
91494
|
+
throw new Error(
|
|
91495
|
+
`startProxy needs a credential: pass either a wallet (x402) or an apiKey (brk_\u2026, from https://user.blockrun.ai/dashboard/keys).`
|
|
91496
|
+
);
|
|
91497
|
+
}
|
|
91498
|
+
const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
|
|
91499
|
+
const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
|
|
91323
91500
|
const paymentChain = options.paymentChain ?? await resolvePaymentChain();
|
|
91324
|
-
const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91325
|
-
if (
|
|
91501
|
+
const apiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91502
|
+
if (authMode === "api-key") {
|
|
91503
|
+
console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
|
|
91504
|
+
console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
91505
|
+
warnIfSpendLimitsUnenforced(options.spendControl);
|
|
91506
|
+
} else if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
|
|
91326
91507
|
console.warn(
|
|
91327
91508
|
`[ClawRouter] \u26A0 Payment chain is Solana but no mnemonic found \u2014 falling back to Base (EVM).`
|
|
91328
91509
|
);
|
|
@@ -91333,12 +91514,31 @@ async function startProxy(options) {
|
|
|
91333
91514
|
} else if (paymentChain === "solana") {
|
|
91334
91515
|
console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
|
|
91335
91516
|
}
|
|
91336
|
-
void loadGatewayCatalog(apiBase);
|
|
91517
|
+
void loadGatewayCatalog(apiBase, apiKey);
|
|
91337
91518
|
const listenPort = options.port ?? getProxyPort();
|
|
91338
91519
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91339
91520
|
if (existingProxy) {
|
|
91340
|
-
const account2 = privateKeyToAccount(walletKey);
|
|
91341
91521
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
91522
|
+
if (existingProxy.authMode !== authMode) {
|
|
91523
|
+
throw new Error(
|
|
91524
|
+
`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.`
|
|
91525
|
+
);
|
|
91526
|
+
}
|
|
91527
|
+
if (authMode === "api-key") {
|
|
91528
|
+
options.onReady?.(listenPort);
|
|
91529
|
+
return {
|
|
91530
|
+
port: listenPort,
|
|
91531
|
+
baseUrl: baseUrl2,
|
|
91532
|
+
walletAddress: "",
|
|
91533
|
+
authMode,
|
|
91534
|
+
apiKeyLabel: maskApiKey(apiKey),
|
|
91535
|
+
balanceMonitor: new ApiKeyBalanceMonitor(),
|
|
91536
|
+
// No-op: we didn't start this proxy, so we shouldn't close it
|
|
91537
|
+
close: async () => {
|
|
91538
|
+
}
|
|
91539
|
+
};
|
|
91540
|
+
}
|
|
91541
|
+
const account2 = privateKeyToAccount(walletKey);
|
|
91342
91542
|
if (existingProxy.wallet !== account2.address) {
|
|
91343
91543
|
console.warn(
|
|
91344
91544
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
@@ -91377,20 +91577,23 @@ async function startProxy(options) {
|
|
|
91377
91577
|
baseUrl: baseUrl2,
|
|
91378
91578
|
walletAddress: existingProxy.wallet,
|
|
91379
91579
|
solanaAddress: reuseSolanaAddress,
|
|
91580
|
+
authMode,
|
|
91380
91581
|
balanceMonitor: balanceMonitor2,
|
|
91381
91582
|
close: async () => {
|
|
91382
91583
|
}
|
|
91383
91584
|
};
|
|
91384
91585
|
}
|
|
91385
|
-
const account = privateKeyToAccount(walletKey);
|
|
91386
|
-
const
|
|
91387
|
-
|
|
91388
|
-
|
|
91389
|
-
|
|
91390
|
-
|
|
91391
|
-
|
|
91586
|
+
const account = walletKey ? privateKeyToAccount(walletKey) : void 0;
|
|
91587
|
+
const x402 = authMode === "wallet" ? new x402Client() : void 0;
|
|
91588
|
+
if (x402 && account) {
|
|
91589
|
+
const evmPublicClient = createPublicClient({ chain: base, transport: http() });
|
|
91590
|
+
const evmSigner = toClientEvmSigner(account, evmPublicClient);
|
|
91591
|
+
const spendControl = options.spendControl ?? new SpendControl();
|
|
91592
|
+
registerSpendPolicyHook(x402, spendControl);
|
|
91593
|
+
registerExactEvmScheme(x402, { signer: evmSigner });
|
|
91594
|
+
}
|
|
91392
91595
|
let solanaAddress;
|
|
91393
|
-
if (solanaPrivateKeyBytes) {
|
|
91596
|
+
if (x402 && solanaPrivateKeyBytes) {
|
|
91394
91597
|
const { registerExactSvmScheme: registerExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91395
91598
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91396
91599
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
@@ -91398,12 +91601,12 @@ async function startProxy(options) {
|
|
|
91398
91601
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91399
91602
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91400
91603
|
}
|
|
91401
|
-
x402
|
|
91604
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91402
91605
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
91403
91606
|
const payload = context.paymentPayload;
|
|
91404
91607
|
payload.extensions = withBuilderCodeServiceCode(payload.extensions);
|
|
91405
91608
|
});
|
|
91406
|
-
x402
|
|
91609
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91407
91610
|
const network = context.selectedRequirements.network;
|
|
91408
91611
|
const chain3 = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
|
|
91409
91612
|
const amountMicros = parseInt(context.selectedRequirements.amount || "0", 10);
|
|
@@ -91412,7 +91615,7 @@ async function startProxy(options) {
|
|
|
91412
91615
|
if (store) store.amountUsd = amountUsd;
|
|
91413
91616
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91414
91617
|
});
|
|
91415
|
-
const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91618
|
+
const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91416
91619
|
skipPreAuth: paymentChain === "solana",
|
|
91417
91620
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91418
91621
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -91422,6 +91625,8 @@ async function startProxy(options) {
|
|
|
91422
91625
|
let balanceMonitor;
|
|
91423
91626
|
if (options._balanceMonitorOverride) {
|
|
91424
91627
|
balanceMonitor = options._balanceMonitorOverride;
|
|
91628
|
+
} else if (authMode === "api-key") {
|
|
91629
|
+
balanceMonitor = new ApiKeyBalanceMonitor();
|
|
91425
91630
|
} else if (paymentChain === "solana" && solanaAddress) {
|
|
91426
91631
|
const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
|
|
91427
91632
|
balanceMonitor = new SolanaBalanceMonitor2(solanaAddress);
|
|
@@ -91463,11 +91668,17 @@ async function startProxy(options) {
|
|
|
91463
91668
|
const full = url2.searchParams.get("full") === "true";
|
|
91464
91669
|
const response = {
|
|
91465
91670
|
status: "ok",
|
|
91466
|
-
|
|
91467
|
-
paymentChain
|
|
91671
|
+
authMode
|
|
91468
91672
|
};
|
|
91469
|
-
if (
|
|
91470
|
-
response.
|
|
91673
|
+
if (authMode === "api-key") {
|
|
91674
|
+
response.apiKey = maskApiKey(apiKey);
|
|
91675
|
+
response.gateway = apiBase;
|
|
91676
|
+
} else {
|
|
91677
|
+
response.wallet = account.address;
|
|
91678
|
+
response.paymentChain = paymentChain;
|
|
91679
|
+
if (solanaAddress) {
|
|
91680
|
+
response.solana = solanaAddress;
|
|
91681
|
+
}
|
|
91471
91682
|
}
|
|
91472
91683
|
if (upstreamProxy) {
|
|
91473
91684
|
response.upstreamProxy = upstreamProxy;
|
|
@@ -91483,9 +91694,15 @@ async function startProxy(options) {
|
|
|
91483
91694
|
).unref()
|
|
91484
91695
|
)
|
|
91485
91696
|
]);
|
|
91486
|
-
|
|
91487
|
-
|
|
91488
|
-
|
|
91697
|
+
if (authMode === "api-key") {
|
|
91698
|
+
response.balance = null;
|
|
91699
|
+
response.billing = "BlockRun account credit";
|
|
91700
|
+
response.topUpUrl = PORTAL_CREDITS_URL;
|
|
91701
|
+
} else {
|
|
91702
|
+
response.balance = balanceInfo.balanceUSD;
|
|
91703
|
+
response.isLow = balanceInfo.isLow;
|
|
91704
|
+
response.isEmpty = balanceInfo.isEmpty;
|
|
91705
|
+
}
|
|
91489
91706
|
} catch {
|
|
91490
91707
|
response.balanceError = "Could not fetch balance";
|
|
91491
91708
|
}
|
|
@@ -91665,7 +91882,7 @@ async function startProxy(options) {
|
|
|
91665
91882
|
res.end("Bad request");
|
|
91666
91883
|
return;
|
|
91667
91884
|
}
|
|
91668
|
-
const filePath =
|
|
91885
|
+
const filePath = join11(IMAGE_DIR, filename);
|
|
91669
91886
|
try {
|
|
91670
91887
|
const s3 = await fsStat(filePath);
|
|
91671
91888
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91696,7 +91913,7 @@ async function startProxy(options) {
|
|
|
91696
91913
|
res.end("Bad request");
|
|
91697
91914
|
return;
|
|
91698
91915
|
}
|
|
91699
|
-
const filePath =
|
|
91916
|
+
const filePath = join11(AUDIO_DIR, filename);
|
|
91700
91917
|
try {
|
|
91701
91918
|
const s3 = await fsStat(filePath);
|
|
91702
91919
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91726,7 +91943,7 @@ async function startProxy(options) {
|
|
|
91726
91943
|
res.end("Bad request");
|
|
91727
91944
|
return;
|
|
91728
91945
|
}
|
|
91729
|
-
const filePath =
|
|
91946
|
+
const filePath = join11(VIDEO_DIR, filename);
|
|
91730
91947
|
try {
|
|
91731
91948
|
const s3 = await fsStat(filePath);
|
|
91732
91949
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91859,7 +92076,7 @@ async function startProxy(options) {
|
|
|
91859
92076
|
}
|
|
91860
92077
|
}
|
|
91861
92078
|
if (result.data?.length) {
|
|
91862
|
-
await
|
|
92079
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91863
92080
|
const port2 = server.address()?.port ?? 8402;
|
|
91864
92081
|
for (const img of result.data) {
|
|
91865
92082
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91867,7 +92084,7 @@ async function startProxy(options) {
|
|
|
91867
92084
|
const [, mimeType, b64] = dataUriMatch;
|
|
91868
92085
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91869
92086
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91870
|
-
await
|
|
92087
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91871
92088
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91872
92089
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91873
92090
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -91878,7 +92095,7 @@ async function startProxy(options) {
|
|
|
91878
92095
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
91879
92096
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91880
92097
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
91881
|
-
await
|
|
92098
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
91882
92099
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91883
92100
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
91884
92101
|
}
|
|
@@ -91982,7 +92199,7 @@ async function startProxy(options) {
|
|
|
91982
92199
|
return;
|
|
91983
92200
|
}
|
|
91984
92201
|
if (result.data?.length) {
|
|
91985
|
-
await
|
|
92202
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91986
92203
|
const port2 = server.address()?.port ?? 8402;
|
|
91987
92204
|
for (const img of result.data) {
|
|
91988
92205
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91990,7 +92207,7 @@ async function startProxy(options) {
|
|
|
91990
92207
|
const [, mimeType, b64] = dataUriMatch;
|
|
91991
92208
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91992
92209
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91993
|
-
await
|
|
92210
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91994
92211
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91995
92212
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91996
92213
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -92001,7 +92218,7 @@ async function startProxy(options) {
|
|
|
92001
92218
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
92002
92219
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92003
92220
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
92004
|
-
await
|
|
92221
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
92005
92222
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
92006
92223
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
92007
92224
|
}
|
|
@@ -92076,7 +92293,7 @@ async function startProxy(options) {
|
|
|
92076
92293
|
return;
|
|
92077
92294
|
}
|
|
92078
92295
|
if (result.data?.length) {
|
|
92079
|
-
await
|
|
92296
|
+
await mkdir5(AUDIO_DIR, { recursive: true });
|
|
92080
92297
|
const port2 = server.address()?.port ?? 8402;
|
|
92081
92298
|
for (const track of result.data) {
|
|
92082
92299
|
if (track.url?.startsWith("https://") || track.url?.startsWith("http://")) {
|
|
@@ -92087,7 +92304,7 @@ async function startProxy(options) {
|
|
|
92087
92304
|
const ext = contentType.includes("wav") ? "wav" : "mp3";
|
|
92088
92305
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92089
92306
|
const buf = Buffer.from(await audioResp.arrayBuffer());
|
|
92090
|
-
await
|
|
92307
|
+
await writeFile3(join11(AUDIO_DIR, filename), buf);
|
|
92091
92308
|
track.url = `http://localhost:${port2}/audio/${filename}`;
|
|
92092
92309
|
console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
|
|
92093
92310
|
}
|
|
@@ -92236,7 +92453,7 @@ async function startProxy(options) {
|
|
|
92236
92453
|
}
|
|
92237
92454
|
}
|
|
92238
92455
|
if (finalResult.data?.length) {
|
|
92239
|
-
await
|
|
92456
|
+
await mkdir5(VIDEO_DIR, { recursive: true });
|
|
92240
92457
|
const port2 = server.address()?.port ?? 8402;
|
|
92241
92458
|
for (const clip of finalResult.data) {
|
|
92242
92459
|
if (clip.url?.startsWith("https://") || clip.url?.startsWith("http://")) {
|
|
@@ -92247,7 +92464,7 @@ async function startProxy(options) {
|
|
|
92247
92464
|
const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
|
|
92248
92465
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92249
92466
|
const buf = Buffer.from(await videoResp.arrayBuffer());
|
|
92250
|
-
await
|
|
92467
|
+
await writeFile3(join11(VIDEO_DIR, filename), buf);
|
|
92251
92468
|
clip.url = `http://localhost:${port2}/videos/${filename}`;
|
|
92252
92469
|
console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
|
|
92253
92470
|
}
|
|
@@ -92430,6 +92647,8 @@ async function startProxy(options) {
|
|
|
92430
92647
|
port: listenPort,
|
|
92431
92648
|
baseUrl: baseUrl2,
|
|
92432
92649
|
walletAddress: error.wallet,
|
|
92650
|
+
authMode,
|
|
92651
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92433
92652
|
balanceMonitor,
|
|
92434
92653
|
close: async () => {
|
|
92435
92654
|
}
|
|
@@ -92480,8 +92699,10 @@ async function startProxy(options) {
|
|
|
92480
92699
|
return {
|
|
92481
92700
|
port,
|
|
92482
92701
|
baseUrl,
|
|
92483
|
-
walletAddress: account
|
|
92702
|
+
walletAddress: account?.address ?? "",
|
|
92484
92703
|
solanaAddress,
|
|
92704
|
+
authMode,
|
|
92705
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92485
92706
|
balanceMonitor,
|
|
92486
92707
|
close: () => new Promise((res, rej) => {
|
|
92487
92708
|
const timeout = setTimeout(() => {
|
|
@@ -94804,6 +95025,7 @@ var init_proxy = __esm({
|
|
|
94804
95025
|
init_response_cache();
|
|
94805
95026
|
init_balance();
|
|
94806
95027
|
init_auth();
|
|
95028
|
+
init_api_key();
|
|
94807
95029
|
init_spend_control();
|
|
94808
95030
|
init_compression();
|
|
94809
95031
|
init_version4();
|
|
@@ -94820,10 +95042,10 @@ var init_proxy = __esm({
|
|
|
94820
95042
|
paymentStore = new AsyncLocalStorage();
|
|
94821
95043
|
BLOCKRUN_API = "https://blockrun.ai/api";
|
|
94822
95044
|
BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
94823
|
-
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ??
|
|
94824
|
-
IMAGE_DIR =
|
|
94825
|
-
AUDIO_DIR =
|
|
94826
|
-
VIDEO_DIR =
|
|
95045
|
+
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join11(homedir8(), ".clawrouter-desktop", "service-token");
|
|
95046
|
+
IMAGE_DIR = join11(homedir8(), ".openclaw", "blockrun", "images");
|
|
95047
|
+
AUDIO_DIR = join11(homedir8(), ".openclaw", "blockrun", "audio");
|
|
95048
|
+
VIDEO_DIR = join11(homedir8(), ".openclaw", "blockrun", "videos");
|
|
94827
95049
|
AUTO_MODEL = "blockrun/auto";
|
|
94828
95050
|
ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
94829
95051
|
"blockrun/eco",
|
|
@@ -117219,8 +117441,8 @@ var init_constants3 = __esm({
|
|
|
117219
117441
|
|
|
117220
117442
|
// src/polymarket/wallet-adapter.ts
|
|
117221
117443
|
import fs2 from "fs";
|
|
117222
|
-
import { homedir as
|
|
117223
|
-
import { join as
|
|
117444
|
+
import { homedir as homedir9 } from "os";
|
|
117445
|
+
import { join as join12 } from "path";
|
|
117224
117446
|
function getOrCreateWalletKey() {
|
|
117225
117447
|
const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
|
|
117226
117448
|
if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
|
|
@@ -117268,7 +117490,7 @@ var init_wallet_adapter = __esm({
|
|
|
117268
117490
|
init_esm();
|
|
117269
117491
|
init_chains();
|
|
117270
117492
|
init_constants3();
|
|
117271
|
-
WALLET_FILE2 =
|
|
117493
|
+
WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
|
|
117272
117494
|
BASE_RPC_URLS = [
|
|
117273
117495
|
"https://mainnet.base.org",
|
|
117274
117496
|
"https://base.llamarpc.com",
|
|
@@ -227660,9 +227882,9 @@ import {
|
|
|
227660
227882
|
unlinkSync
|
|
227661
227883
|
} from "fs";
|
|
227662
227884
|
import { readFile as readFileAsync } from "fs/promises";
|
|
227663
|
-
import { homedir as
|
|
227885
|
+
import { homedir as homedir14 } from "os";
|
|
227664
227886
|
import { randomUUID } from "crypto";
|
|
227665
|
-
import { join as
|
|
227887
|
+
import { join as join17 } from "path";
|
|
227666
227888
|
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
227667
227889
|
const start = Date.now();
|
|
227668
227890
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -227704,8 +227926,8 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
227704
227926
|
return cfg.tools?.web?.search?.enabled === false;
|
|
227705
227927
|
}
|
|
227706
227928
|
function injectModelsConfig(logger48, options = {}) {
|
|
227707
|
-
const configDir =
|
|
227708
|
-
const configPath =
|
|
227929
|
+
const configDir = join17(homedir14(), ".openclaw");
|
|
227930
|
+
const configPath = join17(configDir, "openclaw.json");
|
|
227709
227931
|
let config = {};
|
|
227710
227932
|
let needsWrite = false;
|
|
227711
227933
|
if (!existsSync4(configDir)) {
|
|
@@ -227926,7 +228148,7 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
227926
228148
|
}
|
|
227927
228149
|
function syncAgentModelCache(logger48, options = {}) {
|
|
227928
228150
|
if (!isGatewayMode() && !options.forceWrite) return;
|
|
227929
|
-
const agentsDir =
|
|
228151
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227930
228152
|
if (!existsSync4(agentsDir)) return;
|
|
227931
228153
|
let agentDirs;
|
|
227932
228154
|
try {
|
|
@@ -227936,7 +228158,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227936
228158
|
}
|
|
227937
228159
|
const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
|
|
227938
228160
|
for (const agent of agentDirs) {
|
|
227939
|
-
const cachePath =
|
|
228161
|
+
const cachePath = join17(agentsDir, agent, "agent", "models.json");
|
|
227940
228162
|
if (!existsSync4(cachePath)) continue;
|
|
227941
228163
|
try {
|
|
227942
228164
|
const raw = readTextFileSync(cachePath).trim();
|
|
@@ -227965,7 +228187,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227965
228187
|
}
|
|
227966
228188
|
}
|
|
227967
228189
|
function injectAuthProfile(logger48) {
|
|
227968
|
-
const agentsDir =
|
|
228190
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227969
228191
|
if (!existsSync4(agentsDir)) {
|
|
227970
228192
|
try {
|
|
227971
228193
|
mkdirSync3(agentsDir, { recursive: true });
|
|
@@ -227982,9 +228204,9 @@ function injectAuthProfile(logger48) {
|
|
|
227982
228204
|
agents = ["main", ...agents];
|
|
227983
228205
|
}
|
|
227984
228206
|
for (const agentId of agents) {
|
|
227985
|
-
const authDir =
|
|
227986
|
-
const authPath =
|
|
227987
|
-
const sqlitePath =
|
|
228207
|
+
const authDir = join17(agentsDir, agentId, "agent");
|
|
228208
|
+
const authPath = join17(authDir, "auth-profiles.json");
|
|
228209
|
+
const sqlitePath = join17(authDir, "openclaw-agent.sqlite");
|
|
227988
228210
|
if (existsSync4(sqlitePath)) {
|
|
227989
228211
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
227990
228212
|
continue;
|
|
@@ -228131,9 +228353,21 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228131
228353
|
if (startupGeneration !== void 0 && isProxyStartupCurrent(startupGeneration, proc)) {
|
|
228132
228354
|
proc.__clawrouterStartupPhase = "starting";
|
|
228133
228355
|
}
|
|
228356
|
+
const pluginApiKey = api.pluginConfig?.apiKey;
|
|
228357
|
+
const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
|
|
228358
|
+
if (typeof pluginApiKey === "string" && !isValidApiKey(pluginApiKey)) {
|
|
228359
|
+
api.logger.warn(
|
|
228360
|
+
`pluginConfig.apiKey is set but invalid (expected brk_...) \u2014 ignoring it and looking elsewhere`
|
|
228361
|
+
);
|
|
228362
|
+
}
|
|
228363
|
+
const apiKey = resolvedApiKey?.key;
|
|
228134
228364
|
const configKey = api.pluginConfig?.walletKey;
|
|
228135
228365
|
let wallet;
|
|
228136
|
-
if (
|
|
228366
|
+
if (apiKey) {
|
|
228367
|
+
api.logger.info(
|
|
228368
|
+
`Using BlockRun API key ${maskApiKey(apiKey)} (from ${resolvedApiKey.source}) \u2014 billing account credit, no wallet`
|
|
228369
|
+
);
|
|
228370
|
+
} else if (typeof configKey === "string" && /^0x[0-9a-fA-F]{64}$/.test(configKey)) {
|
|
228137
228371
|
const account = privateKeyToAccount(configKey);
|
|
228138
228372
|
wallet = { key: configKey, address: account.address, source: "config" };
|
|
228139
228373
|
} else {
|
|
@@ -228144,7 +228378,8 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228144
228378
|
}
|
|
228145
228379
|
wallet = await resolveOrGenerateWalletKey();
|
|
228146
228380
|
}
|
|
228147
|
-
if (wallet
|
|
228381
|
+
if (!wallet) {
|
|
228382
|
+
} else if (wallet.source === "generated") {
|
|
228148
228383
|
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`);
|
|
228149
228384
|
api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
|
|
228150
228385
|
api.logger.warn(` Address : ${wallet.address}`);
|
|
@@ -228169,13 +228404,13 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228169
228404
|
);
|
|
228170
228405
|
}
|
|
228171
228406
|
const proxy = await startProxy({
|
|
228172
|
-
wallet,
|
|
228407
|
+
...apiKey ? { apiKey } : { wallet },
|
|
228173
228408
|
routingConfig,
|
|
228174
228409
|
maxCostPerRunUsd,
|
|
228175
228410
|
maxCostPerRunMode,
|
|
228176
228411
|
spendControl: liveSpendControl = new SpendControl(),
|
|
228177
228412
|
onReady: (port) => {
|
|
228178
|
-
api.logger.info(`BlockRun x402 proxy listening on port ${port}`);
|
|
228413
|
+
api.logger.info(`BlockRun ${apiKey ? "API-key" : "x402"} proxy listening on port ${port}`);
|
|
228179
228414
|
},
|
|
228180
228415
|
onError: (error) => {
|
|
228181
228416
|
api.logger.error(`BlockRun proxy error: ${error.message}`);
|
|
@@ -228221,6 +228456,10 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228221
228456
|
}
|
|
228222
228457
|
api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
|
|
228223
228458
|
api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
|
|
228459
|
+
if (apiKey) {
|
|
228460
|
+
api.logger.info(`Billing: BlockRun account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
228461
|
+
return true;
|
|
228462
|
+
}
|
|
228224
228463
|
const currentChain = await resolvePaymentChain();
|
|
228225
228464
|
const displayAddress = currentChain === "solana" && proxy.solanaAddress ? proxy.solanaAddress : wallet.address;
|
|
228226
228465
|
const network = currentChain === "solana" ? "Solana" : "Base";
|
|
@@ -228464,7 +228703,7 @@ function buildImageGenerationProvider() {
|
|
|
228464
228703
|
(result.data ?? []).map(async (img) => {
|
|
228465
228704
|
const filename = img.url?.split("/images/").pop();
|
|
228466
228705
|
if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
|
|
228467
|
-
const filePath =
|
|
228706
|
+
const filePath = join17(IMAGE_DIR2, filename);
|
|
228468
228707
|
const buffer2 = await readFileAsync(filePath);
|
|
228469
228708
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
228470
228709
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
@@ -228516,7 +228755,7 @@ function buildMusicGenerationProvider() {
|
|
|
228516
228755
|
(result.data ?? []).map(async (track) => {
|
|
228517
228756
|
const filename = track.url?.split("/audio/").pop();
|
|
228518
228757
|
if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
|
|
228519
|
-
const filePath =
|
|
228758
|
+
const filePath = join17(AUDIO_DIR2, filename);
|
|
228520
228759
|
const buffer2 = await readFileAsync(filePath);
|
|
228521
228760
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
|
|
228522
228761
|
const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
|
|
@@ -228592,7 +228831,7 @@ function buildVideoGenerationProvider() {
|
|
|
228592
228831
|
(result.data ?? []).map(async (clip) => {
|
|
228593
228832
|
const filename = clip.url?.split("/videos/").pop();
|
|
228594
228833
|
if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
|
|
228595
|
-
const filePath =
|
|
228834
|
+
const filePath = join17(VIDEO_DIR2, filename);
|
|
228596
228835
|
const buffer2 = await readFileAsync(filePath);
|
|
228597
228836
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
|
|
228598
228837
|
const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
|
|
@@ -228645,6 +228884,22 @@ function createWalletCommand(api) {
|
|
|
228645
228884
|
requireAuth: true,
|
|
228646
228885
|
handler: async (ctx) => {
|
|
228647
228886
|
const subcommand = ctx.args?.trim().toLowerCase() || "status";
|
|
228887
|
+
const activeApiKey = (await resolveApiKey())?.key;
|
|
228888
|
+
if (activeApiKey) {
|
|
228889
|
+
return {
|
|
228890
|
+
text: [
|
|
228891
|
+
"**BlockRun account** (API key)",
|
|
228892
|
+
"",
|
|
228893
|
+
`**Key:** \`${maskApiKey(activeApiKey)}\``,
|
|
228894
|
+
"**Billing:** account credit \u2014 no wallet, no payment chain, no gas.",
|
|
228895
|
+
"",
|
|
228896
|
+
`**Add credit:** ${PORTAL_CREDITS_URL}`,
|
|
228897
|
+
`**Usage & keys:** ${PORTAL_KEYS_URL}`,
|
|
228898
|
+
"",
|
|
228899
|
+
"To pay with a USDC wallet over x402 instead, run `clawrouter logout` and restart."
|
|
228900
|
+
].join("\n")
|
|
228901
|
+
};
|
|
228902
|
+
}
|
|
228648
228903
|
let wallet;
|
|
228649
228904
|
try {
|
|
228650
228905
|
wallet = subcommand === "status" || subcommand === "export" ? await resolveExistingWalletKey() : await resolveOrGenerateWalletKey();
|
|
@@ -228902,6 +229157,7 @@ var init_index = __esm({
|
|
|
228902
229157
|
"use strict";
|
|
228903
229158
|
init_provider();
|
|
228904
229159
|
init_proxy();
|
|
229160
|
+
init_api_key();
|
|
228905
229161
|
init_web_search_provider();
|
|
228906
229162
|
init_auth();
|
|
228907
229163
|
init_balance();
|
|
@@ -228941,9 +229197,9 @@ var init_index = __esm({
|
|
|
228941
229197
|
activeProxyHandle = null;
|
|
228942
229198
|
liveSpendControl = null;
|
|
228943
229199
|
pendingConfiguredStartupApi = null;
|
|
228944
|
-
IMAGE_DIR2 =
|
|
228945
|
-
AUDIO_DIR2 =
|
|
228946
|
-
VIDEO_DIR2 =
|
|
229200
|
+
IMAGE_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "images");
|
|
229201
|
+
AUDIO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "audio");
|
|
229202
|
+
VIDEO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "videos");
|
|
228947
229203
|
plugin = {
|
|
228948
229204
|
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
228949
229205
|
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
@@ -229360,7 +229616,7 @@ ${errText}`
|
|
|
229360
229616
|
}
|
|
229361
229617
|
resetProxyStartupState();
|
|
229362
229618
|
try {
|
|
229363
|
-
const configPath =
|
|
229619
|
+
const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
|
|
229364
229620
|
if (existsSync4(configPath)) {
|
|
229365
229621
|
const config = JSON.parse(readTextFileSync(configPath));
|
|
229366
229622
|
if (config.models?.providers?.blockrun) {
|
|
@@ -229402,11 +229658,11 @@ ${errText}`
|
|
|
229402
229658
|
api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
229403
229659
|
}
|
|
229404
229660
|
try {
|
|
229405
|
-
const agentsDir =
|
|
229661
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
229406
229662
|
if (existsSync4(agentsDir)) {
|
|
229407
229663
|
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
229408
229664
|
if (!entry.isDirectory()) continue;
|
|
229409
|
-
const authPath =
|
|
229665
|
+
const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
|
|
229410
229666
|
if (!existsSync4(authPath)) continue;
|
|
229411
229667
|
try {
|
|
229412
229668
|
const store = JSON.parse(readTextFileSync(authPath));
|
|
@@ -229485,6 +229741,7 @@ init_client2();
|
|
|
229485
229741
|
init_esm5();
|
|
229486
229742
|
init_auth();
|
|
229487
229743
|
init_balance();
|
|
229744
|
+
init_api_key();
|
|
229488
229745
|
init_wallet2();
|
|
229489
229746
|
init_stats();
|
|
229490
229747
|
init_proxy();
|
|
@@ -229526,7 +229783,48 @@ function collectSystemInfo() {
|
|
|
229526
229783
|
memoryTotal: formatBytes(totalmem())
|
|
229527
229784
|
};
|
|
229528
229785
|
}
|
|
229529
|
-
async function
|
|
229786
|
+
async function collectApiKeyInfo() {
|
|
229787
|
+
const resolved = await resolveApiKey();
|
|
229788
|
+
if (!resolved) {
|
|
229789
|
+
return {
|
|
229790
|
+
configured: false,
|
|
229791
|
+
masked: null,
|
|
229792
|
+
source: null,
|
|
229793
|
+
gateway: BLOCKRUN_API_KEY_API,
|
|
229794
|
+
accepted: null
|
|
229795
|
+
};
|
|
229796
|
+
}
|
|
229797
|
+
let accepted = null;
|
|
229798
|
+
try {
|
|
229799
|
+
const response = await fetch(`${BLOCKRUN_API_KEY_API}/v1/models`, {
|
|
229800
|
+
headers: { authorization: `Bearer ${resolved.key}` },
|
|
229801
|
+
signal: AbortSignal.timeout(1e4)
|
|
229802
|
+
});
|
|
229803
|
+
if (response.status === 401) accepted = false;
|
|
229804
|
+
else if (response.ok) accepted = true;
|
|
229805
|
+
} catch {
|
|
229806
|
+
}
|
|
229807
|
+
return {
|
|
229808
|
+
configured: true,
|
|
229809
|
+
masked: maskApiKey(resolved.key),
|
|
229810
|
+
source: resolved.source,
|
|
229811
|
+
gateway: BLOCKRUN_API_KEY_API,
|
|
229812
|
+
accepted
|
|
229813
|
+
};
|
|
229814
|
+
}
|
|
229815
|
+
async function collectWalletInfo(apiKeyConfigured) {
|
|
229816
|
+
const empty = {
|
|
229817
|
+
exists: false,
|
|
229818
|
+
valid: false,
|
|
229819
|
+
address: null,
|
|
229820
|
+
solanaAddress: null,
|
|
229821
|
+
balance: null,
|
|
229822
|
+
isLow: false,
|
|
229823
|
+
isEmpty: true,
|
|
229824
|
+
source: null,
|
|
229825
|
+
paymentChain: "base"
|
|
229826
|
+
};
|
|
229827
|
+
if (apiKeyConfigured) return { ...empty, isEmpty: false };
|
|
229530
229828
|
try {
|
|
229531
229829
|
const { key: key2, address: address2, source, solanaPrivateKeyBytes } = await resolveOrGenerateWalletKey();
|
|
229532
229830
|
if (!key2 || !address2) {
|
|
@@ -229645,6 +229943,14 @@ async function collectLogInfo() {
|
|
|
229645
229943
|
}
|
|
229646
229944
|
function identifyIssues(result) {
|
|
229647
229945
|
const issues = [];
|
|
229946
|
+
if (result.apiKey.configured) {
|
|
229947
|
+
if (result.apiKey.accepted === false) {
|
|
229948
|
+
issues.push(
|
|
229949
|
+
`BlockRun rejected the API key (${result.apiKey.masked}) \u2014 it may be revoked or mistyped. Mint a new one at ${PORTAL_KEYS_URL}`
|
|
229950
|
+
);
|
|
229951
|
+
}
|
|
229952
|
+
return finishIssues(result, issues);
|
|
229953
|
+
}
|
|
229648
229954
|
if (!result.wallet.exists) {
|
|
229649
229955
|
issues.push("No wallet found");
|
|
229650
229956
|
}
|
|
@@ -229657,6 +229963,9 @@ function identifyIssues(result) {
|
|
|
229657
229963
|
} else if (result.wallet.isLow) {
|
|
229658
229964
|
issues.push("Wallet balance is low (< $1.00)");
|
|
229659
229965
|
}
|
|
229966
|
+
return finishIssues(result, issues);
|
|
229967
|
+
}
|
|
229968
|
+
function finishIssues(result, issues) {
|
|
229660
229969
|
if (!result.network.blockrunApi.reachable) {
|
|
229661
229970
|
issues.push("Cannot reach BlockRun API - check internet connection");
|
|
229662
229971
|
}
|
|
@@ -229690,6 +229999,22 @@ function printDiagnostics(result) {
|
|
|
229690
229999
|
console.log(
|
|
229691
230000
|
` ${green(`Memory: ${result.system.memoryFree} free / ${result.system.memoryTotal}`)}`
|
|
229692
230001
|
);
|
|
230002
|
+
if (result.apiKey.configured) {
|
|
230003
|
+
console.log("\nBlockRun account (API key)");
|
|
230004
|
+
console.log(` ${green(`Key: ${result.apiKey.masked} (from ${result.apiKey.source})`)}`);
|
|
230005
|
+
console.log(` ${green(`Gateway: ${result.apiKey.gateway}`)}`);
|
|
230006
|
+
if (result.apiKey.accepted === true) {
|
|
230007
|
+
console.log(` ${green("Key accepted by BlockRun")}`);
|
|
230008
|
+
} else if (result.apiKey.accepted === false) {
|
|
230009
|
+
console.log(` ${red("Key REJECTED by BlockRun (401) \u2014 revoked or mistyped")}`);
|
|
230010
|
+
console.log(` ${yellow(`Mint a new key: ${PORTAL_KEYS_URL}`)}`);
|
|
230011
|
+
} else {
|
|
230012
|
+
console.log(` ${yellow("Could not verify the key (gateway unreachable)")}`);
|
|
230013
|
+
}
|
|
230014
|
+
console.log(` ${green(`Credit: billed server-side \u2014 top up at ${PORTAL_CREDITS_URL}`)}`);
|
|
230015
|
+
printRestOfDiagnostics(result);
|
|
230016
|
+
return;
|
|
230017
|
+
}
|
|
229693
230018
|
console.log("\nWallet");
|
|
229694
230019
|
if (result.wallet.exists && result.wallet.valid) {
|
|
229695
230020
|
const walletPath = result.wallet.source === "core" ? CORE_WALLET_FILE : WALLET_FILE;
|
|
@@ -229717,6 +230042,9 @@ function printDiagnostics(result) {
|
|
|
229717
230042
|
} else {
|
|
229718
230043
|
console.log(` ${red("No wallet found")}`);
|
|
229719
230044
|
}
|
|
230045
|
+
printRestOfDiagnostics(result);
|
|
230046
|
+
}
|
|
230047
|
+
function printRestOfDiagnostics(result) {
|
|
229720
230048
|
console.log("\nNetwork");
|
|
229721
230049
|
if (result.network.blockrunApi.reachable) {
|
|
229722
230050
|
console.log(
|
|
@@ -229766,6 +230094,10 @@ function createDoctorX402Client(opts) {
|
|
|
229766
230094
|
return x402;
|
|
229767
230095
|
}
|
|
229768
230096
|
async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
230097
|
+
if (diagnostics.apiKey.configured) {
|
|
230098
|
+
await analyzeWithApiKey(diagnostics, userQuestion, model);
|
|
230099
|
+
return;
|
|
230100
|
+
}
|
|
229769
230101
|
if (diagnostics.wallet.isEmpty) {
|
|
229770
230102
|
console.log("\n\u{1F4B3} Wallet is empty - cannot call AI for analysis.");
|
|
229771
230103
|
console.log(` Fund your EVM wallet with USDC on Base: ${diagnostics.wallet.address}`);
|
|
@@ -229813,30 +230145,7 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
|
229813
230145
|
body: JSON.stringify({
|
|
229814
230146
|
model: modelConfig.id,
|
|
229815
230147
|
stream: false,
|
|
229816
|
-
messages:
|
|
229817
|
-
{
|
|
229818
|
-
role: "system",
|
|
229819
|
-
content: `You are a technical support expert for BlockRun and ClawRouter.
|
|
229820
|
-
Analyze the diagnostics and:
|
|
229821
|
-
1. Identify the root cause of any issues
|
|
229822
|
-
2. Provide specific, actionable fix commands (bash)
|
|
229823
|
-
3. Explain why the issue occurred briefly
|
|
229824
|
-
4. Be concise but thorough
|
|
229825
|
-
5. Format commands in code blocks`
|
|
229826
|
-
},
|
|
229827
|
-
{
|
|
229828
|
-
role: "user",
|
|
229829
|
-
content: userQuestion ? `Here are my system diagnostics:
|
|
229830
|
-
|
|
229831
|
-
${JSON.stringify(diagnostics, null, 2)}
|
|
229832
|
-
|
|
229833
|
-
User's question: ${userQuestion}` : `Here are my system diagnostics:
|
|
229834
|
-
|
|
229835
|
-
${JSON.stringify(diagnostics, null, 2)}
|
|
229836
|
-
|
|
229837
|
-
Please analyze and help me fix any issues.`
|
|
229838
|
-
}
|
|
229839
|
-
],
|
|
230148
|
+
messages: doctorMessages(diagnostics, userQuestion),
|
|
229840
230149
|
max_tokens: 1e3
|
|
229841
230150
|
})
|
|
229842
230151
|
});
|
|
@@ -229860,13 +230169,91 @@ Error calling AI: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
229860
230169
|
console.log("Try again or check your wallet balance.\n");
|
|
229861
230170
|
}
|
|
229862
230171
|
}
|
|
230172
|
+
async function analyzeWithApiKey(diagnostics, userQuestion, model) {
|
|
230173
|
+
const resolved = await resolveApiKey();
|
|
230174
|
+
if (!resolved) return;
|
|
230175
|
+
const modelConfig = DOCTOR_MODELS[model];
|
|
230176
|
+
console.log(`
|
|
230177
|
+
\u{1F4E4} Sending to ${modelConfig.name} (${modelConfig.cost})...
|
|
230178
|
+
`);
|
|
230179
|
+
try {
|
|
230180
|
+
const response = await fetch(`${BLOCKRUN_API_KEY_API}/v1/chat/completions`, {
|
|
230181
|
+
method: "POST",
|
|
230182
|
+
headers: {
|
|
230183
|
+
"Content-Type": "application/json",
|
|
230184
|
+
authorization: `Bearer ${resolved.key}`
|
|
230185
|
+
},
|
|
230186
|
+
body: JSON.stringify({
|
|
230187
|
+
model: modelConfig.id,
|
|
230188
|
+
stream: false,
|
|
230189
|
+
messages: doctorMessages(diagnostics, userQuestion),
|
|
230190
|
+
max_tokens: 1e3
|
|
230191
|
+
})
|
|
230192
|
+
});
|
|
230193
|
+
if (!response.ok) {
|
|
230194
|
+
const text = await response.text();
|
|
230195
|
+
console.log(`Error: ${response.status} - ${text}`);
|
|
230196
|
+
if (response.status === 402) {
|
|
230197
|
+
console.log(`
|
|
230198
|
+
Your BlockRun credit is exhausted. Top up: ${PORTAL_CREDITS_URL}
|
|
230199
|
+
`);
|
|
230200
|
+
} else if (response.status === 401) {
|
|
230201
|
+
console.log(`
|
|
230202
|
+
BlockRun rejected the key. Mint a new one: ${PORTAL_KEYS_URL}
|
|
230203
|
+
`);
|
|
230204
|
+
}
|
|
230205
|
+
return;
|
|
230206
|
+
}
|
|
230207
|
+
const data = await response.json();
|
|
230208
|
+
const content = data.choices?.[0]?.message?.content;
|
|
230209
|
+
if (content) {
|
|
230210
|
+
console.log("\u{1F916} AI Analysis:\n");
|
|
230211
|
+
console.log(content);
|
|
230212
|
+
console.log();
|
|
230213
|
+
} else {
|
|
230214
|
+
console.log("Error: No response from AI");
|
|
230215
|
+
}
|
|
230216
|
+
} catch (err) {
|
|
230217
|
+
console.log(`
|
|
230218
|
+
Error calling AI: ${err instanceof Error ? err.message : String(err)}`);
|
|
230219
|
+
console.log(`Try again, or check your credit at ${PORTAL_CREDITS_URL}
|
|
230220
|
+
`);
|
|
230221
|
+
}
|
|
230222
|
+
}
|
|
230223
|
+
function doctorMessages(diagnostics, userQuestion) {
|
|
230224
|
+
return [
|
|
230225
|
+
{
|
|
230226
|
+
role: "system",
|
|
230227
|
+
content: `You are a technical support expert for BlockRun and ClawRouter.
|
|
230228
|
+
Analyze the diagnostics and:
|
|
230229
|
+
1. Identify the root cause of any issues
|
|
230230
|
+
2. Provide specific, actionable fix commands (bash)
|
|
230231
|
+
3. Explain why the issue occurred briefly
|
|
230232
|
+
4. Be concise but thorough
|
|
230233
|
+
5. Format commands in code blocks`
|
|
230234
|
+
},
|
|
230235
|
+
{
|
|
230236
|
+
role: "user",
|
|
230237
|
+
content: userQuestion ? `Here are my system diagnostics:
|
|
230238
|
+
|
|
230239
|
+
${JSON.stringify(diagnostics, null, 2)}
|
|
230240
|
+
|
|
230241
|
+
User's question: ${userQuestion}` : `Here are my system diagnostics:
|
|
230242
|
+
|
|
230243
|
+
${JSON.stringify(diagnostics, null, 2)}
|
|
230244
|
+
|
|
230245
|
+
Please analyze and help me fix any issues.`
|
|
230246
|
+
}
|
|
230247
|
+
];
|
|
230248
|
+
}
|
|
229863
230249
|
async function runDoctor(userQuestion, model = "sonnet") {
|
|
229864
230250
|
console.log(`
|
|
229865
230251
|
\u{1FA7A} BlockRun Doctor v${VERSION}
|
|
229866
230252
|
`);
|
|
230253
|
+
const apiKey = await collectApiKeyInfo();
|
|
229867
230254
|
const [system, wallet, network, logs, latestVersion] = await Promise.all([
|
|
229868
230255
|
collectSystemInfo(),
|
|
229869
|
-
collectWalletInfo(),
|
|
230256
|
+
collectWalletInfo(apiKey.configured),
|
|
229870
230257
|
collectNetworkInfo(),
|
|
229871
230258
|
collectLogInfo(),
|
|
229872
230259
|
fetchLatestVersion()
|
|
@@ -229876,6 +230263,7 @@ async function runDoctor(userQuestion, model = "sonnet") {
|
|
|
229876
230263
|
latestVersion,
|
|
229877
230264
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
229878
230265
|
system,
|
|
230266
|
+
apiKey,
|
|
229879
230267
|
wallet,
|
|
229880
230268
|
network,
|
|
229881
230269
|
logs,
|
|
@@ -229889,12 +230277,15 @@ async function runDoctor(userQuestion, model = "sonnet") {
|
|
|
229889
230277
|
// src/cli.ts
|
|
229890
230278
|
init_partners();
|
|
229891
230279
|
init_policy();
|
|
230280
|
+
init_api_key();
|
|
229892
230281
|
function printHelp() {
|
|
229893
230282
|
console.log(`
|
|
229894
230283
|
ClawRouter v${VERSION} - Smart LLM Router
|
|
229895
230284
|
|
|
229896
230285
|
Usage:
|
|
229897
230286
|
clawrouter [options]
|
|
230287
|
+
clawrouter login [brk_...] # Use a BlockRun API key (card-funded credit)
|
|
230288
|
+
clawrouter logout # Forget the API key and go back to the wallet
|
|
229898
230289
|
clawrouter status # Live proxy status (wallet, balance, chain)
|
|
229899
230290
|
clawrouter wallet # Wallet details + balance
|
|
229900
230291
|
clawrouter models # List available models
|
|
@@ -229945,11 +230336,20 @@ Management Commands:
|
|
|
229945
230336
|
partners List available partner APIs with pricing
|
|
229946
230337
|
partners test Test partner API endpoints (expect 402 = alive)
|
|
229947
230338
|
wallet recover Restore wallet.key from mnemonic (if generated by ClawRouter)
|
|
230339
|
+
login Store a BlockRun API key in ~/.blockrun/.api-key (persists)
|
|
230340
|
+
logout Delete the stored API key and fall back to the wallet
|
|
229948
230341
|
chain solana Switch to Solana (persists)
|
|
229949
230342
|
chain base Switch to Base EVM (persists)
|
|
229950
230343
|
policy Edit spend limits and counterparty lists (fail-closed; restart proxy to apply)
|
|
229951
230344
|
|
|
230345
|
+
Paying for calls \u2014 two ways, and ClawRouter prefers the API key when both exist:
|
|
230346
|
+
API key Sign up at ${PORTAL_URL}, top up with a card, mint a key, then run
|
|
230347
|
+
"clawrouter login brk_live_...". Calls go to api.blockrun.ai.
|
|
230348
|
+
Wallet The default. A USDC wallet is generated on first run and pays for
|
|
230349
|
+
each call over x402 \u2014 Solana for new installs, Base for older ones.
|
|
230350
|
+
|
|
229952
230351
|
Environment Variables:
|
|
230352
|
+
BLOCKRUN_API_KEY BlockRun API key (brk_...) \u2014 wins over the wallet when set
|
|
229953
230353
|
BLOCKRUN_WALLET_KEY Private key for x402 payments (auto-generated if not set)
|
|
229954
230354
|
BLOCKRUN_PROXY_PORT Default proxy port (default: 8402)
|
|
229955
230355
|
|
|
@@ -230032,7 +230432,7 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
|
|
|
230032
230432
|
const idOrLast = tgt === "last" ? "last" : tgt;
|
|
230033
230433
|
if (all3) {
|
|
230034
230434
|
try {
|
|
230035
|
-
const { writeFile:
|
|
230435
|
+
const { writeFile: writeFile4 } = await import("fs/promises");
|
|
230036
230436
|
const { tmpdir } = await import("os");
|
|
230037
230437
|
const { join: pjoin } = await import("path");
|
|
230038
230438
|
const paths = [];
|
|
@@ -230040,7 +230440,7 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
|
|
|
230040
230440
|
const path5 = idOrLast === "last" ? `/share/last?as=${p}` : `/share/${idOrLast}/render?as=${p}`;
|
|
230041
230441
|
const result = await queryProxy(path5, port);
|
|
230042
230442
|
const file = pjoin(tmpdir(), `claw-share-${result.id}-${p}.txt`);
|
|
230043
|
-
await
|
|
230443
|
+
await writeFile4(file, result.rendered, "utf8");
|
|
230044
230444
|
paths.push(file);
|
|
230045
230445
|
}
|
|
230046
230446
|
console.log("\n\u2713 Wrote all 6 preset variants:");
|
|
@@ -230085,12 +230485,19 @@ async function cmdStatus(port) {
|
|
|
230085
230485
|
ClawRouter Status (port ${port})
|
|
230086
230486
|
`);
|
|
230087
230487
|
console.log(` Status: ${data.status}`);
|
|
230088
|
-
|
|
230089
|
-
|
|
230090
|
-
|
|
230091
|
-
|
|
230092
|
-
|
|
230093
|
-
|
|
230488
|
+
if (data.authMode === "api-key") {
|
|
230489
|
+
console.log(` Auth: BlockRun API key ${data.apiKey}`);
|
|
230490
|
+
console.log(` Gateway: ${data.gateway}`);
|
|
230491
|
+
console.log(` Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
230492
|
+
} else {
|
|
230493
|
+
console.log(` Auth: wallet (x402)`);
|
|
230494
|
+
console.log(` Payment Chain: ${data.paymentChain}`);
|
|
230495
|
+
console.log(` EVM Wallet: ${data.wallet}`);
|
|
230496
|
+
if (data.solana) console.log(` Solana Wallet: ${data.solana}`);
|
|
230497
|
+
console.log(` Balance: ${data.balance ?? "unknown"}`);
|
|
230498
|
+
if (data.isLow) console.log(` \u26A0 Balance is low`);
|
|
230499
|
+
if (data.isEmpty) console.log(` \u26A0 Balance is empty \u2014 using free models`);
|
|
230500
|
+
}
|
|
230094
230501
|
if (data.upstreamProxy) console.log(` Upstream Proxy: ${data.upstreamProxy}`);
|
|
230095
230502
|
console.log();
|
|
230096
230503
|
} catch {
|
|
@@ -230102,6 +230509,20 @@ ClawRouter Status (port ${port})
|
|
|
230102
230509
|
async function cmdWallet(port) {
|
|
230103
230510
|
try {
|
|
230104
230511
|
const data = await queryProxy("/health?full=true", port);
|
|
230512
|
+
if (data.authMode === "api-key") {
|
|
230513
|
+
console.log(`
|
|
230514
|
+
ClawRouter Account
|
|
230515
|
+
`);
|
|
230516
|
+
console.log(` Auth: BlockRun API key ${data.apiKey}`);
|
|
230517
|
+
console.log(` Gateway: ${data.gateway}`);
|
|
230518
|
+
console.log(` Billing: account credit (no wallet, no chain)`);
|
|
230519
|
+
console.log(` Top up: ${PORTAL_CREDITS_URL}`);
|
|
230520
|
+
console.log(` Usage: ${PORTAL_URL}/dashboard`);
|
|
230521
|
+
console.log();
|
|
230522
|
+
console.log(` To pay with a USDC wallet instead: clawrouter logout`);
|
|
230523
|
+
console.log();
|
|
230524
|
+
return;
|
|
230525
|
+
}
|
|
230105
230526
|
console.log(`
|
|
230106
230527
|
ClawRouter Wallet
|
|
230107
230528
|
`);
|
|
@@ -230327,8 +230748,8 @@ async function cmdSetup() {
|
|
|
230327
230748
|
unlinkSync: unlinkSync2,
|
|
230328
230749
|
writeFileSync: writeFileSync4
|
|
230329
230750
|
} = await import("fs");
|
|
230330
|
-
const { dirname: dirname4, join:
|
|
230331
|
-
const { homedir:
|
|
230751
|
+
const { dirname: dirname4, join: join18 } = await import("path");
|
|
230752
|
+
const { homedir: homedir15 } = await import("os");
|
|
230332
230753
|
const { injectModelsConfig: injectModelsConfig2, injectAuthProfile: injectAuthProfile2, syncAgentModelCache: syncAgentModelCache2, VISIBLE_OPENCLAW_MODELS: VISIBLE_OPENCLAW_MODELS2 } = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
230333
230754
|
const { BLOCKRUN_PLUGIN_ID: BLOCKRUN_PLUGIN_ID2, prepareBlockRunPluginConfig: prepareBlockRunPluginConfig2 } = await Promise.resolve().then(() => (init_openclaw_plugin_config(), openclaw_plugin_config_exports));
|
|
230334
230755
|
console.log("\u{1F99E} ClawRouter setup\n");
|
|
@@ -230343,19 +230764,19 @@ async function cmdSetup() {
|
|
|
230343
230764
|
} catch {
|
|
230344
230765
|
}
|
|
230345
230766
|
if (process.env.npm_config_prefix) {
|
|
230346
|
-
candidates.push(
|
|
230767
|
+
candidates.push(join18(process.env.npm_config_prefix, "bin", "openclaw"));
|
|
230347
230768
|
}
|
|
230348
230769
|
try {
|
|
230349
230770
|
const npmRoot = execSync("npm root -g", {
|
|
230350
230771
|
encoding: "utf8",
|
|
230351
230772
|
stdio: ["ignore", "pipe", "ignore"]
|
|
230352
230773
|
}).trim();
|
|
230353
|
-
if (npmRoot) candidates.push(
|
|
230774
|
+
if (npmRoot) candidates.push(join18(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
|
|
230354
230775
|
} catch {
|
|
230355
230776
|
}
|
|
230356
230777
|
candidates.push(
|
|
230357
|
-
|
|
230358
|
-
|
|
230778
|
+
join18(homedir15(), ".npm-global", "bin", "openclaw"),
|
|
230779
|
+
join18(homedir15(), ".local", "bin", "openclaw"),
|
|
230359
230780
|
"/usr/local/bin/openclaw"
|
|
230360
230781
|
);
|
|
230361
230782
|
return candidates.find((candidate) => existsSync5(candidate)) ?? "";
|
|
@@ -230367,7 +230788,7 @@ async function cmdSetup() {
|
|
|
230367
230788
|
process.exit(1);
|
|
230368
230789
|
}
|
|
230369
230790
|
console.log(` \u2713 Found openclaw at ${openclawPath}`);
|
|
230370
|
-
const configPath =
|
|
230791
|
+
const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
|
|
230371
230792
|
const configInitiallyExisted = existsSync5(configPath);
|
|
230372
230793
|
const configRollbackBackup = configInitiallyExisted ? `${configPath}.before-${BLOCKRUN_PLUGIN_ID2}.${Date.now()}` : void 0;
|
|
230373
230794
|
if (configRollbackBackup) copyFileSync2(configPath, configRollbackBackup);
|
|
@@ -230375,23 +230796,23 @@ async function cmdSetup() {
|
|
|
230375
230796
|
return configRollbackBackup;
|
|
230376
230797
|
};
|
|
230377
230798
|
let installAttempted = false;
|
|
230378
|
-
const openclawRoot =
|
|
230799
|
+
const openclawRoot = join18(homedir15(), ".openclaw");
|
|
230379
230800
|
const findManagedBlockRunInstalls = () => {
|
|
230380
230801
|
const candidates = [
|
|
230381
|
-
|
|
230382
|
-
|
|
230383
|
-
|
|
230802
|
+
join18(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
|
|
230803
|
+
join18(openclawRoot, "extensions", "clawrouter"),
|
|
230804
|
+
join18(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
|
|
230384
230805
|
];
|
|
230385
|
-
const projectsDir =
|
|
230806
|
+
const projectsDir = join18(openclawRoot, "npm", "projects");
|
|
230386
230807
|
try {
|
|
230387
230808
|
for (const project of readdirSync2(projectsDir)) {
|
|
230388
|
-
candidates.push(
|
|
230809
|
+
candidates.push(join18(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
|
|
230389
230810
|
}
|
|
230390
230811
|
} catch {
|
|
230391
230812
|
}
|
|
230392
230813
|
return [...new Set(candidates)].filter((candidate) => {
|
|
230393
230814
|
try {
|
|
230394
|
-
const metadata = JSON.parse(readFileSync4(
|
|
230815
|
+
const metadata = JSON.parse(readFileSync4(join18(candidate, "package.json"), "utf8"));
|
|
230395
230816
|
return metadata.name === "@blockrun/clawrouter";
|
|
230396
230817
|
} catch {
|
|
230397
230818
|
return false;
|
|
@@ -230399,7 +230820,7 @@ async function cmdSetup() {
|
|
|
230399
230820
|
});
|
|
230400
230821
|
};
|
|
230401
230822
|
const initialPluginDirs = findManagedBlockRunInstalls();
|
|
230402
|
-
const setupRollbackRoot =
|
|
230823
|
+
const setupRollbackRoot = join18(
|
|
230403
230824
|
openclawRoot,
|
|
230404
230825
|
"blockrun",
|
|
230405
230826
|
"setup-rollbacks",
|
|
@@ -230409,7 +230830,7 @@ async function cmdSetup() {
|
|
|
230409
230830
|
if (initialPluginDirs.length > 0) {
|
|
230410
230831
|
mkdirSync4(setupRollbackRoot, { recursive: true, mode: 448 });
|
|
230411
230832
|
for (const [index2, pluginDir] of initialPluginDirs.entries()) {
|
|
230412
|
-
const backup =
|
|
230833
|
+
const backup = join18(setupRollbackRoot, String(index2));
|
|
230413
230834
|
cpSync(pluginDir, backup, { recursive: true });
|
|
230414
230835
|
pluginRollbackBackups.set(pluginDir, backup);
|
|
230415
230836
|
}
|
|
@@ -230454,8 +230875,8 @@ async function cmdSetup() {
|
|
|
230454
230875
|
if (existsSync5(configPath)) {
|
|
230455
230876
|
try {
|
|
230456
230877
|
const config = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
230457
|
-
const legacyPackagePath =
|
|
230458
|
-
|
|
230878
|
+
const legacyPackagePath = join18(
|
|
230879
|
+
homedir15(),
|
|
230459
230880
|
".openclaw",
|
|
230460
230881
|
"extensions",
|
|
230461
230882
|
"clawrouter",
|
|
@@ -230639,7 +231060,10 @@ function parseArgs(args) {
|
|
|
230639
231060
|
phoneArg: void 0,
|
|
230640
231061
|
phoneAreaCode: void 0,
|
|
230641
231062
|
policy: false,
|
|
230642
|
-
policyArgs: []
|
|
231063
|
+
policyArgs: [],
|
|
231064
|
+
login: false,
|
|
231065
|
+
loginKey: void 0,
|
|
231066
|
+
logout: false
|
|
230643
231067
|
};
|
|
230644
231068
|
for (let i = 0; i < args.length; i++) {
|
|
230645
231069
|
const arg = args[i];
|
|
@@ -230700,6 +231124,14 @@ function parseArgs(args) {
|
|
|
230700
231124
|
} else if ((arg === "chain" || arg === "/wallet") && (args[i + 1] === "solana" || args[i + 1] === "base")) {
|
|
230701
231125
|
result.chain = args[i + 1];
|
|
230702
231126
|
i++;
|
|
231127
|
+
} else if (arg === "login") {
|
|
231128
|
+
result.login = true;
|
|
231129
|
+
if (args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
231130
|
+
result.loginKey = args[i + 1];
|
|
231131
|
+
i++;
|
|
231132
|
+
}
|
|
231133
|
+
} else if (arg === "logout") {
|
|
231134
|
+
result.logout = true;
|
|
230703
231135
|
} else if (arg === "policy") {
|
|
230704
231136
|
result.policy = true;
|
|
230705
231137
|
result.policyArgs = args.slice(i + 1);
|
|
@@ -230782,6 +231214,58 @@ function parseArgs(args) {
|
|
|
230782
231214
|
}
|
|
230783
231215
|
return result;
|
|
230784
231216
|
}
|
|
231217
|
+
async function promptForApiKey() {
|
|
231218
|
+
const { createInterface } = await import("readline/promises");
|
|
231219
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
231220
|
+
try {
|
|
231221
|
+
console.log(`Sign up, add credit with a card, and mint a key at ${PORTAL_KEYS_URL}`);
|
|
231222
|
+
return (await rl.question("Paste your BlockRun API key (brk_...): ")).trim();
|
|
231223
|
+
} finally {
|
|
231224
|
+
rl.close();
|
|
231225
|
+
}
|
|
231226
|
+
}
|
|
231227
|
+
function installShutdownHandlers(proxy) {
|
|
231228
|
+
const shutdown = async (signal) => {
|
|
231229
|
+
console.log(`
|
|
231230
|
+
[ClawRouter] Received ${signal}, shutting down...`);
|
|
231231
|
+
try {
|
|
231232
|
+
await proxy.close();
|
|
231233
|
+
console.log(`[ClawRouter] Proxy closed`);
|
|
231234
|
+
process.exit(0);
|
|
231235
|
+
} catch (err) {
|
|
231236
|
+
console.error(`[ClawRouter] Error during shutdown: ${err}`);
|
|
231237
|
+
process.exit(1);
|
|
231238
|
+
}
|
|
231239
|
+
};
|
|
231240
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
231241
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
231242
|
+
}
|
|
231243
|
+
async function startWithApiKey(apiKey, source, args) {
|
|
231244
|
+
const origin2 = source === "env" ? "BLOCKRUN_API_KEY" : source === "core" ? "~/.blockrun/.api-key" : source;
|
|
231245
|
+
console.log(`[ClawRouter] Using BlockRun API key ${maskApiKey(apiKey)} (from ${origin2})`);
|
|
231246
|
+
const proxy = await startProxy({
|
|
231247
|
+
apiKey,
|
|
231248
|
+
port: args.port,
|
|
231249
|
+
allowExistingProxy: !args.noReuse,
|
|
231250
|
+
onReady: (port) => {
|
|
231251
|
+
console.log(`[ClawRouter] v${VERSION} | Proxy listening on http://127.0.0.1:${port}`);
|
|
231252
|
+
console.log(`[ClawRouter] Health check: http://127.0.0.1:${port}/health`);
|
|
231253
|
+
},
|
|
231254
|
+
onError: (error) => {
|
|
231255
|
+
console.error(`[ClawRouter] Error: ${error.message}`);
|
|
231256
|
+
},
|
|
231257
|
+
onRouted: (decision) => {
|
|
231258
|
+
const cost = decision.costEstimate.toFixed(4);
|
|
231259
|
+
const saved = (decision.savings * 100).toFixed(0);
|
|
231260
|
+
console.log(`[ClawRouter] [${decision.tier}] ${decision.model} $${cost} (saved ${saved}%)`);
|
|
231261
|
+
}
|
|
231262
|
+
});
|
|
231263
|
+
console.log(`[ClawRouter] Billing: BlockRun account credit \u2014 ${PORTAL_CREDITS_URL}`);
|
|
231264
|
+
console.log(`[ClawRouter] Ready - Ctrl+C to stop`);
|
|
231265
|
+
installShutdownHandlers(proxy);
|
|
231266
|
+
await new Promise(() => {
|
|
231267
|
+
});
|
|
231268
|
+
}
|
|
230785
231269
|
async function main() {
|
|
230786
231270
|
const args = parseArgs(process.argv.slice(2));
|
|
230787
231271
|
if (args.version) {
|
|
@@ -230919,6 +231403,37 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
230919
231403
|
await recoverWalletFromMnemonic();
|
|
230920
231404
|
process.exit(0);
|
|
230921
231405
|
}
|
|
231406
|
+
if (args.login) {
|
|
231407
|
+
const key2 = args.loginKey ?? await promptForApiKey();
|
|
231408
|
+
if (!isValidApiKey(key2)) {
|
|
231409
|
+
console.error(`[ClawRouter] That does not look like a BlockRun API key (expected brk_...).`);
|
|
231410
|
+
console.error(`[ClawRouter] Sign up and mint one at ${PORTAL_KEYS_URL}`);
|
|
231411
|
+
process.exit(1);
|
|
231412
|
+
}
|
|
231413
|
+
const path5 = await saveApiKey(key2);
|
|
231414
|
+
console.log(`[ClawRouter] Signed in with ${maskApiKey(key2)}`);
|
|
231415
|
+
console.log(`[ClawRouter] Key saved to ${path5} (readable only by you)`);
|
|
231416
|
+
console.log(`[ClawRouter] Calls now go to api.blockrun.ai and bill your account credit.`);
|
|
231417
|
+
console.log(`[ClawRouter] Top up: ${PORTAL_CREDITS_URL}`);
|
|
231418
|
+
console.log(`[ClawRouter] Restart the proxy to apply.`);
|
|
231419
|
+
process.exit(0);
|
|
231420
|
+
}
|
|
231421
|
+
if (args.logout) {
|
|
231422
|
+
const { removed, envStillSet } = await clearApiKey();
|
|
231423
|
+
if (removed.length === 0) {
|
|
231424
|
+
console.log(`[ClawRouter] No stored API key \u2014 already using the wallet.`);
|
|
231425
|
+
} else {
|
|
231426
|
+
for (const path5 of removed) console.log(`[ClawRouter] Removed ${path5}`);
|
|
231427
|
+
console.log(`[ClawRouter] Back to wallet mode (x402). Restart the proxy to apply.`);
|
|
231428
|
+
}
|
|
231429
|
+
if (envStillSet) {
|
|
231430
|
+
console.warn(
|
|
231431
|
+
`[ClawRouter] \u26A0 BLOCKRUN_API_KEY is still set in this environment and overrides the wallet.`
|
|
231432
|
+
);
|
|
231433
|
+
console.warn(`[ClawRouter] Unset it too: unset BLOCKRUN_API_KEY`);
|
|
231434
|
+
}
|
|
231435
|
+
process.exit(0);
|
|
231436
|
+
}
|
|
230922
231437
|
if (args.chain) {
|
|
230923
231438
|
const targetChain = args.chain;
|
|
230924
231439
|
if (targetChain === "solana") {
|
|
@@ -230955,6 +231470,11 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
230955
231470
|
console.log(report);
|
|
230956
231471
|
process.exit(0);
|
|
230957
231472
|
}
|
|
231473
|
+
const apiKeyResolution = await resolveApiKey();
|
|
231474
|
+
if (apiKeyResolution) {
|
|
231475
|
+
await startWithApiKey(apiKeyResolution.key, apiKeyResolution.source, args);
|
|
231476
|
+
return;
|
|
231477
|
+
}
|
|
230958
231478
|
const wallet = await resolveOrGenerateWalletKey();
|
|
230959
231479
|
if (wallet.source === "generated") {
|
|
230960
231480
|
console.log(`[ClawRouter] Generated new wallet: ${wallet.address}`);
|
|
@@ -231017,20 +231537,7 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
231017
231537
|
console.log(`[ClawRouter] Wallet: ${displayAddress} (balance check pending)`);
|
|
231018
231538
|
}
|
|
231019
231539
|
console.log(`[ClawRouter] Ready - Ctrl+C to stop`);
|
|
231020
|
-
|
|
231021
|
-
console.log(`
|
|
231022
|
-
[ClawRouter] Received ${signal}, shutting down...`);
|
|
231023
|
-
try {
|
|
231024
|
-
await proxy.close();
|
|
231025
|
-
console.log(`[ClawRouter] Proxy closed`);
|
|
231026
|
-
process.exit(0);
|
|
231027
|
-
} catch (err) {
|
|
231028
|
-
console.error(`[ClawRouter] Error during shutdown: ${err}`);
|
|
231029
|
-
process.exit(1);
|
|
231030
|
-
}
|
|
231031
|
-
};
|
|
231032
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
231033
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
231540
|
+
installShutdownHandlers(proxy);
|
|
231034
231541
|
await new Promise(() => {
|
|
231035
231542
|
});
|
|
231036
231543
|
}
|