create-openclaw-bot 5.1.9 → 5.1.11

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,20 @@
1
1
  # Changelog (English)
2
2
 
3
3
 
4
+ ## [5.1.11] — 2026-04-07
5
+
6
+ ### 🌟 Zalo Personal DM Policy
7
+
8
+ - **Open Zalo Inboxes**: The default `dmPolicy` for Zalo Personal deployments has been changed from `pairing` to `open`. This allows any user on the Zalo network to interact with the AI assistant immediately without requiring explicit device pairing approvals natively.
9
+
10
+
11
+ ## [5.1.10] — 2026-04-07
12
+
13
+ ### 🌟 Native UI Auto-Approve Bypasser
14
+
15
+ - **Native PM2 Auto-Approve Loop**: The strict `pairing required` security feature mandates that all users manually execute an approval command in their terminal for new web dashboard authentications. While Docker deployments already included an automated bypass, the Native setup did not. This release introduces a dedicated `auto-approve` PM2 background daemon that infinitely polls and accepts new device keys, delivering a frictionless, zero-touch login experience identical to Docker deployments.
16
+
17
+
4
18
  ## [5.1.9] — 2026-04-07
5
19
 
6
20
  ### 🌟 Strict Schema Fix & WebCrypto UX Improvement
package/CHANGELOG.vi.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Changelog (Tiếng Việt)
2
2
 
3
3
 
4
+ ## [5.1.11] — 2026-04-07
5
+
6
+ ### 🌟 Đổi Chính Sách Bảo Mật Zalo Personal
7
+
8
+ - **Thả Ga Inbox Zalo Cầm Tay**: Lược bỏ rào cản duyệt bảo mật của Zalo Personal. Thông số `dmPolicy` trên cài đặt Zalo cá nhân đã được chuyển mặc định từ `pairing` sang `open`. Bây giờ bất cứ ai trên mạng lưới Zalo nhắn tin vào tài khoản của Bot đều sẽ được AI tự động tiếp đón ngay lập tức thay vì bị chặn lại chờ bạn duyệt lệnh kết nối E2E!
9
+
10
+
11
+ ## [5.1.10] — 2026-04-07
12
+
13
+ ### 🌟 Tự động Auto-Approve Thiết Bị cho Native VPS
14
+
15
+ - **Bỏ Nhập Lệnh Thủ Công Trải Nghiệm PM2**: Cảnh báo `pairing required` (chờ duyệt thiết bị ghép nối E2E) trên giao diện Web buộc người dùng phải gõ lệnh đồng ý dưới Terminal. Ở luồng Docker, tính năng này đã được vô hiệu hóa bằng một đoạn script chạy ngầm tự gật đầu. Nhưng ở Native thì chưa! Phiên bản này chính thức nhúng thêm 1 tiến trình PM2 `auto-approve` siêu nhẹ chạy kẹp với các lệnh chính, giúp tự động gật đầu phê duyệt kết nối web mỗi 5 giây. Đảm bảo trải nghiệm "Click là vào" trên Native VPS mượt mà y hệt Docker!
16
+
17
+
4
18
  ## [5.1.9] — 2026-04-07
5
19
 
6
20
  ### 🌟 Trả lại Schema Chuẩn & Cải thiện UX WebCrypto
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.9-0EA5E9?style=for-the-badge" alt="Version 5.1.9" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.11-0EA5E9?style=for-the-badge" alt="Version 5.1.11" /></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.9
27
+ ## 🆕 What's new in v5.1.11
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.9 for me.
115
+ Read SETUP.md and set up OpenClaw v5.1.11 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.9-0EA5E9?style=for-the-badge" alt="Version 5.1.9" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.11-0EA5E9?style=for-the-badge" alt="Version 5.1.11" /></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.9
27
+ ## 🆕 Có gì mới trong v5.1.11
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.9 for me.
115
+ Read SETUP.md and set up OpenClaw v5.1.11 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,11 +1,11 @@
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';
5
5
  import path from 'path';
6
6
  import os from 'os';
7
7
  import chalk from 'chalk';
8
- import { spawn, execSync, execFileSync } from 'child_process';
8
+ import { spawn, execSync, execFileSync } from 'child_process';
9
9
  const TELEGRAM_RELAY_PLUGIN_ID = 'openclaw-telegram-multibot-relay';
10
10
  // Use plain npm package name — clawhub: protocol not supported in all OpenClaw versions
11
11
  const TELEGRAM_RELAY_PLUGIN_SPEC = TELEGRAM_RELAY_PLUGIN_ID;
@@ -95,7 +95,7 @@ function quotePowerShellSingle(value) {
95
95
  return `'${String(value).replace(/'/g, "''")}'`;
96
96
  }
97
97
 
98
- function resolveWindowsCommand(command) {
98
+ function resolveWindowsCommand(command) {
99
99
  try {
100
100
  const output = execSync(`where.exe ${command}`, {
101
101
  stdio: ['ignore', 'pipe', 'ignore'],
@@ -145,88 +145,88 @@ function spawnBackgroundProcess(command, args, options = {}) {
145
145
  });
146
146
  }
147
147
 
148
- function resolveNative9RouterDesktopLaunch() {
149
- return {
150
- command: process.execPath,
151
- args: [path.join(getGlobalNpmRoot(), '9router', 'app', 'server.js')],
152
- env: {
153
- PORT: '20128',
154
- HOSTNAME: '0.0.0.0'
155
- }
156
- };
157
- }
158
-
159
- function getNative9RouterDataDir() {
160
- if (process.platform === 'win32') {
161
- return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), '9router');
162
- }
163
-
164
- return path.join(os.homedir(), '.9router');
165
- }
166
-
167
- function getGatewayAllowedOrigins(port) {
168
- const normalizedPort = Number(port) || 18791;
169
- const origins = new Set([
170
- `http://localhost:${normalizedPort}`,
171
- `http://127.0.0.1:${normalizedPort}`,
172
- `http://0.0.0.0:${normalizedPort}`
173
- ]);
174
-
175
- for (const entries of Object.values(os.networkInterfaces() || {})) {
176
- for (const entry of entries || []) {
177
- if (!entry || entry.internal || entry.family !== 'IPv4' || !entry.address) {
178
- continue;
179
- }
180
- origins.add(`http://${entry.address}:${normalizedPort}`);
181
- }
182
- }
183
-
184
- return Array.from(origins);
185
- }
186
-
187
- function getReachableDashboardHosts(port) {
188
- const normalizedPort = Number(port) || 18791;
189
- const hosts = [];
190
- const pushHost = (host) => {
191
- if (!host) return;
192
- const url = `http://${host}:${normalizedPort}`;
193
- if (!hosts.includes(url)) {
194
- hosts.push(url);
195
- }
196
- };
197
-
198
- pushHost('127.0.0.1');
199
- pushHost('localhost');
200
-
201
- for (const entries of Object.values(os.networkInterfaces() || {})) {
202
- for (const entry of entries || []) {
203
- if (!entry || entry.internal || entry.family !== 'IPv4' || !entry.address) {
204
- continue;
205
- }
206
- pushHost(entry.address);
207
- }
208
- }
209
-
210
- return hosts;
211
- }
212
-
213
- function rewriteDashboardUrlHost(urlText, fallbackPort, targetBaseUrl) {
214
- try {
215
- const target = new URL(targetBaseUrl);
216
- const parsed = new URL(urlText);
217
- parsed.protocol = target.protocol;
218
- parsed.hostname = target.hostname;
219
- parsed.port = target.port || String(fallbackPort || '');
220
- return parsed.toString();
221
- } catch {
222
- return `${targetBaseUrl}${String(urlText || '').startsWith('/') ? '' : '/'}${String(urlText || '')}`;
223
- }
224
- }
225
-
226
- async function waitFor9RouterApiReady({ port = 20128, timeoutMs = 15000 } = {}) {
227
- const deadline = Date.now() + timeoutMs;
228
- const candidates = [
229
- `http://127.0.0.1:${port}/api/settings/require-login`,
148
+ function resolveNative9RouterDesktopLaunch() {
149
+ return {
150
+ command: process.execPath,
151
+ args: [path.join(getGlobalNpmRoot(), '9router', 'app', 'server.js')],
152
+ env: {
153
+ PORT: '20128',
154
+ HOSTNAME: '0.0.0.0'
155
+ }
156
+ };
157
+ }
158
+
159
+ function getNative9RouterDataDir() {
160
+ if (process.platform === 'win32') {
161
+ return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), '9router');
162
+ }
163
+
164
+ return path.join(os.homedir(), '.9router');
165
+ }
166
+
167
+ function getGatewayAllowedOrigins(port) {
168
+ const normalizedPort = Number(port) || 18791;
169
+ const origins = new Set([
170
+ `http://localhost:${normalizedPort}`,
171
+ `http://127.0.0.1:${normalizedPort}`,
172
+ `http://0.0.0.0:${normalizedPort}`
173
+ ]);
174
+
175
+ for (const entries of Object.values(os.networkInterfaces() || {})) {
176
+ for (const entry of entries || []) {
177
+ if (!entry || entry.internal || entry.family !== 'IPv4' || !entry.address) {
178
+ continue;
179
+ }
180
+ origins.add(`http://${entry.address}:${normalizedPort}`);
181
+ }
182
+ }
183
+
184
+ return Array.from(origins);
185
+ }
186
+
187
+ function getReachableDashboardHosts(port) {
188
+ const normalizedPort = Number(port) || 18791;
189
+ const hosts = [];
190
+ const pushHost = (host) => {
191
+ if (!host) return;
192
+ const url = `http://${host}:${normalizedPort}`;
193
+ if (!hosts.includes(url)) {
194
+ hosts.push(url);
195
+ }
196
+ };
197
+
198
+ pushHost('127.0.0.1');
199
+ pushHost('localhost');
200
+
201
+ for (const entries of Object.values(os.networkInterfaces() || {})) {
202
+ for (const entry of entries || []) {
203
+ if (!entry || entry.internal || entry.family !== 'IPv4' || !entry.address) {
204
+ continue;
205
+ }
206
+ pushHost(entry.address);
207
+ }
208
+ }
209
+
210
+ return hosts;
211
+ }
212
+
213
+ function rewriteDashboardUrlHost(urlText, fallbackPort, targetBaseUrl) {
214
+ try {
215
+ const target = new URL(targetBaseUrl);
216
+ const parsed = new URL(urlText);
217
+ parsed.protocol = target.protocol;
218
+ parsed.hostname = target.hostname;
219
+ parsed.port = target.port || String(fallbackPort || '');
220
+ return parsed.toString();
221
+ } catch {
222
+ return `${targetBaseUrl}${String(urlText || '').startsWith('/') ? '' : '/'}${String(urlText || '')}`;
223
+ }
224
+ }
225
+
226
+ async function waitFor9RouterApiReady({ port = 20128, timeoutMs = 15000 } = {}) {
227
+ const deadline = Date.now() + timeoutMs;
228
+ const candidates = [
229
+ `http://127.0.0.1:${port}/api/settings/require-login`,
230
230
  `http://127.0.0.1:${port}/api/version`
231
231
  ];
232
232
 
@@ -350,9 +350,9 @@ function installLatestOpenClaw({ isVi, osChoice }) {
350
350
  : '✅ openclaw is now on the latest version!'));
351
351
  }
352
352
 
353
- function build9RouterSmartRouteSyncScript(dbPath) {
354
- const safeDbPath = JSON.stringify(dbPath);
355
- return `const fs=require('fs');
353
+ function build9RouterSmartRouteSyncScript(dbPath) {
354
+ const safeDbPath = JSON.stringify(dbPath);
355
+ return `const fs=require('fs');
356
356
  const INTERVAL=30000;
357
357
  const p=${safeDbPath};
358
358
  const ROUTER='http://localhost:20128';
@@ -397,65 +397,65 @@ const sync = async () => {
397
397
  }
398
398
  } catch(e) { console.log('[sync-combo] Error:', e.message); }
399
399
  };
400
- setTimeout(sync, 5000);
401
- setInterval(sync, INTERVAL);`;
402
- }
403
-
404
- function resolveCommandOnPath(command) {
405
- if (process.platform === 'win32') {
406
- return resolveWindowsCommand(command);
407
- }
408
-
409
- try {
410
- return execSync(`command -v ${command}`, {
411
- stdio: ['ignore', 'pipe', 'ignore'],
412
- encoding: 'utf8',
413
- shell: true,
414
- env: process.env
415
- }).trim() || command;
416
- } catch {
417
- return command;
418
- }
419
- }
420
-
421
- function getGlobalNpmRoot() {
422
- try {
423
- return execSync('npm root -g', {
424
- stdio: ['ignore', 'pipe', 'ignore'],
425
- encoding: 'utf8',
426
- shell: true,
427
- env: process.env
428
- }).trim();
429
- } catch {
430
- if (process.platform === 'win32') {
431
- return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
432
- }
433
- return path.join(os.homedir(), '.local', 'lib', 'node_modules');
434
- }
435
- }
436
-
437
- function indentBlock(text, spaces) {
438
- const prefix = ' '.repeat(spaces);
439
- return String(text)
440
- .split('\n')
441
- .map((line) => `${prefix}${line}`)
442
- .join('\n');
443
- }
444
-
445
- function build9RouterComposeEntrypointScript(syncScriptBase64) {
446
- return [
447
- 'npm install -g 9router',
448
- `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
449
- 'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
450
- 'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
451
- ].join('\n');
452
- }
453
-
454
- async function writeNative9RouterSyncScript(projectDir) {
455
- const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
456
- await fs.ensureDir(path.dirname(syncScriptPath));
457
- await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(getNative9RouterDataDir(), 'db.json')));
458
- return syncScriptPath;
400
+ setTimeout(sync, 5000);
401
+ setInterval(sync, INTERVAL);`;
402
+ }
403
+
404
+ function resolveCommandOnPath(command) {
405
+ if (process.platform === 'win32') {
406
+ return resolveWindowsCommand(command);
407
+ }
408
+
409
+ try {
410
+ return execSync(`command -v ${command}`, {
411
+ stdio: ['ignore', 'pipe', 'ignore'],
412
+ encoding: 'utf8',
413
+ shell: true,
414
+ env: process.env
415
+ }).trim() || command;
416
+ } catch {
417
+ return command;
418
+ }
419
+ }
420
+
421
+ function getGlobalNpmRoot() {
422
+ try {
423
+ return execSync('npm root -g', {
424
+ stdio: ['ignore', 'pipe', 'ignore'],
425
+ encoding: 'utf8',
426
+ shell: true,
427
+ env: process.env
428
+ }).trim();
429
+ } catch {
430
+ if (process.platform === 'win32') {
431
+ return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
432
+ }
433
+ return path.join(os.homedir(), '.local', 'lib', 'node_modules');
434
+ }
435
+ }
436
+
437
+ function indentBlock(text, spaces) {
438
+ const prefix = ' '.repeat(spaces);
439
+ return String(text)
440
+ .split('\n')
441
+ .map((line) => `${prefix}${line}`)
442
+ .join('\n');
443
+ }
444
+
445
+ function build9RouterComposeEntrypointScript(syncScriptBase64) {
446
+ return [
447
+ 'npm install -g 9router',
448
+ `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
449
+ 'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
450
+ 'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
451
+ ].join('\n');
452
+ }
453
+
454
+ async function writeNative9RouterSyncScript(projectDir) {
455
+ const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
456
+ await fs.ensureDir(path.dirname(syncScriptPath));
457
+ await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(getNative9RouterDataDir(), 'db.json')));
458
+ return syncScriptPath;
459
459
  }
460
460
 
461
461
  function extractFirstHttpUrl(text) {
@@ -480,68 +480,68 @@ function getTokenizedDashboardUrl(projectDir) {
480
480
  }
481
481
  }
482
482
 
483
- function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gatewayPort = 18791 }) {
484
- const gatewayUrls = getReachableDashboardHosts(gatewayPort);
485
- const dashboardUrl = gatewayUrls[0] || `http://127.0.0.1:${gatewayPort}`;
486
- const tokenizedUrl = getTokenizedDashboardUrl(projectDir);
487
-
488
- console.log(chalk.yellow(`\n🧭 ${isVi ? 'Dashboard OpenClaw:' : 'OpenClaw Dashboard:'} ${dashboardUrl}`));
489
- if (gatewayUrls.length > 1) {
490
- console.log(chalk.gray(isVi
491
- ? ` → Link khac co the mo duoc: ${gatewayUrls.slice(1).join(' , ')}`
492
- : ` → Other reachable URLs: ${gatewayUrls.slice(1).join(' , ')}`));
493
- }
494
-
495
- if (tokenizedUrl) {
496
- const tokenizedUrls = gatewayUrls.map((baseUrl) => rewriteDashboardUrlHost(tokenizedUrl, gatewayPort, baseUrl));
497
- console.log(chalk.green(isVi
498
- ? ` → Mở link đã kèm token: ${tokenizedUrls[0]}`
499
- : ` → Open the tokenized link directly: ${tokenizedUrls[0]}`));
500
- if (tokenizedUrls.length > 1) {
501
- console.log(chalk.gray(isVi
502
- ? ` → Ban mo tu may khac/WSL thi thu: ${tokenizedUrls.slice(1).join(' , ')}`
503
- : ` → If you are opening from another machine/WSL, try: ${tokenizedUrls.slice(1).join(' , ')}`));
504
- }
505
-
506
- const externalHosts = gatewayUrls.filter(u => !u.includes('localhost') && !u.includes('127.0.0.1')).map(u => {
507
- try { return new URL(u).hostname; } catch { return u; }
508
- });
509
-
510
- if (externalHosts.length > 0) {
511
- const mainIp = externalHosts[0];
512
- const username = process.env.USER || 'user';
513
- console.log(chalk.cyan(`\n🔐 ${isVi ? 'Bảo mật WebCrypto (Sửa Lỗi 1008)' : 'WebCrypto Security (Fix Error 1008)'}`));
514
- console.log(chalk.gray(isVi
515
- ? ` Nếu dùng các link IP ngoài (như ${mainIp}) bị lỗi từ chối kết nối WebCrypto (mã 1008):`
516
- : ` If non-localhost IPs (like ${mainIp}) block WebCrypto connections with Error 1008:`));
517
- console.log(chalk.white(isVi
518
- ? ` 1. Trên WSL/Máy nội bộ: Bạn CHỈ CẦN mở link http://127.0.0.1:${gatewayPort} là vào được.`
519
- : ` 1. On WSL/Local Network: Use the http://127.0.0.1:${gatewayPort} link directly.`));
520
- console.log(chalk.white(isVi
521
- ? ` 2. Trên VPS Xa: Mở tab Terminal khác ở MÁY CỦA BẠN (Windows/Mac) chạy lệnh SSH Tunnel sau:`
522
- : ` 2. On Remote VPS: Run this SSH Tunnel command on YOUR LOCAL COMPUTER (Windows/Mac):`));
523
- console.log(chalk.bgBlack.white(` ssh -L ${gatewayPort}:localhost:${gatewayPort} ${username}@${mainIp} `));
524
- console.log(chalk.gray(isVi
525
- ? ` Rồi quay lại trình duyệt mở link http://127.0.0.1:${gatewayPort}/#token=... là xong!`
526
- : ` Then open the http://127.0.0.1:${gatewayPort}/#token=... link in your browser!`));
527
- }
528
- } else {
529
- console.log(chalk.gray(isVi
530
- ? ' → Nếu dashboard đòi Gateway Token, chạy: openclaw dashboard'
531
- : ' → If the dashboard asks for a Gateway Token, run: openclaw dashboard'));
532
- }
533
-
534
- if (providerKey === '9router') {
535
- const routerUrls = getReachableDashboardHosts(20128).map((baseUrl) => `${baseUrl}/dashboard`);
536
- console.log(chalk.yellow(`\n🔀 ${isVi ? '9Router Dashboard:' : '9Router Dashboard:'} ${routerUrls[0] || 'http://127.0.0.1:20128/dashboard'}`));
537
- if (routerUrls.length > 1) {
538
- console.log(chalk.gray(isVi
539
- ? ` → Link khac co the mo duoc: ${routerUrls.slice(1).join(' , ')}`
540
- : ` → Other reachable URLs: ${routerUrls.slice(1).join(' , ')}`));
541
- }
542
- console.log(chalk.gray(isVi
543
- ? ' → Mở dashboard 9Router → đăng nhập OAuth → kết nối provider miễn phí'
544
- : ' → Open the 9Router dashboard → complete OAuth login → connect a free provider'));
483
+ function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gatewayPort = 18791 }) {
484
+ const gatewayUrls = getReachableDashboardHosts(gatewayPort);
485
+ const dashboardUrl = gatewayUrls[0] || `http://127.0.0.1:${gatewayPort}`;
486
+ const tokenizedUrl = getTokenizedDashboardUrl(projectDir);
487
+
488
+ console.log(chalk.yellow(`\n🧭 ${isVi ? 'Dashboard OpenClaw:' : 'OpenClaw Dashboard:'} ${dashboardUrl}`));
489
+ if (gatewayUrls.length > 1) {
490
+ console.log(chalk.gray(isVi
491
+ ? ` → Link khac co the mo duoc: ${gatewayUrls.slice(1).join(' , ')}`
492
+ : ` → Other reachable URLs: ${gatewayUrls.slice(1).join(' , ')}`));
493
+ }
494
+
495
+ if (tokenizedUrl) {
496
+ const tokenizedUrls = gatewayUrls.map((baseUrl) => rewriteDashboardUrlHost(tokenizedUrl, gatewayPort, baseUrl));
497
+ console.log(chalk.green(isVi
498
+ ? ` → Mở link đã kèm token: ${tokenizedUrls[0]}`
499
+ : ` → Open the tokenized link directly: ${tokenizedUrls[0]}`));
500
+ if (tokenizedUrls.length > 1) {
501
+ console.log(chalk.gray(isVi
502
+ ? ` → Ban mo tu may khac/WSL thi thu: ${tokenizedUrls.slice(1).join(' , ')}`
503
+ : ` → If you are opening from another machine/WSL, try: ${tokenizedUrls.slice(1).join(' , ')}`));
504
+ }
505
+
506
+ const externalHosts = gatewayUrls.filter(u => !u.includes('localhost') && !u.includes('127.0.0.1')).map(u => {
507
+ try { return new URL(u).hostname; } catch { return u; }
508
+ });
509
+
510
+ if (externalHosts.length > 0) {
511
+ const mainIp = externalHosts[0];
512
+ const username = process.env.USER || 'user';
513
+ console.log(chalk.cyan(`\n🔐 ${isVi ? 'Bảo mật WebCrypto (Sửa Lỗi 1008)' : 'WebCrypto Security (Fix Error 1008)'}`));
514
+ console.log(chalk.gray(isVi
515
+ ? ` Nếu dùng các link IP ngoài (như ${mainIp}) bị lỗi từ chối kết nối WebCrypto (mã 1008):`
516
+ : ` If non-localhost IPs (like ${mainIp}) block WebCrypto connections with Error 1008:`));
517
+ console.log(chalk.white(isVi
518
+ ? ` 1. Trên WSL/Máy nội bộ: Bạn CHỈ CẦN mở link http://127.0.0.1:${gatewayPort} là vào được.`
519
+ : ` 1. On WSL/Local Network: Use the http://127.0.0.1:${gatewayPort} link directly.`));
520
+ console.log(chalk.white(isVi
521
+ ? ` 2. Trên VPS Xa: Mở tab Terminal khác ở MÁY CỦA BẠN (Windows/Mac) chạy lệnh SSH Tunnel sau:`
522
+ : ` 2. On Remote VPS: Run this SSH Tunnel command on YOUR LOCAL COMPUTER (Windows/Mac):`));
523
+ console.log(chalk.bgBlack.white(` ssh -L ${gatewayPort}:localhost:${gatewayPort} ${username}@${mainIp} `));
524
+ console.log(chalk.gray(isVi
525
+ ? ` Rồi quay lại trình duyệt mở link http://127.0.0.1:${gatewayPort}/#token=... là xong!`
526
+ : ` Then open the http://127.0.0.1:${gatewayPort}/#token=... link in your browser!`));
527
+ }
528
+ } else {
529
+ console.log(chalk.gray(isVi
530
+ ? ' → Nếu dashboard đòi Gateway Token, chạy: openclaw dashboard'
531
+ : ' → If the dashboard asks for a Gateway Token, run: openclaw dashboard'));
532
+ }
533
+
534
+ if (providerKey === '9router') {
535
+ const routerUrls = getReachableDashboardHosts(20128).map((baseUrl) => `${baseUrl}/dashboard`);
536
+ console.log(chalk.yellow(`\n🔀 ${isVi ? '9Router Dashboard:' : '9Router Dashboard:'} ${routerUrls[0] || 'http://127.0.0.1:20128/dashboard'}`));
537
+ if (routerUrls.length > 1) {
538
+ console.log(chalk.gray(isVi
539
+ ? ` → Link khac co the mo duoc: ${routerUrls.slice(1).join(' , ')}`
540
+ : ` → Other reachable URLs: ${routerUrls.slice(1).join(' , ')}`));
541
+ }
542
+ console.log(chalk.gray(isVi
543
+ ? ' → Mở dashboard 9Router → đăng nhập OAuth → kết nối provider miễn phí'
544
+ : ' → Open the 9Router dashboard → complete OAuth login → connect a free provider'));
545
545
  console.log(chalk.gray(isVi
546
546
  ? ' → Sau khi login 9Router xong, bot sẽ tự dùng model smart-route qua http://localhost:20128/v1'
547
547
  : ' → Once 9Router is logged in, the bot will use smart-route through http://localhost:20128/v1'));
@@ -718,62 +718,62 @@ function runPm2Save({ projectDir, isVi }) {
718
718
  }
719
719
  }
720
720
 
721
- function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
722
- const routerAppName = `${appName}-9router`;
723
- const routerLaunch = resolveNative9RouterDesktopLaunch();
724
- execFileSync('pm2', [
725
- 'start',
726
- routerLaunch.command,
727
- '--name',
728
- routerAppName,
729
- '--cwd',
730
- projectDir.replace(/\\/g, '/'),
731
- '--interpreter',
732
- 'none',
733
- '--',
734
- ...routerLaunch.args
735
- ], {
736
- cwd: projectDir,
737
- stdio: 'inherit',
738
- env: { ...process.env, ...routerLaunch.env }
739
- });
740
- if (syncScriptPath) {
741
- const syncAppName = `${appName}-9router-sync`;
742
- const normalizedSyncScriptPath = syncScriptPath.replace(/\\/g, '/');
743
- try {
744
- execFileSync('pm2', [
745
- 'start',
746
- normalizedSyncScriptPath,
747
- '--name',
748
- syncAppName,
749
- '--cwd',
750
- projectDir.replace(/\\/g, '/'),
751
- '--interpreter',
752
- process.execPath
753
- ], {
754
- cwd: projectDir,
755
- stdio: 'inherit',
756
- env: process.env
757
- });
758
- } catch {
759
- try {
760
- execSync(`nohup "${process.execPath}" "${normalizedSyncScriptPath}" >/tmp/${syncAppName}.log 2>&1 &`, {
761
- cwd: projectDir,
762
- stdio: 'ignore',
763
- shell: true,
764
- env: process.env
765
- });
766
- console.log(chalk.yellow(isVi
767
- ? `⚠️ PM2 khong khoi dong duoc sync helper. Da fallback sang background node: /tmp/${syncAppName}.log`
768
- : `⚠️ PM2 could not start the sync helper. Fell back to a background node process: /tmp/${syncAppName}.log`));
769
- } catch {
770
- console.log(chalk.yellow(isVi
771
- ? `⚠️ Khong the khoi dong 9Router sync helper. 9Router van chay, nhung smart-route co the can dong bo thu cong sau.`
772
- : `⚠️ Could not start the 9Router sync helper. 9Router is still running, but smart-route may need manual syncing later.`));
773
- }
774
- }
775
- }
776
- runPm2Save({ projectDir, isVi });
721
+ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
722
+ const routerAppName = `${appName}-9router`;
723
+ const routerLaunch = resolveNative9RouterDesktopLaunch();
724
+ execFileSync('pm2', [
725
+ 'start',
726
+ routerLaunch.command,
727
+ '--name',
728
+ routerAppName,
729
+ '--cwd',
730
+ projectDir.replace(/\\/g, '/'),
731
+ '--interpreter',
732
+ 'none',
733
+ '--',
734
+ ...routerLaunch.args
735
+ ], {
736
+ cwd: projectDir,
737
+ stdio: 'inherit',
738
+ env: { ...process.env, ...routerLaunch.env }
739
+ });
740
+ if (syncScriptPath) {
741
+ const syncAppName = `${appName}-9router-sync`;
742
+ const normalizedSyncScriptPath = syncScriptPath.replace(/\\/g, '/');
743
+ try {
744
+ execFileSync('pm2', [
745
+ 'start',
746
+ normalizedSyncScriptPath,
747
+ '--name',
748
+ syncAppName,
749
+ '--cwd',
750
+ projectDir.replace(/\\/g, '/'),
751
+ '--interpreter',
752
+ process.execPath
753
+ ], {
754
+ cwd: projectDir,
755
+ stdio: 'inherit',
756
+ env: process.env
757
+ });
758
+ } catch {
759
+ try {
760
+ execSync(`nohup "${process.execPath}" "${normalizedSyncScriptPath}" >/tmp/${syncAppName}.log 2>&1 &`, {
761
+ cwd: projectDir,
762
+ stdio: 'ignore',
763
+ shell: true,
764
+ env: process.env
765
+ });
766
+ console.log(chalk.yellow(isVi
767
+ ? `⚠️ PM2 khong khoi dong duoc sync helper. Da fallback sang background node: /tmp/${syncAppName}.log`
768
+ : `⚠️ PM2 could not start the sync helper. Fell back to a background node process: /tmp/${syncAppName}.log`));
769
+ } catch {
770
+ console.log(chalk.yellow(isVi
771
+ ? `⚠️ Khong the khoi dong 9Router sync helper. 9Router van chay, nhung smart-route co the can dong bo thu cong sau.`
772
+ : `⚠️ Could not start the 9Router sync helper. 9Router is still running, but smart-route may need manual syncing later.`));
773
+ }
774
+ }
775
+ }
776
+ runPm2Save({ projectDir, isVi });
777
777
  console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
778
778
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
779
779
  }
@@ -1362,7 +1362,7 @@ async function main() {
1362
1362
  }
1363
1363
 
1364
1364
 
1365
- const patchScript = `const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\`http://\${entry.address}:18791\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
1365
+ const patchScript = `const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\`http://\${entry.address}:18791\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
1366
1366
  const b64Patch = Buffer.from(patchScript).toString('base64');
1367
1367
 
1368
1368
  // Browser Playwright (both desktop & server modes need chromium)
@@ -1405,7 +1405,7 @@ async function main() {
1405
1405
  '# Fix chat.send dropping resolved agent timeout into reply pipeline.',
1406
1406
  '# Without this, Telegram/WebChat paths fall back to an internal 300s default even when',
1407
1407
  '# agents.defaults.timeoutSeconds is higher in config.',
1408
- `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);}"`,
1408
+ `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);}"`,
1409
1409
  '',
1410
1410
  'WORKDIR /root/.openclaw',
1411
1411
  '',
@@ -1421,12 +1421,12 @@ async function main() {
1421
1421
  const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-$/, '') || 'chat';
1422
1422
 
1423
1423
  // ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
1424
- // This script runs inside the 9Router container as a background loop.
1425
- // It reads the persisted 9Router DB directly so smart-route still works
1426
- // even when newer dashboard APIs require auth or change response shape.
1427
- const syncComboScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
1428
- const syncComboScriptBase64 = Buffer.from(syncComboScript).toString('base64');
1429
- const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncComboScriptBase64);
1424
+ // This script runs inside the 9Router container as a background loop.
1425
+ // It reads the persisted 9Router DB directly so smart-route still works
1426
+ // even when newer dashboard APIs require auth or change response shape.
1427
+ const syncComboScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
1428
+ const syncComboScriptBase64 = Buffer.from(syncComboScript).toString('base64');
1429
+ const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncComboScriptBase64);
1430
1430
 
1431
1431
  // ─── Resolve primary model ───────────────────────────────────────────────────
1432
1432
  let modelsPrimary;
@@ -1470,14 +1470,14 @@ ${dependsOn}${extraHosts} ports:
1470
1470
  image: node:22-slim
1471
1471
  container_name: 9router-multibot
1472
1472
  restart: always
1473
- entrypoint:
1474
- - /bin/sh
1475
- - -c
1476
- - |
1477
- ${indentBlock(docker9RouterEntrypointScript, 8)}
1478
- environment:
1479
- - PORT=20128
1480
- - HOSTNAME=0.0.0.0
1473
+ entrypoint:
1474
+ - /bin/sh
1475
+ - -c
1476
+ - |
1477
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
1478
+ environment:
1479
+ - PORT=20128
1480
+ - HOSTNAME=0.0.0.0
1481
1481
  - CI=true
1482
1482
  volumes:
1483
1483
  - 9router-data:/root/.9router
@@ -1562,14 +1562,14 @@ ${hasBrowserDesktop ? ` extra_hosts:\n - "host.docker.internal:host-gate
1562
1562
  image: node:22-slim
1563
1563
  container_name: 9router-${agentId}
1564
1564
  restart: always
1565
- entrypoint:
1566
- - /bin/sh
1567
- - -c
1568
- - |
1569
- ${indentBlock(docker9RouterEntrypointScript, 8)}
1570
- environment:
1571
- - PORT=20128
1572
- - HOSTNAME=0.0.0.0
1565
+ entrypoint:
1566
+ - /bin/sh
1567
+ - -c
1568
+ - |
1569
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
1570
+ environment:
1571
+ - PORT=20128
1572
+ - HOSTNAME=0.0.0.0
1573
1573
  - CI=true
1574
1574
  volumes:
1575
1575
  - 9router-data:/root/.9router
@@ -1821,16 +1821,16 @@ ${hasBrowserDesktop ? ` extra_hosts:
1821
1821
  allow: agentMetas.map((meta) => meta.agentId),
1822
1822
  },
1823
1823
  },
1824
- gateway: {
1825
- port: 18791,
1826
- mode: 'local',
1827
- bind: 'custom',
1828
- customBindHost: '0.0.0.0',
1829
- controlUi: {
1830
- allowedOrigins: getGatewayAllowedOrigins(18791),
1831
- },
1832
- auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
1833
- },
1824
+ gateway: {
1825
+ port: 18791,
1826
+ mode: 'local',
1827
+ bind: 'custom',
1828
+ customBindHost: '0.0.0.0',
1829
+ controlUi: {
1830
+ allowedOrigins: getGatewayAllowedOrigins(18791),
1831
+ },
1832
+ auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
1833
+ },
1834
1834
  };
1835
1835
  sharedConfig.plugins = {
1836
1836
  entries: {
@@ -1869,6 +1869,16 @@ ${hasBrowserDesktop ? ` extra_hosts:
1869
1869
  ` autorestart: true,`,
1870
1870
  ` watch: false,`,
1871
1871
  ` env: { NODE_ENV: 'production' }`,
1872
+ ' },',
1873
+ ' {',
1874
+ ` name: '${botName || 'openclaw-multibot'}-auto-approve',`,
1875
+ ` script: 'sh',`,
1876
+ ` args: '-c "while true; do npx --yes openclaw devices approve --latest 2>/dev/null || true; sleep 5; done"',`,
1877
+ ` cwd: '${projectDir.replace(/\\/g, '/')}',`,
1878
+ ` interpreter: 'none',`,
1879
+ ` autorestart: true,`,
1880
+ ` watch: false,`,
1881
+ ` env: { NODE_ENV: 'production' }`,
1872
1882
  ' }',
1873
1883
  ].join('\n');
1874
1884
  const ecosystemContent = [
@@ -2057,11 +2067,11 @@ ${hasBrowserDesktop ? ` extra_hosts:
2057
2067
  commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
2058
2068
  channels: {},
2059
2069
  tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
2060
- gateway: {
2061
- port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
2062
- controlUi: { allowedOrigins: getGatewayAllowedOrigins(18791 + (isMultiBot ? bIndex : 0)) },
2063
- auth: { mode: 'token', token: 'cli-dummy-token-xyz123' }
2064
- }
2070
+ gateway: {
2071
+ port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
2072
+ controlUi: { allowedOrigins: getGatewayAllowedOrigins(18791 + (isMultiBot ? bIndex : 0)) },
2073
+ auth: { mode: 'token', token: 'cli-dummy-token-xyz123' }
2074
+ }
2065
2075
  };
2066
2076
 
2067
2077
  if (hasBrowserDesktop) {
@@ -2094,12 +2104,12 @@ ${hasBrowserDesktop ? ` extra_hosts:
2094
2104
  };
2095
2105
  }
2096
2106
  botConfig.channels['telegram'] = telegramConfig;
2097
- } else if (channelKey === 'zalo-personal') {
2098
- botConfig.channels['zalouser'] = {
2099
- enabled: true,
2100
- dmPolicy: 'pairing',
2101
- autoReply: true
2102
- };
2107
+ } else if (channelKey === 'zalo-personal') {
2108
+ botConfig.channels['zalouser'] = {
2109
+ enabled: true,
2110
+ dmPolicy: 'open',
2111
+ autoReply: true
2112
+ };
2103
2113
  } else if (channelKey === 'zalo-bot') {
2104
2114
  botConfig.channels['zalo'] = { enabled: true, provider: 'official_account' };
2105
2115
  }
@@ -2313,14 +2323,14 @@ fi
2313
2323
  if (code === 0) {
2314
2324
  console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoàn tất! Bot đang chạy.' : 'Setup complete! Bot is running.'}`));
2315
2325
 
2316
- if (providerKey === '9router') {
2317
- const routerDashboardUrl = `${getReachableDashboardHosts(20128)[0] || 'http://127.0.0.1:20128'}/dashboard`;
2318
- console.log(chalk.yellow(`\n🔀 ${isVi
2319
- ? `9Router Dashboard: ${routerDashboardUrl}`
2320
- : `9Router Dashboard: ${routerDashboardUrl}`}`));
2321
- console.log(chalk.gray(isVi
2322
- ? ' → Mở dashboard → đăng nhập OAuth để kết nối các Provider (iFlow, Gemini CLI, Claude Code...)'
2323
- : ' → Open dashboard → OAuth login to connect Providers (iFlow, Gemini CLI, Claude Code...)'));
2326
+ if (providerKey === '9router') {
2327
+ const routerDashboardUrl = `${getReachableDashboardHosts(20128)[0] || 'http://127.0.0.1:20128'}/dashboard`;
2328
+ console.log(chalk.yellow(`\n🔀 ${isVi
2329
+ ? `9Router Dashboard: ${routerDashboardUrl}`
2330
+ : `9Router Dashboard: ${routerDashboardUrl}`}`));
2331
+ console.log(chalk.gray(isVi
2332
+ ? ' → Mở dashboard → đăng nhập OAuth để kết nối các Provider (iFlow, Gemini CLI, Claude Code...)'
2333
+ : ' → Open dashboard → OAuth login to connect Providers (iFlow, Gemini CLI, Claude Code...)'));
2324
2334
  console.log(chalk.gray(isVi
2325
2335
  ? ' → Sau khi kết nối provider, bot sẽ tự động hoạt động qua combo "smart-route"'
2326
2336
  : ' → After connecting providers, bot works automatically via "smart-route" combo'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.1.9",
3
+ "version": "5.1.11",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -218,7 +218,7 @@ checks.push(() => expectMatch(
218
218
 
219
219
  checks.push(() => expectMatch(
220
220
  cli,
221
- /channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'pairing',\s*autoReply: true/s,
221
+ /channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'open',\s*autoReply: true/s,
222
222
  'CLI must configure Zalo Personal under channels.zalouser'
223
223
  ));
224
224