@blockrun/clawrouter 0.12.266 → 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 +670 -157
- package/dist/index.d.ts +58 -11
- package/dist/index.js +332 -90
- package/docs/configuration.md +102 -9
- package/openclaw.plugin.json +9 -0
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -32955,6 +32955,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
32955
32955
|
const cached = !options?.skipPreAuth ? cache2.get(cacheKey2) : void 0;
|
|
32956
32956
|
const preAuthCovers = cached !== void 0 && Date.now() - cached.cachedAt < ttlMs && cached.coverMicros !== void 0 && needMicros !== void 0 && needMicros <= cached.coverMicros;
|
|
32957
32957
|
if (preAuthCovers) {
|
|
32958
|
+
let paymentInFlight = false;
|
|
32958
32959
|
try {
|
|
32959
32960
|
const payload2 = await client.createPaymentPayload(cached.paymentRequired);
|
|
32960
32961
|
const headers = httpClient.encodePaymentSignatureHeader(payload2);
|
|
@@ -32962,16 +32963,21 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
32962
32963
|
for (const [key2, value] of Object.entries(headers)) {
|
|
32963
32964
|
preAuthRequest.headers.set(key2, value);
|
|
32964
32965
|
}
|
|
32966
|
+
paymentInFlight = true;
|
|
32965
32967
|
const response2 = await baseFetch(preAuthRequest);
|
|
32966
32968
|
if (response2.status !== 402) {
|
|
32967
32969
|
return response2;
|
|
32968
32970
|
}
|
|
32971
|
+
paymentInFlight = false;
|
|
32969
32972
|
cache2.delete(cacheKey2);
|
|
32970
32973
|
} catch (err) {
|
|
32971
32974
|
cache2.delete(cacheKey2);
|
|
32972
32975
|
if (err instanceof SpendPolicyError) {
|
|
32973
32976
|
throw err;
|
|
32974
32977
|
}
|
|
32978
|
+
if (paymentInFlight) {
|
|
32979
|
+
throw err;
|
|
32980
|
+
}
|
|
32975
32981
|
}
|
|
32976
32982
|
}
|
|
32977
32983
|
const clonedRequest = request2.clone();
|
|
@@ -40685,7 +40691,7 @@ var init_errors5 = __esm({
|
|
|
40685
40691
|
});
|
|
40686
40692
|
|
|
40687
40693
|
// src/balance.ts
|
|
40688
|
-
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor;
|
|
40694
|
+
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor, ApiKeyBalanceMonitor;
|
|
40689
40695
|
var init_balance = __esm({
|
|
40690
40696
|
"src/balance.ts"() {
|
|
40691
40697
|
"use strict";
|
|
@@ -40814,6 +40820,38 @@ var init_balance = __esm({
|
|
|
40814
40820
|
};
|
|
40815
40821
|
}
|
|
40816
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
|
+
};
|
|
40817
40855
|
}
|
|
40818
40856
|
});
|
|
40819
40857
|
|
|
@@ -59974,6 +60012,119 @@ var init_auth = __esm({
|
|
|
59974
60012
|
}
|
|
59975
60013
|
});
|
|
59976
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
|
+
|
|
59977
60128
|
// src/compression/types.ts
|
|
59978
60129
|
var DEFAULT_COMPRESSION_CONFIG;
|
|
59979
60130
|
var init_types2 = __esm({
|
|
@@ -61063,8 +61214,8 @@ var init_updater = __esm({
|
|
|
61063
61214
|
|
|
61064
61215
|
// src/exclude-models.ts
|
|
61065
61216
|
import { readFileSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
61066
|
-
import { join as
|
|
61067
|
-
import { homedir as
|
|
61217
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
61218
|
+
import { homedir as homedir6 } from "os";
|
|
61068
61219
|
function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
|
|
61069
61220
|
try {
|
|
61070
61221
|
const mtimeMs = statSync(filePath).mtimeMs;
|
|
@@ -61113,7 +61264,7 @@ var init_exclude_models = __esm({
|
|
|
61113
61264
|
"src/exclude-models.ts"() {
|
|
61114
61265
|
"use strict";
|
|
61115
61266
|
init_models();
|
|
61116
|
-
DEFAULT_FILE_PATH =
|
|
61267
|
+
DEFAULT_FILE_PATH = join9(homedir6(), ".openclaw", "blockrun", "exclude-models.json");
|
|
61117
61268
|
loadCache = /* @__PURE__ */ new Map();
|
|
61118
61269
|
}
|
|
61119
61270
|
});
|
|
@@ -76122,7 +76273,7 @@ var require_snapshot_utils = __commonJS({
|
|
|
76122
76273
|
var require_snapshot_recorder = __commonJS({
|
|
76123
76274
|
"node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
76124
76275
|
"use strict";
|
|
76125
|
-
var { writeFile:
|
|
76276
|
+
var { writeFile: writeFile4, readFile: readFile3, mkdir: mkdir6 } = __require("fs/promises");
|
|
76126
76277
|
var { dirname: dirname4, resolve } = __require("path");
|
|
76127
76278
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
|
|
76128
76279
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
@@ -76369,12 +76520,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
76369
76520
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
76370
76521
|
}
|
|
76371
76522
|
const resolvedPath = resolve(path5);
|
|
76372
|
-
await
|
|
76523
|
+
await mkdir6(dirname4(resolvedPath), { recursive: true });
|
|
76373
76524
|
const data = Array.from(this.#snapshots.entries()).map(([hash5, snapshot]) => ({
|
|
76374
76525
|
hash: hash5,
|
|
76375
76526
|
snapshot
|
|
76376
76527
|
}));
|
|
76377
|
-
await
|
|
76528
|
+
await writeFile4(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
76378
76529
|
}
|
|
76379
76530
|
/**
|
|
76380
76531
|
* Clears all recorded snapshots
|
|
@@ -88703,9 +88854,9 @@ var init_textual_tool_calls = __esm({
|
|
|
88703
88854
|
});
|
|
88704
88855
|
|
|
88705
88856
|
// src/response-store.ts
|
|
88706
|
-
import { appendFile as appendFile2, mkdir as
|
|
88707
|
-
import { homedir as
|
|
88708
|
-
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";
|
|
88709
88860
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
88710
88861
|
function isEnabled() {
|
|
88711
88862
|
const v = process.env.BLOCKRUN_RESPONSE_STORE;
|
|
@@ -88713,12 +88864,12 @@ function isEnabled() {
|
|
|
88713
88864
|
}
|
|
88714
88865
|
async function ensureDir2() {
|
|
88715
88866
|
if (dirReady2) return;
|
|
88716
|
-
await
|
|
88867
|
+
await mkdir4(STORE_DIR, { recursive: true });
|
|
88717
88868
|
dirReady2 = true;
|
|
88718
88869
|
}
|
|
88719
88870
|
function dailyFile(date = /* @__PURE__ */ new Date()) {
|
|
88720
88871
|
const iso = date.toISOString().slice(0, 10);
|
|
88721
|
-
return
|
|
88872
|
+
return join10(STORE_DIR, `responses-${iso}.jsonl`);
|
|
88722
88873
|
}
|
|
88723
88874
|
function genId() {
|
|
88724
88875
|
return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
|
|
@@ -88768,7 +88919,7 @@ async function listRecent(limit = 20, daysBack = 7) {
|
|
|
88768
88919
|
d.setUTCDate(d.getUTCDate() - i);
|
|
88769
88920
|
const iso = d.toISOString().slice(0, 10);
|
|
88770
88921
|
const name = `responses-${iso}.jsonl`;
|
|
88771
|
-
if (files.includes(name)) candidateFiles.push(
|
|
88922
|
+
if (files.includes(name)) candidateFiles.push(join10(STORE_DIR, name));
|
|
88772
88923
|
}
|
|
88773
88924
|
const all3 = [];
|
|
88774
88925
|
for (const f of candidateFiles) {
|
|
@@ -88797,7 +88948,7 @@ var STORE_DIR, dirReady2;
|
|
|
88797
88948
|
var init_response_store = __esm({
|
|
88798
88949
|
"src/response-store.ts"() {
|
|
88799
88950
|
"use strict";
|
|
88800
|
-
STORE_DIR =
|
|
88951
|
+
STORE_DIR = join10(homedir7(), ".openclaw", "blockrun", "responses");
|
|
88801
88952
|
dirReady2 = false;
|
|
88802
88953
|
}
|
|
88803
88954
|
});
|
|
@@ -90448,15 +90599,18 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
90448
90599
|
import { createHmac } from "crypto";
|
|
90449
90600
|
import { createServer } from "http";
|
|
90450
90601
|
import { finished } from "stream";
|
|
90451
|
-
import { homedir as
|
|
90452
|
-
import { join as
|
|
90453
|
-
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";
|
|
90454
90605
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
90455
|
-
async function loadGatewayCatalog(apiBase) {
|
|
90606
|
+
async function loadGatewayCatalog(apiBase, apiKey) {
|
|
90456
90607
|
try {
|
|
90457
90608
|
const controller = new AbortController();
|
|
90458
90609
|
const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
|
|
90459
|
-
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
|
+
});
|
|
90460
90614
|
clearTimeout(timer2);
|
|
90461
90615
|
if (!res.ok) return;
|
|
90462
90616
|
const body = await res.json();
|
|
@@ -90755,8 +90909,9 @@ async function checkExistingProxy(port) {
|
|
|
90755
90909
|
clearTimeout(timeoutId);
|
|
90756
90910
|
if (response.ok) {
|
|
90757
90911
|
const data = await response.json();
|
|
90758
|
-
|
|
90759
|
-
|
|
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 };
|
|
90760
90915
|
}
|
|
90761
90916
|
}
|
|
90762
90917
|
return void 0;
|
|
@@ -91264,7 +91419,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91264
91419
|
});
|
|
91265
91420
|
}
|
|
91266
91421
|
function readImageFileAsDataUri(filePath) {
|
|
91267
|
-
const resolved = filePath.startsWith("~/") ?
|
|
91422
|
+
const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
|
|
91268
91423
|
if (!existsSync2(resolved)) {
|
|
91269
91424
|
throw new Error(`Image file not found: ${resolved}`);
|
|
91270
91425
|
}
|
|
@@ -91307,16 +91462,48 @@ async function uploadDataUriToHost(dataUri) {
|
|
|
91307
91462
|
clearTimeout(uploadTimeout);
|
|
91308
91463
|
}
|
|
91309
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
|
+
}
|
|
91310
91481
|
async function startProxy(options) {
|
|
91311
91482
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91312
91483
|
if (upstreamProxy) {
|
|
91313
91484
|
console.log(`[ClawRouter] Upstream proxy: ${upstreamProxy}`);
|
|
91314
91485
|
}
|
|
91315
|
-
const
|
|
91316
|
-
|
|
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;
|
|
91317
91500
|
const paymentChain = options.paymentChain ?? await resolvePaymentChain();
|
|
91318
|
-
const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91319
|
-
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) {
|
|
91320
91507
|
console.warn(
|
|
91321
91508
|
`[ClawRouter] \u26A0 Payment chain is Solana but no mnemonic found \u2014 falling back to Base (EVM).`
|
|
91322
91509
|
);
|
|
@@ -91327,12 +91514,31 @@ async function startProxy(options) {
|
|
|
91327
91514
|
} else if (paymentChain === "solana") {
|
|
91328
91515
|
console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
|
|
91329
91516
|
}
|
|
91330
|
-
void loadGatewayCatalog(apiBase);
|
|
91517
|
+
void loadGatewayCatalog(apiBase, apiKey);
|
|
91331
91518
|
const listenPort = options.port ?? getProxyPort();
|
|
91332
91519
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91333
91520
|
if (existingProxy) {
|
|
91334
|
-
const account2 = privateKeyToAccount(walletKey);
|
|
91335
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);
|
|
91336
91542
|
if (existingProxy.wallet !== account2.address) {
|
|
91337
91543
|
console.warn(
|
|
91338
91544
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
@@ -91371,20 +91577,23 @@ async function startProxy(options) {
|
|
|
91371
91577
|
baseUrl: baseUrl2,
|
|
91372
91578
|
walletAddress: existingProxy.wallet,
|
|
91373
91579
|
solanaAddress: reuseSolanaAddress,
|
|
91580
|
+
authMode,
|
|
91374
91581
|
balanceMonitor: balanceMonitor2,
|
|
91375
91582
|
close: async () => {
|
|
91376
91583
|
}
|
|
91377
91584
|
};
|
|
91378
91585
|
}
|
|
91379
|
-
const account = privateKeyToAccount(walletKey);
|
|
91380
|
-
const
|
|
91381
|
-
|
|
91382
|
-
|
|
91383
|
-
|
|
91384
|
-
|
|
91385
|
-
|
|
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
|
+
}
|
|
91386
91595
|
let solanaAddress;
|
|
91387
|
-
if (solanaPrivateKeyBytes) {
|
|
91596
|
+
if (x402 && solanaPrivateKeyBytes) {
|
|
91388
91597
|
const { registerExactSvmScheme: registerExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91389
91598
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91390
91599
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
@@ -91392,12 +91601,12 @@ async function startProxy(options) {
|
|
|
91392
91601
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91393
91602
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91394
91603
|
}
|
|
91395
|
-
x402
|
|
91604
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91396
91605
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
91397
91606
|
const payload = context.paymentPayload;
|
|
91398
91607
|
payload.extensions = withBuilderCodeServiceCode(payload.extensions);
|
|
91399
91608
|
});
|
|
91400
|
-
x402
|
|
91609
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91401
91610
|
const network = context.selectedRequirements.network;
|
|
91402
91611
|
const chain3 = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
|
|
91403
91612
|
const amountMicros = parseInt(context.selectedRequirements.amount || "0", 10);
|
|
@@ -91406,7 +91615,7 @@ async function startProxy(options) {
|
|
|
91406
91615
|
if (store) store.amountUsd = amountUsd;
|
|
91407
91616
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91408
91617
|
});
|
|
91409
|
-
const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91618
|
+
const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91410
91619
|
skipPreAuth: paymentChain === "solana",
|
|
91411
91620
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91412
91621
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -91416,6 +91625,8 @@ async function startProxy(options) {
|
|
|
91416
91625
|
let balanceMonitor;
|
|
91417
91626
|
if (options._balanceMonitorOverride) {
|
|
91418
91627
|
balanceMonitor = options._balanceMonitorOverride;
|
|
91628
|
+
} else if (authMode === "api-key") {
|
|
91629
|
+
balanceMonitor = new ApiKeyBalanceMonitor();
|
|
91419
91630
|
} else if (paymentChain === "solana" && solanaAddress) {
|
|
91420
91631
|
const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
|
|
91421
91632
|
balanceMonitor = new SolanaBalanceMonitor2(solanaAddress);
|
|
@@ -91457,11 +91668,17 @@ async function startProxy(options) {
|
|
|
91457
91668
|
const full = url2.searchParams.get("full") === "true";
|
|
91458
91669
|
const response = {
|
|
91459
91670
|
status: "ok",
|
|
91460
|
-
|
|
91461
|
-
paymentChain
|
|
91671
|
+
authMode
|
|
91462
91672
|
};
|
|
91463
|
-
if (
|
|
91464
|
-
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
|
+
}
|
|
91465
91682
|
}
|
|
91466
91683
|
if (upstreamProxy) {
|
|
91467
91684
|
response.upstreamProxy = upstreamProxy;
|
|
@@ -91477,9 +91694,15 @@ async function startProxy(options) {
|
|
|
91477
91694
|
).unref()
|
|
91478
91695
|
)
|
|
91479
91696
|
]);
|
|
91480
|
-
|
|
91481
|
-
|
|
91482
|
-
|
|
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
|
+
}
|
|
91483
91706
|
} catch {
|
|
91484
91707
|
response.balanceError = "Could not fetch balance";
|
|
91485
91708
|
}
|
|
@@ -91659,7 +91882,7 @@ async function startProxy(options) {
|
|
|
91659
91882
|
res.end("Bad request");
|
|
91660
91883
|
return;
|
|
91661
91884
|
}
|
|
91662
|
-
const filePath =
|
|
91885
|
+
const filePath = join11(IMAGE_DIR, filename);
|
|
91663
91886
|
try {
|
|
91664
91887
|
const s3 = await fsStat(filePath);
|
|
91665
91888
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91690,7 +91913,7 @@ async function startProxy(options) {
|
|
|
91690
91913
|
res.end("Bad request");
|
|
91691
91914
|
return;
|
|
91692
91915
|
}
|
|
91693
|
-
const filePath =
|
|
91916
|
+
const filePath = join11(AUDIO_DIR, filename);
|
|
91694
91917
|
try {
|
|
91695
91918
|
const s3 = await fsStat(filePath);
|
|
91696
91919
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91720,7 +91943,7 @@ async function startProxy(options) {
|
|
|
91720
91943
|
res.end("Bad request");
|
|
91721
91944
|
return;
|
|
91722
91945
|
}
|
|
91723
|
-
const filePath =
|
|
91946
|
+
const filePath = join11(VIDEO_DIR, filename);
|
|
91724
91947
|
try {
|
|
91725
91948
|
const s3 = await fsStat(filePath);
|
|
91726
91949
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91853,7 +92076,7 @@ async function startProxy(options) {
|
|
|
91853
92076
|
}
|
|
91854
92077
|
}
|
|
91855
92078
|
if (result.data?.length) {
|
|
91856
|
-
await
|
|
92079
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91857
92080
|
const port2 = server.address()?.port ?? 8402;
|
|
91858
92081
|
for (const img of result.data) {
|
|
91859
92082
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91861,7 +92084,7 @@ async function startProxy(options) {
|
|
|
91861
92084
|
const [, mimeType, b64] = dataUriMatch;
|
|
91862
92085
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91863
92086
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91864
|
-
await
|
|
92087
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91865
92088
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91866
92089
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91867
92090
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -91872,7 +92095,7 @@ async function startProxy(options) {
|
|
|
91872
92095
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
91873
92096
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91874
92097
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
91875
|
-
await
|
|
92098
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
91876
92099
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91877
92100
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
91878
92101
|
}
|
|
@@ -91976,7 +92199,7 @@ async function startProxy(options) {
|
|
|
91976
92199
|
return;
|
|
91977
92200
|
}
|
|
91978
92201
|
if (result.data?.length) {
|
|
91979
|
-
await
|
|
92202
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91980
92203
|
const port2 = server.address()?.port ?? 8402;
|
|
91981
92204
|
for (const img of result.data) {
|
|
91982
92205
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91984,7 +92207,7 @@ async function startProxy(options) {
|
|
|
91984
92207
|
const [, mimeType, b64] = dataUriMatch;
|
|
91985
92208
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91986
92209
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91987
|
-
await
|
|
92210
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91988
92211
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91989
92212
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91990
92213
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -91995,7 +92218,7 @@ async function startProxy(options) {
|
|
|
91995
92218
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
91996
92219
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91997
92220
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
91998
|
-
await
|
|
92221
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
91999
92222
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
92000
92223
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
92001
92224
|
}
|
|
@@ -92070,7 +92293,7 @@ async function startProxy(options) {
|
|
|
92070
92293
|
return;
|
|
92071
92294
|
}
|
|
92072
92295
|
if (result.data?.length) {
|
|
92073
|
-
await
|
|
92296
|
+
await mkdir5(AUDIO_DIR, { recursive: true });
|
|
92074
92297
|
const port2 = server.address()?.port ?? 8402;
|
|
92075
92298
|
for (const track of result.data) {
|
|
92076
92299
|
if (track.url?.startsWith("https://") || track.url?.startsWith("http://")) {
|
|
@@ -92081,7 +92304,7 @@ async function startProxy(options) {
|
|
|
92081
92304
|
const ext = contentType.includes("wav") ? "wav" : "mp3";
|
|
92082
92305
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92083
92306
|
const buf = Buffer.from(await audioResp.arrayBuffer());
|
|
92084
|
-
await
|
|
92307
|
+
await writeFile3(join11(AUDIO_DIR, filename), buf);
|
|
92085
92308
|
track.url = `http://localhost:${port2}/audio/${filename}`;
|
|
92086
92309
|
console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
|
|
92087
92310
|
}
|
|
@@ -92230,7 +92453,7 @@ async function startProxy(options) {
|
|
|
92230
92453
|
}
|
|
92231
92454
|
}
|
|
92232
92455
|
if (finalResult.data?.length) {
|
|
92233
|
-
await
|
|
92456
|
+
await mkdir5(VIDEO_DIR, { recursive: true });
|
|
92234
92457
|
const port2 = server.address()?.port ?? 8402;
|
|
92235
92458
|
for (const clip of finalResult.data) {
|
|
92236
92459
|
if (clip.url?.startsWith("https://") || clip.url?.startsWith("http://")) {
|
|
@@ -92241,7 +92464,7 @@ async function startProxy(options) {
|
|
|
92241
92464
|
const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
|
|
92242
92465
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92243
92466
|
const buf = Buffer.from(await videoResp.arrayBuffer());
|
|
92244
|
-
await
|
|
92467
|
+
await writeFile3(join11(VIDEO_DIR, filename), buf);
|
|
92245
92468
|
clip.url = `http://localhost:${port2}/videos/${filename}`;
|
|
92246
92469
|
console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
|
|
92247
92470
|
}
|
|
@@ -92424,6 +92647,8 @@ async function startProxy(options) {
|
|
|
92424
92647
|
port: listenPort,
|
|
92425
92648
|
baseUrl: baseUrl2,
|
|
92426
92649
|
walletAddress: error.wallet,
|
|
92650
|
+
authMode,
|
|
92651
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92427
92652
|
balanceMonitor,
|
|
92428
92653
|
close: async () => {
|
|
92429
92654
|
}
|
|
@@ -92474,8 +92699,10 @@ async function startProxy(options) {
|
|
|
92474
92699
|
return {
|
|
92475
92700
|
port,
|
|
92476
92701
|
baseUrl,
|
|
92477
|
-
walletAddress: account
|
|
92702
|
+
walletAddress: account?.address ?? "",
|
|
92478
92703
|
solanaAddress,
|
|
92704
|
+
authMode,
|
|
92705
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92479
92706
|
balanceMonitor,
|
|
92480
92707
|
close: () => new Promise((res, rej) => {
|
|
92481
92708
|
const timeout = setTimeout(() => {
|
|
@@ -94798,6 +95025,7 @@ var init_proxy = __esm({
|
|
|
94798
95025
|
init_response_cache();
|
|
94799
95026
|
init_balance();
|
|
94800
95027
|
init_auth();
|
|
95028
|
+
init_api_key();
|
|
94801
95029
|
init_spend_control();
|
|
94802
95030
|
init_compression();
|
|
94803
95031
|
init_version4();
|
|
@@ -94814,10 +95042,10 @@ var init_proxy = __esm({
|
|
|
94814
95042
|
paymentStore = new AsyncLocalStorage();
|
|
94815
95043
|
BLOCKRUN_API = "https://blockrun.ai/api";
|
|
94816
95044
|
BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
94817
|
-
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ??
|
|
94818
|
-
IMAGE_DIR =
|
|
94819
|
-
AUDIO_DIR =
|
|
94820
|
-
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");
|
|
94821
95049
|
AUTO_MODEL = "blockrun/auto";
|
|
94822
95050
|
ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
94823
95051
|
"blockrun/eco",
|
|
@@ -117213,8 +117441,8 @@ var init_constants3 = __esm({
|
|
|
117213
117441
|
|
|
117214
117442
|
// src/polymarket/wallet-adapter.ts
|
|
117215
117443
|
import fs2 from "fs";
|
|
117216
|
-
import { homedir as
|
|
117217
|
-
import { join as
|
|
117444
|
+
import { homedir as homedir9 } from "os";
|
|
117445
|
+
import { join as join12 } from "path";
|
|
117218
117446
|
function getOrCreateWalletKey() {
|
|
117219
117447
|
const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
|
|
117220
117448
|
if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
|
|
@@ -117262,7 +117490,7 @@ var init_wallet_adapter = __esm({
|
|
|
117262
117490
|
init_esm();
|
|
117263
117491
|
init_chains();
|
|
117264
117492
|
init_constants3();
|
|
117265
|
-
WALLET_FILE2 =
|
|
117493
|
+
WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
|
|
117266
117494
|
BASE_RPC_URLS = [
|
|
117267
117495
|
"https://mainnet.base.org",
|
|
117268
117496
|
"https://base.llamarpc.com",
|
|
@@ -227654,9 +227882,9 @@ import {
|
|
|
227654
227882
|
unlinkSync
|
|
227655
227883
|
} from "fs";
|
|
227656
227884
|
import { readFile as readFileAsync } from "fs/promises";
|
|
227657
|
-
import { homedir as
|
|
227885
|
+
import { homedir as homedir14 } from "os";
|
|
227658
227886
|
import { randomUUID } from "crypto";
|
|
227659
|
-
import { join as
|
|
227887
|
+
import { join as join17 } from "path";
|
|
227660
227888
|
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
227661
227889
|
const start = Date.now();
|
|
227662
227890
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -227698,8 +227926,8 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
227698
227926
|
return cfg.tools?.web?.search?.enabled === false;
|
|
227699
227927
|
}
|
|
227700
227928
|
function injectModelsConfig(logger48, options = {}) {
|
|
227701
|
-
const configDir =
|
|
227702
|
-
const configPath =
|
|
227929
|
+
const configDir = join17(homedir14(), ".openclaw");
|
|
227930
|
+
const configPath = join17(configDir, "openclaw.json");
|
|
227703
227931
|
let config = {};
|
|
227704
227932
|
let needsWrite = false;
|
|
227705
227933
|
if (!existsSync4(configDir)) {
|
|
@@ -227920,7 +228148,7 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
227920
228148
|
}
|
|
227921
228149
|
function syncAgentModelCache(logger48, options = {}) {
|
|
227922
228150
|
if (!isGatewayMode() && !options.forceWrite) return;
|
|
227923
|
-
const agentsDir =
|
|
228151
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227924
228152
|
if (!existsSync4(agentsDir)) return;
|
|
227925
228153
|
let agentDirs;
|
|
227926
228154
|
try {
|
|
@@ -227930,7 +228158,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227930
228158
|
}
|
|
227931
228159
|
const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
|
|
227932
228160
|
for (const agent of agentDirs) {
|
|
227933
|
-
const cachePath =
|
|
228161
|
+
const cachePath = join17(agentsDir, agent, "agent", "models.json");
|
|
227934
228162
|
if (!existsSync4(cachePath)) continue;
|
|
227935
228163
|
try {
|
|
227936
228164
|
const raw = readTextFileSync(cachePath).trim();
|
|
@@ -227959,7 +228187,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227959
228187
|
}
|
|
227960
228188
|
}
|
|
227961
228189
|
function injectAuthProfile(logger48) {
|
|
227962
|
-
const agentsDir =
|
|
228190
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227963
228191
|
if (!existsSync4(agentsDir)) {
|
|
227964
228192
|
try {
|
|
227965
228193
|
mkdirSync3(agentsDir, { recursive: true });
|
|
@@ -227976,9 +228204,9 @@ function injectAuthProfile(logger48) {
|
|
|
227976
228204
|
agents = ["main", ...agents];
|
|
227977
228205
|
}
|
|
227978
228206
|
for (const agentId of agents) {
|
|
227979
|
-
const authDir =
|
|
227980
|
-
const authPath =
|
|
227981
|
-
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");
|
|
227982
228210
|
if (existsSync4(sqlitePath)) {
|
|
227983
228211
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
227984
228212
|
continue;
|
|
@@ -228125,9 +228353,21 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228125
228353
|
if (startupGeneration !== void 0 && isProxyStartupCurrent(startupGeneration, proc)) {
|
|
228126
228354
|
proc.__clawrouterStartupPhase = "starting";
|
|
228127
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;
|
|
228128
228364
|
const configKey = api.pluginConfig?.walletKey;
|
|
228129
228365
|
let wallet;
|
|
228130
|
-
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)) {
|
|
228131
228371
|
const account = privateKeyToAccount(configKey);
|
|
228132
228372
|
wallet = { key: configKey, address: account.address, source: "config" };
|
|
228133
228373
|
} else {
|
|
@@ -228138,7 +228378,8 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228138
228378
|
}
|
|
228139
228379
|
wallet = await resolveOrGenerateWalletKey();
|
|
228140
228380
|
}
|
|
228141
|
-
if (wallet
|
|
228381
|
+
if (!wallet) {
|
|
228382
|
+
} else if (wallet.source === "generated") {
|
|
228142
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`);
|
|
228143
228384
|
api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
|
|
228144
228385
|
api.logger.warn(` Address : ${wallet.address}`);
|
|
@@ -228163,13 +228404,13 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228163
228404
|
);
|
|
228164
228405
|
}
|
|
228165
228406
|
const proxy = await startProxy({
|
|
228166
|
-
wallet,
|
|
228407
|
+
...apiKey ? { apiKey } : { wallet },
|
|
228167
228408
|
routingConfig,
|
|
228168
228409
|
maxCostPerRunUsd,
|
|
228169
228410
|
maxCostPerRunMode,
|
|
228170
228411
|
spendControl: liveSpendControl = new SpendControl(),
|
|
228171
228412
|
onReady: (port) => {
|
|
228172
|
-
api.logger.info(`BlockRun x402 proxy listening on port ${port}`);
|
|
228413
|
+
api.logger.info(`BlockRun ${apiKey ? "API-key" : "x402"} proxy listening on port ${port}`);
|
|
228173
228414
|
},
|
|
228174
228415
|
onError: (error) => {
|
|
228175
228416
|
api.logger.error(`BlockRun proxy error: ${error.message}`);
|
|
@@ -228215,6 +228456,10 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228215
228456
|
}
|
|
228216
228457
|
api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
|
|
228217
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
|
+
}
|
|
228218
228463
|
const currentChain = await resolvePaymentChain();
|
|
228219
228464
|
const displayAddress = currentChain === "solana" && proxy.solanaAddress ? proxy.solanaAddress : wallet.address;
|
|
228220
228465
|
const network = currentChain === "solana" ? "Solana" : "Base";
|
|
@@ -228458,7 +228703,7 @@ function buildImageGenerationProvider() {
|
|
|
228458
228703
|
(result.data ?? []).map(async (img) => {
|
|
228459
228704
|
const filename = img.url?.split("/images/").pop();
|
|
228460
228705
|
if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
|
|
228461
|
-
const filePath =
|
|
228706
|
+
const filePath = join17(IMAGE_DIR2, filename);
|
|
228462
228707
|
const buffer2 = await readFileAsync(filePath);
|
|
228463
228708
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
228464
228709
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
@@ -228510,7 +228755,7 @@ function buildMusicGenerationProvider() {
|
|
|
228510
228755
|
(result.data ?? []).map(async (track) => {
|
|
228511
228756
|
const filename = track.url?.split("/audio/").pop();
|
|
228512
228757
|
if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
|
|
228513
|
-
const filePath =
|
|
228758
|
+
const filePath = join17(AUDIO_DIR2, filename);
|
|
228514
228759
|
const buffer2 = await readFileAsync(filePath);
|
|
228515
228760
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
|
|
228516
228761
|
const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
|
|
@@ -228586,7 +228831,7 @@ function buildVideoGenerationProvider() {
|
|
|
228586
228831
|
(result.data ?? []).map(async (clip) => {
|
|
228587
228832
|
const filename = clip.url?.split("/videos/").pop();
|
|
228588
228833
|
if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
|
|
228589
|
-
const filePath =
|
|
228834
|
+
const filePath = join17(VIDEO_DIR2, filename);
|
|
228590
228835
|
const buffer2 = await readFileAsync(filePath);
|
|
228591
228836
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
|
|
228592
228837
|
const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
|
|
@@ -228639,6 +228884,22 @@ function createWalletCommand(api) {
|
|
|
228639
228884
|
requireAuth: true,
|
|
228640
228885
|
handler: async (ctx) => {
|
|
228641
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
|
+
}
|
|
228642
228903
|
let wallet;
|
|
228643
228904
|
try {
|
|
228644
228905
|
wallet = subcommand === "status" || subcommand === "export" ? await resolveExistingWalletKey() : await resolveOrGenerateWalletKey();
|
|
@@ -228896,6 +229157,7 @@ var init_index = __esm({
|
|
|
228896
229157
|
"use strict";
|
|
228897
229158
|
init_provider();
|
|
228898
229159
|
init_proxy();
|
|
229160
|
+
init_api_key();
|
|
228899
229161
|
init_web_search_provider();
|
|
228900
229162
|
init_auth();
|
|
228901
229163
|
init_balance();
|
|
@@ -228935,9 +229197,9 @@ var init_index = __esm({
|
|
|
228935
229197
|
activeProxyHandle = null;
|
|
228936
229198
|
liveSpendControl = null;
|
|
228937
229199
|
pendingConfiguredStartupApi = null;
|
|
228938
|
-
IMAGE_DIR2 =
|
|
228939
|
-
AUDIO_DIR2 =
|
|
228940
|
-
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");
|
|
228941
229203
|
plugin = {
|
|
228942
229204
|
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
228943
229205
|
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
@@ -229354,7 +229616,7 @@ ${errText}`
|
|
|
229354
229616
|
}
|
|
229355
229617
|
resetProxyStartupState();
|
|
229356
229618
|
try {
|
|
229357
|
-
const configPath =
|
|
229619
|
+
const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
|
|
229358
229620
|
if (existsSync4(configPath)) {
|
|
229359
229621
|
const config = JSON.parse(readTextFileSync(configPath));
|
|
229360
229622
|
if (config.models?.providers?.blockrun) {
|
|
@@ -229396,11 +229658,11 @@ ${errText}`
|
|
|
229396
229658
|
api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
229397
229659
|
}
|
|
229398
229660
|
try {
|
|
229399
|
-
const agentsDir =
|
|
229661
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
229400
229662
|
if (existsSync4(agentsDir)) {
|
|
229401
229663
|
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
229402
229664
|
if (!entry.isDirectory()) continue;
|
|
229403
|
-
const authPath =
|
|
229665
|
+
const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
|
|
229404
229666
|
if (!existsSync4(authPath)) continue;
|
|
229405
229667
|
try {
|
|
229406
229668
|
const store = JSON.parse(readTextFileSync(authPath));
|
|
@@ -229479,6 +229741,7 @@ init_client2();
|
|
|
229479
229741
|
init_esm5();
|
|
229480
229742
|
init_auth();
|
|
229481
229743
|
init_balance();
|
|
229744
|
+
init_api_key();
|
|
229482
229745
|
init_wallet2();
|
|
229483
229746
|
init_stats();
|
|
229484
229747
|
init_proxy();
|
|
@@ -229520,7 +229783,48 @@ function collectSystemInfo() {
|
|
|
229520
229783
|
memoryTotal: formatBytes(totalmem())
|
|
229521
229784
|
};
|
|
229522
229785
|
}
|
|
229523
|
-
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 };
|
|
229524
229828
|
try {
|
|
229525
229829
|
const { key: key2, address: address2, source, solanaPrivateKeyBytes } = await resolveOrGenerateWalletKey();
|
|
229526
229830
|
if (!key2 || !address2) {
|
|
@@ -229639,6 +229943,14 @@ async function collectLogInfo() {
|
|
|
229639
229943
|
}
|
|
229640
229944
|
function identifyIssues(result) {
|
|
229641
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
|
+
}
|
|
229642
229954
|
if (!result.wallet.exists) {
|
|
229643
229955
|
issues.push("No wallet found");
|
|
229644
229956
|
}
|
|
@@ -229651,6 +229963,9 @@ function identifyIssues(result) {
|
|
|
229651
229963
|
} else if (result.wallet.isLow) {
|
|
229652
229964
|
issues.push("Wallet balance is low (< $1.00)");
|
|
229653
229965
|
}
|
|
229966
|
+
return finishIssues(result, issues);
|
|
229967
|
+
}
|
|
229968
|
+
function finishIssues(result, issues) {
|
|
229654
229969
|
if (!result.network.blockrunApi.reachable) {
|
|
229655
229970
|
issues.push("Cannot reach BlockRun API - check internet connection");
|
|
229656
229971
|
}
|
|
@@ -229684,6 +229999,22 @@ function printDiagnostics(result) {
|
|
|
229684
229999
|
console.log(
|
|
229685
230000
|
` ${green(`Memory: ${result.system.memoryFree} free / ${result.system.memoryTotal}`)}`
|
|
229686
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
|
+
}
|
|
229687
230018
|
console.log("\nWallet");
|
|
229688
230019
|
if (result.wallet.exists && result.wallet.valid) {
|
|
229689
230020
|
const walletPath = result.wallet.source === "core" ? CORE_WALLET_FILE : WALLET_FILE;
|
|
@@ -229711,6 +230042,9 @@ function printDiagnostics(result) {
|
|
|
229711
230042
|
} else {
|
|
229712
230043
|
console.log(` ${red("No wallet found")}`);
|
|
229713
230044
|
}
|
|
230045
|
+
printRestOfDiagnostics(result);
|
|
230046
|
+
}
|
|
230047
|
+
function printRestOfDiagnostics(result) {
|
|
229714
230048
|
console.log("\nNetwork");
|
|
229715
230049
|
if (result.network.blockrunApi.reachable) {
|
|
229716
230050
|
console.log(
|
|
@@ -229760,6 +230094,10 @@ function createDoctorX402Client(opts) {
|
|
|
229760
230094
|
return x402;
|
|
229761
230095
|
}
|
|
229762
230096
|
async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
230097
|
+
if (diagnostics.apiKey.configured) {
|
|
230098
|
+
await analyzeWithApiKey(diagnostics, userQuestion, model);
|
|
230099
|
+
return;
|
|
230100
|
+
}
|
|
229763
230101
|
if (diagnostics.wallet.isEmpty) {
|
|
229764
230102
|
console.log("\n\u{1F4B3} Wallet is empty - cannot call AI for analysis.");
|
|
229765
230103
|
console.log(` Fund your EVM wallet with USDC on Base: ${diagnostics.wallet.address}`);
|
|
@@ -229807,30 +230145,7 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
|
229807
230145
|
body: JSON.stringify({
|
|
229808
230146
|
model: modelConfig.id,
|
|
229809
230147
|
stream: false,
|
|
229810
|
-
messages:
|
|
229811
|
-
{
|
|
229812
|
-
role: "system",
|
|
229813
|
-
content: `You are a technical support expert for BlockRun and ClawRouter.
|
|
229814
|
-
Analyze the diagnostics and:
|
|
229815
|
-
1. Identify the root cause of any issues
|
|
229816
|
-
2. Provide specific, actionable fix commands (bash)
|
|
229817
|
-
3. Explain why the issue occurred briefly
|
|
229818
|
-
4. Be concise but thorough
|
|
229819
|
-
5. Format commands in code blocks`
|
|
229820
|
-
},
|
|
229821
|
-
{
|
|
229822
|
-
role: "user",
|
|
229823
|
-
content: userQuestion ? `Here are my system diagnostics:
|
|
229824
|
-
|
|
229825
|
-
${JSON.stringify(diagnostics, null, 2)}
|
|
229826
|
-
|
|
229827
|
-
User's question: ${userQuestion}` : `Here are my system diagnostics:
|
|
229828
|
-
|
|
229829
|
-
${JSON.stringify(diagnostics, null, 2)}
|
|
229830
|
-
|
|
229831
|
-
Please analyze and help me fix any issues.`
|
|
229832
|
-
}
|
|
229833
|
-
],
|
|
230148
|
+
messages: doctorMessages(diagnostics, userQuestion),
|
|
229834
230149
|
max_tokens: 1e3
|
|
229835
230150
|
})
|
|
229836
230151
|
});
|
|
@@ -229854,13 +230169,91 @@ Error calling AI: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
229854
230169
|
console.log("Try again or check your wallet balance.\n");
|
|
229855
230170
|
}
|
|
229856
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
|
+
}
|
|
229857
230249
|
async function runDoctor(userQuestion, model = "sonnet") {
|
|
229858
230250
|
console.log(`
|
|
229859
230251
|
\u{1FA7A} BlockRun Doctor v${VERSION}
|
|
229860
230252
|
`);
|
|
230253
|
+
const apiKey = await collectApiKeyInfo();
|
|
229861
230254
|
const [system, wallet, network, logs, latestVersion] = await Promise.all([
|
|
229862
230255
|
collectSystemInfo(),
|
|
229863
|
-
collectWalletInfo(),
|
|
230256
|
+
collectWalletInfo(apiKey.configured),
|
|
229864
230257
|
collectNetworkInfo(),
|
|
229865
230258
|
collectLogInfo(),
|
|
229866
230259
|
fetchLatestVersion()
|
|
@@ -229870,6 +230263,7 @@ async function runDoctor(userQuestion, model = "sonnet") {
|
|
|
229870
230263
|
latestVersion,
|
|
229871
230264
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
229872
230265
|
system,
|
|
230266
|
+
apiKey,
|
|
229873
230267
|
wallet,
|
|
229874
230268
|
network,
|
|
229875
230269
|
logs,
|
|
@@ -229883,12 +230277,15 @@ async function runDoctor(userQuestion, model = "sonnet") {
|
|
|
229883
230277
|
// src/cli.ts
|
|
229884
230278
|
init_partners();
|
|
229885
230279
|
init_policy();
|
|
230280
|
+
init_api_key();
|
|
229886
230281
|
function printHelp() {
|
|
229887
230282
|
console.log(`
|
|
229888
230283
|
ClawRouter v${VERSION} - Smart LLM Router
|
|
229889
230284
|
|
|
229890
230285
|
Usage:
|
|
229891
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
|
|
229892
230289
|
clawrouter status # Live proxy status (wallet, balance, chain)
|
|
229893
230290
|
clawrouter wallet # Wallet details + balance
|
|
229894
230291
|
clawrouter models # List available models
|
|
@@ -229939,11 +230336,20 @@ Management Commands:
|
|
|
229939
230336
|
partners List available partner APIs with pricing
|
|
229940
230337
|
partners test Test partner API endpoints (expect 402 = alive)
|
|
229941
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
|
|
229942
230341
|
chain solana Switch to Solana (persists)
|
|
229943
230342
|
chain base Switch to Base EVM (persists)
|
|
229944
230343
|
policy Edit spend limits and counterparty lists (fail-closed; restart proxy to apply)
|
|
229945
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
|
+
|
|
229946
230351
|
Environment Variables:
|
|
230352
|
+
BLOCKRUN_API_KEY BlockRun API key (brk_...) \u2014 wins over the wallet when set
|
|
229947
230353
|
BLOCKRUN_WALLET_KEY Private key for x402 payments (auto-generated if not set)
|
|
229948
230354
|
BLOCKRUN_PROXY_PORT Default proxy port (default: 8402)
|
|
229949
230355
|
|
|
@@ -230026,7 +230432,7 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
|
|
|
230026
230432
|
const idOrLast = tgt === "last" ? "last" : tgt;
|
|
230027
230433
|
if (all3) {
|
|
230028
230434
|
try {
|
|
230029
|
-
const { writeFile:
|
|
230435
|
+
const { writeFile: writeFile4 } = await import("fs/promises");
|
|
230030
230436
|
const { tmpdir } = await import("os");
|
|
230031
230437
|
const { join: pjoin } = await import("path");
|
|
230032
230438
|
const paths = [];
|
|
@@ -230034,7 +230440,7 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
|
|
|
230034
230440
|
const path5 = idOrLast === "last" ? `/share/last?as=${p}` : `/share/${idOrLast}/render?as=${p}`;
|
|
230035
230441
|
const result = await queryProxy(path5, port);
|
|
230036
230442
|
const file = pjoin(tmpdir(), `claw-share-${result.id}-${p}.txt`);
|
|
230037
|
-
await
|
|
230443
|
+
await writeFile4(file, result.rendered, "utf8");
|
|
230038
230444
|
paths.push(file);
|
|
230039
230445
|
}
|
|
230040
230446
|
console.log("\n\u2713 Wrote all 6 preset variants:");
|
|
@@ -230079,12 +230485,19 @@ async function cmdStatus(port) {
|
|
|
230079
230485
|
ClawRouter Status (port ${port})
|
|
230080
230486
|
`);
|
|
230081
230487
|
console.log(` Status: ${data.status}`);
|
|
230082
|
-
|
|
230083
|
-
|
|
230084
|
-
|
|
230085
|
-
|
|
230086
|
-
|
|
230087
|
-
|
|
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
|
+
}
|
|
230088
230501
|
if (data.upstreamProxy) console.log(` Upstream Proxy: ${data.upstreamProxy}`);
|
|
230089
230502
|
console.log();
|
|
230090
230503
|
} catch {
|
|
@@ -230096,6 +230509,20 @@ ClawRouter Status (port ${port})
|
|
|
230096
230509
|
async function cmdWallet(port) {
|
|
230097
230510
|
try {
|
|
230098
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
|
+
}
|
|
230099
230526
|
console.log(`
|
|
230100
230527
|
ClawRouter Wallet
|
|
230101
230528
|
`);
|
|
@@ -230321,8 +230748,8 @@ async function cmdSetup() {
|
|
|
230321
230748
|
unlinkSync: unlinkSync2,
|
|
230322
230749
|
writeFileSync: writeFileSync4
|
|
230323
230750
|
} = await import("fs");
|
|
230324
|
-
const { dirname: dirname4, join:
|
|
230325
|
-
const { homedir:
|
|
230751
|
+
const { dirname: dirname4, join: join18 } = await import("path");
|
|
230752
|
+
const { homedir: homedir15 } = await import("os");
|
|
230326
230753
|
const { injectModelsConfig: injectModelsConfig2, injectAuthProfile: injectAuthProfile2, syncAgentModelCache: syncAgentModelCache2, VISIBLE_OPENCLAW_MODELS: VISIBLE_OPENCLAW_MODELS2 } = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
230327
230754
|
const { BLOCKRUN_PLUGIN_ID: BLOCKRUN_PLUGIN_ID2, prepareBlockRunPluginConfig: prepareBlockRunPluginConfig2 } = await Promise.resolve().then(() => (init_openclaw_plugin_config(), openclaw_plugin_config_exports));
|
|
230328
230755
|
console.log("\u{1F99E} ClawRouter setup\n");
|
|
@@ -230337,19 +230764,19 @@ async function cmdSetup() {
|
|
|
230337
230764
|
} catch {
|
|
230338
230765
|
}
|
|
230339
230766
|
if (process.env.npm_config_prefix) {
|
|
230340
|
-
candidates.push(
|
|
230767
|
+
candidates.push(join18(process.env.npm_config_prefix, "bin", "openclaw"));
|
|
230341
230768
|
}
|
|
230342
230769
|
try {
|
|
230343
230770
|
const npmRoot = execSync("npm root -g", {
|
|
230344
230771
|
encoding: "utf8",
|
|
230345
230772
|
stdio: ["ignore", "pipe", "ignore"]
|
|
230346
230773
|
}).trim();
|
|
230347
|
-
if (npmRoot) candidates.push(
|
|
230774
|
+
if (npmRoot) candidates.push(join18(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
|
|
230348
230775
|
} catch {
|
|
230349
230776
|
}
|
|
230350
230777
|
candidates.push(
|
|
230351
|
-
|
|
230352
|
-
|
|
230778
|
+
join18(homedir15(), ".npm-global", "bin", "openclaw"),
|
|
230779
|
+
join18(homedir15(), ".local", "bin", "openclaw"),
|
|
230353
230780
|
"/usr/local/bin/openclaw"
|
|
230354
230781
|
);
|
|
230355
230782
|
return candidates.find((candidate) => existsSync5(candidate)) ?? "";
|
|
@@ -230361,7 +230788,7 @@ async function cmdSetup() {
|
|
|
230361
230788
|
process.exit(1);
|
|
230362
230789
|
}
|
|
230363
230790
|
console.log(` \u2713 Found openclaw at ${openclawPath}`);
|
|
230364
|
-
const configPath =
|
|
230791
|
+
const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
|
|
230365
230792
|
const configInitiallyExisted = existsSync5(configPath);
|
|
230366
230793
|
const configRollbackBackup = configInitiallyExisted ? `${configPath}.before-${BLOCKRUN_PLUGIN_ID2}.${Date.now()}` : void 0;
|
|
230367
230794
|
if (configRollbackBackup) copyFileSync2(configPath, configRollbackBackup);
|
|
@@ -230369,23 +230796,23 @@ async function cmdSetup() {
|
|
|
230369
230796
|
return configRollbackBackup;
|
|
230370
230797
|
};
|
|
230371
230798
|
let installAttempted = false;
|
|
230372
|
-
const openclawRoot =
|
|
230799
|
+
const openclawRoot = join18(homedir15(), ".openclaw");
|
|
230373
230800
|
const findManagedBlockRunInstalls = () => {
|
|
230374
230801
|
const candidates = [
|
|
230375
|
-
|
|
230376
|
-
|
|
230377
|
-
|
|
230802
|
+
join18(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
|
|
230803
|
+
join18(openclawRoot, "extensions", "clawrouter"),
|
|
230804
|
+
join18(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
|
|
230378
230805
|
];
|
|
230379
|
-
const projectsDir =
|
|
230806
|
+
const projectsDir = join18(openclawRoot, "npm", "projects");
|
|
230380
230807
|
try {
|
|
230381
230808
|
for (const project of readdirSync2(projectsDir)) {
|
|
230382
|
-
candidates.push(
|
|
230809
|
+
candidates.push(join18(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
|
|
230383
230810
|
}
|
|
230384
230811
|
} catch {
|
|
230385
230812
|
}
|
|
230386
230813
|
return [...new Set(candidates)].filter((candidate) => {
|
|
230387
230814
|
try {
|
|
230388
|
-
const metadata = JSON.parse(readFileSync4(
|
|
230815
|
+
const metadata = JSON.parse(readFileSync4(join18(candidate, "package.json"), "utf8"));
|
|
230389
230816
|
return metadata.name === "@blockrun/clawrouter";
|
|
230390
230817
|
} catch {
|
|
230391
230818
|
return false;
|
|
@@ -230393,7 +230820,7 @@ async function cmdSetup() {
|
|
|
230393
230820
|
});
|
|
230394
230821
|
};
|
|
230395
230822
|
const initialPluginDirs = findManagedBlockRunInstalls();
|
|
230396
|
-
const setupRollbackRoot =
|
|
230823
|
+
const setupRollbackRoot = join18(
|
|
230397
230824
|
openclawRoot,
|
|
230398
230825
|
"blockrun",
|
|
230399
230826
|
"setup-rollbacks",
|
|
@@ -230403,7 +230830,7 @@ async function cmdSetup() {
|
|
|
230403
230830
|
if (initialPluginDirs.length > 0) {
|
|
230404
230831
|
mkdirSync4(setupRollbackRoot, { recursive: true, mode: 448 });
|
|
230405
230832
|
for (const [index2, pluginDir] of initialPluginDirs.entries()) {
|
|
230406
|
-
const backup =
|
|
230833
|
+
const backup = join18(setupRollbackRoot, String(index2));
|
|
230407
230834
|
cpSync(pluginDir, backup, { recursive: true });
|
|
230408
230835
|
pluginRollbackBackups.set(pluginDir, backup);
|
|
230409
230836
|
}
|
|
@@ -230448,8 +230875,8 @@ async function cmdSetup() {
|
|
|
230448
230875
|
if (existsSync5(configPath)) {
|
|
230449
230876
|
try {
|
|
230450
230877
|
const config = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
230451
|
-
const legacyPackagePath =
|
|
230452
|
-
|
|
230878
|
+
const legacyPackagePath = join18(
|
|
230879
|
+
homedir15(),
|
|
230453
230880
|
".openclaw",
|
|
230454
230881
|
"extensions",
|
|
230455
230882
|
"clawrouter",
|
|
@@ -230633,7 +231060,10 @@ function parseArgs(args) {
|
|
|
230633
231060
|
phoneArg: void 0,
|
|
230634
231061
|
phoneAreaCode: void 0,
|
|
230635
231062
|
policy: false,
|
|
230636
|
-
policyArgs: []
|
|
231063
|
+
policyArgs: [],
|
|
231064
|
+
login: false,
|
|
231065
|
+
loginKey: void 0,
|
|
231066
|
+
logout: false
|
|
230637
231067
|
};
|
|
230638
231068
|
for (let i = 0; i < args.length; i++) {
|
|
230639
231069
|
const arg = args[i];
|
|
@@ -230694,6 +231124,14 @@ function parseArgs(args) {
|
|
|
230694
231124
|
} else if ((arg === "chain" || arg === "/wallet") && (args[i + 1] === "solana" || args[i + 1] === "base")) {
|
|
230695
231125
|
result.chain = args[i + 1];
|
|
230696
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;
|
|
230697
231135
|
} else if (arg === "policy") {
|
|
230698
231136
|
result.policy = true;
|
|
230699
231137
|
result.policyArgs = args.slice(i + 1);
|
|
@@ -230776,6 +231214,58 @@ function parseArgs(args) {
|
|
|
230776
231214
|
}
|
|
230777
231215
|
return result;
|
|
230778
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
|
+
}
|
|
230779
231269
|
async function main() {
|
|
230780
231270
|
const args = parseArgs(process.argv.slice(2));
|
|
230781
231271
|
if (args.version) {
|
|
@@ -230913,6 +231403,37 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
230913
231403
|
await recoverWalletFromMnemonic();
|
|
230914
231404
|
process.exit(0);
|
|
230915
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
|
+
}
|
|
230916
231437
|
if (args.chain) {
|
|
230917
231438
|
const targetChain = args.chain;
|
|
230918
231439
|
if (targetChain === "solana") {
|
|
@@ -230949,6 +231470,11 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
230949
231470
|
console.log(report);
|
|
230950
231471
|
process.exit(0);
|
|
230951
231472
|
}
|
|
231473
|
+
const apiKeyResolution = await resolveApiKey();
|
|
231474
|
+
if (apiKeyResolution) {
|
|
231475
|
+
await startWithApiKey(apiKeyResolution.key, apiKeyResolution.source, args);
|
|
231476
|
+
return;
|
|
231477
|
+
}
|
|
230952
231478
|
const wallet = await resolveOrGenerateWalletKey();
|
|
230953
231479
|
if (wallet.source === "generated") {
|
|
230954
231480
|
console.log(`[ClawRouter] Generated new wallet: ${wallet.address}`);
|
|
@@ -231011,20 +231537,7 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
231011
231537
|
console.log(`[ClawRouter] Wallet: ${displayAddress} (balance check pending)`);
|
|
231012
231538
|
}
|
|
231013
231539
|
console.log(`[ClawRouter] Ready - Ctrl+C to stop`);
|
|
231014
|
-
|
|
231015
|
-
console.log(`
|
|
231016
|
-
[ClawRouter] Received ${signal}, shutting down...`);
|
|
231017
|
-
try {
|
|
231018
|
-
await proxy.close();
|
|
231019
|
-
console.log(`[ClawRouter] Proxy closed`);
|
|
231020
|
-
process.exit(0);
|
|
231021
|
-
} catch (err) {
|
|
231022
|
-
console.error(`[ClawRouter] Error during shutdown: ${err}`);
|
|
231023
|
-
process.exit(1);
|
|
231024
|
-
}
|
|
231025
|
-
};
|
|
231026
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
231027
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
231540
|
+
installShutdownHandlers(proxy);
|
|
231028
231541
|
await new Promise(() => {
|
|
231029
231542
|
});
|
|
231030
231543
|
}
|