create-openclaw-bot 5.0.5 → 5.0.7

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.5] — 2026-04-06
3
+ ## [5.0.7] — 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.5] — 2026-04-06
3
+ ## [5.0.7] — 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.5-0EA5E9?style=for-the-badge" alt="Version 5.0.5" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.0.7-0EA5E9?style=for-the-badge" alt="Version 5.0.7" /></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.5
27
+ ## 🆕 What's new in v5.0.7
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.5 for me.
114
+ Read SETUP.md and set up OpenClaw v5.0.7 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.5-0EA5E9?style=for-the-badge" alt="Version 5.0.5" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.0.7-0EA5E9?style=for-the-badge" alt="Version 5.0.7" /></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.5
27
+ ## 🆕 Có gì mới trong v5.0.7
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.5 for me.
114
+ Read SETUP.md and set up OpenClaw v5.0.7 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
@@ -50,14 +50,104 @@ function isPm2Installed() {
50
50
  }
51
51
  }
52
52
 
53
+ function is9RouterInstalled() {
54
+ try {
55
+ execSync('9router --help', { stdio: 'ignore' });
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ function getUserNpmPrefixInfo() {
63
+ if (process.platform === 'win32') {
64
+ return null;
65
+ }
66
+
67
+ const prefixDir = path.join(os.homedir(), '.local');
68
+ return {
69
+ prefixDir,
70
+ binDir: path.join(prefixDir, 'bin')
71
+ };
72
+ }
73
+
74
+ function ensureBinDirOnPath(binDir) {
75
+ const delimiter = path.delimiter;
76
+ const pathParts = String(process.env.PATH || '').split(delimiter).filter(Boolean);
77
+ if (!pathParts.includes(binDir)) {
78
+ process.env.PATH = [binDir, ...pathParts].join(delimiter);
79
+ }
80
+ }
81
+
82
+ function appendLineIfMissing(filePath, line) {
83
+ let content = '';
84
+ if (fs.existsSync(filePath)) {
85
+ content = fs.readFileSync(filePath, 'utf8');
86
+ }
87
+
88
+ if (!content.includes(line)) {
89
+ const prefix = content && !content.endsWith('\n') ? '\n' : '';
90
+ fs.appendFileSync(filePath, `${prefix}${line}\n`);
91
+ }
92
+ }
93
+
94
+ function ensureUserWritableGlobalNpm({ isVi, osChoice }) {
95
+ if (process.platform === 'win32') {
96
+ return true;
97
+ }
98
+
99
+ const npmInfo = getUserNpmPrefixInfo();
100
+ if (!npmInfo) {
101
+ return true;
102
+ }
103
+
104
+ try {
105
+ fs.ensureDirSync(npmInfo.binDir);
106
+ process.env.npm_config_prefix = npmInfo.prefixDir;
107
+ ensureBinDirOnPath(npmInfo.binDir);
108
+
109
+ execSync(`npm config set prefix "${npmInfo.prefixDir.replace(/"/g, '\\"')}"`, {
110
+ stdio: 'ignore',
111
+ shell: true,
112
+ env: process.env
113
+ });
114
+
115
+ appendLineIfMissing(path.join(os.homedir(), '.profile'), 'export PATH="$HOME/.local/bin:$PATH"');
116
+ appendLineIfMissing(
117
+ path.join(os.homedir(), osChoice === 'macos' ? '.zshrc' : '.bashrc'),
118
+ 'export PATH="$HOME/.local/bin:$PATH"'
119
+ );
120
+ return true;
121
+ } catch {
122
+ console.log(chalk.yellow(isVi
123
+ ? '⚠️ Không thể cấu hình npm global prefix trong ~/.local. Tiếp tục thử cài đặt trực tiếp.'
124
+ : '⚠️ Could not configure npm global prefix in ~/.local. Falling back to direct install.'));
125
+ return false;
126
+ }
127
+ }
128
+
129
+ const userNpmInfo = getUserNpmPrefixInfo();
130
+ if (userNpmInfo) {
131
+ ensureBinDirOnPath(userNpmInfo.binDir);
132
+ }
133
+
53
134
  function installGlobalPackage(pkg, { isVi, osChoice, displayName }) {
54
- const installCommands = osChoice === 'windows'
55
- ? [`npm install -g ${pkg}`]
56
- : [`npm install -g ${pkg}`, `sudo npm install -g ${pkg}`];
135
+ const installCommands = [];
136
+
137
+ if (osChoice === 'windows') {
138
+ installCommands.push(`npm install -g ${pkg}`);
139
+ } else {
140
+ ensureUserWritableGlobalNpm({ isVi, osChoice });
141
+ installCommands.push(`npm install -g ${pkg}`);
142
+ const npmInfo = getUserNpmPrefixInfo();
143
+ if (npmInfo) {
144
+ installCommands.push(`npm install -g --prefix "${npmInfo.prefixDir.replace(/"/g, '\\"')}" ${pkg}`);
145
+ }
146
+ }
57
147
 
58
148
  for (const cmd of installCommands) {
59
149
  try {
60
- execSync(cmd, { stdio: 'inherit', shell: true });
150
+ execSync(cmd, { stdio: 'inherit', shell: true, env: process.env });
61
151
  return true;
62
152
  } catch {
63
153
  // try next candidate
@@ -65,11 +155,75 @@ function installGlobalPackage(pkg, { isVi, osChoice, displayName }) {
65
155
  }
66
156
 
67
157
  console.log(chalk.yellow(isVi
68
- ? `⚠️ Không thể tự cài ${displayName}. Chạy thủ công: ${osChoice === 'windows' ? `npm install -g ${pkg}` : `sudo npm install -g ${pkg}`}`
69
- : `⚠️ Could not auto-install ${displayName}. Run manually: ${osChoice === 'windows' ? `npm install -g ${pkg}` : `sudo npm install -g ${pkg}`}`));
158
+ ? `⚠️ Không thể tự cài ${displayName}. Chạy thủ công: ${osChoice === 'windows' ? `npm install -g ${pkg}` : `npm config set prefix ~/.local && npm install -g ${pkg}`}`
159
+ : `⚠️ Could not auto-install ${displayName}. Run manually: ${osChoice === 'windows' ? `npm install -g ${pkg}` : `npm config set prefix ~/.local && npm install -g ${pkg}`}`));
70
160
  return false;
71
161
  }
72
162
 
163
+ function extractFirstHttpUrl(text) {
164
+ const match = String(text || '').match(/https?:\/\/[^\s"'`]+/);
165
+ return match ? match[0] : null;
166
+ }
167
+
168
+ function getTokenizedDashboardUrl(projectDir) {
169
+ try {
170
+ const output = execSync('openclaw dashboard', {
171
+ cwd: projectDir,
172
+ env: process.env,
173
+ encoding: 'utf8',
174
+ shell: true,
175
+ stdio: ['ignore', 'pipe', 'pipe'],
176
+ timeout: 15000
177
+ });
178
+ return extractFirstHttpUrl(output);
179
+ } catch (error) {
180
+ const combined = `${error?.stdout || ''}\n${error?.stderr || ''}`;
181
+ return extractFirstHttpUrl(combined);
182
+ }
183
+ }
184
+
185
+ function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gatewayPort = 18791 }) {
186
+ const dashboardUrl = `http://localhost:${gatewayPort}`;
187
+ const tokenizedUrl = getTokenizedDashboardUrl(projectDir);
188
+
189
+ console.log(chalk.yellow(`\n🧭 ${isVi ? 'Dashboard OpenClaw:' : 'OpenClaw Dashboard:'} ${dashboardUrl}`));
190
+
191
+ if (tokenizedUrl) {
192
+ console.log(chalk.green(isVi
193
+ ? ` → Mở link đã kèm token: ${tokenizedUrl}`
194
+ : ` → Open the tokenized link directly: ${tokenizedUrl}`));
195
+ } else {
196
+ console.log(chalk.gray(isVi
197
+ ? ' → Nếu dashboard đòi Gateway Token, chạy: openclaw dashboard'
198
+ : ' → If the dashboard asks for a Gateway Token, run: openclaw dashboard'));
199
+ }
200
+
201
+ if (providerKey === '9router') {
202
+ console.log(chalk.yellow(`\n🔀 ${isVi ? '9Router Dashboard:' : '9Router Dashboard:'} http://localhost:20128/dashboard`));
203
+ console.log(chalk.gray(isVi
204
+ ? ' → Mở dashboard 9Router → đăng nhập OAuth → kết nối provider miễn phí'
205
+ : ' → Open the 9Router dashboard → complete OAuth login → connect a free provider'));
206
+ console.log(chalk.gray(isVi
207
+ ? ' → Sau khi login 9Router xong, bot sẽ tự dùng model smart-route qua http://localhost:20128/v1'
208
+ : ' → Once 9Router is logged in, the bot will use smart-route through http://localhost:20128/v1'));
209
+ }
210
+ }
211
+
212
+ function startNative9RouterPm2({ isVi, projectDir, appName }) {
213
+ const routerAppName = `${appName}-9router`;
214
+ 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`,
216
+ {
217
+ cwd: projectDir,
218
+ stdio: 'inherit',
219
+ shell: true,
220
+ env: process.env
221
+ }
222
+ );
223
+ console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
224
+ console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
225
+ }
226
+
73
227
  async function syncLocalConfigToHome(projectDir, isVi) {
74
228
  const homedir = os.homedir();
75
229
  const globalClawDir = path.join(homedir, '.openclaw');
@@ -1109,7 +1263,7 @@ ${hasBrowserDesktop ? ` extra_hosts:
1109
1263
  mode: 'merge',
1110
1264
  providers: {
1111
1265
  '9router': {
1112
- baseUrl: 'http://9router:20128/v1',
1266
+ baseUrl: deployMode === 'native' ? 'http://localhost:20128/v1' : 'http://9router:20128/v1',
1113
1267
  apiKey: 'sk-no-key',
1114
1268
  api: 'openai-completions',
1115
1269
  models: [
@@ -1355,7 +1509,7 @@ ${hasBrowserDesktop ? ` extra_hosts:
1355
1509
  mode: 'merge',
1356
1510
  providers: {
1357
1511
  '9router': {
1358
- baseUrl: 'http://9router:20128/v1',
1512
+ baseUrl: deployMode === 'native' ? 'http://localhost:20128/v1' : 'http://9router:20128/v1',
1359
1513
  apiKey: 'sk-no-key',
1360
1514
  api: 'openai-completions',
1361
1515
  models: [
@@ -1712,7 +1866,7 @@ fi
1712
1866
  : `Could not auto-sync config. Run manually:\n cp -rn ${localClawDir}/. ${globalClawDir}/`}`));
1713
1867
  }
1714
1868
 
1715
- console.log(chalk.cyan(`\n👉 ${isVi ? 'Đã tạo xong file cấu hình native.' : 'Native config files are ready.'}`));
1869
+ console.log(chalk.cyan(`\n👉 ${isVi ? 'Đã tạo xong file cấu hình Docker.' : 'Docker config files are ready.'}`));
1716
1870
  console.log(chalk.gray(isVi
1717
1871
  ? ` Cấu trúc config: ${isMultiBot && channelKey === 'telegram' ? '.openclaw/ dùng chung + agents/workspace-*' : (isMultiBot ? 'bot1/, bot2/, ...' : '.openclaw/')}`
1718
1872
  : ` Config layout: ${isMultiBot && channelKey === 'telegram' ? 'shared .openclaw/ with agents/workspace-*' : (isMultiBot ? 'bot1/, bot2/, ...' : '.openclaw/')}`));
@@ -1736,12 +1890,22 @@ fi
1736
1890
  console.log(chalk.cyan(isVi
1737
1891
  ? '\n📦 Dang cai openclaw binary (npm install -g openclaw)...'
1738
1892
  : '\n📦 Installing openclaw binary (npm install -g openclaw)...'));
1739
- if (!installGlobalPackage('openclaw', { isVi, osChoice, displayName: 'openclaw' })) {
1893
+ if (!installGlobalPackage('openclaw@latest', { isVi, osChoice, displayName: 'openclaw' })) {
1740
1894
  process.exit(1);
1741
1895
  }
1742
1896
  console.log(chalk.green(isVi ? '✅ openclaw da cai xong!' : '✅ openclaw installed!'));
1743
1897
  }
1744
1898
 
1899
+ if (providerKey === '9router' && !is9RouterInstalled()) {
1900
+ console.log(chalk.cyan(isVi
1901
+ ? '\n📦 Dang cai 9Router binary (npm install -g 9router)...'
1902
+ : '\n📦 Installing 9Router binary (npm install -g 9router)...'));
1903
+ if (!installGlobalPackage('9router@latest', { isVi, osChoice, displayName: '9Router' })) {
1904
+ process.exit(1);
1905
+ }
1906
+ console.log(chalk.green(isVi ? '✅ 9Router da cai xong!' : '✅ 9Router installed!'));
1907
+ }
1908
+
1745
1909
  await syncLocalConfigToHome(projectDir, isVi);
1746
1910
 
1747
1911
  if (isMultiBot && channelKey === 'telegram') {
@@ -1751,12 +1915,15 @@ fi
1751
1915
  if (osChoice === 'vps') {
1752
1916
  if (!isPm2Installed()) {
1753
1917
  console.log(chalk.cyan(isVi ? '\n📦 Dang cai PM2...' : '\n📦 Installing PM2...'));
1754
- if (!installGlobalPackage('pm2', { isVi, osChoice, displayName: 'PM2' })) {
1918
+ if (!installGlobalPackage('pm2@latest', { isVi, osChoice, displayName: 'PM2' })) {
1755
1919
  process.exit(1);
1756
1920
  }
1757
1921
  }
1758
1922
 
1759
1923
  if (isMultiBot && channelKey === 'telegram') {
1924
+ if (providerKey === '9router') {
1925
+ startNative9RouterPm2({ isVi, projectDir, appName: botName || 'openclaw-multibot' });
1926
+ }
1760
1927
  execSync('pm2 start ecosystem.config.js && pm2 save', {
1761
1928
  cwd: projectDir,
1762
1929
  stdio: 'inherit',
@@ -1764,8 +1931,12 @@ fi
1764
1931
  });
1765
1932
  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.'}`));
1766
1933
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${botName || 'openclaw-multibot'}` : ` View logs: pm2 logs ${botName || 'openclaw-multibot'}`));
1934
+ printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
1767
1935
  } else {
1768
1936
  const appName = botName || 'openclaw';
1937
+ if (providerKey === '9router') {
1938
+ startNative9RouterPm2({ isVi, projectDir, appName });
1939
+ }
1769
1940
  execSync(`pm2 start "openclaw gateway run" --name "${appName}" --cwd "${projectDir.replace(/\\/g, '/')}" && pm2 save`, {
1770
1941
  cwd: projectDir,
1771
1942
  stdio: 'inherit',
@@ -1773,8 +1944,21 @@ fi
1773
1944
  });
1774
1945
  console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoan tat! Bot native dang chay qua PM2.' : 'Setup complete! Native bot is running via PM2.'}`));
1775
1946
  console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${appName}` : ` View logs: pm2 logs ${appName}`));
1947
+ printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
1776
1948
  }
1777
1949
  } else {
1950
+ if (providerKey === '9router') {
1951
+ console.log(chalk.yellow(`\n${isVi ? 'Khoi dong 9Router native (background)...' : 'Starting native 9Router (background)...'}`));
1952
+ spawn('9router', ['-n', '-t', '-l', '-H', '0.0.0.0', '-p', '20128', '--skip-update'], {
1953
+ cwd: projectDir,
1954
+ detached: true,
1955
+ stdio: 'ignore',
1956
+ shell: process.platform === 'win32'
1957
+ }).unref();
1958
+ console.log(chalk.gray(isVi
1959
+ ? ' 9Router dashboard: http://localhost:20128/dashboard'
1960
+ : ' 9Router dashboard: http://localhost:20128/dashboard'));
1961
+ }
1778
1962
  console.log(chalk.yellow(`\n${isVi ? 'Khoi dong native bot (foreground)...' : 'Starting native bot (foreground)...'}`));
1779
1963
  const child = spawn('openclaw', ['gateway', 'run'], {
1780
1964
  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.5+ natively supports deployment script generation for Windows, Linux, VPS, and Hosting environments.
5
+ OpenClaw v5.0.7+ 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.5+ tự động sinh sẵn các script cài đặt dành riêng cho Windows, Linux, VPS và Hosting.
5
+ OpenClaw v5.0.7+ 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.5",
3
+ "version": "5.0.7",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/setup.js CHANGED
@@ -2836,18 +2836,18 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
2836
2836
  const pluginCmd = allPlugins.length > 0 ? ('npm exec openclaw plugins install ' + allPlugins.join(' ')) : '';
2837
2837
 
2838
2838
  // ─── Shared initializer (provider install) ───────────────────────────────
2839
- function providerLines(arr, shell) {
2840
- if (is9Router) {
2841
- if (shell === 'bat') {
2842
- arr.push('npm install -g 9router');
2843
- arr.push('start "9Router" cmd /k "9router"');
2844
- arr.push('timeout /t 5 /nobreak >nul');
2845
- } else {
2846
- arr.push('npm install -g 9router');
2847
- arr.push('9router &');
2848
- arr.push('sleep 3');
2849
- }
2850
- } else if (isOllama) {
2839
+ function providerLines(arr, shell) {
2840
+ if (is9Router) {
2841
+ if (shell === 'bat') {
2842
+ arr.push('npm install -g 9router');
2843
+ arr.push('start "9Router" cmd /k "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update"');
2844
+ arr.push('timeout /t 5 /nobreak >nul');
2845
+ } else {
2846
+ arr.push('npm install -g 9router');
2847
+ arr.push('nohup 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update >/tmp/9router.log 2>&1 &');
2848
+ arr.push('sleep 3');
2849
+ }
2850
+ } else if (isOllama) {
2851
2851
  if (shell === 'bat') {
2852
2852
  arr.push('where ollama >nul 2>&1 || (powershell -Command "Invoke-WebRequest -Uri https://ollama.com/download/OllamaSetup.exe -OutFile OllamaSetup.exe" && OllamaSetup.exe && del OllamaSetup.exe)');
2853
2853
  arr.push('ollama pull ' + selectedModel);
@@ -3452,12 +3452,17 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3452
3452
  } else if (state.nativeOs === 'linux') {
3453
3453
  const isDocker = state.deployMode === 'docker';
3454
3454
  scriptName = isDocker ? 'setup-openclaw-docker-macos.sh' : 'setup-openclaw-macos.sh';
3455
- const sh = [
3456
- '#!/usr/bin/env bash', 'set -e',
3457
- `echo "=== OpenClaw Setup — macOS${isDocker ? ' Docker' : ' Native'} ==="`,
3458
- 'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
3459
- 'npm install -g openclaw@latest',
3460
- ];
3455
+ const sh = [
3456
+ '#!/usr/bin/env bash', 'set -e',
3457
+ `echo "=== OpenClaw Setup — macOS${isDocker ? ' Docker' : ' Native'} ==="`,
3458
+ 'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
3459
+ 'mkdir -p "$HOME/.local/bin"',
3460
+ 'npm config set prefix "$HOME/.local"',
3461
+ 'export PATH="$HOME/.local/bin:$PATH"',
3462
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.zshrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.zshrc"',
3463
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3464
+ 'npm install -g openclaw@latest',
3465
+ ];
3461
3466
  providerLines(sh, 'sh');
3462
3467
  if (pluginCmd) sh.push(pluginCmd);
3463
3468
 
@@ -3474,16 +3479,21 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3474
3479
  // ─── VPS/Ubuntu PM2 .SH ──────────────────────────────────────────────────
3475
3480
  } else if (state.nativeOs === 'vps') {
3476
3481
  scriptName = 'setup-openclaw-vps.sh';
3477
- const vps = [
3478
- '#!/usr/bin/env bash', 'set -e',
3479
- `echo "=== OpenClaw Setup — Ubuntu/VPS${isMultiBot ? ` Multi-Bot (${state.botCount} bots)` : ''} ==="`,
3480
- '# Auto-install Node.js 20 LTS if missing',
3481
- 'if ! command -v node > /dev/null 2>&1; then',
3482
- ' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
3483
- ' sudo apt-get install -y nodejs',
3484
- 'fi',
3485
- 'npm install -g openclaw@latest pm2',
3486
- ];
3482
+ const vps = [
3483
+ '#!/usr/bin/env bash', 'set -e',
3484
+ `echo "=== OpenClaw Setup — Ubuntu/VPS${isMultiBot ? ` Multi-Bot (${state.botCount} bots)` : ''} ==="`,
3485
+ '# Auto-install Node.js 20 LTS if missing',
3486
+ 'if ! command -v node > /dev/null 2>&1; then',
3487
+ ' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
3488
+ ' sudo apt-get install -y nodejs',
3489
+ 'fi',
3490
+ 'mkdir -p "$HOME/.local/bin"',
3491
+ 'npm config set prefix "$HOME/.local"',
3492
+ 'export PATH="$HOME/.local/bin:$PATH"',
3493
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3494
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3495
+ 'npm install -g openclaw@latest pm2@latest',
3496
+ ];
3487
3497
  providerLines(vps, 'sh');
3488
3498
  if (pluginCmd) vps.push(pluginCmd);
3489
3499
 
@@ -3509,15 +3519,20 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3509
3519
  // ─── Linux Desktop .SH ───────────────────────────────────────────────────
3510
3520
  } else if (state.nativeOs === 'linux-desktop') {
3511
3521
  scriptName = 'setup-openclaw-linux.sh';
3512
- const lnx = [
3513
- '#!/usr/bin/env bash', 'set -e',
3514
- `echo "=== OpenClaw Setup — Linux Desktop${isMultiBot ? ' Multi-Bot' : ''} ==="`,
3515
- 'if ! command -v node > /dev/null 2>&1; then',
3516
- ' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
3517
- ' sudo apt-get install -y nodejs',
3518
- 'fi',
3519
- 'npm install -g openclaw@latest',
3520
- ];
3522
+ const lnx = [
3523
+ '#!/usr/bin/env bash', 'set -e',
3524
+ `echo "=== OpenClaw Setup — Linux Desktop${isMultiBot ? ' Multi-Bot' : ''} ==="`,
3525
+ 'if ! command -v node > /dev/null 2>&1; then',
3526
+ ' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
3527
+ ' sudo apt-get install -y nodejs',
3528
+ 'fi',
3529
+ 'mkdir -p "$HOME/.local/bin"',
3530
+ 'npm config set prefix "$HOME/.local"',
3531
+ 'export PATH="$HOME/.local/bin:$PATH"',
3532
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3533
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3534
+ 'npm install -g openclaw@latest',
3535
+ ];
3521
3536
  providerLines(lnx, 'sh');
3522
3537
  if (pluginCmd) lnx.push(pluginCmd);
3523
3538
 
@@ -40,16 +40,28 @@ checks.push(() => expectMatch(
40
40
 
41
41
  checks.push(() => expectMatch(
42
42
  cli,
43
- /if \(!isOpenClawInstalled\(\)\) \{[\s\S]*installGlobalPackage\('openclaw', \{ isVi, osChoice, displayName: 'openclaw' \}\)/,
43
+ /if \(!isOpenClawInstalled\(\)\) \{[\s\S]*installGlobalPackage\('openclaw@latest', \{ isVi, osChoice, displayName: 'openclaw' \}\)/,
44
44
  'Native branch must auto-install openclaw'
45
45
  ));
46
46
 
47
47
  checks.push(() => expectMatch(
48
48
  cli,
49
- /if \(osChoice === 'vps'\) \{[\s\S]*installGlobalPackage\('pm2', \{ isVi, osChoice, displayName: 'PM2' \}\)/,
49
+ /if \(osChoice === 'vps'\) \{[\s\S]*installGlobalPackage\('pm2@latest', \{ isVi, osChoice, displayName: 'PM2' \}\)/,
50
50
  'VPS native branch must auto-install PM2'
51
51
  ));
52
52
 
53
+ checks.push(() => expectMatch(
54
+ cli,
55
+ /if \(providerKey === '9router' && !is9RouterInstalled\(\)\) \{[\s\S]*installGlobalPackage\('9router@latest', \{ isVi, osChoice, displayName: '9Router' \}\)/,
56
+ 'Native 9Router flow must auto-install 9Router'
57
+ ));
58
+
59
+ checks.push(() => expectMatch(
60
+ cli,
61
+ /function ensureUserWritableGlobalNpm\(\{ isVi, osChoice \}\) \{[\s\S]*process\.env\.npm_config_prefix = npmInfo\.prefixDir[\s\S]*npm config set prefix "\$\{npmInfo\.prefixDir\.replace/s,
62
+ 'Native CLI must configure a user-writable npm global prefix for non-Windows installs'
63
+ ));
64
+
53
65
  checks.push(() => expectMatch(
54
66
  cli,
55
67
  /execSync\('pm2 start ecosystem\.config\.js && pm2 save'/,
@@ -62,6 +74,24 @@ checks.push(() => expectMatch(
62
74
  'Native single-bot VPS must start gateway through PM2'
63
75
  ));
64
76
 
77
+ checks.push(() => expectMatch(
78
+ cli,
79
+ /function printNativeDashboardAccessInfo\(\{ isVi, providerKey, projectDir, gatewayPort = 18791 \}\) \{[\s\S]*openclaw dashboard[\s\S]*9Router Dashboard:[\s\S]*localhost:20128\/dashboard/s,
80
+ 'Native PM2 flow must expose dashboard access info and the tokenized dashboard command'
81
+ ));
82
+
83
+ checks.push(() => expectMatch(
84
+ cli,
85
+ /baseUrl: deployMode === 'native' \? 'http:\/\/localhost:20128\/v1' : 'http:\/\/9router:20128\/v1'/,
86
+ 'Native 9Router config must target localhost instead of the Docker hostname'
87
+ ));
88
+
89
+ checks.push(() => expectMatch(
90
+ 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,
92
+ 'VPS native 9Router flow must start a standalone 9Router dashboard on port 20128 via PM2'
93
+ ));
94
+
65
95
  checks.push(() => expectMatch(
66
96
  cli,
67
97
  /const child = spawn\('openclaw', \['gateway', 'run'\], \{/,
@@ -107,16 +137,22 @@ checks.push(() => expectMatch(
107
137
 
108
138
  checks.push(() => expectMatch(
109
139
  setup,
110
- /else if \(state\.nativeOs === 'linux'\) \{[\s\S]*scriptName = isDocker \? 'setup-openclaw-docker-macos\.sh' : 'setup-openclaw-macos\.sh';[\s\S]*npm install -g openclaw@latest[\s\S]*openclaw gateway run/s,
140
+ /else if \(state\.nativeOs === 'linux'\) \{[\s\S]*scriptName = isDocker \? 'setup-openclaw-docker-macos\.sh' : 'setup-openclaw-macos\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@latest[\s\S]*openclaw gateway run/s,
111
141
  'macOS script generation must use the correct file name and start command'
112
142
  ));
113
143
 
114
144
  checks.push(() => expectMatch(
115
145
  setup,
116
- /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*scriptName = 'setup-openclaw-vps\.sh';[\s\S]*npm install -g openclaw@latest pm2[\s\S]*pm2 save && pm2 startup/s,
146
+ /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*scriptName = 'setup-openclaw-vps\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@latest pm2@latest[\s\S]*pm2 save && pm2 startup/s,
117
147
  'VPS native script generation must install openclaw+pm2 and persist PM2 startup'
118
148
  ));
119
149
 
150
+ checks.push(() => expectMatch(
151
+ setup,
152
+ /function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*9router -n -t -l -H 0\.0\.0\.0 -p 20128 --skip-update/s,
153
+ 'Native script generation must install and start a standalone 9Router dashboard on port 20128'
154
+ ));
155
+
120
156
  checks.push(() => expectMatch(
121
157
  setup,
122
158
  /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw-multibot/s,
@@ -131,7 +167,7 @@ checks.push(() => expectMatch(
131
167
 
132
168
  checks.push(() => expectMatch(
133
169
  setup,
134
- /else if \(state\.nativeOs === 'linux-desktop'\) \{[\s\S]*scriptName = 'setup-openclaw-linux\.sh';[\s\S]*npm install -g openclaw@latest[\s\S]*openclaw gateway run/s,
170
+ /else if \(state\.nativeOs === 'linux-desktop'\) \{[\s\S]*scriptName = 'setup-openclaw-linux\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@latest[\s\S]*openclaw gateway run/s,
135
171
  'Linux Desktop native script generation must install openclaw and run the gateway'
136
172
  ));
137
173