@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.
@@ -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.$(date +%s)"
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
- # 1b. Remove Crossmint/lobster extension
143
- # lobster.cash conflicts with /wallet command — remove it so ClawRouter owns /wallet.
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
- // Clean plugin entries (all case variants to prevent duplicate plugin warnings)
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(p => p !== 'clawrouter' && p !== 'ClawRouter' && p !== '@blockrun/clawrouter');
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(' Removed ClawRouter from plugins.allow (re-added after install)');
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
- CREDS_DIR="$HOME/.openclaw/credentials"
378
- CREDS_BACKUP=""
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
- [ -d "$stale" ] && rm -rf "$stale"
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 @blockrun/clawrouter || {
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 @blockrun/clawrouter
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/"* "$CREDS_DIR/"
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 -e "
676
- const os = require('os');
733
+ node - "$CONFIG_PATH" "$CONFIG_BACKUP" "$LEGACY_BLOCKRUN_INSTALL" <<'NODE'
677
734
  const fs = require('fs');
678
- const path = require('path');
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 plugins.allow exists and includes clawrouter
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('clawrouter') && !config.plugins.allow.includes('@blockrun/clawrouter')) {
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 [ -d "$stage_dir" ]; then
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."* "$HOME/.openclaw/blockrun/clawrouter.backup."*; do
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"
@@ -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
- rm -rf ~/.openclaw/extensions/clawrouter
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
- // Remove plugin entries (check all case variants — OpenClaw stores PascalCase)
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 {}