@blockrun/clawrouter 0.12.263 → 0.12.265
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +215 -85
- package/dist/index.d.ts +8 -1
- package/dist/index.js +204 -80
- package/openclaw.plugin.json +5 -5
- package/package.json +2 -1
- package/scripts/sync-brand-numbers.mjs +0 -270
package/dist/cli.js
CHANGED
|
@@ -117032,6 +117032,35 @@ var init_client5 = __esm({
|
|
|
117032
117032
|
}
|
|
117033
117033
|
});
|
|
117034
117034
|
|
|
117035
|
+
// src/polymarket/spend-policy.ts
|
|
117036
|
+
function resolveSpendControl(deps) {
|
|
117037
|
+
if (deps?.spendControl) return deps.spendControl;
|
|
117038
|
+
defaultControl ??= new SpendControl();
|
|
117039
|
+
return defaultControl;
|
|
117040
|
+
}
|
|
117041
|
+
function usdToMicros(usd) {
|
|
117042
|
+
return String(Math.ceil(usd * 1e6));
|
|
117043
|
+
}
|
|
117044
|
+
async function signUnderSpendPolicy(deps, quote, action, sign4) {
|
|
117045
|
+
const control = resolveSpendControl(deps);
|
|
117046
|
+
const reservation = assertSpendPolicyAllows(control, quote);
|
|
117047
|
+
try {
|
|
117048
|
+
const signed2 = await sign4();
|
|
117049
|
+
if (reservation !== void 0) control.settleReservation(reservation, { action });
|
|
117050
|
+
return signed2;
|
|
117051
|
+
} catch (err) {
|
|
117052
|
+
if (reservation !== void 0) control.releaseReservation(reservation);
|
|
117053
|
+
throw err;
|
|
117054
|
+
}
|
|
117055
|
+
}
|
|
117056
|
+
var defaultControl;
|
|
117057
|
+
var init_spend_policy = __esm({
|
|
117058
|
+
"src/polymarket/spend-policy.ts"() {
|
|
117059
|
+
"use strict";
|
|
117060
|
+
init_spend_control();
|
|
117061
|
+
}
|
|
117062
|
+
});
|
|
117063
|
+
|
|
117035
117064
|
// src/polymarket/orders.ts
|
|
117036
117065
|
function getSessionLedger() {
|
|
117037
117066
|
return {
|
|
@@ -117100,6 +117129,9 @@ function isCredsMismatchError(err) {
|
|
|
117100
117129
|
return msg.includes("unauthorized") || msg.includes("api key not found") || msg.includes("invalid api key");
|
|
117101
117130
|
}
|
|
117102
117131
|
async function mapClobError(err) {
|
|
117132
|
+
if (err instanceof SpendPolicyError) {
|
|
117133
|
+
return `Refused by spend policy \u2014 nothing was signed. ${err.message} (see ~/.openclaw/blockrun/spending.json)`;
|
|
117134
|
+
}
|
|
117103
117135
|
const e7 = err;
|
|
117104
117136
|
const dataText = e7?.data ? ` \u2014 ${typeof e7.data === "string" ? e7.data : JSON.stringify(e7.data)}` : "";
|
|
117105
117137
|
const message = `${e7?.message ?? String(err)}${dataText}`;
|
|
@@ -117146,7 +117178,7 @@ async function withCredsRetry(fn) {
|
|
|
117146
117178
|
return fn(fresh);
|
|
117147
117179
|
}
|
|
117148
117180
|
}
|
|
117149
|
-
async function executeTrade(input) {
|
|
117181
|
+
async function executeTrade(input, deps) {
|
|
117150
117182
|
const side = input.action === "buy" ? Side.BUY : Side.SELL;
|
|
117151
117183
|
const isLimit = input.price !== void 0;
|
|
117152
117184
|
if (isLimit && input.size === void 0) {
|
|
@@ -117158,6 +117190,18 @@ async function executeTrade(input) {
|
|
|
117158
117190
|
isError: true
|
|
117159
117191
|
};
|
|
117160
117192
|
}
|
|
117193
|
+
if (!isLimit && input.action === "buy" && input.amount_usd !== void 0 && !(Number.isFinite(input.amount_usd) && input.amount_usd > 0)) {
|
|
117194
|
+
return {
|
|
117195
|
+
text: `amount_usd must be a positive dollar amount to spend, got ${input.amount_usd}.`,
|
|
117196
|
+
isError: true
|
|
117197
|
+
};
|
|
117198
|
+
}
|
|
117199
|
+
if (isLimit && input.size !== void 0 && !(Number.isFinite(input.size) && input.size > 0)) {
|
|
117200
|
+
return {
|
|
117201
|
+
text: `size must be a positive number of shares, got ${input.size}.`,
|
|
117202
|
+
isError: true
|
|
117203
|
+
};
|
|
117204
|
+
}
|
|
117161
117205
|
if (!isLimit && input.action === "sell" && input.size === void 0) {
|
|
117162
117206
|
return { text: `Market sells need size (shares to sell).`, isError: true };
|
|
117163
117207
|
}
|
|
@@ -117257,39 +117301,44 @@ Re-call with confirm:true to sign and submit.`,
|
|
|
117257
117301
|
}
|
|
117258
117302
|
const options = { tickSize, negRisk };
|
|
117259
117303
|
reserveBet(notional, input.agent_id);
|
|
117260
|
-
|
|
117304
|
+
const policyQuote = {
|
|
117305
|
+
payTo: negRisk ? NEG_RISK_CTF_EXCHANGE_V2 : CTF_EXCHANGE_V2,
|
|
117306
|
+
network: `eip155:${POLYGON_CHAIN_ID}`,
|
|
117307
|
+
asset: PUSD_COLLATERAL,
|
|
117308
|
+
amount: usdToMicros(notional)
|
|
117309
|
+
};
|
|
117310
|
+
let r2;
|
|
117261
117311
|
try {
|
|
117262
|
-
|
|
117263
|
-
|
|
117264
|
-
|
|
117265
|
-
|
|
117266
|
-
|
|
117267
|
-
|
|
117268
|
-
|
|
117269
|
-
|
|
117270
|
-
|
|
117271
|
-
|
|
117272
|
-
|
|
117273
|
-
|
|
117274
|
-
|
|
117275
|
-
|
|
117276
|
-
|
|
117277
|
-
|
|
117278
|
-
|
|
117279
|
-
|
|
117280
|
-
|
|
117281
|
-
|
|
117282
|
-
|
|
117312
|
+
r2 = await signUnderSpendPolicy(deps, policyQuote, "polymarket order", async () => {
|
|
117313
|
+
const res = await (isLimit ? clob.createAndPostOrder(
|
|
117314
|
+
{
|
|
117315
|
+
tokenID: token.tokenId,
|
|
117316
|
+
price,
|
|
117317
|
+
size: size5,
|
|
117318
|
+
side,
|
|
117319
|
+
...orderKind === "GTD" && input.expires_at ? { expiration: input.expires_at } : {}
|
|
117320
|
+
},
|
|
117321
|
+
options,
|
|
117322
|
+
orderKind === "GTD" ? OrderType.GTD : OrderType.GTC,
|
|
117323
|
+
input.post_only ?? false
|
|
117324
|
+
) : clob.createAndPostMarketOrder(
|
|
117325
|
+
{
|
|
117326
|
+
tokenID: token.tokenId,
|
|
117327
|
+
amount: input.action === "buy" ? input.amount_usd : size5,
|
|
117328
|
+
side,
|
|
117329
|
+
orderType: orderKind === "FAK" ? OrderType.FAK : OrderType.FOK
|
|
117330
|
+
},
|
|
117331
|
+
options,
|
|
117332
|
+
orderKind === "FAK" ? OrderType.FAK : OrderType.FOK
|
|
117333
|
+
));
|
|
117334
|
+
const placed = res?.success !== false && (res?.orderID || res?.status === "matched");
|
|
117335
|
+
if (!placed) throw new Error(res?.errorMsg || "order rejected");
|
|
117336
|
+
return res;
|
|
117337
|
+
});
|
|
117283
117338
|
} catch (submitErr) {
|
|
117284
117339
|
releaseBet(notional, input.agent_id);
|
|
117285
117340
|
throw submitErr;
|
|
117286
117341
|
}
|
|
117287
|
-
const r2 = response;
|
|
117288
|
-
const placed = r2?.success !== false && (r2?.orderID || r2?.status === "matched");
|
|
117289
|
-
if (!placed) {
|
|
117290
|
-
releaseBet(notional, input.agent_id);
|
|
117291
|
-
throw new Error(r2?.errorMsg || "order rejected");
|
|
117292
|
-
}
|
|
117293
117342
|
commitBet();
|
|
117294
117343
|
const filled = r2?.status === "matched" || (r2?.transactionsHashes?.length ?? 0) > 0;
|
|
117295
117344
|
return {
|
|
@@ -117378,6 +117427,8 @@ var init_orders = __esm({
|
|
|
117378
117427
|
init_client5();
|
|
117379
117428
|
init_constants3();
|
|
117380
117429
|
init_creds();
|
|
117430
|
+
init_spend_control();
|
|
117431
|
+
init_spend_policy();
|
|
117381
117432
|
ledger = {
|
|
117382
117433
|
totalUsd: 0,
|
|
117383
117434
|
count: 0,
|
|
@@ -225312,7 +225363,7 @@ var init_setup = __esm({
|
|
|
225312
225363
|
});
|
|
225313
225364
|
|
|
225314
225365
|
// src/polymarket/redeem.ts
|
|
225315
|
-
async function redeemPosition(input) {
|
|
225366
|
+
async function redeemPosition(input, deps) {
|
|
225316
225367
|
if (!input.condition_id) {
|
|
225317
225368
|
return {
|
|
225318
225369
|
text: `Pass condition_id:"0x\u2026" (see action:"positions" for redeemable markets).`,
|
|
@@ -225388,20 +225439,31 @@ async function redeemPosition(input) {
|
|
|
225388
225439
|
]
|
|
225389
225440
|
});
|
|
225390
225441
|
const target = negRisk ? NEG_RISK_ADAPTER : CONDITIONAL_TOKENS;
|
|
225391
|
-
|
|
225392
|
-
|
|
225393
|
-
|
|
225394
|
-
|
|
225395
|
-
|
|
225396
|
-
|
|
225397
|
-
|
|
225398
|
-
|
|
225399
|
-
|
|
225400
|
-
|
|
225401
|
-
|
|
225402
|
-
|
|
225403
|
-
|
|
225404
|
-
|
|
225442
|
+
const eoa = getSigType() !== 3;
|
|
225443
|
+
const txHash = await signUnderSpendPolicy(
|
|
225444
|
+
deps,
|
|
225445
|
+
{
|
|
225446
|
+
payTo: target,
|
|
225447
|
+
network: `eip155:${POLYGON_CHAIN_ID}`,
|
|
225448
|
+
asset: PUSD_COLLATERAL,
|
|
225449
|
+
amount: "0"
|
|
225450
|
+
},
|
|
225451
|
+
"polymarket redeem",
|
|
225452
|
+
async () => {
|
|
225453
|
+
if (!eoa) {
|
|
225454
|
+
const res = await sendWalletBatch([{ target, value: "0", data }], owner, "Redeem");
|
|
225455
|
+
return res.transactionHash;
|
|
225456
|
+
}
|
|
225457
|
+
const account = getPolymarketAccount();
|
|
225458
|
+
const wallet = createWalletClient({
|
|
225459
|
+
account,
|
|
225460
|
+
chain: polygon,
|
|
225461
|
+
transport: http(POLYGON_RPC_URLS[0])
|
|
225462
|
+
});
|
|
225463
|
+
return wallet.sendTransaction({ to: target, data, chain: polygon, account });
|
|
225464
|
+
}
|
|
225465
|
+
);
|
|
225466
|
+
if (eoa) await pc.waitForTransactionReceipt({ hash: txHash });
|
|
225405
225467
|
const balanceAfter = await getPusdBalance(owner).catch(() => null);
|
|
225406
225468
|
return {
|
|
225407
225469
|
text: [
|
|
@@ -225432,6 +225494,7 @@ var init_redeem = __esm({
|
|
|
225432
225494
|
init_positions();
|
|
225433
225495
|
init_relayer();
|
|
225434
225496
|
init_setup();
|
|
225497
|
+
init_spend_policy();
|
|
225435
225498
|
}
|
|
225436
225499
|
});
|
|
225437
225500
|
|
|
@@ -225444,7 +225507,13 @@ async function rawPusdBalance(owner) {
|
|
|
225444
225507
|
args: [owner]
|
|
225445
225508
|
});
|
|
225446
225509
|
}
|
|
225447
|
-
async function withdrawFunds(input) {
|
|
225510
|
+
async function withdrawFunds(input, deps) {
|
|
225511
|
+
if (input.amount_usd !== void 0 && !(Number.isFinite(input.amount_usd) && input.amount_usd > 0)) {
|
|
225512
|
+
return {
|
|
225513
|
+
text: `amount_usd must be a positive dollar amount, got ${input.amount_usd}. Omit it to withdraw the full balance.`,
|
|
225514
|
+
isError: true
|
|
225515
|
+
};
|
|
225516
|
+
}
|
|
225448
225517
|
let owner;
|
|
225449
225518
|
try {
|
|
225450
225519
|
owner = getFundsAddress();
|
|
@@ -225515,29 +225584,40 @@ async function withdrawFunds(input) {
|
|
|
225515
225584
|
functionName: "transfer",
|
|
225516
225585
|
args: [bridgeEvm, amountRaw]
|
|
225517
225586
|
});
|
|
225518
|
-
|
|
225519
|
-
|
|
225520
|
-
|
|
225521
|
-
|
|
225522
|
-
|
|
225523
|
-
|
|
225524
|
-
|
|
225525
|
-
|
|
225526
|
-
|
|
225527
|
-
|
|
225528
|
-
|
|
225529
|
-
|
|
225530
|
-
|
|
225531
|
-
|
|
225532
|
-
|
|
225533
|
-
|
|
225534
|
-
|
|
225535
|
-
|
|
225536
|
-
|
|
225537
|
-
account
|
|
225538
|
-
|
|
225539
|
-
|
|
225540
|
-
|
|
225587
|
+
const eoa = getSigType() !== 3;
|
|
225588
|
+
const txHash = await signUnderSpendPolicy(
|
|
225589
|
+
deps,
|
|
225590
|
+
{
|
|
225591
|
+
payTo: recipient,
|
|
225592
|
+
network: `eip155:${BASE_CHAIN_ID}`,
|
|
225593
|
+
asset: BASE_USDC,
|
|
225594
|
+
amount: amountRaw.toString()
|
|
225595
|
+
},
|
|
225596
|
+
"polymarket withdraw",
|
|
225597
|
+
async () => {
|
|
225598
|
+
if (!eoa) {
|
|
225599
|
+
const res = await sendWalletBatch(
|
|
225600
|
+
[{ target: PUSD_COLLATERAL, value: "0", data }],
|
|
225601
|
+
owner,
|
|
225602
|
+
"Withdraw"
|
|
225603
|
+
);
|
|
225604
|
+
return res.transactionHash;
|
|
225605
|
+
}
|
|
225606
|
+
const account = getPolymarketAccount();
|
|
225607
|
+
const wallet = createWalletClient({
|
|
225608
|
+
account,
|
|
225609
|
+
chain: polygon,
|
|
225610
|
+
transport: http(POLYGON_RPC_URLS[0])
|
|
225611
|
+
});
|
|
225612
|
+
return wallet.sendTransaction({
|
|
225613
|
+
to: PUSD_COLLATERAL,
|
|
225614
|
+
data,
|
|
225615
|
+
chain: polygon,
|
|
225616
|
+
account
|
|
225617
|
+
});
|
|
225618
|
+
}
|
|
225619
|
+
);
|
|
225620
|
+
if (eoa) await getPublicClient().waitForTransactionReceipt({ hash: txHash });
|
|
225541
225621
|
return {
|
|
225542
225622
|
text: [
|
|
225543
225623
|
`\u2705 Withdrawal submitted: $${amountUsd.toFixed(2)} pUSD \u2192 USDC on Base`,
|
|
@@ -225572,6 +225652,7 @@ var init_withdraw = __esm({
|
|
|
225572
225652
|
init_positions();
|
|
225573
225653
|
init_relayer();
|
|
225574
225654
|
init_setup();
|
|
225655
|
+
init_spend_policy();
|
|
225575
225656
|
}
|
|
225576
225657
|
});
|
|
225577
225658
|
|
|
@@ -226217,7 +226298,7 @@ async function bridgeAddressFor(vault) {
|
|
|
226217
226298
|
throw new Error(`Bridge did not return a deposit address (got: ${JSON.stringify(res.data)}).`);
|
|
226218
226299
|
return evm;
|
|
226219
226300
|
}
|
|
226220
|
-
async function fundVault(input) {
|
|
226301
|
+
async function fundVault(input, deps) {
|
|
226221
226302
|
if (input.amount_usd === void 0 || input.amount_usd <= 0) {
|
|
226222
226303
|
return {
|
|
226223
226304
|
text: `Pass amount_usd \u2014 the USDC amount to move from your Base wallet into your Polymarket vault (e.g. amount_usd:5).`,
|
|
@@ -226274,12 +226355,12 @@ async function fundVault(input) {
|
|
|
226274
226355
|
}
|
|
226275
226356
|
const privateKey = getOrCreateWalletKey();
|
|
226276
226357
|
const amountMicro = String(Math.floor(amountUsd * 10 ** USDC_DECIMALS));
|
|
226277
|
-
const
|
|
226278
|
-
|
|
226279
|
-
|
|
226280
|
-
bridge,
|
|
226281
|
-
|
|
226282
|
-
|
|
226358
|
+
const network = `eip155:${BASE_CHAIN_ID}`;
|
|
226359
|
+
const depositAuthorization = await signUnderSpendPolicy(
|
|
226360
|
+
deps,
|
|
226361
|
+
{ payTo: bridge, network, asset: BASE_USDC, amount: amountMicro },
|
|
226362
|
+
"polymarket fund",
|
|
226363
|
+
() => createPaymentPayload(privateKey, agent, bridge, amountMicro, network)
|
|
226283
226364
|
);
|
|
226284
226365
|
const client = new BlockrunClient({ privateKey });
|
|
226285
226366
|
const result = await client.post("/v1/polymarket/fund", {
|
|
@@ -226329,6 +226410,7 @@ var init_fund = __esm({
|
|
|
226329
226410
|
init_constants3();
|
|
226330
226411
|
init_positions();
|
|
226331
226412
|
init_setup();
|
|
226413
|
+
init_spend_policy();
|
|
226332
226414
|
FUND_FEE_USD = 0.01;
|
|
226333
226415
|
USDC_DECIMALS = 6;
|
|
226334
226416
|
FUND_MIN_USD = Number(process.env.POLYMARKET_FUND_MIN_USD || "2");
|
|
@@ -226753,6 +226835,7 @@ var index_exports = {};
|
|
|
226753
226835
|
__export(index_exports, {
|
|
226754
226836
|
BALANCE_THRESHOLDS: () => BALANCE_THRESHOLDS,
|
|
226755
226837
|
BLOCKRUN_MODELS: () => BLOCKRUN_MODELS,
|
|
226838
|
+
BLOCKRUN_PLUGIN_ID: () => BLOCKRUN_PLUGIN_ID,
|
|
226756
226839
|
BalanceMonitor: () => BalanceMonitor,
|
|
226757
226840
|
CAIP2_BASE: () => CAIP2_BASE,
|
|
226758
226841
|
CAIP2_SOLANA_MAINNET: () => CAIP2_SOLANA_MAINNET,
|
|
@@ -227067,6 +227150,35 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
227067
227150
|
logger48.info(`Added ${addedCount} models to allowlist (${TOP_MODELS.length} total)`);
|
|
227068
227151
|
}
|
|
227069
227152
|
}
|
|
227153
|
+
const pluginEntries = config.plugins?.entries;
|
|
227154
|
+
if (pluginEntries && pluginEntries.clawrouter && !pluginEntries[BLOCKRUN_PLUGIN_ID]) {
|
|
227155
|
+
pluginEntries[BLOCKRUN_PLUGIN_ID] = pluginEntries.clawrouter;
|
|
227156
|
+
delete pluginEntries.clawrouter;
|
|
227157
|
+
needsWrite = true;
|
|
227158
|
+
logger48.info(
|
|
227159
|
+
"Migrated plugins.entries.clawrouter -> blockrun-clawrouter (OpenClaw bundles its own `clawrouter` plugin; see #305)"
|
|
227160
|
+
);
|
|
227161
|
+
}
|
|
227162
|
+
const pluginsCfg = config.plugins;
|
|
227163
|
+
const allow = pluginsCfg?.allow;
|
|
227164
|
+
if (Array.isArray(allow) && allow.includes("clawrouter") && !allow.includes(BLOCKRUN_PLUGIN_ID)) {
|
|
227165
|
+
allow.push(BLOCKRUN_PLUGIN_ID);
|
|
227166
|
+
needsWrite = true;
|
|
227167
|
+
logger48.info(`Added ${BLOCKRUN_PLUGIN_ID} to plugins.allow (renamed from clawrouter; see #305)`);
|
|
227168
|
+
}
|
|
227169
|
+
const deny = pluginsCfg?.deny;
|
|
227170
|
+
if (Array.isArray(deny) && deny.includes("clawrouter") && !deny.includes(BLOCKRUN_PLUGIN_ID)) {
|
|
227171
|
+
deny.push(BLOCKRUN_PLUGIN_ID);
|
|
227172
|
+
needsWrite = true;
|
|
227173
|
+
logger48.info(`Added ${BLOCKRUN_PLUGIN_ID} to plugins.deny (renamed from clawrouter; see #305)`);
|
|
227174
|
+
}
|
|
227175
|
+
const installs = pluginsCfg?.installs;
|
|
227176
|
+
if (installs && installs.clawrouter && !installs[BLOCKRUN_PLUGIN_ID]) {
|
|
227177
|
+
installs[BLOCKRUN_PLUGIN_ID] = installs.clawrouter;
|
|
227178
|
+
delete installs.clawrouter;
|
|
227179
|
+
needsWrite = true;
|
|
227180
|
+
logger48.info(`Migrated plugins.installs.clawrouter -> ${BLOCKRUN_PLUGIN_ID} (see #305)`);
|
|
227181
|
+
}
|
|
227070
227182
|
if (!config.tools || typeof config.tools !== "object" || Array.isArray(config.tools)) {
|
|
227071
227183
|
config.tools = {};
|
|
227072
227184
|
needsWrite = true;
|
|
@@ -228059,7 +228171,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
|
|
|
228059
228171
|
}
|
|
228060
228172
|
};
|
|
228061
228173
|
}
|
|
228062
|
-
var activeProxyHandle, pendingConfiguredStartupApi, IMAGE_DIR2, AUDIO_DIR2, VIDEO_DIR2, plugin, index_default2;
|
|
228174
|
+
var activeProxyHandle, pendingConfiguredStartupApi, IMAGE_DIR2, AUDIO_DIR2, VIDEO_DIR2, BLOCKRUN_PLUGIN_ID, plugin, index_default2;
|
|
228063
228175
|
var init_index = __esm({
|
|
228064
228176
|
"src/index.ts"() {
|
|
228065
228177
|
"use strict";
|
|
@@ -228102,9 +228214,16 @@ var init_index = __esm({
|
|
|
228102
228214
|
IMAGE_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "images");
|
|
228103
228215
|
AUDIO_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "audio");
|
|
228104
228216
|
VIDEO_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "videos");
|
|
228217
|
+
BLOCKRUN_PLUGIN_ID = "blockrun-clawrouter";
|
|
228105
228218
|
plugin = {
|
|
228106
|
-
|
|
228107
|
-
|
|
228219
|
+
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
228220
|
+
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
228221
|
+
// CLAWROUTER_API_KEY, clawrouter.openclaw.ai). A duplicate id loses to the
|
|
228222
|
+
// bundled one — "global plugin will be overridden by bundled plugin" — so this
|
|
228223
|
+
// plugin silently never loaded and the local proxy never started. Different
|
|
228224
|
+
// product, different id. See #305.
|
|
228225
|
+
id: BLOCKRUN_PLUGIN_ID,
|
|
228226
|
+
name: "BlockRun ClawRouter",
|
|
228108
228227
|
description: "Smart LLM router \u2014 55+ models, x402 micropayments, 78% cost savings",
|
|
228109
228228
|
version: VERSION,
|
|
228110
228229
|
register(api) {
|
|
@@ -228513,13 +228632,18 @@ ${errText}`
|
|
|
228513
228632
|
delete config.models.providers.blockrun;
|
|
228514
228633
|
}
|
|
228515
228634
|
removeManagedBlockrunMcpServerConfig(config);
|
|
228516
|
-
for (const key2 of [
|
|
228635
|
+
for (const key2 of [
|
|
228636
|
+
BLOCKRUN_PLUGIN_ID,
|
|
228637
|
+
"clawrouter",
|
|
228638
|
+
"ClawRouter",
|
|
228639
|
+
"@blockrun/clawrouter"
|
|
228640
|
+
]) {
|
|
228517
228641
|
if (config.plugins?.entries?.[key2]) delete config.plugins.entries[key2];
|
|
228518
228642
|
if (config.plugins?.installs?.[key2]) delete config.plugins.installs[key2];
|
|
228519
228643
|
}
|
|
228520
228644
|
if (Array.isArray(config.plugins?.allow)) {
|
|
228521
228645
|
config.plugins.allow = config.plugins.allow.filter(
|
|
228522
|
-
(p) => p !== "clawrouter" && p !== "ClawRouter" && p !== "@blockrun/clawrouter"
|
|
228646
|
+
(p) => p !== BLOCKRUN_PLUGIN_ID && p !== "clawrouter" && p !== "ClawRouter" && p !== "@blockrun/clawrouter"
|
|
228523
228647
|
);
|
|
228524
228648
|
}
|
|
228525
228649
|
if (config.agents?.defaults?.models) {
|
|
@@ -228628,6 +228752,7 @@ init_balance();
|
|
|
228628
228752
|
init_wallet2();
|
|
228629
228753
|
init_stats();
|
|
228630
228754
|
init_proxy();
|
|
228755
|
+
init_spend_control();
|
|
228631
228756
|
init_version4();
|
|
228632
228757
|
import { platform, arch, freemem, totalmem } from "os";
|
|
228633
228758
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
@@ -228894,6 +229019,15 @@ var DOCTOR_MODELS = {
|
|
|
228894
229019
|
cost: "~$0.01"
|
|
228895
229020
|
}
|
|
228896
229021
|
};
|
|
229022
|
+
function createDoctorX402Client(opts) {
|
|
229023
|
+
const account = privateKeyToAccount(opts.walletKey);
|
|
229024
|
+
const publicClient = createPublicClient({ chain: base, transport: http() });
|
|
229025
|
+
const evmSigner = toClientEvmSigner(account, publicClient);
|
|
229026
|
+
const x402 = new x402Client();
|
|
229027
|
+
registerSpendPolicyHook(x402, opts.spendControl ?? new SpendControl());
|
|
229028
|
+
registerExactEvmScheme(x402, { signer: evmSigner });
|
|
229029
|
+
return x402;
|
|
229030
|
+
}
|
|
228897
229031
|
async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
228898
229032
|
if (diagnostics.wallet.isEmpty) {
|
|
228899
229033
|
console.log("\n\u{1F4B3} Wallet is empty - cannot call AI for analysis.");
|
|
@@ -228911,11 +229045,7 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
|
228911
229045
|
`);
|
|
228912
229046
|
try {
|
|
228913
229047
|
const { key: key2 } = await resolveOrGenerateWalletKey();
|
|
228914
|
-
const
|
|
228915
|
-
const publicClient = createPublicClient({ chain: base, transport: http() });
|
|
228916
|
-
const evmSigner = toClientEvmSigner(account, publicClient);
|
|
228917
|
-
const x402 = new x402Client();
|
|
228918
|
-
registerExactEvmScheme(x402, { signer: evmSigner });
|
|
229048
|
+
const x402 = createDoctorX402Client({ walletKey: key2 });
|
|
228919
229049
|
const paymentChain = diagnostics.wallet.paymentChain;
|
|
228920
229050
|
if (paymentChain === "solana") {
|
|
228921
229051
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -1729,6 +1729,13 @@ declare function parseCallArgs(raw: string): {
|
|
|
1729
1729
|
* Delegates to the local proxy (which handles x402 payment).
|
|
1730
1730
|
*/
|
|
1731
1731
|
declare function buildImageGenerationProvider(): ImageGenerationProviderPlugin;
|
|
1732
|
+
/**
|
|
1733
|
+
* This plugin's OpenClaw id. NOT "clawrouter" — OpenClaw bundles its own plugin
|
|
1734
|
+
* under that id since the 2026.7.1 line, and a duplicate loses to the bundled
|
|
1735
|
+
* one. Kept as one constant so the manifest, the plugin definition and the
|
|
1736
|
+
* config migration can never drift apart. See #305.
|
|
1737
|
+
*/
|
|
1738
|
+
declare const BLOCKRUN_PLUGIN_ID = "blockrun-clawrouter";
|
|
1732
1739
|
declare const plugin: OpenClawPluginDefinition;
|
|
1733
1740
|
|
|
1734
|
-
export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, CAIP2_BASE, CAIP2_SOLANA_MAINNET, type CachedLLMResponse, type CachedResponse, type CheckResult, type CounterpartyInfo, DEFAULT_RETRY_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, type DerivedKeys, EmptyWalletError, FileSpendControlStorage, InMemorySpendControlStorage, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, MalformedSpendPolicyError, OPENCLAW_MODELS, PARTNER_SERVICES, type PartnerServiceDefinition, type PartnerToolDefinition, type PaymentChain, type PolicyList, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SolanaBalanceInfo, SolanaBalanceMonitor, SpendControl, type SpendControlOptions, type SpendControlStorage, type SpendLimits, SpendPolicyError, type SpendRecord, type SpendWindow, type SpendingStatus, type SufficiencyResult, type UsageEntry, VISIBLE_OPENCLAW_MODELS, type WalletConfig, type WalletResolution, blockrunProvider, buildImageGenerationProvider, buildPartnerTools, buildProviderModels, clearStats, plugin as default, deriveAllKeys, deriveEvmKey, deriveSolanaKeyBytes, fetchWithRetry, formatDuration, formatStatsAscii, generateWalletMnemonic, getAgenticModels, getModelContextWindow, getPartnerService, getProxyPort, getSessionId, getStats, hashRequestContent, injectAuthProfile, injectModelsConfig, isAgenticModel, isBalanceError, isBlockrunWebSearchDisabled, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, isValidMnemonic, loadPaymentChain, logUsage, parseCallArgs, registerSpendPolicyHook, resolveModelAlias, resolvePaymentChain, savePaymentChain, setupSolana, startProxy, syncAgentModelCache };
|
|
1741
|
+
export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, BLOCKRUN_PLUGIN_ID, type BalanceInfo, BalanceMonitor, CAIP2_BASE, CAIP2_SOLANA_MAINNET, type CachedLLMResponse, type CachedResponse, type CheckResult, type CounterpartyInfo, DEFAULT_RETRY_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, type DerivedKeys, EmptyWalletError, FileSpendControlStorage, InMemorySpendControlStorage, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, MalformedSpendPolicyError, OPENCLAW_MODELS, PARTNER_SERVICES, type PartnerServiceDefinition, type PartnerToolDefinition, type PaymentChain, type PolicyList, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SolanaBalanceInfo, SolanaBalanceMonitor, SpendControl, type SpendControlOptions, type SpendControlStorage, type SpendLimits, SpendPolicyError, type SpendRecord, type SpendWindow, type SpendingStatus, type SufficiencyResult, type UsageEntry, VISIBLE_OPENCLAW_MODELS, type WalletConfig, type WalletResolution, blockrunProvider, buildImageGenerationProvider, buildPartnerTools, buildProviderModels, clearStats, plugin as default, deriveAllKeys, deriveEvmKey, deriveSolanaKeyBytes, fetchWithRetry, formatDuration, formatStatsAscii, generateWalletMnemonic, getAgenticModels, getModelContextWindow, getPartnerService, getProxyPort, getSessionId, getStats, hashRequestContent, injectAuthProfile, injectModelsConfig, isAgenticModel, isBalanceError, isBlockrunWebSearchDisabled, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, isValidMnemonic, loadPaymentChain, logUsage, parseCallArgs, registerSpendPolicyHook, resolveModelAlias, resolvePaymentChain, savePaymentChain, setupSolana, startProxy, syncAgentModelCache };
|
package/dist/index.js
CHANGED
|
@@ -116851,6 +116851,35 @@ var init_client5 = __esm({
|
|
|
116851
116851
|
}
|
|
116852
116852
|
});
|
|
116853
116853
|
|
|
116854
|
+
// src/polymarket/spend-policy.ts
|
|
116855
|
+
function resolveSpendControl(deps) {
|
|
116856
|
+
if (deps?.spendControl) return deps.spendControl;
|
|
116857
|
+
defaultControl ??= new SpendControl();
|
|
116858
|
+
return defaultControl;
|
|
116859
|
+
}
|
|
116860
|
+
function usdToMicros(usd) {
|
|
116861
|
+
return String(Math.ceil(usd * 1e6));
|
|
116862
|
+
}
|
|
116863
|
+
async function signUnderSpendPolicy(deps, quote, action, sign4) {
|
|
116864
|
+
const control = resolveSpendControl(deps);
|
|
116865
|
+
const reservation = assertSpendPolicyAllows(control, quote);
|
|
116866
|
+
try {
|
|
116867
|
+
const signed2 = await sign4();
|
|
116868
|
+
if (reservation !== void 0) control.settleReservation(reservation, { action });
|
|
116869
|
+
return signed2;
|
|
116870
|
+
} catch (err) {
|
|
116871
|
+
if (reservation !== void 0) control.releaseReservation(reservation);
|
|
116872
|
+
throw err;
|
|
116873
|
+
}
|
|
116874
|
+
}
|
|
116875
|
+
var defaultControl;
|
|
116876
|
+
var init_spend_policy = __esm({
|
|
116877
|
+
"src/polymarket/spend-policy.ts"() {
|
|
116878
|
+
"use strict";
|
|
116879
|
+
init_spend_control();
|
|
116880
|
+
}
|
|
116881
|
+
});
|
|
116882
|
+
|
|
116854
116883
|
// src/polymarket/orders.ts
|
|
116855
116884
|
function getSessionLedger() {
|
|
116856
116885
|
return {
|
|
@@ -116919,6 +116948,9 @@ function isCredsMismatchError(err) {
|
|
|
116919
116948
|
return msg.includes("unauthorized") || msg.includes("api key not found") || msg.includes("invalid api key");
|
|
116920
116949
|
}
|
|
116921
116950
|
async function mapClobError(err) {
|
|
116951
|
+
if (err instanceof SpendPolicyError) {
|
|
116952
|
+
return `Refused by spend policy \u2014 nothing was signed. ${err.message} (see ~/.openclaw/blockrun/spending.json)`;
|
|
116953
|
+
}
|
|
116922
116954
|
const e7 = err;
|
|
116923
116955
|
const dataText = e7?.data ? ` \u2014 ${typeof e7.data === "string" ? e7.data : JSON.stringify(e7.data)}` : "";
|
|
116924
116956
|
const message = `${e7?.message ?? String(err)}${dataText}`;
|
|
@@ -116965,7 +116997,7 @@ async function withCredsRetry(fn) {
|
|
|
116965
116997
|
return fn(fresh);
|
|
116966
116998
|
}
|
|
116967
116999
|
}
|
|
116968
|
-
async function executeTrade(input) {
|
|
117000
|
+
async function executeTrade(input, deps) {
|
|
116969
117001
|
const side = input.action === "buy" ? Side.BUY : Side.SELL;
|
|
116970
117002
|
const isLimit = input.price !== void 0;
|
|
116971
117003
|
if (isLimit && input.size === void 0) {
|
|
@@ -116977,6 +117009,18 @@ async function executeTrade(input) {
|
|
|
116977
117009
|
isError: true
|
|
116978
117010
|
};
|
|
116979
117011
|
}
|
|
117012
|
+
if (!isLimit && input.action === "buy" && input.amount_usd !== void 0 && !(Number.isFinite(input.amount_usd) && input.amount_usd > 0)) {
|
|
117013
|
+
return {
|
|
117014
|
+
text: `amount_usd must be a positive dollar amount to spend, got ${input.amount_usd}.`,
|
|
117015
|
+
isError: true
|
|
117016
|
+
};
|
|
117017
|
+
}
|
|
117018
|
+
if (isLimit && input.size !== void 0 && !(Number.isFinite(input.size) && input.size > 0)) {
|
|
117019
|
+
return {
|
|
117020
|
+
text: `size must be a positive number of shares, got ${input.size}.`,
|
|
117021
|
+
isError: true
|
|
117022
|
+
};
|
|
117023
|
+
}
|
|
116980
117024
|
if (!isLimit && input.action === "sell" && input.size === void 0) {
|
|
116981
117025
|
return { text: `Market sells need size (shares to sell).`, isError: true };
|
|
116982
117026
|
}
|
|
@@ -117076,39 +117120,44 @@ Re-call with confirm:true to sign and submit.`,
|
|
|
117076
117120
|
}
|
|
117077
117121
|
const options = { tickSize, negRisk };
|
|
117078
117122
|
reserveBet(notional, input.agent_id);
|
|
117079
|
-
|
|
117123
|
+
const policyQuote = {
|
|
117124
|
+
payTo: negRisk ? NEG_RISK_CTF_EXCHANGE_V2 : CTF_EXCHANGE_V2,
|
|
117125
|
+
network: `eip155:${POLYGON_CHAIN_ID}`,
|
|
117126
|
+
asset: PUSD_COLLATERAL,
|
|
117127
|
+
amount: usdToMicros(notional)
|
|
117128
|
+
};
|
|
117129
|
+
let r2;
|
|
117080
117130
|
try {
|
|
117081
|
-
|
|
117082
|
-
|
|
117083
|
-
|
|
117084
|
-
|
|
117085
|
-
|
|
117086
|
-
|
|
117087
|
-
|
|
117088
|
-
|
|
117089
|
-
|
|
117090
|
-
|
|
117091
|
-
|
|
117092
|
-
|
|
117093
|
-
|
|
117094
|
-
|
|
117095
|
-
|
|
117096
|
-
|
|
117097
|
-
|
|
117098
|
-
|
|
117099
|
-
|
|
117100
|
-
|
|
117101
|
-
|
|
117131
|
+
r2 = await signUnderSpendPolicy(deps, policyQuote, "polymarket order", async () => {
|
|
117132
|
+
const res = await (isLimit ? clob.createAndPostOrder(
|
|
117133
|
+
{
|
|
117134
|
+
tokenID: token.tokenId,
|
|
117135
|
+
price,
|
|
117136
|
+
size: size5,
|
|
117137
|
+
side,
|
|
117138
|
+
...orderKind === "GTD" && input.expires_at ? { expiration: input.expires_at } : {}
|
|
117139
|
+
},
|
|
117140
|
+
options,
|
|
117141
|
+
orderKind === "GTD" ? OrderType.GTD : OrderType.GTC,
|
|
117142
|
+
input.post_only ?? false
|
|
117143
|
+
) : clob.createAndPostMarketOrder(
|
|
117144
|
+
{
|
|
117145
|
+
tokenID: token.tokenId,
|
|
117146
|
+
amount: input.action === "buy" ? input.amount_usd : size5,
|
|
117147
|
+
side,
|
|
117148
|
+
orderType: orderKind === "FAK" ? OrderType.FAK : OrderType.FOK
|
|
117149
|
+
},
|
|
117150
|
+
options,
|
|
117151
|
+
orderKind === "FAK" ? OrderType.FAK : OrderType.FOK
|
|
117152
|
+
));
|
|
117153
|
+
const placed = res?.success !== false && (res?.orderID || res?.status === "matched");
|
|
117154
|
+
if (!placed) throw new Error(res?.errorMsg || "order rejected");
|
|
117155
|
+
return res;
|
|
117156
|
+
});
|
|
117102
117157
|
} catch (submitErr) {
|
|
117103
117158
|
releaseBet(notional, input.agent_id);
|
|
117104
117159
|
throw submitErr;
|
|
117105
117160
|
}
|
|
117106
|
-
const r2 = response;
|
|
117107
|
-
const placed = r2?.success !== false && (r2?.orderID || r2?.status === "matched");
|
|
117108
|
-
if (!placed) {
|
|
117109
|
-
releaseBet(notional, input.agent_id);
|
|
117110
|
-
throw new Error(r2?.errorMsg || "order rejected");
|
|
117111
|
-
}
|
|
117112
117161
|
commitBet();
|
|
117113
117162
|
const filled = r2?.status === "matched" || (r2?.transactionsHashes?.length ?? 0) > 0;
|
|
117114
117163
|
return {
|
|
@@ -117197,6 +117246,8 @@ var init_orders = __esm({
|
|
|
117197
117246
|
init_client5();
|
|
117198
117247
|
init_constants3();
|
|
117199
117248
|
init_creds();
|
|
117249
|
+
init_spend_control();
|
|
117250
|
+
init_spend_policy();
|
|
117200
117251
|
ledger = {
|
|
117201
117252
|
totalUsd: 0,
|
|
117202
117253
|
count: 0,
|
|
@@ -225131,7 +225182,7 @@ var init_setup = __esm({
|
|
|
225131
225182
|
});
|
|
225132
225183
|
|
|
225133
225184
|
// src/polymarket/redeem.ts
|
|
225134
|
-
async function redeemPosition(input) {
|
|
225185
|
+
async function redeemPosition(input, deps) {
|
|
225135
225186
|
if (!input.condition_id) {
|
|
225136
225187
|
return {
|
|
225137
225188
|
text: `Pass condition_id:"0x\u2026" (see action:"positions" for redeemable markets).`,
|
|
@@ -225207,20 +225258,31 @@ async function redeemPosition(input) {
|
|
|
225207
225258
|
]
|
|
225208
225259
|
});
|
|
225209
225260
|
const target = negRisk ? NEG_RISK_ADAPTER : CONDITIONAL_TOKENS;
|
|
225210
|
-
|
|
225211
|
-
|
|
225212
|
-
|
|
225213
|
-
|
|
225214
|
-
|
|
225215
|
-
|
|
225216
|
-
|
|
225217
|
-
|
|
225218
|
-
|
|
225219
|
-
|
|
225220
|
-
|
|
225221
|
-
|
|
225222
|
-
|
|
225223
|
-
|
|
225261
|
+
const eoa = getSigType() !== 3;
|
|
225262
|
+
const txHash = await signUnderSpendPolicy(
|
|
225263
|
+
deps,
|
|
225264
|
+
{
|
|
225265
|
+
payTo: target,
|
|
225266
|
+
network: `eip155:${POLYGON_CHAIN_ID}`,
|
|
225267
|
+
asset: PUSD_COLLATERAL,
|
|
225268
|
+
amount: "0"
|
|
225269
|
+
},
|
|
225270
|
+
"polymarket redeem",
|
|
225271
|
+
async () => {
|
|
225272
|
+
if (!eoa) {
|
|
225273
|
+
const res = await sendWalletBatch([{ target, value: "0", data }], owner, "Redeem");
|
|
225274
|
+
return res.transactionHash;
|
|
225275
|
+
}
|
|
225276
|
+
const account = getPolymarketAccount();
|
|
225277
|
+
const wallet = createWalletClient({
|
|
225278
|
+
account,
|
|
225279
|
+
chain: polygon,
|
|
225280
|
+
transport: http(POLYGON_RPC_URLS[0])
|
|
225281
|
+
});
|
|
225282
|
+
return wallet.sendTransaction({ to: target, data, chain: polygon, account });
|
|
225283
|
+
}
|
|
225284
|
+
);
|
|
225285
|
+
if (eoa) await pc.waitForTransactionReceipt({ hash: txHash });
|
|
225224
225286
|
const balanceAfter = await getPusdBalance(owner).catch(() => null);
|
|
225225
225287
|
return {
|
|
225226
225288
|
text: [
|
|
@@ -225251,6 +225313,7 @@ var init_redeem = __esm({
|
|
|
225251
225313
|
init_positions();
|
|
225252
225314
|
init_relayer();
|
|
225253
225315
|
init_setup();
|
|
225316
|
+
init_spend_policy();
|
|
225254
225317
|
}
|
|
225255
225318
|
});
|
|
225256
225319
|
|
|
@@ -225263,7 +225326,13 @@ async function rawPusdBalance(owner) {
|
|
|
225263
225326
|
args: [owner]
|
|
225264
225327
|
});
|
|
225265
225328
|
}
|
|
225266
|
-
async function withdrawFunds(input) {
|
|
225329
|
+
async function withdrawFunds(input, deps) {
|
|
225330
|
+
if (input.amount_usd !== void 0 && !(Number.isFinite(input.amount_usd) && input.amount_usd > 0)) {
|
|
225331
|
+
return {
|
|
225332
|
+
text: `amount_usd must be a positive dollar amount, got ${input.amount_usd}. Omit it to withdraw the full balance.`,
|
|
225333
|
+
isError: true
|
|
225334
|
+
};
|
|
225335
|
+
}
|
|
225267
225336
|
let owner;
|
|
225268
225337
|
try {
|
|
225269
225338
|
owner = getFundsAddress();
|
|
@@ -225334,29 +225403,40 @@ async function withdrawFunds(input) {
|
|
|
225334
225403
|
functionName: "transfer",
|
|
225335
225404
|
args: [bridgeEvm, amountRaw]
|
|
225336
225405
|
});
|
|
225337
|
-
|
|
225338
|
-
|
|
225339
|
-
|
|
225340
|
-
|
|
225341
|
-
|
|
225342
|
-
|
|
225343
|
-
|
|
225344
|
-
|
|
225345
|
-
|
|
225346
|
-
|
|
225347
|
-
|
|
225348
|
-
|
|
225349
|
-
|
|
225350
|
-
|
|
225351
|
-
|
|
225352
|
-
|
|
225353
|
-
|
|
225354
|
-
|
|
225355
|
-
|
|
225356
|
-
account
|
|
225357
|
-
|
|
225358
|
-
|
|
225359
|
-
|
|
225406
|
+
const eoa = getSigType() !== 3;
|
|
225407
|
+
const txHash = await signUnderSpendPolicy(
|
|
225408
|
+
deps,
|
|
225409
|
+
{
|
|
225410
|
+
payTo: recipient,
|
|
225411
|
+
network: `eip155:${BASE_CHAIN_ID}`,
|
|
225412
|
+
asset: BASE_USDC,
|
|
225413
|
+
amount: amountRaw.toString()
|
|
225414
|
+
},
|
|
225415
|
+
"polymarket withdraw",
|
|
225416
|
+
async () => {
|
|
225417
|
+
if (!eoa) {
|
|
225418
|
+
const res = await sendWalletBatch(
|
|
225419
|
+
[{ target: PUSD_COLLATERAL, value: "0", data }],
|
|
225420
|
+
owner,
|
|
225421
|
+
"Withdraw"
|
|
225422
|
+
);
|
|
225423
|
+
return res.transactionHash;
|
|
225424
|
+
}
|
|
225425
|
+
const account = getPolymarketAccount();
|
|
225426
|
+
const wallet = createWalletClient({
|
|
225427
|
+
account,
|
|
225428
|
+
chain: polygon,
|
|
225429
|
+
transport: http(POLYGON_RPC_URLS[0])
|
|
225430
|
+
});
|
|
225431
|
+
return wallet.sendTransaction({
|
|
225432
|
+
to: PUSD_COLLATERAL,
|
|
225433
|
+
data,
|
|
225434
|
+
chain: polygon,
|
|
225435
|
+
account
|
|
225436
|
+
});
|
|
225437
|
+
}
|
|
225438
|
+
);
|
|
225439
|
+
if (eoa) await getPublicClient().waitForTransactionReceipt({ hash: txHash });
|
|
225360
225440
|
return {
|
|
225361
225441
|
text: [
|
|
225362
225442
|
`\u2705 Withdrawal submitted: $${amountUsd.toFixed(2)} pUSD \u2192 USDC on Base`,
|
|
@@ -225391,6 +225471,7 @@ var init_withdraw = __esm({
|
|
|
225391
225471
|
init_positions();
|
|
225392
225472
|
init_relayer();
|
|
225393
225473
|
init_setup();
|
|
225474
|
+
init_spend_policy();
|
|
225394
225475
|
}
|
|
225395
225476
|
});
|
|
225396
225477
|
|
|
@@ -226036,7 +226117,7 @@ async function bridgeAddressFor(vault) {
|
|
|
226036
226117
|
throw new Error(`Bridge did not return a deposit address (got: ${JSON.stringify(res.data)}).`);
|
|
226037
226118
|
return evm;
|
|
226038
226119
|
}
|
|
226039
|
-
async function fundVault(input) {
|
|
226120
|
+
async function fundVault(input, deps) {
|
|
226040
226121
|
if (input.amount_usd === void 0 || input.amount_usd <= 0) {
|
|
226041
226122
|
return {
|
|
226042
226123
|
text: `Pass amount_usd \u2014 the USDC amount to move from your Base wallet into your Polymarket vault (e.g. amount_usd:5).`,
|
|
@@ -226093,12 +226174,12 @@ async function fundVault(input) {
|
|
|
226093
226174
|
}
|
|
226094
226175
|
const privateKey = getOrCreateWalletKey();
|
|
226095
226176
|
const amountMicro = String(Math.floor(amountUsd * 10 ** USDC_DECIMALS));
|
|
226096
|
-
const
|
|
226097
|
-
|
|
226098
|
-
|
|
226099
|
-
bridge,
|
|
226100
|
-
|
|
226101
|
-
|
|
226177
|
+
const network = `eip155:${BASE_CHAIN_ID}`;
|
|
226178
|
+
const depositAuthorization = await signUnderSpendPolicy(
|
|
226179
|
+
deps,
|
|
226180
|
+
{ payTo: bridge, network, asset: BASE_USDC, amount: amountMicro },
|
|
226181
|
+
"polymarket fund",
|
|
226182
|
+
() => createPaymentPayload(privateKey, agent, bridge, amountMicro, network)
|
|
226102
226183
|
);
|
|
226103
226184
|
const client = new BlockrunClient({ privateKey });
|
|
226104
226185
|
const result = await client.post("/v1/polymarket/fund", {
|
|
@@ -226148,6 +226229,7 @@ var init_fund = __esm({
|
|
|
226148
226229
|
init_constants3();
|
|
226149
226230
|
init_positions();
|
|
226150
226231
|
init_setup();
|
|
226232
|
+
init_spend_policy();
|
|
226151
226233
|
FUND_FEE_USD = 0.01;
|
|
226152
226234
|
USDC_DECIMALS = 6;
|
|
226153
226235
|
FUND_MIN_USD = Number(process.env.POLYMARKET_FUND_MIN_USD || "2");
|
|
@@ -226813,6 +226895,35 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
226813
226895
|
logger48.info(`Added ${addedCount} models to allowlist (${TOP_MODELS.length} total)`);
|
|
226814
226896
|
}
|
|
226815
226897
|
}
|
|
226898
|
+
const pluginEntries = config.plugins?.entries;
|
|
226899
|
+
if (pluginEntries && pluginEntries.clawrouter && !pluginEntries[BLOCKRUN_PLUGIN_ID]) {
|
|
226900
|
+
pluginEntries[BLOCKRUN_PLUGIN_ID] = pluginEntries.clawrouter;
|
|
226901
|
+
delete pluginEntries.clawrouter;
|
|
226902
|
+
needsWrite = true;
|
|
226903
|
+
logger48.info(
|
|
226904
|
+
"Migrated plugins.entries.clawrouter -> blockrun-clawrouter (OpenClaw bundles its own `clawrouter` plugin; see #305)"
|
|
226905
|
+
);
|
|
226906
|
+
}
|
|
226907
|
+
const pluginsCfg = config.plugins;
|
|
226908
|
+
const allow = pluginsCfg?.allow;
|
|
226909
|
+
if (Array.isArray(allow) && allow.includes("clawrouter") && !allow.includes(BLOCKRUN_PLUGIN_ID)) {
|
|
226910
|
+
allow.push(BLOCKRUN_PLUGIN_ID);
|
|
226911
|
+
needsWrite = true;
|
|
226912
|
+
logger48.info(`Added ${BLOCKRUN_PLUGIN_ID} to plugins.allow (renamed from clawrouter; see #305)`);
|
|
226913
|
+
}
|
|
226914
|
+
const deny = pluginsCfg?.deny;
|
|
226915
|
+
if (Array.isArray(deny) && deny.includes("clawrouter") && !deny.includes(BLOCKRUN_PLUGIN_ID)) {
|
|
226916
|
+
deny.push(BLOCKRUN_PLUGIN_ID);
|
|
226917
|
+
needsWrite = true;
|
|
226918
|
+
logger48.info(`Added ${BLOCKRUN_PLUGIN_ID} to plugins.deny (renamed from clawrouter; see #305)`);
|
|
226919
|
+
}
|
|
226920
|
+
const installs = pluginsCfg?.installs;
|
|
226921
|
+
if (installs && installs.clawrouter && !installs[BLOCKRUN_PLUGIN_ID]) {
|
|
226922
|
+
installs[BLOCKRUN_PLUGIN_ID] = installs.clawrouter;
|
|
226923
|
+
delete installs.clawrouter;
|
|
226924
|
+
needsWrite = true;
|
|
226925
|
+
logger48.info(`Migrated plugins.installs.clawrouter -> ${BLOCKRUN_PLUGIN_ID} (see #305)`);
|
|
226926
|
+
}
|
|
226816
226927
|
if (!config.tools || typeof config.tools !== "object" || Array.isArray(config.tools)) {
|
|
226817
226928
|
config.tools = {};
|
|
226818
226929
|
needsWrite = true;
|
|
@@ -227805,7 +227916,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
|
|
|
227805
227916
|
}
|
|
227806
227917
|
};
|
|
227807
227918
|
}
|
|
227808
|
-
var activeProxyHandle, pendingConfiguredStartupApi, IMAGE_DIR2, AUDIO_DIR2, VIDEO_DIR2, plugin, index_default2;
|
|
227919
|
+
var activeProxyHandle, pendingConfiguredStartupApi, IMAGE_DIR2, AUDIO_DIR2, VIDEO_DIR2, BLOCKRUN_PLUGIN_ID, plugin, index_default2;
|
|
227809
227920
|
var init_index = __esm({
|
|
227810
227921
|
"src/index.ts"() {
|
|
227811
227922
|
init_provider();
|
|
@@ -227847,9 +227958,16 @@ var init_index = __esm({
|
|
|
227847
227958
|
IMAGE_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "images");
|
|
227848
227959
|
AUDIO_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "audio");
|
|
227849
227960
|
VIDEO_DIR2 = join16(homedir13(), ".openclaw", "blockrun", "videos");
|
|
227961
|
+
BLOCKRUN_PLUGIN_ID = "blockrun-clawrouter";
|
|
227850
227962
|
plugin = {
|
|
227851
|
-
|
|
227852
|
-
|
|
227963
|
+
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
227964
|
+
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
227965
|
+
// CLAWROUTER_API_KEY, clawrouter.openclaw.ai). A duplicate id loses to the
|
|
227966
|
+
// bundled one — "global plugin will be overridden by bundled plugin" — so this
|
|
227967
|
+
// plugin silently never loaded and the local proxy never started. Different
|
|
227968
|
+
// product, different id. See #305.
|
|
227969
|
+
id: BLOCKRUN_PLUGIN_ID,
|
|
227970
|
+
name: "BlockRun ClawRouter",
|
|
227853
227971
|
description: "Smart LLM router \u2014 55+ models, x402 micropayments, 78% cost savings",
|
|
227854
227972
|
version: VERSION,
|
|
227855
227973
|
register(api) {
|
|
@@ -228258,13 +228376,18 @@ ${errText}`
|
|
|
228258
228376
|
delete config.models.providers.blockrun;
|
|
228259
228377
|
}
|
|
228260
228378
|
removeManagedBlockrunMcpServerConfig(config);
|
|
228261
|
-
for (const key2 of [
|
|
228379
|
+
for (const key2 of [
|
|
228380
|
+
BLOCKRUN_PLUGIN_ID,
|
|
228381
|
+
"clawrouter",
|
|
228382
|
+
"ClawRouter",
|
|
228383
|
+
"@blockrun/clawrouter"
|
|
228384
|
+
]) {
|
|
228262
228385
|
if (config.plugins?.entries?.[key2]) delete config.plugins.entries[key2];
|
|
228263
228386
|
if (config.plugins?.installs?.[key2]) delete config.plugins.installs[key2];
|
|
228264
228387
|
}
|
|
228265
228388
|
if (Array.isArray(config.plugins?.allow)) {
|
|
228266
228389
|
config.plugins.allow = config.plugins.allow.filter(
|
|
228267
|
-
(p) => p !== "clawrouter" && p !== "ClawRouter" && p !== "@blockrun/clawrouter"
|
|
228390
|
+
(p) => p !== BLOCKRUN_PLUGIN_ID && p !== "clawrouter" && p !== "ClawRouter" && p !== "@blockrun/clawrouter"
|
|
228268
228391
|
);
|
|
228269
228392
|
}
|
|
228270
228393
|
if (config.agents?.defaults?.models) {
|
|
@@ -228315,6 +228438,7 @@ init_index();
|
|
|
228315
228438
|
export {
|
|
228316
228439
|
BALANCE_THRESHOLDS,
|
|
228317
228440
|
BLOCKRUN_MODELS,
|
|
228441
|
+
BLOCKRUN_PLUGIN_ID,
|
|
228318
228442
|
BalanceMonitor,
|
|
228319
228443
|
CAIP2_BASE,
|
|
228320
228444
|
CAIP2_SOLANA_MAINNET,
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "clawrouter",
|
|
3
|
-
"name": "ClawRouter",
|
|
4
|
-
"description": "Smart LLM router
|
|
2
|
+
"id": "blockrun-clawrouter",
|
|
3
|
+
"name": "BlockRun ClawRouter",
|
|
4
|
+
"description": "Smart LLM router \u2014 55+ models, x402 micropayments, 92% cost savings. Phone lookup + AI voice calls (Twilio + Bland.ai). Surf crypto-data API (84 endpoints, on-chain SQL, 100M+ labeled wallets) via skill.",
|
|
5
5
|
"skills": ["./skills"],
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"properties": {
|
|
45
45
|
"walletKey": {
|
|
46
46
|
"type": "string",
|
|
47
|
-
"description": "EVM wallet private key (0x...). Optional
|
|
47
|
+
"description": "EVM wallet private key (0x...). Optional \u2014 auto-generated if not set."
|
|
48
48
|
},
|
|
49
49
|
"routing": {
|
|
50
50
|
"type": "object",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"walletKey": {
|
|
57
57
|
"label": "Wallet Private Key",
|
|
58
58
|
"sensitive": true,
|
|
59
|
-
"placeholder": "0x... (optional
|
|
59
|
+
"placeholder": "0x... (optional \u2014 auto-generated)"
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/clawrouter",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.265",
|
|
4
4
|
"description": "Smart LLM router \u2014 save 84% on inference costs. 76 models (7 free), one wallet, x402 micropayments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"docs/*.png",
|
|
35
35
|
"scripts",
|
|
36
36
|
"!scripts/smoke-dist.mjs",
|
|
37
|
+
"!scripts/sync-brand-numbers.mjs",
|
|
37
38
|
"skills",
|
|
38
39
|
"src/top-models.json",
|
|
39
40
|
"openclaw.plugin.json"
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Sync marketing numbers from BlockRun's canonical brand artifact.
|
|
4
|
-
*
|
|
5
|
-
* This file is copied byte-for-byte into every public repo as
|
|
6
|
-
* scripts/sync-brand-numbers.mjs. It is a copy rather than an npm package on
|
|
7
|
-
* purpose: a package would mean 37 dependency bumps, and several consuming
|
|
8
|
-
* repos have no package.json at all. Zero dependencies, plain Node.
|
|
9
|
-
*
|
|
10
|
-
* node scripts/sync-brand-numbers.mjs rewrite markers in place
|
|
11
|
-
* node scripts/sync-brand-numbers.mjs --check exit 1 on drift, write nothing
|
|
12
|
-
* node scripts/sync-brand-numbers.mjs --refresh re-fetch the artifact first
|
|
13
|
-
*
|
|
14
|
-
* --check NEVER touches the network. PR CI must be deterministic and offline:
|
|
15
|
-
* if it fetched, a deploy in progress would fail every repo in the org at once.
|
|
16
|
-
* Freshness is the fan-out job's problem, not the pull request's.
|
|
17
|
-
*
|
|
18
|
-
* Markers look like: <!-- br:models.chatVisible -->66<!-- /br:models.chatVisible -->
|
|
19
|
-
* and wrap the WHOLE token, so a badge URL, its alt text and the prose number
|
|
20
|
-
* can all regenerate from one key.
|
|
21
|
-
*/
|
|
22
|
-
import { existsSync, lstatSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
|
|
23
|
-
import { join, relative, extname } from "node:path";
|
|
24
|
-
|
|
25
|
-
const ROOT = process.cwd();
|
|
26
|
-
const SNAPSHOT = join(ROOT, "brand-numbers.json");
|
|
27
|
-
// ORIGIN is tried first because it IS the truth — the mirror can only ever be
|
|
28
|
-
// as fresh as the last time someone refreshed it. The mirror exists so a repo
|
|
29
|
-
// can still sync while blockrun.ai is down, not to front the origin.
|
|
30
|
-
//
|
|
31
|
-
// The mirror is awesome-blockrun's own brand-numbers.json: that repo consumes
|
|
32
|
-
// the artifact like every other, and its snapshot doubles as the org's copy.
|
|
33
|
-
// One file, one role per repo, nothing to keep in step by hand.
|
|
34
|
-
const ORIGIN = "https://blockrun.ai/brand/numbers.json";
|
|
35
|
-
const MIRROR =
|
|
36
|
-
"https://raw.githubusercontent.com/BlockRunAI/awesome-blockrun/main/brand-numbers.json";
|
|
37
|
-
|
|
38
|
-
const argv = new Set(process.argv.slice(2));
|
|
39
|
-
const check = argv.has("--check");
|
|
40
|
-
const refresh = argv.has("--refresh");
|
|
41
|
-
|
|
42
|
-
const SKIP_DIRS = new Set([
|
|
43
|
-
"node_modules", ".git", "dist", "build", "out", ".next", "coverage",
|
|
44
|
-
"vendor", "target", "__pycache__", ".venv", "venv",
|
|
45
|
-
]);
|
|
46
|
-
// .txt is here for llms.txt, which is a first-class marketing surface: it is
|
|
47
|
-
// what agents read to find out what BlockRun serves. Scanning other .txt files
|
|
48
|
-
// costs a read and changes nothing — only files with markers are ever written.
|
|
49
|
-
const TEXT_EXT = new Set([".md", ".mdx", ".txt"]);
|
|
50
|
-
|
|
51
|
-
/* ── 1. numbers ──────────────────────────────────────────────────────────── */
|
|
52
|
-
|
|
53
|
-
async function loadNumbers() {
|
|
54
|
-
if (!refresh) {
|
|
55
|
-
try {
|
|
56
|
-
return JSON.parse(readFileSync(SNAPSHOT, "utf8"));
|
|
57
|
-
} catch {
|
|
58
|
-
fail(
|
|
59
|
-
`no brand-numbers.json in ${ROOT}\n` +
|
|
60
|
-
` run with --refresh once to seed it from ${ORIGIN}`,
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
for (const url of [ORIGIN, MIRROR]) {
|
|
65
|
-
try {
|
|
66
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });
|
|
67
|
-
if (!res.ok) continue;
|
|
68
|
-
const json = await res.json();
|
|
69
|
-
writeFileSync(SNAPSHOT, `${JSON.stringify(json, null, 2)}\n`);
|
|
70
|
-
return json;
|
|
71
|
-
} catch {
|
|
72
|
-
/* try the next source */
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
fail(`could not refresh from ${MIRROR} or ${ORIGIN}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Flatten nested numbers into dotted keys, ignoring $comment / rationale prose. */
|
|
79
|
-
function flatten(obj, prefix = "") {
|
|
80
|
-
return Object.entries(obj).flatMap(([k, v]) => {
|
|
81
|
-
if (k.startsWith("$")) return [];
|
|
82
|
-
const key = `${prefix}${k}`;
|
|
83
|
-
if (v && typeof v === "object" && !Array.isArray(v)) return flatten(v, `${key}.`);
|
|
84
|
-
if (v === null) return [];
|
|
85
|
-
return [[key, v]];
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/* ── 2. renderers ────────────────────────────────────────────────────────── */
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* How a key becomes text. Default is the bare value.
|
|
93
|
-
*
|
|
94
|
-
* A marker may carry an `@modifier` — `<!-- br:mcp.tools@badge -->` — which
|
|
95
|
-
* selects a renderer without changing which number is looked up. The modifier
|
|
96
|
-
* is what makes a key reusable: the same mcp.tools appears as a shields badge
|
|
97
|
-
* at the top of a README and as a bare "19 tools" in a table two screens down,
|
|
98
|
-
* and one marker still keeps the badge URL, its alt text and the label in step.
|
|
99
|
-
*
|
|
100
|
-
* Renderers are registered under the FULL marker name so a badge's label is
|
|
101
|
-
* written out rather than guessed from the key.
|
|
102
|
-
*/
|
|
103
|
-
const badge = (label) => (n) =>
|
|
104
|
-
`<img src="https://img.shields.io/badge/${label}-${n}-5B9BF6?style=flat-square&labelColor=0B0A0F" alt="${n} ${label}">`;
|
|
105
|
-
|
|
106
|
-
const RENDER = {
|
|
107
|
-
"mcp.tools@badge": badge("tools"),
|
|
108
|
-
"models.totalVisible@badge": badge("models"),
|
|
109
|
-
"models.chatVisible@badge": badge("models"),
|
|
110
|
-
};
|
|
111
|
-
const render = (marker, value) => (RENDER[marker] ?? String)(value);
|
|
112
|
-
|
|
113
|
-
/** `mcp.tools@badge` looks up `mcp.tools`. Unmodified markers are unaffected. */
|
|
114
|
-
const keyOf = (marker) => marker.split("@")[0];
|
|
115
|
-
|
|
116
|
-
/* ── 3. marker rewriting ─────────────────────────────────────────────────── */
|
|
117
|
-
|
|
118
|
-
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
119
|
-
const OPEN_ANY = /<!--\s*br:([A-Za-z0-9_.@]+)\s*-->/g;
|
|
120
|
-
const CLOSE_ANY = /<!--\s*\/br:([A-Za-z0-9_.@]+)\s*-->/g;
|
|
121
|
-
|
|
122
|
-
/** Byte ranges of fenced code blocks — markers inside them are documentation. */
|
|
123
|
-
function fencedRanges(text) {
|
|
124
|
-
const ranges = [];
|
|
125
|
-
const fence = /^(\s*)(`{3,}|~{3,})[^\n]*$/gm;
|
|
126
|
-
let open = null;
|
|
127
|
-
for (let m; (m = fence.exec(text)); ) {
|
|
128
|
-
if (open === null) open = m.index;
|
|
129
|
-
else {
|
|
130
|
-
ranges.push([open, m.index + m[0].length]);
|
|
131
|
-
open = null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return ranges;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function syncFile(file, numbers, problems) {
|
|
138
|
-
const before = readFileSync(file, "utf8");
|
|
139
|
-
const rel = relative(ROOT, file);
|
|
140
|
-
const fenced = fencedRanges(before);
|
|
141
|
-
const inFence = (i) => fenced.some(([a, b]) => i >= a && i < b);
|
|
142
|
-
const known = new Map(numbers);
|
|
143
|
-
const used = new Set();
|
|
144
|
-
|
|
145
|
-
// Markers actually present, so a file is only ever rewritten for what it uses
|
|
146
|
-
// and an @modifier is carried through to the renderer verbatim.
|
|
147
|
-
const markers = new Set();
|
|
148
|
-
// A marker naming a key that does not exist is an error, never a silent
|
|
149
|
-
// no-op: a typo'd marker would otherwise sit there looking synced forever.
|
|
150
|
-
for (const [re, shown] of [
|
|
151
|
-
[OPEN_ANY, (n) => `<!-- br:${n} -->`],
|
|
152
|
-
[CLOSE_ANY, (n) => `<!-- /br:${n} -->`],
|
|
153
|
-
]) {
|
|
154
|
-
for (const m of before.matchAll(re)) {
|
|
155
|
-
if (inFence(m.index)) continue;
|
|
156
|
-
markers.add(m[1]);
|
|
157
|
-
if (!known.has(keyOf(m[1]))) problems.push(`${rel}: unknown key ${shown(m[1])}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let after = before;
|
|
162
|
-
for (const marker of markers) {
|
|
163
|
-
const key = keyOf(marker);
|
|
164
|
-
if (!known.has(key)) continue;
|
|
165
|
-
const value = known.get(key);
|
|
166
|
-
const pair = new RegExp(
|
|
167
|
-
`(<!--\\s*br:${esc(marker)}\\s*-->)([\\s\\S]*?)(<!--\\s*/br:${esc(marker)}\\s*-->)`,
|
|
168
|
-
"g",
|
|
169
|
-
);
|
|
170
|
-
after = after.replace(pair, (whole, open, inner, close, offset) => {
|
|
171
|
-
if (inFence(offset)) return whole;
|
|
172
|
-
// Nesting means the closing tag of an inner marker would be consumed by
|
|
173
|
-
// the outer one. Refuse rather than produce mangled output.
|
|
174
|
-
if (/<!--\s*\/?br:/.test(inner)) {
|
|
175
|
-
problems.push(`${rel}: nested marker inside br:${marker}`);
|
|
176
|
-
return whole;
|
|
177
|
-
}
|
|
178
|
-
used.add(marker);
|
|
179
|
-
return open + render(marker, value) + close;
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// An opening tag with no partner silently swallows the rest of the file on
|
|
183
|
-
// a naive regex, so catch it explicitly.
|
|
184
|
-
const opens = [...before.matchAll(new RegExp(`<!--\\s*br:${esc(marker)}\\s*-->`, "g"))]
|
|
185
|
-
.filter((m) => !inFence(m.index)).length;
|
|
186
|
-
const closes = [...before.matchAll(new RegExp(`<!--\\s*/br:${esc(marker)}\\s*-->`, "g"))]
|
|
187
|
-
.filter((m) => !inFence(m.index)).length;
|
|
188
|
-
if (opens !== closes) problems.push(`${rel}: unbalanced marker br:${marker} (${opens} open, ${closes} close)`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return { before, after, changed: before !== after, used };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/* ── 4. walk ─────────────────────────────────────────────────────────────── */
|
|
195
|
-
|
|
196
|
-
function* walk(dir) {
|
|
197
|
-
for (const name of readdirSync(dir)) {
|
|
198
|
-
if (SKIP_DIRS.has(name)) continue;
|
|
199
|
-
const p = join(dir, name);
|
|
200
|
-
// lstat, not stat: a symlinked directory is reached by its real path or not
|
|
201
|
-
// at all. blockrun's docs/ -> awesome-blockrun/docs is exactly the case that
|
|
202
|
-
// matters — following it would edit a submodule's files behind the skip
|
|
203
|
-
// below, and a link pointing at an ancestor would recurse forever.
|
|
204
|
-
const s = lstatSync(p);
|
|
205
|
-
if (s.isSymbolicLink()) continue;
|
|
206
|
-
if (s.isDirectory()) {
|
|
207
|
-
// A nested repo is a submodule or vendored checkout: it carries its own
|
|
208
|
-
// brand-numbers.json and syncs itself. Rewriting its markers from THIS
|
|
209
|
-
// repo's snapshot would dirty a submodule nobody asked us to touch, and
|
|
210
|
-
// would report drift that belongs to another repo's CI.
|
|
211
|
-
if (existsSync(join(p, ".git"))) continue;
|
|
212
|
-
yield* walk(p);
|
|
213
|
-
} else if (TEXT_EXT.has(extname(name))) yield p;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function fail(msg) {
|
|
218
|
-
console.error(`brand-numbers: ${msg}`);
|
|
219
|
-
process.exit(1);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/* ── 5. run ──────────────────────────────────────────────────────────────── */
|
|
223
|
-
|
|
224
|
-
const raw = await loadNumbers();
|
|
225
|
-
const numbers = flatten(raw);
|
|
226
|
-
const problems = [];
|
|
227
|
-
const drifted = [];
|
|
228
|
-
const everUsed = new Set();
|
|
229
|
-
|
|
230
|
-
for (const file of walk(ROOT)) {
|
|
231
|
-
const { before, after, changed, used } = syncFile(file, numbers, problems);
|
|
232
|
-
used.forEach((k) => everUsed.add(k));
|
|
233
|
-
if (!changed) continue;
|
|
234
|
-
drifted.push({ file: relative(ROOT, file), before, after });
|
|
235
|
-
if (!check) writeFileSync(file, after);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (problems.length) {
|
|
239
|
-
for (const p of problems) console.error(` ${p}`);
|
|
240
|
-
fail(`${problems.length} marker problem(s)`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (check) {
|
|
244
|
-
if (drifted.length === 0) {
|
|
245
|
-
console.log(`brand-numbers: up to date (${everUsed.size} keys in use)`);
|
|
246
|
-
process.exit(0);
|
|
247
|
-
}
|
|
248
|
-
console.error("brand-numbers: these files disagree with brand-numbers.json\n");
|
|
249
|
-
for (const { file, before, after } of drifted) {
|
|
250
|
-
const b = before.split("\n");
|
|
251
|
-
const a = after.split("\n");
|
|
252
|
-
for (let i = 0; i < Math.max(b.length, a.length); i++) {
|
|
253
|
-
if (b[i] !== a[i]) {
|
|
254
|
-
console.error(` ${file}:${i + 1}`);
|
|
255
|
-
console.error(` - ${(b[i] ?? "").trim()}`);
|
|
256
|
-
console.error(` + ${(a[i] ?? "").trim()}`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
console.error(
|
|
261
|
-
"\n fix with: node scripts/sync-brand-numbers.mjs && git commit -am 'chore: sync brand numbers'",
|
|
262
|
-
);
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
console.log(
|
|
267
|
-
drifted.length
|
|
268
|
-
? `brand-numbers: updated ${drifted.length} file(s)`
|
|
269
|
-
: `brand-numbers: already up to date (${everUsed.size} keys in use)`,
|
|
270
|
-
);
|