create-openclaw-bot 5.0.9 → 5.1.1
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 +33 -3
- package/CHANGELOG.vi.md +33 -3
- package/README.md +5 -4
- package/README.vi.md +5 -4
- package/cli.js +2376 -1998
- package/docs/install-native.md +1 -1
- package/docs/install-native.vi.md +1 -1
- package/package.json +1 -1
- package/setup.js +132 -128
- package/tests/smoke-cli-logic.mjs +157 -5
package/docs/install-native.md
CHANGED
|
@@ -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
|
+
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
|
|
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
package/setup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* ============================================
|
|
1
|
+
/* ============================================
|
|
2
2
|
OpenClaw Setup Wizard — Logic v2
|
|
3
3
|
Multi-model, Multi-plugin, Multi-channel
|
|
4
4
|
============================================ */
|
|
@@ -277,7 +277,7 @@
|
|
|
277
277
|
envExtra: '',
|
|
278
278
|
credSteps: [
|
|
279
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: '
|
|
280
|
+
{ textVi: 'Native setup sẽ tự chạy login và copy QR về thư mục project. Nếu cần chạy lại thủ công, dùng <code>openclaw channels login --channel zalouser --verbose</code>.', textEn: 'Native setup now auto-runs the login flow and copies the QR into the project folder. If needed, rerun it manually with <code>openclaw channels login --channel zalouser --verbose</code>.' },
|
|
281
281
|
],
|
|
282
282
|
channelConfig: {
|
|
283
283
|
zalouser: {
|
|
@@ -728,8 +728,8 @@
|
|
|
728
728
|
icon: '🐧',
|
|
729
729
|
titleVi: 'Ubuntu / VPS — Khuyên dùng Native (Không Docker)',
|
|
730
730
|
titleEn: 'Ubuntu / VPS — Recommended: Native (No Docker)',
|
|
731
|
-
descVi: 'Chạy thẳng trên máy, tiết kiệm RAM, khởi động nhanh. Script tự cài Node.js 20 LTS, OpenClaw CLI, PM2, 9Router/Ollama và giữ bot chạy liên tục sau reboot.',
|
|
732
|
-
descEn: 'Run directly on machine — lower RAM, faster startup. Script auto-installs Node.js 20 LTS, OpenClaw CLI, PM2, 9Router/Ollama and keeps bot running across reboots.',
|
|
731
|
+
descVi: 'Chạy thẳng trên máy, tiết kiệm RAM, khởi động nhanh. Script tự cài Node.js 20 LTS, OpenClaw CLI, PM2, 9Router/Ollama và giữ bot chạy liên tục sau reboot.',
|
|
732
|
+
descEn: 'Run directly on machine — lower RAM, faster startup. Script auto-installs Node.js 20 LTS, OpenClaw CLI, PM2, 9Router/Ollama and keeps bot running across reboots.',
|
|
733
733
|
deploy: 'native',
|
|
734
734
|
badgeVi: '💻 Native + PM2',
|
|
735
735
|
badgeEn: '💻 Native + PM2',
|
|
@@ -739,8 +739,8 @@
|
|
|
739
739
|
icon: '🖥️',
|
|
740
740
|
titleVi: 'Linux Desktop — Khuyên dùng Native',
|
|
741
741
|
titleEn: 'Linux Desktop — Recommended: Native',
|
|
742
|
-
descVi: 'Không cần Docker. Script tự cài Node.js 20 LTS nếu chưa có, cài OpenClaw CLI, rồi cài 9Router hoặc Ollama theo provider bạn chọn và khởi động bot ngay.',
|
|
743
|
-
descEn: 'No Docker needed. Script auto-installs Node.js 20 LTS if missing, installs OpenClaw CLI, then installs 9Router or Ollama based on your provider choice and starts the bot immediately.',
|
|
742
|
+
descVi: 'Không cần Docker. Script tự cài Node.js 20 LTS nếu chưa có, cài OpenClaw CLI, rồi cài 9Router hoặc Ollama theo provider bạn chọn và khởi động bot ngay.',
|
|
743
|
+
descEn: 'No Docker needed. Script auto-installs Node.js 20 LTS if missing, installs OpenClaw CLI, then installs 9Router or Ollama based on your provider choice and starts the bot immediately.',
|
|
744
744
|
deploy: 'native',
|
|
745
745
|
badgeVi: '💻 Native',
|
|
746
746
|
badgeEn: '💻 Native',
|
|
@@ -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
|
|
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
|
|
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) & ';
|
|
@@ -1815,7 +1815,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
|
|
|
1815
1815
|
|
|
1816
1816
|
ARG CACHEBUST=${Date.now()}
|
|
1817
1817
|
RUN npm install -g openclaw@latest${skillLines}${browserInstallLines}
|
|
1818
|
-
RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const file=(fs.readdirSync(dir).find(n=>/^gateway-cli-.*\\.js$/.test(n))||'');if(!file){console.warn('gateway cli dist file not found; skipping timeout patch');process.exit(0);}const p=path.join(dir,file);let s=fs.readFileSync(p,'utf8');const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';if(s.includes(to)){process.exit(0);}if(!s.includes(from)){console.warn('chat.send patch anchor not found; skipping timeout patch');process.exit(0);}s=s.replace(from,to);fs.writeFileSync(p,s);}"
|
|
1818
|
+
RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const file=(fs.readdirSync(dir).find(n=>/^gateway-cli-.*\\.js$/.test(n))||'');if(!file){console.warn('gateway cli dist file not found; skipping timeout patch');process.exit(0);}const p=path.join(dir,file);let s=fs.readFileSync(p,'utf8');const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';if(s.includes(to)){process.exit(0);}if(!s.includes(from)){console.warn('chat.send patch anchor not found; skipping timeout patch');process.exit(0);}s=s.replace(from,to);fs.writeFileSync(p,s);}"
|
|
1819
1819
|
WORKDIR /root/.openclaw
|
|
1820
1820
|
|
|
1821
1821
|
EXPOSE 18791
|
|
@@ -1841,17 +1841,34 @@ const sync = async () => {
|
|
|
1841
1841
|
try {
|
|
1842
1842
|
let db = {};
|
|
1843
1843
|
try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e){}
|
|
1844
|
-
|
|
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 res = await fetch('http://localhost:20128/api/providers');
|
|
1854
|
+
if (!res.ok) { console.log('[sync-combo] API not ready, retrying...'); return; }
|
|
1855
|
+
const d = await res.json();
|
|
1856
|
+
const a = (d.connections || [])
|
|
1845
1857
|
.filter(c => c && c.provider && c.isActive !== false && !c.disabled)
|
|
1846
1858
|
.map(c => c.provider);
|
|
1847
|
-
if (!a.length)
|
|
1859
|
+
if (!a.length) {
|
|
1860
|
+
removeSmartRoute();
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1848
1863
|
|
|
1849
1864
|
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
1865
|
a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
|
|
1851
1866
|
|
|
1852
|
-
const m = a.flatMap(
|
|
1853
|
-
if (!m.length)
|
|
1854
|
-
|
|
1867
|
+
const m = a.flatMap(pv => PM[pv] || []);
|
|
1868
|
+
if (!m.length) {
|
|
1869
|
+
removeSmartRoute();
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1855
1872
|
|
|
1856
1873
|
const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
|
|
1857
1874
|
const i = db.combos.findIndex(x => x.id === 'smart-route');
|
|
@@ -1868,7 +1885,7 @@ const sync = async () => {
|
|
|
1868
1885
|
}
|
|
1869
1886
|
} catch (e) { }
|
|
1870
1887
|
};
|
|
1871
|
-
sync
|
|
1888
|
+
setTimeout(sync, 5000);
|
|
1872
1889
|
setInterval(sync, INTERVAL);`;
|
|
1873
1890
|
|
|
1874
1891
|
let compose;
|
|
@@ -1903,9 +1920,7 @@ ${dependsOn}${extraHosts} volumes:
|
|
|
1903
1920
|
- -c
|
|
1904
1921
|
- |
|
|
1905
1922
|
npm install -g 9router
|
|
1906
|
-
|
|
1907
|
-
${syncScript.replace(/\$/g, '$$$$').replace(/\n/g, '\n ')}
|
|
1908
|
-
CLAWEOF
|
|
1923
|
+
node -e "require('fs').writeFileSync('/tmp/sync.js',${JSON.stringify(syncScript)})"
|
|
1909
1924
|
node /tmp/sync.js > /tmp/sync.log 2>&1 &
|
|
1910
1925
|
exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
|
|
1911
1926
|
environment:
|
|
@@ -2001,9 +2016,7 @@ ${extraHostsBlock}
|
|
|
2001
2016
|
- -c
|
|
2002
2017
|
- |
|
|
2003
2018
|
npm install -g 9router
|
|
2004
|
-
|
|
2005
|
-
${syncScript.replace(/\$/g, '$$$$').replace(/\n/g, '\n ')}
|
|
2006
|
-
CLAWEOF
|
|
2019
|
+
node -e "require('fs').writeFileSync('/tmp/sync.js',${JSON.stringify(syncScript)})"
|
|
2007
2020
|
node /tmp/sync.js > /tmp/sync.log 2>&1 &
|
|
2008
2021
|
exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
|
|
2009
2022
|
environment:
|
|
@@ -2835,19 +2848,30 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
|
|
|
2835
2848
|
if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
|
|
2836
2849
|
const pluginCmd = allPlugins.length > 0 ? ('npm exec openclaw plugins install ' + allPlugins.join(' ')) : '';
|
|
2837
2850
|
|
|
2851
|
+
function native9RouterSyncScriptContent() {
|
|
2852
|
+
return `const fs=require('fs');
|
|
2853
|
+
const path=require('path');
|
|
2854
|
+
const INTERVAL=30000;
|
|
2855
|
+
const p=path.join(process.env.HOME||process.env.USERPROFILE||'.','.9router','db.json');
|
|
2856
|
+
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']};
|
|
2857
|
+
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);`;
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2838
2860
|
// ─── 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 -n -t -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
2844
|
-
arr.push('
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
arr.push('
|
|
2848
|
-
arr.push('
|
|
2849
|
-
|
|
2850
|
-
|
|
2861
|
+
function providerLines(arr, shell) {
|
|
2862
|
+
if (is9Router) {
|
|
2863
|
+
if (shell === 'bat') {
|
|
2864
|
+
arr.push('npm install -g 9router');
|
|
2865
|
+
arr.push('start "9Router" cmd /k "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
2866
|
+
arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
|
|
2867
|
+
arr.push('timeout /t 5 /nobreak >nul');
|
|
2868
|
+
} else {
|
|
2869
|
+
arr.push('npm install -g 9router');
|
|
2870
|
+
arr.push('nohup 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update >/tmp/9router.log 2>&1 &');
|
|
2871
|
+
arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
|
|
2872
|
+
arr.push('sleep 3');
|
|
2873
|
+
}
|
|
2874
|
+
} else if (isOllama) {
|
|
2851
2875
|
if (shell === 'bat') {
|
|
2852
2876
|
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
2877
|
arr.push('ollama pull ' + selectedModel);
|
|
@@ -3022,6 +3046,7 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
|
|
|
3022
3046
|
'.openclaw/auth-profiles.json': sharedNativeAuthProfilesContent(),
|
|
3023
3047
|
'TELEGRAM-POST-INSTALL.md': buildTelegramPostInstallChecklist(),
|
|
3024
3048
|
};
|
|
3049
|
+
if (is9Router) files['.openclaw/9router-smart-route-sync.js'] = native9RouterSyncScriptContent();
|
|
3025
3050
|
const teamMd = isVi
|
|
3026
3051
|
? `# 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
3052
|
: `# 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.`;
|
|
@@ -3372,6 +3397,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3372
3397
|
files[`${base}/.openclaw/openclaw.json`] = botConfigContent(botIndex);
|
|
3373
3398
|
files[`${base}/.openclaw/exec-approvals.json`] = botExecApprovalsContent(botIndex);
|
|
3374
3399
|
files[`${base}/.openclaw/auth-profiles.json`] = botAuthProfilesContent(botIndex);
|
|
3400
|
+
if (is9Router) files[`${base}/.openclaw/9router-smart-route-sync.js`] = native9RouterSyncScriptContent();
|
|
3375
3401
|
files[`${base}/.openclaw/agents/${agentId}.yaml`] = botAgentYamlContent(botIndex);
|
|
3376
3402
|
files[`${base}/.openclaw/agents/${agentId}/agent/auth-profiles.json`] = botAuthProfilesContent(botIndex);
|
|
3377
3403
|
Object.entries(botWorkspaceFiles(botIndex)).forEach(([name, content]) => {
|
|
@@ -3452,17 +3478,17 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3452
3478
|
} else if (state.nativeOs === 'linux') {
|
|
3453
3479
|
const isDocker = state.deployMode === 'docker';
|
|
3454
3480
|
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
|
-
'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
|
-
];
|
|
3481
|
+
const sh = [
|
|
3482
|
+
'#!/usr/bin/env bash', 'set -e',
|
|
3483
|
+
`echo "=== OpenClaw Setup — macOS${isDocker ? ' Docker' : ' Native'} ==="`,
|
|
3484
|
+
'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
|
|
3485
|
+
'mkdir -p "$HOME/.local/bin"',
|
|
3486
|
+
'npm config set prefix "$HOME/.local"',
|
|
3487
|
+
'export PATH="$HOME/.local/bin:$PATH"',
|
|
3488
|
+
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.zshrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.zshrc"',
|
|
3489
|
+
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
|
|
3490
|
+
'npm install -g openclaw@latest',
|
|
3491
|
+
];
|
|
3466
3492
|
providerLines(sh, 'sh');
|
|
3467
3493
|
if (pluginCmd) sh.push(pluginCmd);
|
|
3468
3494
|
|
|
@@ -3479,21 +3505,21 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3479
3505
|
// ─── VPS/Ubuntu PM2 .SH ──────────────────────────────────────────────────
|
|
3480
3506
|
} else if (state.nativeOs === 'vps') {
|
|
3481
3507
|
scriptName = 'setup-openclaw-vps.sh';
|
|
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
|
-
];
|
|
3508
|
+
const vps = [
|
|
3509
|
+
'#!/usr/bin/env bash', 'set -e',
|
|
3510
|
+
`echo "=== OpenClaw Setup — Ubuntu/VPS${isMultiBot ? ` Multi-Bot (${state.botCount} bots)` : ''} ==="`,
|
|
3511
|
+
'# Auto-install Node.js 20 LTS if missing',
|
|
3512
|
+
'if ! command -v node > /dev/null 2>&1; then',
|
|
3513
|
+
' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
|
|
3514
|
+
' sudo apt-get install -y nodejs',
|
|
3515
|
+
'fi',
|
|
3516
|
+
'mkdir -p "$HOME/.local/bin"',
|
|
3517
|
+
'npm config set prefix "$HOME/.local"',
|
|
3518
|
+
'export PATH="$HOME/.local/bin:$PATH"',
|
|
3519
|
+
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
|
|
3520
|
+
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
|
|
3521
|
+
'npm install -g openclaw@latest pm2@latest',
|
|
3522
|
+
];
|
|
3497
3523
|
providerLines(vps, 'sh');
|
|
3498
3524
|
if (pluginCmd) vps.push(pluginCmd);
|
|
3499
3525
|
|
|
@@ -3501,6 +3527,10 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3501
3527
|
vps.push('echo "--- Creating shared multi-agent runtime ---"');
|
|
3502
3528
|
appendShWriteCommands(vps, sharedNativeFileMap());
|
|
3503
3529
|
vps.push('echo "--- Starting shared gateway via PM2 ---"');
|
|
3530
|
+
if (is9Router) {
|
|
3531
|
+
vps.push('pm2 start --name openclaw-multibot-9router -- sh -c "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
3532
|
+
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3533
|
+
}
|
|
3504
3534
|
vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
|
|
3505
3535
|
vps.push('pm2 save && pm2 startup');
|
|
3506
3536
|
vps.push(`echo ""`);
|
|
@@ -3510,6 +3540,10 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3510
3540
|
vps.push(`echo " pm2 logs openclaw-multibot"`);
|
|
3511
3541
|
} else {
|
|
3512
3542
|
appendShWriteCommands(vps, botFiles(0));
|
|
3543
|
+
if (is9Router) {
|
|
3544
|
+
vps.push('pm2 start --name openclaw-9router -- sh -c "9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
3545
|
+
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3546
|
+
}
|
|
3513
3547
|
vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
|
|
3514
3548
|
vps.push('pm2 save && pm2 startup');
|
|
3515
3549
|
vps.push('echo "Bot dang chay! Xem log: pm2 logs openclaw"');
|
|
@@ -3519,20 +3553,20 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3519
3553
|
// ─── Linux Desktop .SH ───────────────────────────────────────────────────
|
|
3520
3554
|
} else if (state.nativeOs === 'linux-desktop') {
|
|
3521
3555
|
scriptName = 'setup-openclaw-linux.sh';
|
|
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
|
-
];
|
|
3556
|
+
const lnx = [
|
|
3557
|
+
'#!/usr/bin/env bash', 'set -e',
|
|
3558
|
+
`echo "=== OpenClaw Setup — Linux Desktop${isMultiBot ? ' Multi-Bot' : ''} ==="`,
|
|
3559
|
+
'if ! command -v node > /dev/null 2>&1; then',
|
|
3560
|
+
' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
|
|
3561
|
+
' sudo apt-get install -y nodejs',
|
|
3562
|
+
'fi',
|
|
3563
|
+
'mkdir -p "$HOME/.local/bin"',
|
|
3564
|
+
'npm config set prefix "$HOME/.local"',
|
|
3565
|
+
'export PATH="$HOME/.local/bin:$PATH"',
|
|
3566
|
+
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
|
|
3567
|
+
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
|
|
3568
|
+
'npm install -g openclaw@latest',
|
|
3569
|
+
];
|
|
3536
3570
|
providerLines(lnx, 'sh');
|
|
3537
3571
|
if (pluginCmd) lnx.push(pluginCmd);
|
|
3538
3572
|
|
|
@@ -3684,7 +3718,9 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
|
|
|
3684
3718
|
ps += `Write-Host " ${isVi ? 'Mở http://localhost:30128/dashboard để login OAuth' : 'Open http://localhost:30128/dashboard to login OAuth'}" -ForegroundColor White\n`;
|
|
3685
3719
|
}
|
|
3686
3720
|
if (state.channel === 'zalo-personal') {
|
|
3687
|
-
ps += `Write-Host " ${isVi ? 'Chạy: docker exec -it
|
|
3721
|
+
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`;
|
|
3722
|
+
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`;
|
|
3723
|
+
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`;
|
|
3688
3724
|
}
|
|
3689
3725
|
|
|
3690
3726
|
ps += `Write-Host ""
|
|
@@ -3858,69 +3894,37 @@ echo ""
|
|
|
3858
3894
|
}
|
|
3859
3895
|
|
|
3860
3896
|
|
|
3861
|
-
// ========== Zalo Personal
|
|
3897
|
+
// ========== Zalo Personal Login Guide (post-setup) ==========
|
|
3862
3898
|
|
|
3863
3899
|
function generateZaloOnboardGuide() {
|
|
3864
3900
|
const lang = document.getElementById('cfg-language')?.value || 'vi';
|
|
3865
|
-
setOutput('out-zalo-onboard-cmd', `docker exec -it
|
|
3901
|
+
setOutput('out-zalo-onboard-cmd', `docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose`);
|
|
3866
3902
|
|
|
3867
3903
|
if (lang === 'vi') {
|
|
3868
3904
|
setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
|
|
3869
|
-
│
|
|
3870
|
-
|
|
3871
|
-
│
|
|
3872
|
-
|
|
3873
|
-
│
|
|
3874
|
-
│
|
|
3875
|
-
│
|
|
3876
|
-
│
|
|
3877
|
-
│
|
|
3878
|
-
│
|
|
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. │
|
|
3905
|
+
│ Chạy lệnh bên trái để OpenClaw tạo QR đăng nhập. │
|
|
3906
|
+
├─────────────────────────────────────────────────────┤
|
|
3907
|
+
│ 1. Đảm bảo container/gateway đã chạy xong. │
|
|
3908
|
+
│ 2. Chạy lệnh login để tạo QR cho zalouser. │
|
|
3909
|
+
│ 3. OpenClaw sẽ in ra đường dẫn file QR trong /tmp. │
|
|
3910
|
+
│ 4. Copy file QR ra ngoài nếu cần: │
|
|
3911
|
+
│ docker compose cp ai-bot:/tmp/openclaw/ │
|
|
3912
|
+
│ openclaw-zalouser-qr-default.png . │
|
|
3913
|
+
│ 5. Mở ảnh QR → quét bằng app Zalo → xác nhận. │
|
|
3914
|
+
│ 6. Sau khi login xong, restart bot nếu cần. │
|
|
3895
3915
|
└─────────────────────────────────────────────────────┘`);
|
|
3896
3916
|
} else {
|
|
3897
3917
|
setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
|
|
3898
|
-
│
|
|
3899
|
-
|
|
3900
|
-
│
|
|
3901
|
-
|
|
3902
|
-
│
|
|
3903
|
-
│
|
|
3904
|
-
│
|
|
3905
|
-
│
|
|
3906
|
-
│
|
|
3907
|
-
│
|
|
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. │
|
|
3918
|
+
│ Run the command on the left to generate a Zalo QR. │
|
|
3919
|
+
├─────────────────────────────────────────────────────┤
|
|
3920
|
+
│ 1. Make sure the container/gateway is already up. │
|
|
3921
|
+
│ 2. Run the login command for zalouser. │
|
|
3922
|
+
│ 3. OpenClaw prints the QR image path under /tmp. │
|
|
3923
|
+
│ 4. Copy the QR out if needed: │
|
|
3924
|
+
│ docker compose cp ai-bot:/tmp/openclaw/ │
|
|
3925
|
+
│ openclaw-zalouser-qr-default.png . │
|
|
3926
|
+
│ 5. Open the image → scan with Zalo mobile app. │
|
|
3927
|
+
│ 6. Restart the bot afterwards if needed. │
|
|
3924
3928
|
└─────────────────────────────────────────────────────┘`);
|
|
3925
3929
|
}
|
|
3926
3930
|
}
|