create-openclaw-bot 5.0.9 → 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/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.9-0EA5E9?style=for-the-badge" alt="Version 5.0.9" /></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.9
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.9 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.9-0EA5E9?style=for-the-badge" alt="Version 5.0.9" /></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.9
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.9 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,6 +226,25 @@ function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gateway
209
226
  }
210
227
  }
211
228
 
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
+
212
248
  function runPm2Save({ projectDir, isVi }) {
213
249
  try {
214
250
  execSync('pm2 save', {
@@ -224,7 +260,7 @@ function runPm2Save({ projectDir, isVi }) {
224
260
  }
225
261
  }
226
262
 
227
- function startNative9RouterPm2({ isVi, projectDir, appName }) {
263
+ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
228
264
  const routerAppName = `${appName}-9router`;
229
265
  execSync(
230
266
  `pm2 start "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update" --name "${routerAppName}" --cwd "${projectDir.replace(/\\/g, '/')}"`,
@@ -235,6 +271,18 @@ function startNative9RouterPm2({ isVi, projectDir, appName }) {
235
271
  env: process.env
236
272
  }
237
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
+ }
238
286
  runPm2Save({ projectDir, isVi });
239
287
  console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
240
288
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
@@ -893,17 +941,31 @@ const sync = async () => {
893
941
  try {
894
942
  let db = {};
895
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
+ };
896
953
  const a = (db.providerConnections || [])
897
954
  .filter(c => c && c.provider && c.isActive !== false && !c.disabled)
898
955
  .map(c => c.provider);
899
- if (!a.length) return;
956
+ if (!a.length) {
957
+ removeSmartRoute();
958
+ return;
959
+ }
900
960
 
901
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'];
902
962
  a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
903
963
 
904
964
  const m = a.flatMap(p => PM[p] || []);
905
- if (!m.length) return;
906
- if (!db.combos) db.combos = [];
965
+ if (!m.length) {
966
+ removeSmartRoute();
967
+ return;
968
+ }
907
969
 
908
970
  const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
909
971
  const i = db.combos.findIndex(x => x.id === 'smart-route');
@@ -1596,7 +1658,11 @@ ${hasBrowserDesktop ? ` extra_hosts:
1596
1658
  }
1597
1659
  botConfig.channels['telegram'] = telegramConfig;
1598
1660
  } else if (channelKey === 'zalo-personal') {
1599
- botConfig.channels['zalo'] = { enabled: true, provider: 'client', autoReply: true };
1661
+ botConfig.channels['zalouser'] = {
1662
+ enabled: true,
1663
+ dmPolicy: 'pairing',
1664
+ autoReply: true
1665
+ };
1600
1666
  } else if (channelKey === 'zalo-bot') {
1601
1667
  botConfig.channels['zalo'] = { enabled: true, provider: 'official_account' };
1602
1668
  }
@@ -1833,8 +1899,7 @@ fi
1833
1899
  : ' → Run scripts/telegram-post-install-check.mjs to get the real links, verify group/privacy, then add the bots and disable privacy mode.'));
1834
1900
  }
1835
1901
  } else if (channelKey === 'zalo-personal') {
1836
- 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):'}`));
1837
- console.log(`cd ${projectDir} && docker compose exec -it openclaw bun run core:onboard`);
1902
+ printZaloPersonalLoginInfo({ isVi, deployMode: 'docker', projectDir });
1838
1903
  }
1839
1904
  } else {
1840
1905
  console.log(chalk.red(`\n\u274c Docker exited with code ${code}`));
@@ -1922,6 +1987,11 @@ fi
1922
1987
  console.log(chalk.green(isVi ? '✅ 9Router da cai xong!' : '✅ 9Router installed!'));
1923
1988
  }
1924
1989
 
1990
+ let native9RouterSyncScriptPath = null;
1991
+ if (providerKey === '9router') {
1992
+ native9RouterSyncScriptPath = await writeNative9RouterSyncScript(projectDir);
1993
+ }
1994
+
1925
1995
  await syncLocalConfigToHome(projectDir, isVi);
1926
1996
 
1927
1997
  if (isMultiBot && channelKey === 'telegram') {
@@ -1938,7 +2008,7 @@ fi
1938
2008
 
1939
2009
  if (isMultiBot && channelKey === 'telegram') {
1940
2010
  if (providerKey === '9router') {
1941
- startNative9RouterPm2({ isVi, projectDir, appName: botName || 'openclaw-multibot' });
2011
+ startNative9RouterPm2({ isVi, projectDir, appName: botName || 'openclaw-multibot', syncScriptPath: native9RouterSyncScriptPath });
1942
2012
  }
1943
2013
  execSync('pm2 start ecosystem.config.js && pm2 save', {
1944
2014
  cwd: projectDir,
@@ -1948,10 +2018,13 @@ fi
1948
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.'}`));
1949
2019
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${botName || 'openclaw-multibot'}` : ` View logs: pm2 logs ${botName || 'openclaw-multibot'}`));
1950
2020
  printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
2021
+ if (channelKey === 'zalo-personal') {
2022
+ printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
2023
+ }
1951
2024
  } else {
1952
2025
  const appName = botName || 'openclaw';
1953
2026
  if (providerKey === '9router') {
1954
- startNative9RouterPm2({ isVi, projectDir, appName });
2027
+ startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath: native9RouterSyncScriptPath });
1955
2028
  }
1956
2029
  execSync(`pm2 start "openclaw gateway run" --name "${appName}" --cwd "${projectDir.replace(/\\/g, '/')}" && pm2 save`, {
1957
2030
  cwd: projectDir,
@@ -1961,6 +2034,9 @@ fi
1961
2034
  console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoan tat! Bot native dang chay qua PM2.' : 'Setup complete! Native bot is running via PM2.'}`));
1962
2035
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${appName}` : ` View logs: pm2 logs ${appName}`));
1963
2036
  printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
2037
+ if (channelKey === 'zalo-personal') {
2038
+ printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
2039
+ }
1964
2040
  }
1965
2041
  } else {
1966
2042
  if (providerKey === '9router') {
@@ -1971,10 +2047,21 @@ fi
1971
2047
  stdio: 'ignore',
1972
2048
  shell: process.platform === 'win32'
1973
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
+ }
1974
2058
  console.log(chalk.gray(isVi
1975
2059
  ? ' 9Router dashboard: http://localhost:20128/dashboard'
1976
2060
  : ' 9Router dashboard: http://localhost:20128/dashboard'));
1977
2061
  }
2062
+ if (channelKey === 'zalo-personal') {
2063
+ printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
2064
+ }
1978
2065
  console.log(chalk.yellow(`\n${isVi ? 'Khoi dong native bot (foreground)...' : 'Starting native bot (foreground)...'}`));
1979
2066
  const child = spawn('openclaw', ['gateway', 'run'], {
1980
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.9+ 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.9+ 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.9",
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,7 +106,13 @@ 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]*runPm2Save\(\{ projectDir, isVi \}\)/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
 
@@ -159,6 +183,24 @@ checks.push(() => expectMatch(
159
183
  'Native script generation must install and start a standalone 9Router dashboard on port 20128'
160
184
  ));
161
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
+
162
204
  checks.push(() => expectMatch(
163
205
  cli,
164
206
  /readdirSync\(dir\)\.find\(n=>\/\^gateway-cli-.*\\\\\.js\$\/\.test\(n\)\)[\s\S]*skipping timeout patch/,
@@ -201,6 +243,12 @@ checks.push(() => expectMatch(
201
243
  'Auto-steps summary must mention OpenClaw CLI installation'
202
244
  ));
203
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
+
204
252
  for (const check of checks) {
205
253
  check();
206
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}`);