create-openclaw-bot 5.1.2 → 5.1.4

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/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # Changelog (English)
2
2
 
3
3
 
4
+ ## [5.1.4] — 2026-04-06
5
+
6
+ ### 🐞 Fix CLI Startup BOM Error & Improve Docker Timeout Patch
7
+
8
+ - **Fix CLI BOM**: Removed the unexpected byte order mark (BOM) `\uFEFF` at the beginning of `cli.js` which could cause the shebang `#!/usr/bin/env node` to fail resolving or cause SyntaxErrors in certain environments
9
+ - **Improve Docker Timeout Patching**: The backend timeout override injection (`300s`) during Docker build now defensively scans all `.js` files in the `openclaw/dist` directory rather than trying to fuzzy-find a specific `gateway-cli-*` hash. This ensures the patch succeeds across different OpenClaw backend builds without noisy console warnings
10
+
11
+
12
+ ## [5.1.3] — 2026-04-06
13
+
14
+ ### 🐜 Fix Docker Compose Variable Interpolation Leak
15
+
16
+ The previous base64 fix introduced a regression where the template literal `${Buffer.from(...)}` was mistakenly escaped in the composition script, causing the literal string to leak into `docker-compose.yml` instead of the actual base64 computed value.
17
+
18
+ - **Fix**: Precompute the base64 string completely in JavaScript (`const syncScriptBase64 = encodeBase64Utf8(syncScript)`) before injecting it into the compose template
19
+ - This guarantees the generated compose file receives the raw base64 string without any template interpolator conflicts
20
+ - Also cleans up testing logic validating these fixes
21
+
22
+
4
23
  ## [5.1.2] — 2026-04-06
5
24
 
6
25
  ### 🐛 Fix Shell Injection: Sync Script Now Uses Base64 Encoding
package/CHANGELOG.vi.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # Changelog (Tiếng Việt)
2
2
 
3
3
 
4
+ ## [5.1.4] — 2026-04-06
5
+
6
+ ### 🐞 Sửa lỗi BOM khởi động CLI & Tối ưu luồng vá Timeout trên Docker
7
+
8
+ - **Sửa file CLI (BOM)**: Xóa tự động chèn BOM (`\uFEFF`) ở đầu file `cli.js`. Ký tự thừa này vốn làm hỏng shebang `#!/usr/bin/env node` và gây `SyntaxError: Unexpected token` trong nhiều môi trường khi chạy npx
9
+ - **Cải thiện Docker Timeout Patch**: Quá trình can thiệp timeout (`300s`) trong lúc build Docker giờ chuyển sang scan quét toàn bộ các file `.js` trong thư mục `openclaw/dist` thay vì cố tìm file trùng hash `gateway-cli-*`. Giúp bản vá luôn áp dụng thành công trên các phiên bản backend khác biệt mà không in ra warning rác trên console
10
+
11
+
12
+ ## [5.1.3] — 2026-04-06
13
+
14
+ ### 🐜 Lỗi lọt biến nội suy vào giao diện Docker Compose
15
+
16
+ Bản vá lỗi base64 trước đó đã gây ra lỗi mới (regression) do dùng ngoặc `${Buffer.from(...)}` bên trong chuỗi string sinh ra docker-compose. Điều này làm lọt nguyên đoạn text nội suy vào `docker-compose.yml` thay vì sinh ra chuỗi base64 thật.
17
+
18
+ - **Fix**: Thực hiện tạo mã base64 hoàn chỉnh qua JavaScript (`const syncScriptBase64 = encodeBase64Utf8(syncScript)`) ngay từ ban đầu trước khi ghép chuỗi vào file compose
19
+ - Đảm bảo file compose tạo thành nhận chính xác mã base64 thuần túy mà không bị lọt biến môi trường
20
+ - Dọn dẹp lại script test tương ứng
21
+
22
+
4
23
  ## [5.1.2] — 2026-04-06
5
24
 
6
25
  ### 🐛 Fix Shell Injection: Sync Script Dùng Base64
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # 🦞 OpenClaw Setup
4
4
 
5
5
  <p align="center">
6
- <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.2-0EA5E9?style=for-the-badge" alt="Version 5.1.2" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.4-0EA5E9?style=for-the-badge" alt="Version 5.1.4" /></a>
7
7
  <a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
8
8
  <a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
9
9
  <a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
@@ -24,7 +24,7 @@ An interactive **CLI tool** and **Setup Wizard** to deploy your own free AI Bot
24
24
 
25
25
  ---
26
26
 
27
- ## 🆕 What's new in v5.1.2
27
+ ## 🆕 What's new in v5.1.4
28
28
 
29
29
  - 💻 **OS-First Setup** — Step 1 is now choosing your OS (Windows, macOS, Ubuntu, VPS). All scripts, configs, and instructions are generated to match.
30
30
  - 🧠 **Gemma 4 — 4 sizes** — `gemma4:e2b` (~4 GB), `gemma4:e4b` (~8 GB), `gemma4:26b` (~18 GB), `gemma4:31b` (~24 GB). Auto-pulled on first launch.
@@ -112,7 +112,7 @@ Run in your terminal → follow the interactive prompts → startup script is ge
112
112
  2. Open this repo as your workspace
113
113
  3. Paste into chat:
114
114
  ```
115
- Read SETUP.md and set up OpenClaw v5.1.2 for me.
115
+ Read SETUP.md and set up OpenClaw v5.1.4 for me.
116
116
  My bot token is X. Use 9Router (no API key).
117
117
  My project folder: <YOUR_PATH>
118
118
  ```
package/README.vi.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # 🦞 OpenClaw Setup
4
4
 
5
5
  <p align="center">
6
- <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.2-0EA5E9?style=for-the-badge" alt="Version 5.1.2" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.4-0EA5E9?style=for-the-badge" alt="Version 5.1.4" /></a>
7
7
  <a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
8
8
  <a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
9
9
  <a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
@@ -24,7 +24,7 @@ Công cụ **CLI tương tác** và **Setup Wizard** để tự triển khai Bot
24
24
 
25
25
  ---
26
26
 
27
- ## 🆕 Có gì mới trong v5.1.2
27
+ ## 🆕 Có gì mới trong v5.1.4
28
28
 
29
29
  - 💻 **OS-First Setup** — Bước đầu tiên bây giờ là chọn hệ điều hành của bạn (Windows, macOS, Ubuntu, VPS). Toàn bộ script, cấu hình và hướng dẫn được tạo ra phù hợp với lựa chọn đó.
30
30
  - 🧠 **Gemma 4 — 4 kích thước** — `gemma4:e2b` (~4 GB), `gemma4:e4b` (~8 GB), `gemma4:26b` (~18 GB), `gemma4:31b` (~24 GB). Tự pull về khi bot khởi động lần đầu.
@@ -112,7 +112,7 @@ Chạy lệnh trên trong Terminal → làm theo các prompt tương tác → sc
112
112
  2. Mở repo này làm workspace
113
113
  3. Paste vào chat:
114
114
  ```
115
- Read SETUP.md and set up OpenClaw v5.1.2 for me.
115
+ Read SETUP.md and set up OpenClaw v5.1.4 for me.
116
116
  My bot token is X. Use 9Router (no API key).
117
117
  My project folder: <THƯ_MỤC_CỦA_BẠN>
118
118
  ```
package/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { input, select, checkbox, confirm } from '@inquirer/prompts';
4
4
  import fs from 'fs-extra';
@@ -312,9 +312,9 @@ function installLatestOpenClaw({ isVi, osChoice }) {
312
312
  : '✅ openclaw is now on the latest version!'));
313
313
  }
314
314
 
315
- function build9RouterSmartRouteSyncScript(dbPath) {
316
- const safeDbPath = JSON.stringify(dbPath);
317
- return `const fs=require('fs');
315
+ function build9RouterSmartRouteSyncScript(dbPath) {
316
+ const safeDbPath = JSON.stringify(dbPath);
317
+ return `const fs=require('fs');
318
318
  const INTERVAL=30000;
319
319
  const p=${safeDbPath};
320
320
  const ROUTER='http://localhost:20128';
@@ -359,15 +359,32 @@ const sync = async () => {
359
359
  }
360
360
  } catch(e) { console.log('[sync-combo] Error:', e.message); }
361
361
  };
362
- setTimeout(sync, 5000);
363
- setInterval(sync, INTERVAL);`;
364
- }
365
-
366
- async function writeNative9RouterSyncScript(projectDir) {
367
- const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
368
- await fs.ensureDir(path.dirname(syncScriptPath));
369
- await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(getNative9RouterDataDir(), 'db.json')));
370
- return syncScriptPath;
362
+ setTimeout(sync, 5000);
363
+ setInterval(sync, INTERVAL);`;
364
+ }
365
+
366
+ function indentBlock(text, spaces) {
367
+ const prefix = ' '.repeat(spaces);
368
+ return String(text)
369
+ .split('\n')
370
+ .map((line) => `${prefix}${line}`)
371
+ .join('\n');
372
+ }
373
+
374
+ function build9RouterComposeEntrypointScript(syncScriptBase64) {
375
+ return [
376
+ 'npm install -g 9router',
377
+ `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
378
+ 'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
379
+ 'exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update'
380
+ ].join('\n');
381
+ }
382
+
383
+ async function writeNative9RouterSyncScript(projectDir) {
384
+ const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
385
+ await fs.ensureDir(path.dirname(syncScriptPath));
386
+ await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(getNative9RouterDataDir(), 'db.json')));
387
+ return syncScriptPath;
371
388
  }
372
389
 
373
390
  function extractFirstHttpUrl(text) {
@@ -1244,7 +1261,7 @@ async function main() {
1244
1261
  '# Fix chat.send dropping resolved agent timeout into reply pipeline.',
1245
1262
  '# Without this, Telegram/WebChat paths fall back to an internal 300s default even when',
1246
1263
  '# agents.defaults.timeoutSeconds is higher in config.',
1247
- `RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const file=(fs.readdirSync(dir).find(n=>/^gateway-cli-.*\\.js$/.test(n))||'');if(!file){console.warn('gateway cli dist file not found; skipping timeout patch');process.exit(0);}const p=path.join(dir,file);let s=fs.readFileSync(p,'utf8');const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';if(s.includes(to)){process.exit(0);}if(!s.includes(from)){console.warn('chat.send patch anchor not found; skipping timeout patch');process.exit(0);}s=s.replace(from,to);fs.writeFileSync(p,s);"`,
1264
+ `RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"`,
1248
1265
  '',
1249
1266
  'WORKDIR /root/.openclaw',
1250
1267
  '',
@@ -1260,10 +1277,12 @@ async function main() {
1260
1277
  const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-$/, '') || 'chat';
1261
1278
 
1262
1279
  // ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
1263
- // This script runs inside the 9Router container as a background loop.
1264
- // It reads the persisted 9Router DB directly so smart-route still works
1265
- // even when newer dashboard APIs require auth or change response shape.
1266
- const syncComboScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
1280
+ // This script runs inside the 9Router container as a background loop.
1281
+ // It reads the persisted 9Router DB directly so smart-route still works
1282
+ // even when newer dashboard APIs require auth or change response shape.
1283
+ const syncComboScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
1284
+ const syncComboScriptBase64 = Buffer.from(syncComboScript).toString('base64');
1285
+ const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncComboScriptBase64);
1267
1286
 
1268
1287
  // ─── Resolve primary model ───────────────────────────────────────────────────
1269
1288
  let modelsPrimary;
@@ -1307,17 +1326,14 @@ ${dependsOn}${extraHosts} ports:
1307
1326
  image: node:22-slim
1308
1327
  container_name: 9router-multibot
1309
1328
  restart: always
1310
- entrypoint:
1311
- - /bin/sh
1312
- - -c
1313
- - |
1314
- npm install -g 9router
1315
- node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('\${Buffer.from(syncComboScript).toString('base64')}','base64').toString())"
1316
- node /tmp/sync.js > /tmp/sync.log 2>&1 &
1317
- exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
1318
- environment:
1319
- - PORT=20128
1320
- - HOSTNAME=0.0.0.0
1329
+ entrypoint:
1330
+ - /bin/sh
1331
+ - -c
1332
+ - |
1333
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
1334
+ environment:
1335
+ - PORT=20128
1336
+ - HOSTNAME=0.0.0.0
1321
1337
  - CI=true
1322
1338
  volumes:
1323
1339
  - 9router-data:/root/.9router
@@ -1402,17 +1418,14 @@ ${hasBrowserDesktop ? ` extra_hosts:\n - "host.docker.internal:host-gate
1402
1418
  image: node:22-slim
1403
1419
  container_name: 9router-${agentId}
1404
1420
  restart: always
1405
- entrypoint:
1406
- - /bin/sh
1407
- - -c
1408
- - |
1409
- npm install -g 9router
1410
- node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('\${Buffer.from(syncComboScript).toString('base64')}','base64').toString())"
1411
- node /tmp/sync.js > /tmp/sync.log 2>&1 &
1412
- exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
1413
- environment:
1414
- - PORT=20128
1415
- - HOSTNAME=0.0.0.0
1421
+ entrypoint:
1422
+ - /bin/sh
1423
+ - -c
1424
+ - |
1425
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
1426
+ environment:
1427
+ - PORT=20128
1428
+ - HOSTNAME=0.0.0.0
1416
1429
  - CI=true
1417
1430
  volumes:
1418
1431
  - 9router-data:/root/.9router
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.1.2",
3
+ "version": "5.1.4",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/setup.js CHANGED
@@ -1758,7 +1758,19 @@ model:
1758
1758
  // 3. Dockerfile
1759
1759
  const allPlugins = [];
1760
1760
  if (ch.pluginInstall) allPlugins.push(ch.pluginInstall);
1761
- state.config.plugins.forEach((pid) => {
1761
+ const encodeBase64Utf8 = (value) => btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
1762
+ const indentBlock = (text, spaces) => {
1763
+ const prefix = ' '.repeat(spaces);
1764
+ return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
1765
+ };
1766
+ const build9RouterComposeEntrypointScript = (syncScriptBase64) => [
1767
+ 'npm install -g 9router',
1768
+ `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
1769
+ 'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
1770
+ 'exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update'
1771
+ ].join('\n');
1772
+
1773
+ state.config.plugins.forEach((pid) => {
1762
1774
  const plug = PLUGINS.find((p) => p.id === pid);
1763
1775
  if (plug) allPlugins.push(plug.package);
1764
1776
  });
@@ -1815,7 +1827,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
1815
1827
 
1816
1828
  ARG CACHEBUST=${Date.now()}
1817
1829
  RUN npm install -g openclaw@latest${skillLines}${browserInstallLines}
1818
- RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const file=(fs.readdirSync(dir).find(n=>/^gateway-cli-.*\\.js$/.test(n))||'');if(!file){console.warn('gateway cli dist file not found; skipping timeout patch');process.exit(0);}const p=path.join(dir,file);let s=fs.readFileSync(p,'utf8');const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';if(s.includes(to)){process.exit(0);}if(!s.includes(from)){console.warn('chat.send patch anchor not found; skipping timeout patch');process.exit(0);}s=s.replace(from,to);fs.writeFileSync(p,s);}"
1830
+ RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"
1819
1831
  WORKDIR /root/.openclaw
1820
1832
 
1821
1833
  EXPOSE 18791
@@ -1834,7 +1846,7 @@ ${finalCmd}`;
1834
1846
  // Background loop inside 9Router container every 30s.
1835
1847
  // Read providerConnections directly from db.json so smart-route survives
1836
1848
  // dashboard auth/response changes in newer 9Router builds.
1837
- const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
1849
+ const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
1838
1850
  const PM={codex:['cx/gpt-5.4','cx/gpt-5.3-codex','cx/gpt-5.3-codex-high','cx/gpt-5.2-codex','cx/gpt-5.2','cx/gpt-5.1-codex-max','cx/gpt-5.1-codex','cx/gpt-5.1','cx/gpt-5-codex'],'claude-code':['cc/claude-opus-4-6','cc/claude-sonnet-4-6','cc/claude-opus-4-5-20251101','cc/claude-sonnet-4-5-20250929','cc/claude-haiku-4-5-20251001'],github:['gh/gpt-5.4','gh/gpt-5.3-codex','gh/gpt-5.2-codex','gh/gpt-5.2','gh/gpt-5.1-codex-max','gh/gpt-5.1-codex','gh/gpt-5.1','gh/gpt-5','gh/gpt-4.1','gh/gpt-4o','gh/claude-opus-4.6','gh/claude-sonnet-4.6','gh/claude-sonnet-4.5','gh/claude-opus-4.5','gh/claude-haiku-4.5','gh/gemini-3-pro-preview','gh/gemini-3-flash-preview','gh/gemini-2.5-pro'],cursor:['cu/default','cu/claude-4.6-opus-max','cu/claude-4.5-opus-high-thinking','cu/claude-4.5-sonnet-thinking','cu/claude-4.5-sonnet','cu/gpt-5.3-codex','cu/gpt-5.2-codex','cu/gemini-3-flash-preview'],kilo:['kc/anthropic/claude-sonnet-4-20250514','kc/anthropic/claude-opus-4-20250514','kc/google/gemini-2.5-pro','kc/google/gemini-2.5-flash','kc/openai/gpt-4.1','kc/deepseek/deepseek-chat'],cline:['cl/anthropic/claude-sonnet-4.6','cl/anthropic/claude-opus-4.6','cl/openai/gpt-5.3-codex','cl/openai/gpt-5.4','cl/google/gemini-3.1-pro-preview'],'gemini-cli':['gc/gemini-3-flash-preview','gc/gemini-3-pro-preview'],iflow:['if/qwen3-coder-plus','if/kimi-k2','if/kimi-k2-thinking','if/glm-4.7','if/deepseek-r1','if/deepseek-v3.2','if/deepseek-v3','if/qwen3-max','if/qwen3-235b','if/iflow-rome-30ba3b'],qwen:['qw/qwen3-coder-plus','qw/qwen3-coder-flash','qw/vision-model','qw/coder-model'],kiro:['kr/claude-sonnet-4.5','kr/claude-haiku-4.5','kr/deepseek-3.2','kr/deepseek-3.1','kr/qwen3-coder-next'],ollama:['ollama/gemma4:e2b','ollama/gemma4:e4b','ollama/gemma4:26b','ollama/gemma4:31b','ollama/qwen3.5','ollama/kimi-k2.5','ollama/glm-5','ollama/glm-4.7-flash','ollama/minimax-m2.5','ollama/gpt-oss:120b'],'kimi-coding':['kmc/kimi-k2.5','kmc/kimi-k2.5-thinking','kmc/kimi-latest'],glm:['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],'glm-cn':['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],minimax:['minimax/MiniMax-M2.7','minimax/MiniMax-M2.5','minimax/MiniMax-M2.1'],kimi:['kimi/kimi-k2.5','kimi/kimi-k2.5-thinking','kimi/kimi-latest'],deepseek:['deepseek/deepseek-chat','deepseek/deepseek-reasoner'],xai:['xai/grok-4','xai/grok-4-fast-reasoning','xai/grok-code-fast-1'],mistral:['mistral/mistral-large-latest','mistral/codestral-latest'],groq:['groq/llama-3.3-70b-versatile','groq/openai/gpt-oss-120b'],cerebras:['cerebras/gpt-oss-120b'],alicode:['alicode/qwen3.5-plus','alicode/qwen3-coder-plus'],openai:['openai/gpt-4o','openai/gpt-4.1'],anthropic:['anthropic/claude-sonnet-4','anthropic/claude-haiku-3.5'],gemini:['gemini/gemini-2.5-flash','gemini/gemini-2.5-pro']};
1839
1851
  console.log('[sync-combo] 9Router sync loop started...');
1840
1852
  const sync = async () => {
@@ -1884,9 +1896,11 @@ const sync = async () => {
1884
1896
  console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
1885
1897
  }
1886
1898
  } catch (e) { }
1887
- };
1888
- setTimeout(sync, 5000);
1889
- setInterval(sync, INTERVAL);`;
1899
+ };
1900
+ setTimeout(sync, 5000);
1901
+ setInterval(sync, INTERVAL);`;
1902
+ const syncScriptBase64 = encodeBase64Utf8(syncScript);
1903
+ const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64);
1890
1904
 
1891
1905
  let compose;
1892
1906
  if (isMultiBotWizard) {
@@ -1915,17 +1929,14 @@ ${dependsOn}${extraHosts} volumes:
1915
1929
  image: node:22-slim
1916
1930
  container_name: 9router-multibot
1917
1931
  restart: always
1918
- entrypoint:
1919
- - /bin/sh
1920
- - -c
1921
- - |
1922
- npm install -g 9router
1923
- node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('\${Buffer.from(syncScript).toString('base64')}','base64').toString())"
1924
- node /tmp/sync.js > /tmp/sync.log 2>&1 &
1925
- exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
1926
- environment:
1927
- - PORT=20128
1928
- - HOSTNAME=0.0.0.0
1932
+ entrypoint:
1933
+ - /bin/sh
1934
+ - -c
1935
+ - |
1936
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
1937
+ environment:
1938
+ - PORT=20128
1939
+ - HOSTNAME=0.0.0.0
1929
1940
  - CI=true
1930
1941
  volumes:
1931
1942
  - 9router-data:/root/.9router
@@ -2011,17 +2022,14 @@ ${extraHostsBlock}
2011
2022
  image: node:22-slim
2012
2023
  container_name: 9router
2013
2024
  restart: always
2014
- entrypoint:
2015
- - /bin/sh
2016
- - -c
2017
- - |
2018
- npm install -g 9router
2019
- node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('\${Buffer.from(syncScript).toString('base64')}','base64').toString())"
2020
- node /tmp/sync.js > /tmp/sync.log 2>&1 &
2021
- exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
2022
- environment:
2023
- - PORT=20128
2024
- - HOSTNAME=0.0.0.0
2025
+ entrypoint:
2026
+ - /bin/sh
2027
+ - -c
2028
+ - |
2029
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
2030
+ environment:
2031
+ - PORT=20128
2032
+ - HOSTNAME=0.0.0.0
2025
2033
  - CI=true
2026
2034
  volumes:
2027
2035
  - 9router-data:/root/.9router
@@ -5,6 +5,7 @@ const root = process.cwd();
5
5
  const cli = fs.readFileSync(path.join(root, 'cli.js'), 'utf8');
6
6
  const setup = fs.readFileSync(path.join(root, 'setup.js'), 'utf8');
7
7
  const indexHtml = fs.readFileSync(path.join(root, 'index.html'), 'utf8');
8
+ const cliBytes = fs.readFileSync(path.join(root, 'cli.js'));
8
9
 
9
10
  function expect(condition, message) {
10
11
  if (!condition) {
@@ -32,6 +33,11 @@ checks.push(() => expectMatch(
32
33
  'Ubuntu/VPS must default to native deploy mode'
33
34
  ));
34
35
 
36
+ checks.push(() => expect(
37
+ !(cliBytes[0] === 0xef && cliBytes[1] === 0xbb && cliBytes[2] === 0xbf),
38
+ 'CLI entrypoint must not include a UTF-8 BOM before the shebang'
39
+ ));
40
+
35
41
  checks.push(() => expectMatch(
36
42
  cli,
37
43
  /if \(deployMode === 'docker' && !isDockerInstalled\(\)\)/,
@@ -86,9 +92,12 @@ checks.push(() => expectMatch(
86
92
  'CLI fast-test mode must be able to reuse an existing 9Router install'
87
93
  ));
88
94
 
89
- checks.push(() => expectMatch(
90
- cli,
91
- /function build9RouterSmartRouteSyncScript\(dbPath\) \{[\s\S]*safeDbPath[\s\S]*providerConnections[\s\S]*smart-route[\s\S]*async function writeNative9RouterSyncScript\(projectDir\) \{[\s\S]*getNative9RouterDataDir\(\), 'db\.json'/s,
95
+ checks.push(() => expect(
96
+ cli.includes('function build9RouterSmartRouteSyncScript(dbPath) {')
97
+ && cli.includes('const safeDbPath = JSON.stringify(dbPath);')
98
+ && cli.includes("const ROUTER='http://localhost:20128';")
99
+ && cli.includes("fetch(ROUTER + '/api/providers')")
100
+ && cli.includes("build9RouterSmartRouteSyncScript(path.join(getNative9RouterDataDir(), 'db.json'))"),
92
101
  'Native 9Router flow must write a smart-route sync script based on the platform-specific 9Router data directory'
93
102
  ));
94
103
 
@@ -100,8 +109,9 @@ checks.push(() => expectMatch(
100
109
 
101
110
  checks.push(() => expect(
102
111
  cli.includes("Removed smart-route (no active providers)")
103
- && cli.includes("if(!a.length){removeSmartRoute();return;}")
104
- && cli.includes("if(!m.length){removeSmartRoute();return;}"),
112
+ && cli.includes("if (!a.length) {")
113
+ && cli.includes("if (!m.length) {")
114
+ && cli.includes("removeSmartRoute();"),
105
115
  '9Router sync logic in CLI must remove stale smart-route combos when providers are disabled'
106
116
  ));
107
117
 
@@ -282,20 +292,21 @@ checks.push(() => expectMatch(
282
292
 
283
293
  checks.push(() => expectMatch(
284
294
  setup,
285
- /function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*npm root -g[\s\S]*Join-Path \$npmRoot[\s\S]*Start-Process -WindowStyle Hidden -FilePath ''node\.exe''[\s\S]*9router-smart-route-sync\.js/s,
295
+ /function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*start "9Router" cmd \/k "9router -n -t -l -H 0\.0\.0\.0 -p 20128 --skip-update"[\s\S]*9router-smart-route-sync\.js/s,
286
296
  'Native script generation must install and start a standalone 9Router dashboard on port 20128'
287
297
  ));
288
298
 
289
299
  checks.push(() => expectMatch(
290
300
  setup,
291
- /function native9RouterSyncScriptContent\(\) \{[\s\S]*process\.platform==='win32'[\s\S]*AppData[\s\S]*Roaming[\s\S]*providerConnections[\s\S]*smart-route/s,
292
- 'Native script generation must embed a 9Router smart-route sync script with the correct Windows data directory'
301
+ /function native9RouterSyncScriptContent\(\) \{[\s\S]*path\.join\(process\.env\.HOME\|\|process\.env\.USERPROFILE\|\|'\.'\,\'\.9router\'\,\'db\.json\'\)[\s\S]*providerConnections[\s\S]*smart-route/s,
302
+ 'Native script generation must embed a 9Router smart-route sync script'
293
303
  ));
294
304
 
295
305
  checks.push(() => expect(
296
306
  setup.includes("Removed smart-route (no active providers)")
297
- && setup.includes("if(!a.length){removeSmartRoute();return;}")
298
- && setup.includes("if(!m.length){removeSmartRoute();return;}"),
307
+ && setup.includes("if (!a.length) {")
308
+ && setup.includes("if (!m.length) {")
309
+ && setup.includes("removeSmartRoute();"),
299
310
  '9Router sync logic in setup.js must remove stale smart-route combos when providers are disabled'
300
311
  ));
301
312
 
@@ -307,14 +318,24 @@ checks.push(() => expectMatch(
307
318
 
308
319
  checks.push(() => expectMatch(
309
320
  cli,
310
- /readdirSync\(dir\)\.find\(n=>\/\^gateway-cli-.*\\\\\.js\$\/\.test\(n\)\)[\s\S]*skipping timeout patch/,
311
- 'Dockerfile patching in CLI must resolve gateway-cli dist files dynamically instead of hardcoding one hash'
321
+ /const files=fs\.readdirSync\(dir\)\.filter\(n=>\/\\\\\.js\$\/\.test\(n\)\)[\s\S]*let patched=0[\s\S]*if\(!patched\)\{process\.exit\(0\);\}/,
322
+ 'Dockerfile patching in CLI must scan all OpenClaw dist JS files and silently skip when no timeout patch anchor exists'
323
+ ));
324
+
325
+ checks.push(() => expect(
326
+ !cli.includes("Buffer.from('\\${Buffer.from(syncComboScript).toString('base64')}','base64')"),
327
+ 'CLI must precompute the 9Router sync script base64 instead of leaking Docker Compose interpolation markers'
312
328
  ));
313
329
 
314
330
  checks.push(() => expectMatch(
315
331
  setup,
316
- /readdirSync\(dir\)\.find\(n=>\/\^gateway-cli-.*\\\\\.js\$\/\.test\(n\)\)[\s\S]*skipping timeout patch/,
317
- 'Dockerfile patching in setup.js must resolve gateway-cli dist files dynamically instead of hardcoding one hash'
332
+ /const files=fs\.readdirSync\(dir\)\.filter\(n=>\/\\\\\.js\$\/\.test\(n\)\)[\s\S]*let patched=0[\s\S]*if\(!patched\)\{process\.exit\(0\);\}/,
333
+ 'Dockerfile patching in setup.js must scan all OpenClaw dist JS files and silently skip when no timeout patch anchor exists'
334
+ ));
335
+
336
+ checks.push(() => expect(
337
+ !setup.includes("Buffer.from('\\${Buffer.from(syncScript).toString('base64')}','base64')"),
338
+ 'Wizard compose generation must precompute the 9Router sync script base64 instead of leaking Docker Compose interpolation markers'
318
339
  ));
319
340
 
320
341
  checks.push(() => expectMatch(