create-openclaw-bot 5.0.8 → 5.1.0

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,6 @@
1
1
  # Changelog (English)
2
2
 
3
- ## [5.0.8] — 2026-04-06
3
+ ## [5.0.9] — 2026-04-06
4
4
 
5
5
  ### 🚀 Native Install Mode — No Docker Required
6
6
 
package/CHANGELOG.vi.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog (Tiếng Việt)
2
2
 
3
- ## [5.0.8] — 2026-04-06
3
+ ## [5.0.9] — 2026-04-06
4
4
 
5
5
  ### 🚀 Chế độ Native Install — Không cần Docker
6
6
 
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.0.8-0EA5E9?style=for-the-badge" alt="Version 5.0.8" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.0-0EA5E9?style=for-the-badge" alt="Version 5.1.0" /></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.0.8
27
+ ## 🆕 What's new in v5.1.0
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.
@@ -111,7 +111,7 @@ Run in your terminal → follow the interactive prompts → startup script is ge
111
111
  2. Open this repo as your workspace
112
112
  3. Paste into chat:
113
113
  ```
114
- Read SETUP.md and set up OpenClaw v5.0.8 for me.
114
+ Read SETUP.md and set up OpenClaw v5.1.0 for me.
115
115
  My bot token is X. Use 9Router (no API key).
116
116
  My project folder: <YOUR_PATH>
117
117
  ```
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.0.8-0EA5E9?style=for-the-badge" alt="Version 5.0.8" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.0-0EA5E9?style=for-the-badge" alt="Version 5.1.0" /></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.0.8
27
+ ## 🆕 Có gì mới trong v5.1.0
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.
@@ -111,7 +111,7 @@ Chạy lệnh trên trong Terminal → làm theo các prompt tương tác → sc
111
111
  2. Mở repo này làm workspace
112
112
  3. Paste vào chat:
113
113
  ```
114
- Read SETUP.md and set up OpenClaw v5.0.8 for me.
114
+ Read SETUP.md and set up OpenClaw v5.1.0 for me.
115
115
  My bot token is X. Use 9Router (no API key).
116
116
  My project folder: <THƯ_MỤC_CỦA_BẠN>
117
117
  ```
package/cli.js CHANGED
@@ -160,6 +160,23 @@ function installGlobalPackage(pkg, { isVi, osChoice, displayName }) {
160
160
  return false;
161
161
  }
162
162
 
163
+ function build9RouterSmartRouteSyncScript(dbPath) {
164
+ const safeDbPath = JSON.stringify(dbPath);
165
+ return `const fs=require('fs');
166
+ const INTERVAL=30000;
167
+ const p=${safeDbPath};
168
+ 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']};
169
+ console.log('[sync-combo] 9Router sync loop started...');
170
+ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catch{}if(!db.combos)db.combos=[];const removeSmartRoute=()=>{const next=db.combos.filter(x=>x.id!=='smart-route');if(next.length!==db.combos.length){db.combos=next;fs.writeFileSync(p,JSON.stringify(db,null,2));console.log('[sync-combo] Removed smart-route (no active providers)');}};const a=(db.providerConnections||[]).filter(c=>c&&c.provider&&c.isActive!==false&&!c.disabled).map(c=>c.provider);if(!a.length){removeSmartRoute();return;}const PREF=['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];a.sort((x,y)=>(PREF.indexOf(x)===-1?99:PREF.indexOf(x))-(PREF.indexOf(y)===-1?99:PREF.indexOf(y)));const m=a.flatMap(provider=>PM[provider]||[]);if(!m.length){removeSmartRoute();return;}const c={id:'smart-route',name:'smart-route',alias:'smart-route',models:m};const i=db.combos.findIndex(x=>x.id==='smart-route');if(i>=0){if(JSON.stringify(db.combos[i].models)!==JSON.stringify(c.models)){db.combos[i]=c;fs.writeFileSync(p,JSON.stringify(db,null,2));console.log('[sync-combo] Updated smart-route: '+c.models.length+' models');}}else{db.combos.push(c);fs.writeFileSync(p,JSON.stringify(db,null,2));console.log('[sync-combo] Created smart-route: '+c.models.length+' models');}}catch{}};sync();setInterval(sync,INTERVAL);`;
171
+ }
172
+
173
+ async function writeNative9RouterSyncScript(projectDir) {
174
+ const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
175
+ await fs.ensureDir(path.dirname(syncScriptPath));
176
+ await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(os.homedir(), '.9router', 'db.json')));
177
+ return syncScriptPath;
178
+ }
179
+
163
180
  function extractFirstHttpUrl(text) {
164
181
  const match = String(text || '').match(/https?:\/\/[^\s"'`]+/);
165
182
  return match ? match[0] : null;
@@ -209,10 +226,44 @@ function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gateway
209
226
  }
210
227
  }
211
228
 
212
- function startNative9RouterPm2({ isVi, projectDir, appName }) {
229
+ function printZaloPersonalLoginInfo({ isVi, deployMode, projectDir }) {
230
+ const nativeCmd = 'openclaw channels login --channel zalouser --verbose';
231
+ const dockerCmd = 'docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose';
232
+ const cmd = deployMode === 'native' ? nativeCmd : dockerCmd;
233
+ const qrPath = '/tmp/openclaw/openclaw-zalouser-qr-default.png';
234
+ const copyCmd = deployMode === 'native'
235
+ ? `cp ${qrPath} ./zalo-login-qr.png`
236
+ : `docker compose cp ai-bot:${qrPath} ./zalo-login-qr.png`;
237
+
238
+ console.log(chalk.yellow(`\n📱 ${isVi ? 'Đăng nhập Zalo Personal (1 lần):' : 'Zalo Personal login (one time):'}`));
239
+ console.log(chalk.white(` cd ${projectDir}${deployMode === 'native' ? '' : '/docker/openclaw'} && ${cmd}`));
240
+ console.log(chalk.gray(isVi
241
+ ? ` → OpenClaw sẽ tạo file QR tại: ${qrPath}`
242
+ : ` → OpenClaw will generate a QR image at: ${qrPath}`));
243
+ console.log(chalk.gray(isVi
244
+ ? ` → Nếu cần copy QR ra ngoài, dùng: ${copyCmd}`
245
+ : ` → If needed, copy the QR out with: ${copyCmd}`));
246
+ }
247
+
248
+ function runPm2Save({ projectDir, isVi }) {
249
+ try {
250
+ execSync('pm2 save', {
251
+ cwd: projectDir,
252
+ stdio: 'inherit',
253
+ shell: true,
254
+ env: process.env
255
+ });
256
+ } catch {
257
+ console.log(chalk.yellow(isVi
258
+ ? '⚠️ PM2 save khong hoan tat. Bot van co the dang chay, nhung hay thu chay lai `pm2 save` sau.'
259
+ : '⚠️ PM2 save did not complete. The app may still be running, but try `pm2 save` again afterwards.'));
260
+ }
261
+ }
262
+
263
+ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
213
264
  const routerAppName = `${appName}-9router`;
214
265
  execSync(
215
- `pm2 start "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update" --name "${routerAppName}" --cwd "${projectDir.replace(/\\/g, '/')}" && pm2 save`,
266
+ `pm2 start "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update" --name "${routerAppName}" --cwd "${projectDir.replace(/\\/g, '/')}"`,
216
267
  {
217
268
  cwd: projectDir,
218
269
  stdio: 'inherit',
@@ -220,6 +271,19 @@ function startNative9RouterPm2({ isVi, projectDir, appName }) {
220
271
  env: process.env
221
272
  }
222
273
  );
274
+ if (syncScriptPath) {
275
+ const syncAppName = `${appName}-9router-sync`;
276
+ execSync(
277
+ `pm2 start "${syncScriptPath.replace(/\\/g, '/')}" --name "${syncAppName}" --cwd "${projectDir.replace(/\\/g, '/')}"`,
278
+ {
279
+ cwd: projectDir,
280
+ stdio: 'inherit',
281
+ shell: true,
282
+ env: process.env
283
+ }
284
+ );
285
+ }
286
+ runPm2Save({ projectDir, isVi });
223
287
  console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
224
288
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
225
289
  }
@@ -877,17 +941,31 @@ const sync = async () => {
877
941
  try {
878
942
  let db = {};
879
943
  try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e){}
944
+ if (!db.combos) db.combos = [];
945
+ const removeSmartRoute = () => {
946
+ const next = db.combos.filter(x => x.id !== 'smart-route');
947
+ if (next.length !== db.combos.length) {
948
+ db.combos = next;
949
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
950
+ console.log('[sync-combo] Removed smart-route (no active providers)');
951
+ }
952
+ };
880
953
  const a = (db.providerConnections || [])
881
954
  .filter(c => c && c.provider && c.isActive !== false && !c.disabled)
882
955
  .map(c => c.provider);
883
- if (!a.length) return;
956
+ if (!a.length) {
957
+ removeSmartRoute();
958
+ return;
959
+ }
884
960
 
885
961
  const PREF = ['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];
886
962
  a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
887
963
 
888
964
  const m = a.flatMap(p => PM[p] || []);
889
- if (!m.length) return;
890
- if (!db.combos) db.combos = [];
965
+ if (!m.length) {
966
+ removeSmartRoute();
967
+ return;
968
+ }
891
969
 
892
970
  const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
893
971
  const i = db.combos.findIndex(x => x.id === 'smart-route');
@@ -1580,7 +1658,11 @@ ${hasBrowserDesktop ? ` extra_hosts:
1580
1658
  }
1581
1659
  botConfig.channels['telegram'] = telegramConfig;
1582
1660
  } else if (channelKey === 'zalo-personal') {
1583
- botConfig.channels['zalo'] = { enabled: true, provider: 'client', autoReply: true };
1661
+ botConfig.channels['zalouser'] = {
1662
+ enabled: true,
1663
+ dmPolicy: 'pairing',
1664
+ autoReply: true
1665
+ };
1584
1666
  } else if (channelKey === 'zalo-bot') {
1585
1667
  botConfig.channels['zalo'] = { enabled: true, provider: 'official_account' };
1586
1668
  }
@@ -1817,8 +1899,7 @@ fi
1817
1899
  : ' → Run scripts/telegram-post-install-check.mjs to get the real links, verify group/privacy, then add the bots and disable privacy mode.'));
1818
1900
  }
1819
1901
  } else if (channelKey === 'zalo-personal') {
1820
- console.log(chalk.yellow(`\n📱 ${isVi ? 'Vui lòng chạy lệnh sau để đăng nhập Zalo Personal (1 lần duy nhất):' : 'Please run this command to login to Zalo Personal (once):'}`));
1821
- console.log(`cd ${projectDir} && docker compose exec -it openclaw bun run core:onboard`);
1902
+ printZaloPersonalLoginInfo({ isVi, deployMode: 'docker', projectDir });
1822
1903
  }
1823
1904
  } else {
1824
1905
  console.log(chalk.red(`\n\u274c Docker exited with code ${code}`));
@@ -1906,6 +1987,11 @@ fi
1906
1987
  console.log(chalk.green(isVi ? '✅ 9Router da cai xong!' : '✅ 9Router installed!'));
1907
1988
  }
1908
1989
 
1990
+ let native9RouterSyncScriptPath = null;
1991
+ if (providerKey === '9router') {
1992
+ native9RouterSyncScriptPath = await writeNative9RouterSyncScript(projectDir);
1993
+ }
1994
+
1909
1995
  await syncLocalConfigToHome(projectDir, isVi);
1910
1996
 
1911
1997
  if (isMultiBot && channelKey === 'telegram') {
@@ -1922,7 +2008,7 @@ fi
1922
2008
 
1923
2009
  if (isMultiBot && channelKey === 'telegram') {
1924
2010
  if (providerKey === '9router') {
1925
- startNative9RouterPm2({ isVi, projectDir, appName: botName || 'openclaw-multibot' });
2011
+ startNative9RouterPm2({ isVi, projectDir, appName: botName || 'openclaw-multibot', syncScriptPath: native9RouterSyncScriptPath });
1926
2012
  }
1927
2013
  execSync('pm2 start ecosystem.config.js && pm2 save', {
1928
2014
  cwd: projectDir,
@@ -1932,10 +2018,13 @@ fi
1932
2018
  console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoan tat! Multi-bot native dang chay qua PM2.' : 'Setup complete! Native multi-bot is running via PM2.'}`));
1933
2019
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${botName || 'openclaw-multibot'}` : ` View logs: pm2 logs ${botName || 'openclaw-multibot'}`));
1934
2020
  printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
2021
+ if (channelKey === 'zalo-personal') {
2022
+ printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
2023
+ }
1935
2024
  } else {
1936
2025
  const appName = botName || 'openclaw';
1937
2026
  if (providerKey === '9router') {
1938
- startNative9RouterPm2({ isVi, projectDir, appName });
2027
+ startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath: native9RouterSyncScriptPath });
1939
2028
  }
1940
2029
  execSync(`pm2 start "openclaw gateway run" --name "${appName}" --cwd "${projectDir.replace(/\\/g, '/')}" && pm2 save`, {
1941
2030
  cwd: projectDir,
@@ -1945,6 +2034,9 @@ fi
1945
2034
  console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoan tat! Bot native dang chay qua PM2.' : 'Setup complete! Native bot is running via PM2.'}`));
1946
2035
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${appName}` : ` View logs: pm2 logs ${appName}`));
1947
2036
  printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
2037
+ if (channelKey === 'zalo-personal') {
2038
+ printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
2039
+ }
1948
2040
  }
1949
2041
  } else {
1950
2042
  if (providerKey === '9router') {
@@ -1955,10 +2047,21 @@ fi
1955
2047
  stdio: 'ignore',
1956
2048
  shell: process.platform === 'win32'
1957
2049
  }).unref();
2050
+ if (native9RouterSyncScriptPath) {
2051
+ spawn('node', [native9RouterSyncScriptPath], {
2052
+ cwd: projectDir,
2053
+ detached: true,
2054
+ stdio: 'ignore',
2055
+ shell: process.platform === 'win32'
2056
+ }).unref();
2057
+ }
1958
2058
  console.log(chalk.gray(isVi
1959
2059
  ? ' 9Router dashboard: http://localhost:20128/dashboard'
1960
2060
  : ' 9Router dashboard: http://localhost:20128/dashboard'));
1961
2061
  }
2062
+ if (channelKey === 'zalo-personal') {
2063
+ printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
2064
+ }
1962
2065
  console.log(chalk.yellow(`\n${isVi ? 'Khoi dong native bot (foreground)...' : 'Starting native bot (foreground)...'}`));
1963
2066
  const child = spawn('openclaw', ['gateway', 'run'], {
1964
2067
  cwd: projectDir,
@@ -2,7 +2,7 @@
2
2
 
3
3
  Native installation is designed for users who cannot or prefer not to use Docker. This includes deployments on Shared Hosting (cPanel), low-tier VPS environments, or Windows desktops for direct access.
4
4
 
5
- OpenClaw v5.0.8+ natively supports deployment script generation for Windows, Linux, VPS, and Hosting environments.
5
+ OpenClaw v5.1.0+ natively supports deployment script generation for Windows, Linux, VPS, and Hosting environments.
6
6
 
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Chế độ Native được thiết kế dành cho những ai không thể hoặc không muốn cài Docker. Chế độ này thường tối ưu cho Shared Hosting (cPanel), các gói VPS cấu hình rất thấp, hoặc cài trực tiếp trên máy Window để chạy cá nhân.
4
4
 
5
- OpenClaw v5.0.8+ tự động sinh sẵn các script cài đặt dành riêng cho Windows, Linux, VPS và Hosting.
5
+ OpenClaw v5.1.0+ tự động sinh sẵn các script cài đặt dành riêng cho Windows, Linux, VPS và Hosting.
6
6
 
7
7
  ---
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.0.8",
3
+ "version": "5.1.0",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/setup.js CHANGED
@@ -271,16 +271,16 @@
271
271
  },
272
272
  pluginInstall: '',
273
273
  },
274
- 'zalo-personal': {
275
- name: 'Zalo Personal',
276
- envKeys: [],
277
- envExtra: '',
278
- credSteps: [
279
- { textVi: '⚠️ Zalo Personal dùng <strong>unofficial API (zca-js)</strong> — chỉ nên dùng tài khoản phụ', textEn: '⚠️ Zalo Personal uses <strong>unofficial API (zca-js)</strong> — use an alternate account' },
280
- { textVi: 'Sau khi Docker chạy, chạy <code>docker exec -it openclaw-bot openclaw onboard</code> để <strong>quét QR code</strong> login Zalo.', textEn: 'After Docker starts, run <code>docker exec -it openclaw-bot openclaw onboard</code> to <strong>scan QR code</strong> and login Zalo. 1-time setup.' },
281
- ],
282
- channelConfig: {
283
- zalouser: {
274
+ 'zalo-personal': {
275
+ name: 'Zalo Personal',
276
+ envKeys: [],
277
+ envExtra: '',
278
+ credSteps: [
279
+ { textVi: '⚠️ Zalo Personal dùng <strong>unofficial API (zca-js)</strong> — chỉ nên dùng tài khoản phụ', textEn: '⚠️ Zalo Personal uses <strong>unofficial API (zca-js)</strong> — use an alternate account' },
280
+ { textVi: 'Sau khi runtime chạy, dùng <code>openclaw channels login --channel zalouser --verbose</code> để tạo <strong> QR đăng nhập Zalo</strong>. Không cần onboard lại.', textEn: 'After the runtime is up, use <code>openclaw channels login --channel zalouser --verbose</code> to generate the <strong>Zalo login QR</strong>. No full onboard needed.' },
281
+ ],
282
+ channelConfig: {
283
+ zalouser: {
284
284
  enabled: true,
285
285
  accounts: {
286
286
  default: {
@@ -1520,7 +1520,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
1520
1520
  // Generate native script if native mode
1521
1521
  if (isNativeMode) generateNativeScript();
1522
1522
 
1523
- // Show/hide Zalo Personal onboard notice
1523
+ // Show/hide Zalo Personal login notice
1524
1524
  const zaloNotice = document.getElementById('zalo-onboard-notice');
1525
1525
  const isZaloPersonal = state.channel === 'zalo-personal';
1526
1526
  if (zaloNotice) {
@@ -1802,7 +1802,7 @@ model:
1802
1802
  const browserPrefix = hasBrowser
1803
1803
  ? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
1804
1804
  : '';
1805
- // Patch config on every startup to survive openclaw onboard overwrites
1805
+ // Patch config on every startup to keep gateway settings stable
1806
1806
  const patchCmd = `node -e \\"const fs=require('fs'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));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'});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
1807
1807
  // Auto-approve device pairing after gateway starts (required since v2026.3.x)
1808
1808
  const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
@@ -1841,17 +1841,31 @@ const sync = async () => {
1841
1841
  try {
1842
1842
  let db = {};
1843
1843
  try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e){}
1844
- const a = (db.providerConnections || [])
1845
- .filter(c => c && c.provider && c.isActive !== false && !c.disabled)
1846
- .map(c => c.provider);
1847
- if (!a.length) return;
1844
+ if (!db.combos) db.combos = [];
1845
+ const removeSmartRoute = () => {
1846
+ const next = db.combos.filter(x => x.id !== 'smart-route');
1847
+ if (next.length !== db.combos.length) {
1848
+ db.combos = next;
1849
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
1850
+ console.log('[sync-combo] Removed smart-route (no active providers)');
1851
+ }
1852
+ };
1853
+ const a = (db.providerConnections || [])
1854
+ .filter(c => c && c.provider && c.isActive !== false && !c.disabled)
1855
+ .map(c => c.provider);
1856
+ if (!a.length) {
1857
+ removeSmartRoute();
1858
+ return;
1859
+ }
1848
1860
 
1849
1861
  const PREF = ['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];
1850
1862
  a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
1851
1863
 
1852
- const m = a.flatMap(p => PM[p] || []);
1853
- if (!m.length) return;
1854
- if (!db.combos) db.combos = [];
1864
+ const m = a.flatMap(p => PM[p] || []);
1865
+ if (!m.length) {
1866
+ removeSmartRoute();
1867
+ return;
1868
+ }
1855
1869
 
1856
1870
  const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
1857
1871
  const i = db.combos.findIndex(x => x.id === 'smart-route');
@@ -2832,8 +2846,17 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
2832
2846
  const p = PLUGINS.find((x) => x.id === pid);
2833
2847
  if (p) allPlugins.push(p.package);
2834
2848
  });
2835
- if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
2836
- const pluginCmd = allPlugins.length > 0 ? ('npm exec openclaw plugins install ' + allPlugins.join(' ')) : '';
2849
+ if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
2850
+ const pluginCmd = allPlugins.length > 0 ? ('npm exec openclaw plugins install ' + allPlugins.join(' ')) : '';
2851
+
2852
+ function native9RouterSyncScriptContent() {
2853
+ return `const fs=require('fs');
2854
+ const path=require('path');
2855
+ const INTERVAL=30000;
2856
+ const p=path.join(process.env.HOME||process.env.USERPROFILE||'.','.9router','db.json');
2857
+ 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']};
2858
+ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catch{}if(!db.combos)db.combos=[];const removeSmartRoute=()=>{const next=db.combos.filter(x=>x.id!=='smart-route');if(next.length!==db.combos.length){db.combos=next;fs.writeFileSync(p,JSON.stringify(db,null,2));}};const a=(db.providerConnections||[]).filter(c=>c&&c.provider&&c.isActive!==false&&!c.disabled).map(c=>c.provider);if(!a.length){removeSmartRoute();return;}const PREF=['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];a.sort((x,y)=>(PREF.indexOf(x)===-1?99:PREF.indexOf(x))-(PREF.indexOf(y)===-1?99:PREF.indexOf(y)));const m=a.flatMap(provider=>PM[provider]||[]);if(!m.length){removeSmartRoute();return;}const c={id:'smart-route',name:'smart-route',alias:'smart-route',models:m};const i=db.combos.findIndex(x=>x.id==='smart-route');if(i>=0){if(JSON.stringify(db.combos[i].models)!==JSON.stringify(c.models)){db.combos[i]=c;fs.writeFileSync(p,JSON.stringify(db,null,2));}}else{db.combos.push(c);fs.writeFileSync(p,JSON.stringify(db,null,2));}}catch{}};sync();setInterval(sync,INTERVAL);`;
2859
+ }
2837
2860
 
2838
2861
  // ─── Shared initializer (provider install) ───────────────────────────────
2839
2862
  function providerLines(arr, shell) {
@@ -2841,10 +2864,12 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
2841
2864
  if (shell === 'bat') {
2842
2865
  arr.push('npm install -g 9router');
2843
2866
  arr.push('start "9Router" cmd /k "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update"');
2867
+ arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
2844
2868
  arr.push('timeout /t 5 /nobreak >nul');
2845
2869
  } else {
2846
2870
  arr.push('npm install -g 9router');
2847
2871
  arr.push('nohup 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update >/tmp/9router.log 2>&1 &');
2872
+ arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
2848
2873
  arr.push('sleep 3');
2849
2874
  }
2850
2875
  } else if (isOllama) {
@@ -3015,13 +3040,14 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
3015
3040
  }
3016
3041
 
3017
3042
  function sharedNativeFileMap() {
3018
- const files = {
3019
- '.env': sharedNativeEnvContent(),
3020
- '.openclaw/openclaw.json': sharedNativeConfigContent(),
3021
- '.openclaw/exec-approvals.json': sharedNativeExecApprovalsContent(),
3022
- '.openclaw/auth-profiles.json': sharedNativeAuthProfilesContent(),
3023
- 'TELEGRAM-POST-INSTALL.md': buildTelegramPostInstallChecklist(),
3024
- };
3043
+ const files = {
3044
+ '.env': sharedNativeEnvContent(),
3045
+ '.openclaw/openclaw.json': sharedNativeConfigContent(),
3046
+ '.openclaw/exec-approvals.json': sharedNativeExecApprovalsContent(),
3047
+ '.openclaw/auth-profiles.json': sharedNativeAuthProfilesContent(),
3048
+ 'TELEGRAM-POST-INSTALL.md': buildTelegramPostInstallChecklist(),
3049
+ };
3050
+ if (is9Router) files['.openclaw/9router-smart-route-sync.js'] = native9RouterSyncScriptContent();
3025
3051
  const teamMd = isVi
3026
3052
  ? `# Doi Bot\n\n${multiBotAgentMetas.map((meta) => `## ${meta.name}\n- Vai tro: ${meta.desc}\n- Agent ID: \`${meta.agentId}\`\n- Telegram accountId: \`${meta.accountId}\`\n- Slash command: ${meta.slashCmd || '_(chua co)_'}\n- Tinh cach: ${meta.persona || '_(khong ghi ro)_'}`).join('\n\n')}\n\n## Quy uoc phoi hop\n- Tat ca bot trong doi biet ro vai tro cua nhau.\n- Neu user bao ban hoi mot bot khac, hay dung agent-to-agent noi bo thay vi doi Telegram chuyen tin cua bot.\n- Bot mo loi chi noi 1 cau ngan, sau do chuyen turn noi bo cho bot dich.\n- Bot dich phai tra loi cong khai bang chinh Telegram account cua minh trong cung chat/thread hien tai.\n- Neu can fallback, chi bot mo loi moi duoc phep tom tat thay.`
3027
3053
  : `# Bot Team\n\n${multiBotAgentMetas.map((meta) => `## ${meta.name}\n- Role: ${meta.desc}\n- Agent ID: \`${meta.agentId}\`\n- Telegram accountId: \`${meta.accountId}\`\n- Slash command: ${meta.slashCmd || '_(not set)_'}\n- Persona: ${meta.persona || '_(not specified)_'}`).join('\n\n')}\n\n## Coordination Rules\n- Every bot knows the full roster.\n- If the user asks you to consult another bot, use internal agent-to-agent handoff instead of waiting for Telegram bot-to-bot delivery.\n- The caller bot only sends one short opener, then hands off internally.\n- The target bot must publish the real answer with its own Telegram account in the same chat/thread.\n- If a fallback is needed, only the caller bot may summarize on behalf of the target.`;
@@ -3369,10 +3395,11 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3369
3395
  const base = '.';
3370
3396
  const files = {};
3371
3397
  files[`${base}/.env`] = botEnvContent(botIndex);
3372
- files[`${base}/.openclaw/openclaw.json`] = botConfigContent(botIndex);
3373
- files[`${base}/.openclaw/exec-approvals.json`] = botExecApprovalsContent(botIndex);
3374
- files[`${base}/.openclaw/auth-profiles.json`] = botAuthProfilesContent(botIndex);
3375
- files[`${base}/.openclaw/agents/${agentId}.yaml`] = botAgentYamlContent(botIndex);
3398
+ files[`${base}/.openclaw/openclaw.json`] = botConfigContent(botIndex);
3399
+ files[`${base}/.openclaw/exec-approvals.json`] = botExecApprovalsContent(botIndex);
3400
+ files[`${base}/.openclaw/auth-profiles.json`] = botAuthProfilesContent(botIndex);
3401
+ if (is9Router) files[`${base}/.openclaw/9router-smart-route-sync.js`] = native9RouterSyncScriptContent();
3402
+ files[`${base}/.openclaw/agents/${agentId}.yaml`] = botAgentYamlContent(botIndex);
3376
3403
  files[`${base}/.openclaw/agents/${agentId}/agent/auth-profiles.json`] = botAuthProfilesContent(botIndex);
3377
3404
  Object.entries(botWorkspaceFiles(botIndex)).forEach(([name, content]) => {
3378
3405
  files[`${base}/.openclaw/workspace/${name}`] = content;
@@ -3497,21 +3524,29 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3497
3524
  providerLines(vps, 'sh');
3498
3525
  if (pluginCmd) vps.push(pluginCmd);
3499
3526
 
3500
- if (isMultiBot) {
3501
- vps.push('echo "--- Creating shared multi-agent runtime ---"');
3502
- appendShWriteCommands(vps, sharedNativeFileMap());
3503
- vps.push('echo "--- Starting shared gateway via PM2 ---"');
3504
- vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
3505
- vps.push('pm2 save && pm2 startup');
3527
+ if (isMultiBot) {
3528
+ vps.push('echo "--- Creating shared multi-agent runtime ---"');
3529
+ appendShWriteCommands(vps, sharedNativeFileMap());
3530
+ vps.push('echo "--- Starting shared gateway via PM2 ---"');
3531
+ if (is9Router) {
3532
+ vps.push('pm2 start --name openclaw-multibot-9router -- sh -c "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update"');
3533
+ vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
3534
+ }
3535
+ vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
3536
+ vps.push('pm2 save && pm2 startup');
3506
3537
  vps.push(`echo ""`);
3507
3538
  vps.push(`echo "=== ✅ Shared multi-bot gateway running via PM2 ==="`);
3508
3539
  vps.push(`echo "Commands:"`);
3509
3540
  vps.push(`echo " pm2 status # Status gateway"`);
3510
3541
  vps.push(`echo " pm2 logs openclaw-multibot"`);
3511
- } else {
3512
- appendShWriteCommands(vps, botFiles(0));
3513
- vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
3514
- vps.push('pm2 save && pm2 startup');
3542
+ } else {
3543
+ appendShWriteCommands(vps, botFiles(0));
3544
+ if (is9Router) {
3545
+ vps.push('pm2 start --name openclaw-9router -- sh -c "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update"');
3546
+ vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
3547
+ }
3548
+ vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
3549
+ vps.push('pm2 save && pm2 startup');
3515
3550
  vps.push('echo "Bot dang chay! Xem log: pm2 logs openclaw"');
3516
3551
  }
3517
3552
  scriptContent = vps.filter(Boolean).join('\n');
@@ -3683,9 +3718,11 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
3683
3718
  if (is9Router) {
3684
3719
  ps += `Write-Host " ${isVi ? 'Mở http://localhost:30128/dashboard để login OAuth' : 'Open http://localhost:30128/dashboard to login OAuth'}" -ForegroundColor White\n`;
3685
3720
  }
3686
- if (state.channel === 'zalo-personal') {
3687
- ps += `Write-Host " ${isVi ? 'Chạy: docker exec -it openclaw-bot openclaw onboard (quét QR)' : 'Run: docker exec -it openclaw-bot openclaw onboard (scan QR)'}" -ForegroundColor White\n`;
3688
- }
3721
+ if (state.channel === 'zalo-personal') {
3722
+ ps += `Write-Host " ${isVi ? 'Chạy: docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose' : 'Run: docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose'}" -ForegroundColor White\n`;
3723
+ ps += `Write-Host " ${isVi ? 'QR sẽ nằm tại /tmp/openclaw/openclaw-zalouser-qr-default.png' : 'QR will be written to /tmp/openclaw/openclaw-zalouser-qr-default.png'}" -ForegroundColor DarkGray\n`;
3724
+ ps += `Write-Host " ${isVi ? 'Copy QR ra ngoài: docker compose cp ai-bot:/tmp/openclaw/openclaw-zalouser-qr-default.png ./zalo-login-qr.png' : 'Copy the QR out: docker compose cp ai-bot:/tmp/openclaw/openclaw-zalouser-qr-default.png ./zalo-login-qr.png'}" -ForegroundColor DarkGray\n`;
3725
+ }
3689
3726
 
3690
3727
  ps += `Write-Host ""
3691
3728
  } catch {
@@ -3858,72 +3895,40 @@ echo ""
3858
3895
  }
3859
3896
 
3860
3897
 
3861
- // ========== Zalo Personal Onboard Guide (post-Docker-setup) ==========
3862
-
3863
- function generateZaloOnboardGuide() {
3864
- const lang = document.getElementById('cfg-language')?.value || 'vi';
3865
- setOutput('out-zalo-onboard-cmd', `docker exec -it openclaw-bot openclaw onboard`);
3866
-
3867
- if (lang === 'vi') {
3868
- setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
3869
- OpenClaw sẽ hỏi lần lượt chọn như sau:
3870
- ├──────────────────────┬──────────────────────────────┤
3871
- Câu hỏi Chọn │
3872
- ├──────────────────────┼──────────────────────────────┤
3873
- Security warning │ ✅ Yes
3874
- Setup mode │ ✅ QuickStart
3875
- Config handling │ ✅ Use existing values
3876
- Model/auth provider Chọn tuỳ ý (VD: Google) │
3877
- API key │ Nhập key (hoặc Enter nếu
3878
- đã trong .env)
3879
- │ Select channel │ ✅ Zalo (Personal Account)
3880
- │ Login via QR? │ ✅ Yes │
3881
- │ ─── QR LOGIN ─── │ 📱 Mở file QR → Quét Zalo │
3882
- Did you scan QR? │ ✅ Yes
3883
- │ DM policy │ ✅ Pairing (recommended) │
3884
- Configure groups? │ ✅ No
3885
- Configure skills? │ ✅ No
3886
- Enable hooks? │ ✅ Enter (chọn mặc định)
3887
- Hatch your bot? │ ✅ Do this later
3888
- ├──────────────────────┴──────────────────────────────┤
3889
- 💡 Bước QR Login:
3890
- Khi bước QR hiện ra, test_openclaw sẽ lưu file QR
3891
- vào thư mục /tmp trong container.
3892
- │ Dùng lệnh: docker cp openclaw-bot:/tmp/qr.png . │
3893
- │ Mở file ảnh → quét bằng Zalo điện thoại → │
3894
- xác nhận kết nối → quay lại chọn Yes. │
3895
- └─────────────────────────────────────────────────────┘`);
3896
- } else {
3897
- setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
3898
- │ OpenClaw will prompt you — choose as follows: │
3899
- ├──────────────────────┬──────────────────────────────┤
3900
- │ Prompt │ Choice │
3901
- ├──────────────────────┼──────────────────────────────┤
3902
- │ Security warning │ ✅ Yes │
3903
- │ Setup mode │ ✅ QuickStart │
3904
- │ Config handling │ ✅ Use existing values │
3905
- │ Model/auth provider │ Choose any (e.g. Google) │
3906
- │ API key │ Enter key (or press Enter │
3907
- │ │ if already in .env) │
3908
- │ Select channel │ ✅ Zalo (Personal Account) │
3909
- │ Login via QR? │ ✅ Yes │
3910
- │ ─── QR LOGIN ─── │ 📱 Open QR file → Scan Zalo │
3911
- │ Did you scan QR? │ ✅ Yes │
3912
- │ DM policy │ ✅ Pairing (recommended) │
3913
- │ Configure groups? │ ✅ No │
3914
- │ Configure skills? │ ✅ No │
3915
- │ Enable hooks? │ ✅ Enter (default) │
3916
- │ Hatch your bot? │ ✅ Do this later │
3917
- ├──────────────────────┴──────────────────────────────┤
3918
- │ 💡 QR Login Step: │
3919
- │ When prompted, OpenClaw saves the QR code to │
3920
- │ /tmp inside the container. │
3921
- │ Run: docker cp openclaw-bot:/tmp/qr.png . │
3922
- │ Open image → scan with Zalo mobile app → │
3923
- │ confirm login → go back & select Yes. │
3924
- └─────────────────────────────────────────────────────┘`);
3925
- }
3926
- }
3898
+ // ========== Zalo Personal Login Guide (post-setup) ==========
3899
+
3900
+ function generateZaloOnboardGuide() {
3901
+ const lang = document.getElementById('cfg-language')?.value || 'vi';
3902
+ setOutput('out-zalo-onboard-cmd', `docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose`);
3903
+
3904
+ if (lang === 'vi') {
3905
+ setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
3906
+ Chạy lệnh bên trái để OpenClaw tạo QR đăng nhập.
3907
+ ├─────────────────────────────────────────────────────┤
3908
+ 1. Đảm bảo container/gateway đã chạy xong.
3909
+ │ 2. Chạy lệnh login để tạo QR cho zalouser. │
3910
+ 3. OpenClaw sẽ in ra đường dẫn file QR trong /tmp.
3911
+ 4. Copy file QR ra ngoài nếu cần:
3912
+ docker compose cp ai-bot:/tmp/openclaw/
3913
+ openclaw-zalouser-qr-default.png .
3914
+ 5. Mở ảnh QR quét bằng app Zalo → xác nhận.
3915
+ 6. Sau khi login xong, restart bot nếu cần.
3916
+ └─────────────────────────────────────────────────────┘`);
3917
+ } else {
3918
+ setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
3919
+ Run the command on the left to generate a Zalo QR.
3920
+ ├─────────────────────────────────────────────────────┤
3921
+ 1. Make sure the container/gateway is already up.
3922
+ 2. Run the login command for zalouser.
3923
+ 3. OpenClaw prints the QR image path under /tmp.
3924
+ 4. Copy the QR out if needed:
3925
+ │ docker compose cp ai-bot:/tmp/openclaw/ │
3926
+ openclaw-zalouser-qr-default.png .
3927
+ 5. Open the image scan with Zalo mobile app.
3928
+ 6. Restart the bot afterwards if needed.
3929
+ └─────────────────────────────────────────────────────┘`);
3930
+ }
3931
+ }
3927
3932
 
3928
3933
  function setOutput(id, text) {
3929
3934
  const el = document.getElementById(id);
@@ -56,6 +56,18 @@ checks.push(() => expectMatch(
56
56
  'Native 9Router flow must auto-install 9Router'
57
57
  ));
58
58
 
59
+ checks.push(() => expectMatch(
60
+ cli,
61
+ /async function writeNative9RouterSyncScript\(projectDir\) \{[\s\S]*9router-smart-route-sync\.js[\s\S]*providerConnections[\s\S]*smart-route/s,
62
+ 'Native 9Router flow must write a smart-route sync script based on ~/.9router/db.json'
63
+ ));
64
+
65
+ checks.push(() => expectMatch(
66
+ cli,
67
+ /Removed smart-route \(no active providers\)[\s\S]*if \(!a\.length\) \{[\s\S]*removeSmartRoute\(\)[\s\S]*if \(!m\.length\) \{[\s\S]*removeSmartRoute\(\)/s,
68
+ '9Router sync logic in CLI must remove stale smart-route combos when providers are disabled'
69
+ ));
70
+
59
71
  checks.push(() => expectMatch(
60
72
  cli,
61
73
  /function ensureUserWritableGlobalNpm\(\{ isVi, osChoice \}\) \{[\s\S]*process\.env\.npm_config_prefix = npmInfo\.prefixDir[\s\S]*npm config set prefix "\$\{npmInfo\.prefixDir\.replace/s,
@@ -80,6 +92,12 @@ checks.push(() => expectMatch(
80
92
  'Native PM2 flow must expose dashboard access info and the tokenized dashboard command'
81
93
  ));
82
94
 
95
+ checks.push(() => expectMatch(
96
+ cli,
97
+ /function printZaloPersonalLoginInfo\(\{ isVi, deployMode, projectDir \}\) \{[\s\S]*docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose[\s\S]*const copyCmd = deployMode === 'native'[\s\S]*docker compose cp ai-bot:\$\{qrPath\} \.\/zalo-login-qr\.png/s,
98
+ 'CLI must print the dedicated Docker/native Zalo Personal login commands and QR copy path instead of onboarding'
99
+ ));
100
+
83
101
  checks.push(() => expectMatch(
84
102
  cli,
85
103
  /baseUrl: deployMode === 'native' \? 'http:\/\/localhost:20128\/v1' : 'http:\/\/9router:20128\/v1'/,
@@ -88,10 +106,22 @@ checks.push(() => expectMatch(
88
106
 
89
107
  checks.push(() => expectMatch(
90
108
  cli,
91
- /function startNative9RouterPm2\(\{ isVi, projectDir, appName \}\) \{[\s\S]*9router -n -t -l -H 0\.0\.0\.0 -p 20128 --skip-update[\s\S]*pm2 save/s,
109
+ /channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'pairing',\s*autoReply: true/s,
110
+ 'CLI must configure Zalo Personal under channels.zalouser'
111
+ ));
112
+
113
+ checks.push(() => expectMatch(
114
+ cli,
115
+ /function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*9router -n -t -l -H 0\.0\.0\.0 -p 20128 --skip-update[\s\S]*9router-sync[\s\S]*runPm2Save\(\{ projectDir, isVi \}\)/s,
92
116
  'VPS native 9Router flow must start a standalone 9Router dashboard on port 20128 via PM2'
93
117
  ));
94
118
 
119
+ checks.push(() => expectMatch(
120
+ cli,
121
+ /function runPm2Save\(\{ projectDir, isVi \}\) \{[\s\S]*execSync\('pm2 save'[\s\S]*PM2 save did not complete/s,
122
+ 'Native PM2 save should be handled as a separate recoverable step'
123
+ ));
124
+
95
125
  checks.push(() => expectMatch(
96
126
  cli,
97
127
  /const child = spawn\('openclaw', \['gateway', 'run'\], \{/,
@@ -153,6 +183,24 @@ checks.push(() => expectMatch(
153
183
  'Native script generation must install and start a standalone 9Router dashboard on port 20128'
154
184
  ));
155
185
 
186
+ checks.push(() => expectMatch(
187
+ setup,
188
+ /function native9RouterSyncScriptContent\(\) \{[\s\S]*providerConnections[\s\S]*smart-route/s,
189
+ 'Native script generation must embed a 9Router smart-route sync script'
190
+ ));
191
+
192
+ checks.push(() => expectMatch(
193
+ setup,
194
+ /Removed smart-route \(no active providers\)[\s\S]*if \(!a\.length\) \{[\s\S]*removeSmartRoute\(\)[\s\S]*if \(!m\.length\) \{[\s\S]*removeSmartRoute\(\)/s,
195
+ '9Router sync logic in setup.js must remove stale smart-route combos when providers are disabled'
196
+ ));
197
+
198
+ checks.push(() => expectMatch(
199
+ setup,
200
+ /\.openclaw\/9router-smart-route-sync\.js[\s\S]*pm2 start --name openclaw-9router-sync/s,
201
+ 'VPS native script generation must write and run the 9Router smart-route sync loop'
202
+ ));
203
+
156
204
  checks.push(() => expectMatch(
157
205
  cli,
158
206
  /readdirSync\(dir\)\.find\(n=>\/\^gateway-cli-.*\\\\\.js\$\/\.test\(n\)\)[\s\S]*skipping timeout patch/,
@@ -195,6 +243,12 @@ checks.push(() => expectMatch(
195
243
  'Auto-steps summary must mention OpenClaw CLI installation'
196
244
  ));
197
245
 
246
+ checks.push(() => expectMatch(
247
+ setup,
248
+ /docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose[\s\S]*docker compose cp ai-bot:\/tmp\/openclaw\/openclaw-zalouser-qr-default\.png \.\/zalo-login-qr\.png/s,
249
+ 'Wizard copy must use the dedicated zalouser login command and Docker QR copy path'
250
+ ));
251
+
198
252
  for (const check of checks) {
199
253
  check();
200
254
  }
@@ -0,0 +1,20 @@
1
+ const fs = require('fs');
2
+
3
+ const configPath = process.argv[2];
4
+
5
+ if (!configPath) {
6
+ console.error('Usage: node live-enable-zalouser.cjs <configPath>');
7
+ process.exit(1);
8
+ }
9
+
10
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
11
+ config.channels = config.channels || {};
12
+ config.channels.zalouser = {
13
+ ...(config.channels.zalouser || {}),
14
+ enabled: true,
15
+ dmPolicy: 'pairing',
16
+ autoReply: true
17
+ };
18
+
19
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
20
+ console.log(`Enabled channels.zalouser in ${configPath}`);
@@ -0,0 +1,20 @@
1
+ const fs = require('fs');
2
+
3
+ const configPath = process.argv[2];
4
+
5
+ if (!configPath) {
6
+ console.error('Usage: node live-enable-zalouser.js <configPath>');
7
+ process.exit(1);
8
+ }
9
+
10
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
11
+ config.channels = config.channels || {};
12
+ config.channels.zalouser = {
13
+ ...(config.channels.zalouser || {}),
14
+ enabled: true,
15
+ dmPolicy: 'pairing',
16
+ autoReply: true
17
+ };
18
+
19
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
20
+ console.log(`Enabled channels.zalouser in ${configPath}`);
@@ -0,0 +1,40 @@
1
+ const fs = require('fs');
2
+
3
+ const dbPath = process.argv[2];
4
+
5
+ if (!dbPath) {
6
+ console.error('Usage: node live-patch-9router.cjs <dbPath>');
7
+ process.exit(1);
8
+ }
9
+
10
+ const models = [
11
+ 'cx/gpt-5.4',
12
+ 'cx/gpt-5.3-codex',
13
+ 'cx/gpt-5.3-codex-high',
14
+ 'cx/gpt-5.2-codex',
15
+ 'cx/gpt-5.2',
16
+ 'cx/gpt-5.1-codex-max',
17
+ 'cx/gpt-5.1-codex',
18
+ 'cx/gpt-5.1',
19
+ 'cx/gpt-5-codex'
20
+ ];
21
+
22
+ const db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
23
+ db.combos = db.combos || [];
24
+
25
+ const combo = {
26
+ id: 'smart-route',
27
+ name: 'smart-route',
28
+ alias: 'smart-route',
29
+ models
30
+ };
31
+
32
+ const index = db.combos.findIndex((entry) => entry && entry.id === 'smart-route');
33
+ if (index >= 0) {
34
+ db.combos[index] = combo;
35
+ } else {
36
+ db.combos.push(combo);
37
+ }
38
+
39
+ fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
40
+ console.log(`Patched smart-route in ${dbPath}`);
@@ -0,0 +1,40 @@
1
+ const fs = require('fs');
2
+
3
+ const dbPath = process.argv[2];
4
+
5
+ if (!dbPath) {
6
+ console.error('Usage: node live-patch-9router.js <dbPath>');
7
+ process.exit(1);
8
+ }
9
+
10
+ const models = [
11
+ 'cx/gpt-5.4',
12
+ 'cx/gpt-5.3-codex',
13
+ 'cx/gpt-5.3-codex-high',
14
+ 'cx/gpt-5.2-codex',
15
+ 'cx/gpt-5.2',
16
+ 'cx/gpt-5.1-codex-max',
17
+ 'cx/gpt-5.1-codex',
18
+ 'cx/gpt-5.1',
19
+ 'cx/gpt-5-codex'
20
+ ];
21
+
22
+ const db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
23
+ db.combos = db.combos || [];
24
+
25
+ const combo = {
26
+ id: 'smart-route',
27
+ name: 'smart-route',
28
+ alias: 'smart-route',
29
+ models
30
+ };
31
+
32
+ const index = db.combos.findIndex((entry) => entry && entry.id === 'smart-route');
33
+ if (index >= 0) {
34
+ db.combos[index] = combo;
35
+ } else {
36
+ db.combos.push(combo);
37
+ }
38
+
39
+ fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
40
+ console.log(`Patched smart-route in ${dbPath}`);