create-openclaw-bot 5.1.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.1.0",
3
+ "version": "5.1.1",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "cli.js",
6
6
  "bin": {
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
  ============================================ */
@@ -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 runtime chạy, dùng <code>openclaw channels login --channel zalouser --verbose</code> để tạo <strong>mã 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: {
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: '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
+ ],
282
+ channelConfig: {
283
+ zalouser: {
284
284
  enabled: true,
285
285
  accounts: {
286
286
  default: {
@@ -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 login 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 keep gateway settings stable
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,31 +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
- 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
- }
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 || [])
1857
+ .filter(c => c && c.provider && c.isActive !== false && !c.disabled)
1858
+ .map(c => c.provider);
1859
+ if (!a.length) {
1860
+ removeSmartRoute();
1861
+ return;
1862
+ }
1860
1863
 
1861
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'];
1862
1865
  a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
1863
1866
 
1864
- const m = a.flatMap(p => PM[p] || []);
1865
- if (!m.length) {
1866
- removeSmartRoute();
1867
- return;
1868
- }
1867
+ const m = a.flatMap(pv => PM[pv] || []);
1868
+ if (!m.length) {
1869
+ removeSmartRoute();
1870
+ return;
1871
+ }
1869
1872
 
1870
1873
  const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
1871
1874
  const i = db.combos.findIndex(x => x.id === 'smart-route');
@@ -1882,7 +1885,7 @@ const sync = async () => {
1882
1885
  }
1883
1886
  } catch (e) { }
1884
1887
  };
1885
- sync();
1888
+ setTimeout(sync, 5000);
1886
1889
  setInterval(sync, INTERVAL);`;
1887
1890
 
1888
1891
  let compose;
@@ -1917,9 +1920,7 @@ ${dependsOn}${extraHosts} volumes:
1917
1920
  - -c
1918
1921
  - |
1919
1922
  npm install -g 9router
1920
- cat << 'CLAWEOF' > /tmp/sync.js
1921
- ${syncScript.replace(/\$/g, '$$$$').replace(/\n/g, '\n ')}
1922
- CLAWEOF
1923
+ node -e "require('fs').writeFileSync('/tmp/sync.js',${JSON.stringify(syncScript)})"
1923
1924
  node /tmp/sync.js > /tmp/sync.log 2>&1 &
1924
1925
  exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
1925
1926
  environment:
@@ -2015,9 +2016,7 @@ ${extraHostsBlock}
2015
2016
  - -c
2016
2017
  - |
2017
2018
  npm install -g 9router
2018
- cat << 'CLAWEOF' > /tmp/sync.js
2019
- ${syncScript.replace(/\$/g, '$$$$').replace(/\n/g, '\n ')}
2020
- CLAWEOF
2019
+ node -e "require('fs').writeFileSync('/tmp/sync.js',${JSON.stringify(syncScript)})"
2021
2020
  node /tmp/sync.js > /tmp/sync.log 2>&1 &
2022
2021
  exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
2023
2022
  environment:
@@ -2846,33 +2845,33 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
2846
2845
  const p = PLUGINS.find((x) => x.id === pid);
2847
2846
  if (p) allPlugins.push(p.package);
2848
2847
  });
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
- }
2848
+ if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
2849
+ const pluginCmd = allPlugins.length > 0 ? ('npm exec openclaw plugins install ' + allPlugins.join(' ')) : '';
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
+ }
2860
2859
 
2861
2860
  // ─── Shared initializer (provider install) ───────────────────────────────
2862
- function providerLines(arr, shell) {
2863
- if (is9Router) {
2864
- if (shell === 'bat') {
2865
- arr.push('npm install -g 9router');
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"');
2868
- arr.push('timeout /t 5 /nobreak >nul');
2869
- } else {
2870
- arr.push('npm install -g 9router');
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 &');
2873
- arr.push('sleep 3');
2874
- }
2875
- } else if (isOllama) {
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) {
2876
2875
  if (shell === 'bat') {
2877
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)');
2878
2877
  arr.push('ollama pull ' + selectedModel);
@@ -3040,14 +3039,14 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3040
3039
  }
3041
3040
 
3042
3041
  function sharedNativeFileMap() {
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();
3042
+ const files = {
3043
+ '.env': sharedNativeEnvContent(),
3044
+ '.openclaw/openclaw.json': sharedNativeConfigContent(),
3045
+ '.openclaw/exec-approvals.json': sharedNativeExecApprovalsContent(),
3046
+ '.openclaw/auth-profiles.json': sharedNativeAuthProfilesContent(),
3047
+ 'TELEGRAM-POST-INSTALL.md': buildTelegramPostInstallChecklist(),
3048
+ };
3049
+ if (is9Router) files['.openclaw/9router-smart-route-sync.js'] = native9RouterSyncScriptContent();
3051
3050
  const teamMd = isVi
3052
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.`
3053
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.`;
@@ -3395,11 +3394,11 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3395
3394
  const base = '.';
3396
3395
  const files = {};
3397
3396
  files[`${base}/.env`] = botEnvContent(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);
3397
+ files[`${base}/.openclaw/openclaw.json`] = botConfigContent(botIndex);
3398
+ files[`${base}/.openclaw/exec-approvals.json`] = botExecApprovalsContent(botIndex);
3399
+ files[`${base}/.openclaw/auth-profiles.json`] = botAuthProfilesContent(botIndex);
3400
+ if (is9Router) files[`${base}/.openclaw/9router-smart-route-sync.js`] = native9RouterSyncScriptContent();
3401
+ files[`${base}/.openclaw/agents/${agentId}.yaml`] = botAgentYamlContent(botIndex);
3403
3402
  files[`${base}/.openclaw/agents/${agentId}/agent/auth-profiles.json`] = botAuthProfilesContent(botIndex);
3404
3403
  Object.entries(botWorkspaceFiles(botIndex)).forEach(([name, content]) => {
3405
3404
  files[`${base}/.openclaw/workspace/${name}`] = content;
@@ -3479,17 +3478,17 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3479
3478
  } else if (state.nativeOs === 'linux') {
3480
3479
  const isDocker = state.deployMode === 'docker';
3481
3480
  scriptName = isDocker ? 'setup-openclaw-docker-macos.sh' : 'setup-openclaw-macos.sh';
3482
- const sh = [
3483
- '#!/usr/bin/env bash', 'set -e',
3484
- `echo "=== OpenClaw Setup — macOS${isDocker ? ' Docker' : ' Native'} ==="`,
3485
- 'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
3486
- 'mkdir -p "$HOME/.local/bin"',
3487
- 'npm config set prefix "$HOME/.local"',
3488
- 'export PATH="$HOME/.local/bin:$PATH"',
3489
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.zshrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.zshrc"',
3490
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3491
- 'npm install -g openclaw@latest',
3492
- ];
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
+ ];
3493
3492
  providerLines(sh, 'sh');
3494
3493
  if (pluginCmd) sh.push(pluginCmd);
3495
3494
 
@@ -3506,47 +3505,47 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3506
3505
  // ─── VPS/Ubuntu PM2 .SH ──────────────────────────────────────────────────
3507
3506
  } else if (state.nativeOs === 'vps') {
3508
3507
  scriptName = 'setup-openclaw-vps.sh';
3509
- const vps = [
3510
- '#!/usr/bin/env bash', 'set -e',
3511
- `echo "=== OpenClaw Setup — Ubuntu/VPS${isMultiBot ? ` Multi-Bot (${state.botCount} bots)` : ''} ==="`,
3512
- '# Auto-install Node.js 20 LTS if missing',
3513
- 'if ! command -v node > /dev/null 2>&1; then',
3514
- ' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
3515
- ' sudo apt-get install -y nodejs',
3516
- 'fi',
3517
- 'mkdir -p "$HOME/.local/bin"',
3518
- 'npm config set prefix "$HOME/.local"',
3519
- 'export PATH="$HOME/.local/bin:$PATH"',
3520
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3521
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3522
- 'npm install -g openclaw@latest pm2@latest',
3523
- ];
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
+ ];
3524
3523
  providerLines(vps, 'sh');
3525
3524
  if (pluginCmd) vps.push(pluginCmd);
3526
3525
 
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');
3526
+ if (isMultiBot) {
3527
+ vps.push('echo "--- Creating shared multi-agent runtime ---"');
3528
+ appendShWriteCommands(vps, sharedNativeFileMap());
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
+ }
3534
+ vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
3535
+ vps.push('pm2 save && pm2 startup');
3537
3536
  vps.push(`echo ""`);
3538
3537
  vps.push(`echo "=== ✅ Shared multi-bot gateway running via PM2 ==="`);
3539
3538
  vps.push(`echo "Commands:"`);
3540
3539
  vps.push(`echo " pm2 status # Status gateway"`);
3541
3540
  vps.push(`echo " pm2 logs openclaw-multibot"`);
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');
3541
+ } else {
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
+ }
3547
+ vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
3548
+ vps.push('pm2 save && pm2 startup');
3550
3549
  vps.push('echo "Bot dang chay! Xem log: pm2 logs openclaw"');
3551
3550
  }
3552
3551
  scriptContent = vps.filter(Boolean).join('\n');
@@ -3554,20 +3553,20 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3554
3553
  // ─── Linux Desktop .SH ───────────────────────────────────────────────────
3555
3554
  } else if (state.nativeOs === 'linux-desktop') {
3556
3555
  scriptName = 'setup-openclaw-linux.sh';
3557
- const lnx = [
3558
- '#!/usr/bin/env bash', 'set -e',
3559
- `echo "=== OpenClaw Setup — Linux Desktop${isMultiBot ? ' Multi-Bot' : ''} ==="`,
3560
- 'if ! command -v node > /dev/null 2>&1; then',
3561
- ' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
3562
- ' sudo apt-get install -y nodejs',
3563
- 'fi',
3564
- 'mkdir -p "$HOME/.local/bin"',
3565
- 'npm config set prefix "$HOME/.local"',
3566
- 'export PATH="$HOME/.local/bin:$PATH"',
3567
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3568
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3569
- 'npm install -g openclaw@latest',
3570
- ];
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
+ ];
3571
3570
  providerLines(lnx, 'sh');
3572
3571
  if (pluginCmd) lnx.push(pluginCmd);
3573
3572
 
@@ -3718,11 +3717,11 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
3718
3717
  if (is9Router) {
3719
3718
  ps += `Write-Host " ${isVi ? 'Mở http://localhost:30128/dashboard để login OAuth' : 'Open http://localhost:30128/dashboard to login OAuth'}" -ForegroundColor White\n`;
3720
3719
  }
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
- }
3720
+ if (state.channel === 'zalo-personal') {
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`;
3724
+ }
3726
3725
 
3727
3726
  ps += `Write-Host ""
3728
3727
  } catch {
@@ -3895,40 +3894,40 @@ echo ""
3895
3894
  }
3896
3895
 
3897
3896
 
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
- }
3897
+ // ========== Zalo Personal Login Guide (post-setup) ==========
3898
+
3899
+ function generateZaloOnboardGuide() {
3900
+ const lang = document.getElementById('cfg-language')?.value || 'vi';
3901
+ setOutput('out-zalo-onboard-cmd', `docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose`);
3902
+
3903
+ if (lang === 'vi') {
3904
+ setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
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. │
3915
+ └─────────────────────────────────────────────────────┘`);
3916
+ } else {
3917
+ setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
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. │
3928
+ └─────────────────────────────────────────────────────┘`);
3929
+ }
3930
+ }
3932
3931
 
3933
3932
  function setOutput(id, text) {
3934
3933
  const el = document.getElementById(id);