@blockrun/clawrouter 0.12.264 → 0.12.266
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 +15 -1
- package/dist/cli.js +1396 -388
- package/dist/index.d.ts +52 -16
- package/dist/index.js +1114 -359
- package/docs/clawrouter-cuts-llm-api-costs-500x.md +1 -1
- package/docs/configuration.md +26 -24
- package/docs/troubleshooting.md +6 -2
- package/openclaw.plugin.json +7 -6
- package/package.json +4 -2
- package/scripts/reinstall.sh +181 -77
- package/scripts/uninstall.sh +53 -8
- package/scripts/update.ps1 +215 -33
- package/scripts/update.sh +223 -86
- package/scripts/sync-brand-numbers.mjs +0 -270
package/scripts/reinstall.sh
CHANGED
|
@@ -3,20 +3,58 @@ set -e
|
|
|
3
3
|
set -o pipefail
|
|
4
4
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PLUGIN_DIR="$HOME/.openclaw/extensions/clawrouter"
|
|
6
|
+
PLUGIN_DIR="$HOME/.openclaw/extensions/blockrun-clawrouter"
|
|
7
|
+
LEGACY_PLUGIN_DIR="$HOME/.openclaw/extensions/clawrouter"
|
|
7
8
|
CONFIG_PATH="$HOME/.openclaw/openclaw.json"
|
|
8
9
|
WALLET_FILE="$HOME/.openclaw/blockrun/wallet.key"
|
|
9
10
|
WALLET_BACKUP=""
|
|
11
|
+
WALLET_EXISTED=0
|
|
10
12
|
PLUGIN_BACKUP=""
|
|
13
|
+
LEGACY_PLUGIN_BACKUP=""
|
|
14
|
+
LEGACY_BLOCKRUN_INSTALL=0
|
|
11
15
|
CONFIG_BACKUP=""
|
|
16
|
+
CONFIG_EXISTED=0
|
|
17
|
+
CREDS_DIR="$HOME/.openclaw/credentials"
|
|
18
|
+
CREDS_BACKUP=""
|
|
19
|
+
CREDS_EXISTED=0
|
|
20
|
+
INSTALL_SPEC="${BLOCKRUN_CLAWROUTER_INSTALL_SPEC:-@blockrun/clawrouter}"
|
|
21
|
+
|
|
22
|
+
[ -f "$WALLET_FILE" ] && WALLET_EXISTED=1
|
|
23
|
+
[ -f "$CONFIG_PATH" ] && CONFIG_EXISTED=1
|
|
24
|
+
[ -d "$CREDS_DIR" ] && CREDS_EXISTED=1
|
|
25
|
+
|
|
26
|
+
is_blockrun_plugin_dir() {
|
|
27
|
+
local candidate_dir="$1"
|
|
28
|
+
[ -d "$candidate_dir" ] || return 1
|
|
29
|
+
node -e '
|
|
30
|
+
const fs = require("fs");
|
|
31
|
+
const path = require("path");
|
|
32
|
+
const dir = process.argv[1];
|
|
33
|
+
for (const candidate of [path.join(dir, "package.json"), path.join(dir, "package", "package.json")]) {
|
|
34
|
+
try {
|
|
35
|
+
if (JSON.parse(fs.readFileSync(candidate, "utf8")).name === "@blockrun/clawrouter") process.exit(0);
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
process.exit(1);
|
|
39
|
+
' "$candidate_dir"
|
|
40
|
+
}
|
|
12
41
|
|
|
13
42
|
cleanup_backups() {
|
|
43
|
+
if [ -n "$WALLET_BACKUP" ] && [ -f "$WALLET_BACKUP" ]; then
|
|
44
|
+
rm -f "$WALLET_BACKUP"
|
|
45
|
+
fi
|
|
14
46
|
if [ -n "$PLUGIN_BACKUP" ] && [ -d "$PLUGIN_BACKUP" ]; then
|
|
15
47
|
rm -rf "$PLUGIN_BACKUP"
|
|
16
48
|
fi
|
|
49
|
+
if [ -n "$LEGACY_PLUGIN_BACKUP" ] && [ -d "$LEGACY_PLUGIN_BACKUP" ]; then
|
|
50
|
+
rm -rf "$LEGACY_PLUGIN_BACKUP"
|
|
51
|
+
fi
|
|
17
52
|
if [ -n "$CONFIG_BACKUP" ] && [ -f "$CONFIG_BACKUP" ]; then
|
|
18
53
|
rm -f "$CONFIG_BACKUP"
|
|
19
54
|
fi
|
|
55
|
+
if [ -n "$CREDS_BACKUP" ] && [ -d "$CREDS_BACKUP" ]; then
|
|
56
|
+
rm -rf "$(dirname "$CREDS_BACKUP")"
|
|
57
|
+
fi
|
|
20
58
|
}
|
|
21
59
|
|
|
22
60
|
restore_previous_install() {
|
|
@@ -35,9 +73,34 @@ restore_previous_install() {
|
|
|
35
73
|
echo " ✓ Restored previous plugin files"
|
|
36
74
|
fi
|
|
37
75
|
|
|
76
|
+
if [ -n "$LEGACY_PLUGIN_BACKUP" ] && [ -d "$LEGACY_PLUGIN_BACKUP" ]; then
|
|
77
|
+
mv "$LEGACY_PLUGIN_BACKUP" "$LEGACY_PLUGIN_DIR"
|
|
78
|
+
echo " ✓ Restored previous legacy BlockRun plugin files"
|
|
79
|
+
fi
|
|
80
|
+
|
|
38
81
|
if [ -n "$CONFIG_BACKUP" ] && [ -f "$CONFIG_BACKUP" ]; then
|
|
39
82
|
cp "$CONFIG_BACKUP" "$CONFIG_PATH"
|
|
40
83
|
echo " ✓ Restored previous OpenClaw config"
|
|
84
|
+
elif [ "$CONFIG_EXISTED" = "0" ]; then
|
|
85
|
+
rm -f "$CONFIG_PATH"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ "$WALLET_EXISTED" = "1" ] && [ -n "$WALLET_BACKUP" ] && [ -f "$WALLET_BACKUP" ]; then
|
|
89
|
+
mkdir -p "$(dirname "$WALLET_FILE")"
|
|
90
|
+
cp "$WALLET_BACKUP" "$WALLET_FILE"
|
|
91
|
+
chmod 600 "$WALLET_FILE"
|
|
92
|
+
echo " ✓ Restored previous wallet"
|
|
93
|
+
elif [ "$WALLET_EXISTED" = "0" ]; then
|
|
94
|
+
rm -f "$WALLET_FILE"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if [ "$CREDS_EXISTED" = "1" ] && [ -n "$CREDS_BACKUP" ] && [ -d "$CREDS_BACKUP" ]; then
|
|
98
|
+
rm -rf "$CREDS_DIR"
|
|
99
|
+
mkdir -p "$CREDS_DIR"
|
|
100
|
+
cp -a "$CREDS_BACKUP/." "$CREDS_DIR/"
|
|
101
|
+
echo " ✓ Restored OpenClaw credentials"
|
|
102
|
+
elif [ "$CREDS_EXISTED" = "0" ]; then
|
|
103
|
+
rm -rf "$CREDS_DIR"
|
|
41
104
|
fi
|
|
42
105
|
fi
|
|
43
106
|
|
|
@@ -107,10 +170,11 @@ validate_config
|
|
|
107
170
|
# 0. Back up wallet key BEFORE removing anything
|
|
108
171
|
echo "→ Backing up wallet..."
|
|
109
172
|
if [ -f "$WALLET_FILE" ]; then
|
|
173
|
+
WALLET_EXISTED=1
|
|
110
174
|
WALLET_KEY=$(cat "$WALLET_FILE" | tr -d '[:space:]')
|
|
111
175
|
KEY_LEN=${#WALLET_KEY}
|
|
112
176
|
if [[ "$WALLET_KEY" == 0x* ]] && [ "$KEY_LEN" -eq 66 ]; then
|
|
113
|
-
WALLET_BACKUP="$HOME/.openclaw/blockrun/wallet.key.bak
|
|
177
|
+
WALLET_BACKUP="$(mktemp "$HOME/.openclaw/blockrun/wallet.key.bak.XXXXXX")"
|
|
114
178
|
cp "$WALLET_FILE" "$WALLET_BACKUP"
|
|
115
179
|
chmod 600 "$WALLET_BACKUP"
|
|
116
180
|
echo " ✓ Wallet backed up to: $WALLET_BACKUP"
|
|
@@ -132,57 +196,34 @@ else
|
|
|
132
196
|
echo " ℹ No existing plugin files found"
|
|
133
197
|
fi
|
|
134
198
|
|
|
199
|
+
if is_blockrun_plugin_dir "$LEGACY_PLUGIN_DIR"; then
|
|
200
|
+
LEGACY_BLOCKRUN_INSTALL=1
|
|
201
|
+
LEGACY_PLUGIN_BACKUP="$HOME/.openclaw/blockrun/clawrouter.legacy.backup.$(date +%s)"
|
|
202
|
+
mv "$LEGACY_PLUGIN_DIR" "$LEGACY_PLUGIN_BACKUP"
|
|
203
|
+
echo " ✓ Legacy BlockRun plugin staged at: $LEGACY_PLUGIN_BACKUP"
|
|
204
|
+
fi
|
|
205
|
+
|
|
135
206
|
if [ -f "$CONFIG_PATH" ]; then
|
|
207
|
+
CONFIG_EXISTED=1
|
|
136
208
|
CONFIG_BACKUP="$CONFIG_PATH.clawrouter-reinstall.$(date +%s).bak"
|
|
137
209
|
cp "$CONFIG_PATH" "$CONFIG_BACKUP"
|
|
138
210
|
echo " ✓ Config backed up to: $CONFIG_BACKUP"
|
|
139
211
|
fi
|
|
140
212
|
echo ""
|
|
141
213
|
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
echo "→ Removing Crossmint/lobster extension..."
|
|
145
|
-
LOBSTER_DIR="$HOME/.openclaw/extensions/lobster.cash"
|
|
146
|
-
if [ -d "$LOBSTER_DIR" ]; then
|
|
147
|
-
rm -rf "$LOBSTER_DIR"
|
|
148
|
-
echo " ✓ Removed $LOBSTER_DIR"
|
|
149
|
-
else
|
|
150
|
-
echo " ✓ Not installed"
|
|
151
|
-
fi
|
|
152
|
-
node -e "
|
|
153
|
-
const fs = require('fs');
|
|
154
|
-
const configPath = '$CONFIG_PATH';
|
|
155
|
-
if (!fs.existsSync(configPath)) process.exit(0);
|
|
156
|
-
try {
|
|
157
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
158
|
-
let changed = false;
|
|
159
|
-
for (const key of ['lobster.cash', 'lobster', 'crossmint']) {
|
|
160
|
-
if (config?.plugins?.entries?.[key]) { delete config.plugins.entries[key]; changed = true; console.log(' Removed plugins.entries.' + key); }
|
|
161
|
-
if (config?.plugins?.installs?.[key]) { delete config.plugins.installs[key]; changed = true; }
|
|
162
|
-
}
|
|
163
|
-
if (Array.isArray(config?.plugins?.allow)) {
|
|
164
|
-
const before = config.plugins.allow.length;
|
|
165
|
-
config.plugins.allow = config.plugins.allow.filter(p => !['lobster.cash','lobster','crossmint'].includes(p));
|
|
166
|
-
if (config.plugins.allow.length !== before) { changed = true; console.log(' Removed lobster/crossmint from plugins.allow'); }
|
|
167
|
-
}
|
|
168
|
-
if (changed) {
|
|
169
|
-
const tmp = configPath + '.tmp.' + process.pid;
|
|
170
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
171
|
-
fs.renameSync(tmp, configPath);
|
|
172
|
-
} else { console.log(' Config clean'); }
|
|
173
|
-
} catch (e) { console.log(' Skipped: ' + e.message); }
|
|
174
|
-
"
|
|
175
|
-
echo ""
|
|
214
|
+
# Third-party plugins are never removed by a ClawRouter reinstall. OpenClaw's
|
|
215
|
+
# conflict diagnostics let the user choose which slash-command owner wins.
|
|
176
216
|
|
|
177
217
|
# 2. Clean config entries
|
|
178
218
|
echo "→ Cleaning config entries..."
|
|
179
|
-
node -e "
|
|
219
|
+
LEGACY_BLOCKRUN_INSTALL="$LEGACY_BLOCKRUN_INSTALL" node -e "
|
|
180
220
|
const f = require('os').homedir() + '/.openclaw/openclaw.json';
|
|
181
221
|
const fs = require('fs');
|
|
182
222
|
function atomicWrite(filePath, data) {
|
|
183
223
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
184
|
-
fs.writeFileSync(tmpPath, data);
|
|
224
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
185
225
|
fs.renameSync(tmpPath, filePath);
|
|
226
|
+
fs.chmodSync(filePath, 0o600);
|
|
186
227
|
}
|
|
187
228
|
if (!fs.existsSync(f)) {
|
|
188
229
|
console.log(' No openclaw.json found, skipping');
|
|
@@ -204,19 +245,29 @@ try {
|
|
|
204
245
|
process.exit(0);
|
|
205
246
|
}
|
|
206
247
|
|
|
207
|
-
//
|
|
208
|
-
for (const key of ['clawrouter', 'ClawRouter', '@blockrun/clawrouter']) {
|
|
248
|
+
// Never remove lowercase clawrouter: OpenClaw's official router owns it.
|
|
249
|
+
for (const key of ['blockrun-clawrouter', 'ClawRouter', '@blockrun/clawrouter']) {
|
|
209
250
|
if (c.plugins?.entries?.[key]) delete c.plugins.entries[key];
|
|
210
251
|
if (c.plugins?.installs?.[key]) delete c.plugins.installs[key];
|
|
211
252
|
}
|
|
253
|
+
if (process.env.LEGACY_BLOCKRUN_INSTALL === '1' && c.plugins?.entries?.clawrouter) {
|
|
254
|
+
const legacy = c.plugins.entries.clawrouter;
|
|
255
|
+
for (const key of ['walletKey', 'routing']) {
|
|
256
|
+
delete legacy[key];
|
|
257
|
+
if (legacy.config) delete legacy.config[key];
|
|
258
|
+
}
|
|
259
|
+
if (legacy.config && Object.keys(legacy.config).length === 0) delete legacy.config;
|
|
260
|
+
}
|
|
212
261
|
|
|
213
262
|
// Clean plugins.allow — remove only ClawRouter entries; preserve every other
|
|
214
263
|
// plugin the user has allowed, including bare local/custom plugin IDs.
|
|
215
264
|
if (Array.isArray(c.plugins?.allow)) {
|
|
216
265
|
const before = c.plugins.allow.length;
|
|
217
|
-
c.plugins.allow = c.plugins.allow.filter(
|
|
266
|
+
c.plugins.allow = c.plugins.allow.filter(
|
|
267
|
+
p => p !== 'blockrun-clawrouter' && p !== 'ClawRouter' && p !== '@blockrun/clawrouter'
|
|
268
|
+
);
|
|
218
269
|
const removed = before - c.plugins.allow.length;
|
|
219
|
-
if (removed > 0) console.log('
|
|
270
|
+
if (removed > 0) console.log(' Staged BlockRun plugin config for reinstall');
|
|
220
271
|
}
|
|
221
272
|
|
|
222
273
|
// OpenClaw 2026.5.2+ validates tools.web.search.provider at config-load time.
|
|
@@ -251,8 +302,9 @@ const authDir = path.join(os.homedir(), '.openclaw', 'agents', 'main', 'agent');
|
|
|
251
302
|
const authPath = path.join(authDir, 'auth-profiles.json');
|
|
252
303
|
function atomicWrite(filePath, data) {
|
|
253
304
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
254
|
-
fs.writeFileSync(tmpPath, data);
|
|
305
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
255
306
|
fs.renameSync(tmpPath, filePath);
|
|
307
|
+
fs.chmodSync(filePath, 0o600);
|
|
256
308
|
}
|
|
257
309
|
|
|
258
310
|
// Create directory if needed
|
|
@@ -293,14 +345,15 @@ if (!store.profiles[profileKey]) {
|
|
|
293
345
|
# 5. Ensure apiKey is present for /model picker (but DON'T override default model)
|
|
294
346
|
echo "→ Finalizing setup..."
|
|
295
347
|
node -e "
|
|
296
|
-
const os = require('os');
|
|
297
348
|
const fs = require('fs');
|
|
349
|
+
const os = require('os');
|
|
298
350
|
const path = require('path');
|
|
299
351
|
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
300
352
|
function atomicWrite(filePath, data) {
|
|
301
353
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
302
|
-
fs.writeFileSync(tmpPath, data);
|
|
354
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
303
355
|
fs.renameSync(tmpPath, filePath);
|
|
356
|
+
fs.chmodSync(filePath, 0o600);
|
|
304
357
|
}
|
|
305
358
|
|
|
306
359
|
if (fs.existsSync(configPath)) {
|
|
@@ -362,8 +415,9 @@ try {
|
|
|
362
415
|
|
|
363
416
|
if (changed) {
|
|
364
417
|
const tmpPath = configPath + '.tmp.' + process.pid;
|
|
365
|
-
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
|
|
418
|
+
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
366
419
|
fs.renameSync(tmpPath, configPath);
|
|
420
|
+
fs.chmodSync(configPath, 0o600);
|
|
367
421
|
} else {
|
|
368
422
|
console.log(' Provider config OK');
|
|
369
423
|
}
|
|
@@ -374,9 +428,8 @@ try {
|
|
|
374
428
|
|
|
375
429
|
# 6. Install plugin (config is ready, but no allow list yet to avoid validation error)
|
|
376
430
|
# Back up OpenClaw credentials (channels, WhatsApp/Telegram state) before plugin install
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if [ -d "$CREDS_DIR" ] && [ "$(ls -A "$CREDS_DIR" 2>/dev/null)" ]; then
|
|
431
|
+
if [ -d "$CREDS_DIR" ]; then
|
|
432
|
+
CREDS_EXISTED=1
|
|
380
433
|
CREDS_BACKUP="$(mktemp -d)/openclaw-credentials-backup"
|
|
381
434
|
cp -a "$CREDS_DIR" "$CREDS_BACKUP"
|
|
382
435
|
echo " ✓ Backed up OpenClaw credentials"
|
|
@@ -406,10 +459,16 @@ fi
|
|
|
406
459
|
# openclaw plugins install scans the directory. If they exist during install,
|
|
407
460
|
# OpenClaw writes them into config as duplicate plugins.
|
|
408
461
|
for stale in "$HOME/.openclaw/extensions/clawrouter.backup."* "$HOME/.openclaw/extensions/.openclaw-install-stage-"*; do
|
|
409
|
-
|
|
462
|
+
if is_blockrun_plugin_dir "$stale"; then
|
|
463
|
+
rm -rf "$stale"
|
|
464
|
+
fi
|
|
410
465
|
done
|
|
411
466
|
|
|
412
467
|
echo "→ Installing ClawRouter..."
|
|
468
|
+
OPENCLAW_CAPABILITY_ARGS=()
|
|
469
|
+
if openclaw plugins install --help 2>&1 | grep -q -- '--accept-capabilities'; then
|
|
470
|
+
OPENCLAW_CAPABILITY_ARGS=(--accept-capabilities)
|
|
471
|
+
fi
|
|
413
472
|
# `--force` is required when the plugin is already installed at the same path.
|
|
414
473
|
# Reinstall.sh covers both fresh and re-install flows; without --force the
|
|
415
474
|
# re-install flow fails with "plugin already exists", our EXIT trap rolls back,
|
|
@@ -419,7 +478,7 @@ echo "→ Installing ClawRouter..."
|
|
|
419
478
|
# "Installed plugin: clawrouter" in OpenClaw v2026.4.5 (parallel plugin loading).
|
|
420
479
|
# 120s is enough for slow connections; the install itself completes in ~30s.
|
|
421
480
|
if command -v timeout >/dev/null 2>&1; then
|
|
422
|
-
timeout 120 openclaw plugins install --force @
|
|
481
|
+
timeout 120 openclaw plugins install --force "${OPENCLAW_CAPABILITY_ARGS[@]}" "$INSTALL_SPEC" || {
|
|
423
482
|
exit_code=$?
|
|
424
483
|
if [ $exit_code -eq 124 ]; then
|
|
425
484
|
echo " (install command timed out — this is normal with OpenClaw v2026.4.5)"
|
|
@@ -429,17 +488,13 @@ if command -v timeout >/dev/null 2>&1; then
|
|
|
429
488
|
fi
|
|
430
489
|
}
|
|
431
490
|
else
|
|
432
|
-
openclaw plugins install --force @
|
|
491
|
+
openclaw plugins install --force "${OPENCLAW_CAPABILITY_ARGS[@]}" "$INSTALL_SPEC"
|
|
433
492
|
fi
|
|
434
493
|
|
|
435
|
-
# Install is complete — clear the rollback trap immediately.
|
|
436
|
-
# From this point on, Ctrl+C or errors should NOT roll back the install.
|
|
437
|
-
trap - EXIT INT TERM
|
|
438
|
-
|
|
439
494
|
# Restore credentials after plugin install (always restore to preserve user's channels)
|
|
440
495
|
if [ -n "$CREDS_BACKUP" ] && [ -d "$CREDS_BACKUP" ]; then
|
|
441
496
|
mkdir -p "$CREDS_DIR"
|
|
442
|
-
cp -a "$CREDS_BACKUP
|
|
497
|
+
cp -a "$CREDS_BACKUP/." "$CREDS_DIR/"
|
|
443
498
|
echo " ✓ Restored OpenClaw credentials (channels preserved)"
|
|
444
499
|
rm -rf "$(dirname "$CREDS_BACKUP")"
|
|
445
500
|
fi
|
|
@@ -450,8 +505,9 @@ if [ -n "$CHANNEL_CONFIG_BACKUP" ] && [ -f "$CHANNEL_CONFIG_BACKUP" ] && [ -f "$
|
|
|
450
505
|
const fs = require('fs');
|
|
451
506
|
function atomicWrite(filePath, data) {
|
|
452
507
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
453
|
-
fs.writeFileSync(tmpPath, data);
|
|
508
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
454
509
|
fs.renameSync(tmpPath, filePath);
|
|
510
|
+
fs.chmodSync(filePath, 0o600);
|
|
455
511
|
}
|
|
456
512
|
try {
|
|
457
513
|
const config = JSON.parse(fs.readFileSync('$CONFIG_PATH', 'utf8'));
|
|
@@ -566,8 +622,9 @@ const path = require('path');
|
|
|
566
622
|
const topModelsPath = '$SCRIPT_DIR/../src/top-models.json';
|
|
567
623
|
function atomicWrite(filePath, data) {
|
|
568
624
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
569
|
-
fs.writeFileSync(tmpPath, data);
|
|
625
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
570
626
|
fs.renameSync(tmpPath, filePath);
|
|
627
|
+
fs.chmodSync(filePath, 0o600);
|
|
571
628
|
}
|
|
572
629
|
|
|
573
630
|
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
@@ -651,8 +708,9 @@ const fs = require('fs');
|
|
|
651
708
|
const path = require('path');
|
|
652
709
|
function atomicWrite(filePath, data) {
|
|
653
710
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
654
|
-
fs.writeFileSync(tmpPath, data);
|
|
711
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
655
712
|
fs.renameSync(tmpPath, filePath);
|
|
713
|
+
fs.chmodSync(filePath, 0o600);
|
|
656
714
|
}
|
|
657
715
|
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
658
716
|
if (!fs.existsSync(configPath)) { console.log(' No config, skipping'); process.exit(0); }
|
|
@@ -672,32 +730,67 @@ try {
|
|
|
672
730
|
|
|
673
731
|
# 7. Add plugin to allow list (done AFTER install so plugin files exist for validation)
|
|
674
732
|
echo "→ Adding to plugins allow list..."
|
|
675
|
-
node -
|
|
676
|
-
const os = require('os');
|
|
733
|
+
node - "$CONFIG_PATH" "$CONFIG_BACKUP" "$LEGACY_BLOCKRUN_INSTALL" <<'NODE'
|
|
677
734
|
const fs = require('fs');
|
|
678
|
-
const
|
|
679
|
-
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
735
|
+
const [configPath, backupPath, legacyOwned] = process.argv.slice(2);
|
|
680
736
|
function atomicWrite(filePath, data) {
|
|
681
737
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
682
|
-
fs.writeFileSync(tmpPath, data);
|
|
738
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
683
739
|
fs.renameSync(tmpPath, filePath);
|
|
740
|
+
fs.chmodSync(filePath, 0o600);
|
|
684
741
|
}
|
|
685
742
|
|
|
686
743
|
if (fs.existsSync(configPath)) {
|
|
687
744
|
try {
|
|
688
745
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
689
746
|
|
|
690
|
-
// Ensure
|
|
747
|
+
// Ensure the distinct BlockRun id is enabled only after its package exists.
|
|
691
748
|
if (!config.plugins) config.plugins = {};
|
|
749
|
+
if (!config.plugins.entries) config.plugins.entries = {};
|
|
750
|
+
const preserved = backupPath && fs.existsSync(backupPath)
|
|
751
|
+
? JSON.parse(fs.readFileSync(backupPath, 'utf8'))
|
|
752
|
+
: {};
|
|
753
|
+
const current = config.plugins.entries['blockrun-clawrouter'] || {};
|
|
754
|
+
const moveOwnedFields = (source, destination, removeFromSource = false) => {
|
|
755
|
+
if (!source || typeof source !== 'object') return;
|
|
756
|
+
for (const key of ['walletKey', 'routing']) {
|
|
757
|
+
if (!(key in destination) && key in source) destination[key] = source[key];
|
|
758
|
+
if (removeFromSource) delete source[key];
|
|
759
|
+
}
|
|
760
|
+
if (source.config && typeof source.config === 'object') {
|
|
761
|
+
destination.config ??= {};
|
|
762
|
+
for (const key of ['walletKey', 'routing']) {
|
|
763
|
+
if (!(key in destination.config) && key in source.config) destination.config[key] = source.config[key];
|
|
764
|
+
if (removeFromSource) delete source.config[key];
|
|
765
|
+
}
|
|
766
|
+
if (Object.keys(destination.config).length === 0) delete destination.config;
|
|
767
|
+
if (removeFromSource && Object.keys(source.config).length === 0) delete source.config;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
moveOwnedFields(preserved?.plugins?.entries?.['blockrun-clawrouter'], current);
|
|
771
|
+
if (legacyOwned === '1') {
|
|
772
|
+
const legacy = config.plugins.entries.clawrouter;
|
|
773
|
+
const preservedLegacy = preserved?.plugins?.entries?.clawrouter;
|
|
774
|
+
if (typeof preservedLegacy?.enabled === 'boolean' && typeof current.enabled !== 'boolean') {
|
|
775
|
+
current.enabled = preservedLegacy.enabled;
|
|
776
|
+
}
|
|
777
|
+
moveOwnedFields(preservedLegacy, current);
|
|
778
|
+
moveOwnedFields(legacy, current, true);
|
|
779
|
+
}
|
|
780
|
+
current.enabled = true;
|
|
781
|
+
config.plugins.entries['blockrun-clawrouter'] = current;
|
|
692
782
|
if (!Array.isArray(config.plugins.allow)) {
|
|
693
783
|
config.plugins.allow = [];
|
|
694
784
|
}
|
|
695
|
-
if (!config.plugins.allow.includes('
|
|
696
|
-
config.plugins.allow.push('clawrouter');
|
|
697
|
-
console.log(' Added clawrouter to plugins.allow');
|
|
785
|
+
if (!config.plugins.allow.includes('blockrun-clawrouter')) {
|
|
786
|
+
config.plugins.allow.push('blockrun-clawrouter');
|
|
787
|
+
console.log(' Added blockrun-clawrouter to plugins.allow');
|
|
698
788
|
} else {
|
|
699
789
|
console.log(' Plugin already in allow list');
|
|
700
790
|
}
|
|
791
|
+
if (Array.isArray(config.plugins.deny)) {
|
|
792
|
+
config.plugins.deny = config.plugins.deny.filter(id => id !== 'blockrun-clawrouter');
|
|
793
|
+
}
|
|
701
794
|
|
|
702
795
|
atomicWrite(configPath, JSON.stringify(config, null, 2));
|
|
703
796
|
} catch (e) {
|
|
@@ -706,7 +799,7 @@ if (fs.existsSync(configPath)) {
|
|
|
706
799
|
} else {
|
|
707
800
|
console.log(' No openclaw.json found, skipping');
|
|
708
801
|
}
|
|
709
|
-
|
|
802
|
+
NODE
|
|
710
803
|
|
|
711
804
|
# 8. Ensure gateway.mode is set (required by OpenClaw v2026.4.5+)
|
|
712
805
|
echo "→ Ensuring gateway.mode is set..."
|
|
@@ -717,8 +810,9 @@ const path = require('path');
|
|
|
717
810
|
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
718
811
|
function atomicWrite(filePath, data) {
|
|
719
812
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
720
|
-
fs.writeFileSync(tmpPath, data);
|
|
813
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
721
814
|
fs.renameSync(tmpPath, filePath);
|
|
815
|
+
fs.chmodSync(filePath, 0o600);
|
|
722
816
|
}
|
|
723
817
|
|
|
724
818
|
if (fs.existsSync(configPath)) {
|
|
@@ -747,7 +841,7 @@ if (fs.existsSync(configPath)) {
|
|
|
747
841
|
echo "→ Cleaning up stale install stages..."
|
|
748
842
|
CLEANED=0
|
|
749
843
|
for stage_dir in "$HOME/.openclaw/extensions/.openclaw-install-stage-"*; do
|
|
750
|
-
if
|
|
844
|
+
if is_blockrun_plugin_dir "$stage_dir"; then
|
|
751
845
|
rm -rf "$stage_dir"
|
|
752
846
|
CLEANED=$((CLEANED + 1))
|
|
753
847
|
fi
|
|
@@ -762,7 +856,13 @@ fi
|
|
|
762
856
|
# plugin detection), new ones live in blockrun/. Clean both locations.
|
|
763
857
|
echo "→ Cleaning up stale plugin backups..."
|
|
764
858
|
CLEANED=0
|
|
765
|
-
for backup_dir in "$HOME/.openclaw/extensions/clawrouter.backup."
|
|
859
|
+
for backup_dir in "$HOME/.openclaw/extensions/clawrouter.backup."*; do
|
|
860
|
+
if is_blockrun_plugin_dir "$backup_dir"; then
|
|
861
|
+
rm -rf "$backup_dir"
|
|
862
|
+
CLEANED=$((CLEANED + 1))
|
|
863
|
+
fi
|
|
864
|
+
done
|
|
865
|
+
for backup_dir in "$HOME/.openclaw/blockrun/clawrouter.backup."*; do
|
|
766
866
|
if [ -d "$backup_dir" ]; then
|
|
767
867
|
rm -rf "$backup_dir"
|
|
768
868
|
CLEANED=$((CLEANED + 1))
|
|
@@ -784,11 +884,12 @@ try {
|
|
|
784
884
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
785
885
|
let changed = false;
|
|
786
886
|
const isStale = (p) => p.includes('.openclaw-install-stage-') || p.includes('clawrouter.backup.');
|
|
887
|
+
const blockrunIds = new Set(['blockrun-clawrouter', 'ClawRouter', '@blockrun/clawrouter']);
|
|
787
888
|
// Remove plugins.entries pointing to stale directories
|
|
788
889
|
if (config?.plugins?.entries) {
|
|
789
890
|
for (const [key, val] of Object.entries(config.plugins.entries)) {
|
|
790
891
|
const path = typeof val === 'string' ? val : val?.path || val?.main || '';
|
|
791
|
-
if (isStale(path)) {
|
|
892
|
+
if (blockrunIds.has(key) && isStale(path)) {
|
|
792
893
|
delete config.plugins.entries[key];
|
|
793
894
|
changed = true;
|
|
794
895
|
console.log(' Removed plugins.entries.' + key + ' (stale)');
|
|
@@ -799,7 +900,7 @@ try {
|
|
|
799
900
|
if (config?.plugins?.installs) {
|
|
800
901
|
for (const [key, val] of Object.entries(config.plugins.installs)) {
|
|
801
902
|
const path = typeof val === 'string' ? val : val?.path || val?.main || '';
|
|
802
|
-
if (isStale(path)) {
|
|
903
|
+
if (blockrunIds.has(key) && isStale(path)) {
|
|
803
904
|
delete config.plugins.installs[key];
|
|
804
905
|
changed = true;
|
|
805
906
|
console.log(' Removed plugins.installs.' + key + ' (stale)');
|
|
@@ -808,8 +909,9 @@ try {
|
|
|
808
909
|
}
|
|
809
910
|
if (changed) {
|
|
810
911
|
const tmp = configPath + '.tmp.' + process.pid;
|
|
811
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
912
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
812
913
|
fs.renameSync(tmp, configPath);
|
|
914
|
+
fs.chmodSync(configPath, 0o600);
|
|
813
915
|
console.log(' ✓ Registry cleaned');
|
|
814
916
|
} else {
|
|
815
917
|
console.log(' ✓ Registry clean');
|
|
@@ -840,6 +942,8 @@ else
|
|
|
840
942
|
fi
|
|
841
943
|
fi
|
|
842
944
|
|
|
945
|
+
cleanup_backups
|
|
946
|
+
trap - EXIT INT TERM
|
|
843
947
|
echo ""
|
|
844
948
|
echo "✓ Done! Smart routing enabled by default."
|
|
845
949
|
echo ""
|
|
@@ -910,4 +1014,4 @@ echo " npx @blockrun/clawrouter report weekly # weekly report"
|
|
|
910
1014
|
echo " npx @blockrun/clawrouter report monthly # monthly report"
|
|
911
1015
|
echo " npx @blockrun/clawrouter doctor # AI diagnostics"
|
|
912
1016
|
echo ""
|
|
913
|
-
echo "To uninstall: bash ~/.openclaw/extensions/clawrouter/scripts/uninstall.sh"
|
|
1017
|
+
echo "To uninstall: bash ~/.openclaw/extensions/blockrun-clawrouter/scripts/uninstall.sh"
|
package/scripts/uninstall.sh
CHANGED
|
@@ -28,15 +28,31 @@ echo ""
|
|
|
28
28
|
|
|
29
29
|
# 1. Stop proxy
|
|
30
30
|
echo "→ Stopping proxy..."
|
|
31
|
-
kill_port_processes 8402
|
|
31
|
+
kill_port_processes "${BLOCKRUN_PROXY_PORT:-8402}"
|
|
32
32
|
|
|
33
33
|
# 2. Remove plugin files
|
|
34
34
|
echo "→ Removing plugin files..."
|
|
35
|
-
|
|
35
|
+
if command -v openclaw >/dev/null 2>&1; then
|
|
36
|
+
openclaw plugins uninstall --force blockrun-clawrouter >/dev/null 2>&1 || true
|
|
37
|
+
fi
|
|
38
|
+
rm -rf ~/.openclaw/extensions/blockrun-clawrouter
|
|
39
|
+
|
|
40
|
+
# Remove the pre-rename directory only when package metadata proves that it is
|
|
41
|
+
# BlockRun's package. Never remove an unrelated official `clawrouter` plugin.
|
|
42
|
+
LEGACY_PLUGIN_DIR="$HOME/.openclaw/extensions/clawrouter"
|
|
43
|
+
LEGACY_BLOCKRUN_INSTALL=0
|
|
44
|
+
if [ -f "$LEGACY_PLUGIN_DIR/package.json" ] && node -e '
|
|
45
|
+
const fs = require("fs");
|
|
46
|
+
const pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
47
|
+
process.exit(pkg.name === "@blockrun/clawrouter" ? 0 : 1);
|
|
48
|
+
' "$LEGACY_PLUGIN_DIR/package.json"; then
|
|
49
|
+
LEGACY_BLOCKRUN_INSTALL=1
|
|
50
|
+
rm -rf "$LEGACY_PLUGIN_DIR"
|
|
51
|
+
fi
|
|
36
52
|
|
|
37
53
|
# 3. Clean openclaw.json
|
|
38
54
|
echo "→ Cleaning openclaw.json..."
|
|
39
|
-
node -e "
|
|
55
|
+
LEGACY_BLOCKRUN_INSTALL="$LEGACY_BLOCKRUN_INSTALL" node -e "
|
|
40
56
|
const os = require('os');
|
|
41
57
|
const fs = require('fs');
|
|
42
58
|
const path = require('path');
|
|
@@ -58,8 +74,8 @@ try {
|
|
|
58
74
|
changed = true;
|
|
59
75
|
}
|
|
60
76
|
|
|
61
|
-
//
|
|
62
|
-
for (const key of ['clawrouter', 'ClawRouter', '@blockrun/clawrouter']) {
|
|
77
|
+
// Never remove lowercase clawrouter: OpenClaw's bundled router owns it.
|
|
78
|
+
for (const key of ['blockrun-clawrouter', 'ClawRouter', '@blockrun/clawrouter']) {
|
|
63
79
|
if (config.plugins?.entries?.[key]) {
|
|
64
80
|
delete config.plugins.entries[key];
|
|
65
81
|
console.log(' Removed plugins.entries.' + key);
|
|
@@ -72,11 +88,29 @@ try {
|
|
|
72
88
|
}
|
|
73
89
|
}
|
|
74
90
|
|
|
91
|
+
// If the directory proved the old id was our pre-rename package, remove
|
|
92
|
+
// only BlockRun-owned fields from the now-official entry. Preserve its
|
|
93
|
+
// enabled state and all unrelated official settings.
|
|
94
|
+
if (process.env.LEGACY_BLOCKRUN_INSTALL === '1' && config.plugins?.entries?.clawrouter) {
|
|
95
|
+
const legacy = config.plugins.entries.clawrouter;
|
|
96
|
+
for (const key of ['walletKey', 'routing']) {
|
|
97
|
+
if (Object.prototype.hasOwnProperty.call(legacy, key)) {
|
|
98
|
+
delete legacy[key];
|
|
99
|
+
changed = true;
|
|
100
|
+
}
|
|
101
|
+
if (legacy.config && Object.prototype.hasOwnProperty.call(legacy.config, key)) {
|
|
102
|
+
delete legacy.config[key];
|
|
103
|
+
changed = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (legacy.config && Object.keys(legacy.config).length === 0) delete legacy.config;
|
|
107
|
+
}
|
|
108
|
+
|
|
75
109
|
// Remove from plugins.allow
|
|
76
110
|
if (Array.isArray(config.plugins?.allow)) {
|
|
77
111
|
const before = config.plugins.allow.length;
|
|
78
112
|
config.plugins.allow = config.plugins.allow.filter(
|
|
79
|
-
p => p !== 'clawrouter' && p !== 'ClawRouter' && p !== '@blockrun/clawrouter'
|
|
113
|
+
p => p !== 'blockrun-clawrouter' && p !== 'ClawRouter' && p !== '@blockrun/clawrouter'
|
|
80
114
|
);
|
|
81
115
|
if (config.plugins.allow.length !== before) {
|
|
82
116
|
console.log(' Removed from plugins.allow');
|
|
@@ -84,6 +118,15 @@ try {
|
|
|
84
118
|
}
|
|
85
119
|
}
|
|
86
120
|
|
|
121
|
+
// Remove BlockRun ids from plugins.deny while preserving official clawrouter.
|
|
122
|
+
if (Array.isArray(config.plugins?.deny)) {
|
|
123
|
+
const before = config.plugins.deny.length;
|
|
124
|
+
config.plugins.deny = config.plugins.deny.filter(
|
|
125
|
+
p => p !== 'blockrun-clawrouter' && p !== 'ClawRouter' && p !== '@blockrun/clawrouter'
|
|
126
|
+
);
|
|
127
|
+
if (config.plugins.deny.length !== before) changed = true;
|
|
128
|
+
}
|
|
129
|
+
|
|
87
130
|
// Reset default model if it's blockrun/auto
|
|
88
131
|
if (config.agents?.defaults?.model?.primary === 'blockrun/auto') {
|
|
89
132
|
delete config.agents.defaults.model.primary;
|
|
@@ -108,7 +151,8 @@ try {
|
|
|
108
151
|
}
|
|
109
152
|
|
|
110
153
|
if (changed) {
|
|
111
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
154
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
155
|
+
fs.chmodSync(configPath, 0o600);
|
|
112
156
|
console.log(' Config cleaned');
|
|
113
157
|
} else {
|
|
114
158
|
console.log(' No changes needed');
|
|
@@ -143,7 +187,8 @@ for (const agentId of agents) {
|
|
|
143
187
|
const store = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
144
188
|
if (store.profiles?.['blockrun:default']) {
|
|
145
189
|
delete store.profiles['blockrun:default'];
|
|
146
|
-
fs.writeFileSync(authPath, JSON.stringify(store, null, 2));
|
|
190
|
+
fs.writeFileSync(authPath, JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
191
|
+
fs.chmodSync(authPath, 0o600);
|
|
147
192
|
console.log(' Removed blockrun auth from ' + agentId);
|
|
148
193
|
}
|
|
149
194
|
} catch {}
|