create-openclaw-bot 4.0.9 → 4.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,28 +1,28 @@
1
- {
2
- "name": "create-openclaw-bot",
3
- "version": "4.0.9",
4
- "description": "Interactive CLI installer for OpenClaw Bot",
5
- "main": "cli.js",
6
- "bin": {
7
- "create-openclaw-bot": "./cli.js"
8
- },
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
11
- },
12
- "keywords": [
13
- "openclaw",
14
- "cli",
15
- "bot",
16
- "zalo",
17
- "telegram",
18
- "ai"
19
- ],
20
- "author": "tuanminhhole",
21
- "license": "MIT",
22
- "dependencies": {
23
- "@inquirer/prompts": "^4.3.1",
24
- "chalk": "^5.3.0",
25
- "fs-extra": "^11.2.0"
26
- },
27
- "type": "module"
28
- }
1
+ {
2
+ "name": "create-openclaw-bot",
3
+ "version": "4.1.1",
4
+ "description": "Interactive CLI installer for OpenClaw Bot",
5
+ "main": "cli.js",
6
+ "bin": {
7
+ "create-openclaw-bot": "./cli.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "openclaw",
14
+ "cli",
15
+ "bot",
16
+ "zalo",
17
+ "telegram",
18
+ "ai"
19
+ ],
20
+ "author": "tuanminhhole",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "@inquirer/prompts": "^4.3.1",
24
+ "chalk": "^5.3.0",
25
+ "fs-extra": "^11.2.0"
26
+ },
27
+ "type": "module"
28
+ }
package/setup.js CHANGED
@@ -258,6 +258,7 @@
258
258
  envKeys: [],
259
259
  envExtra: 'ZALO_BOT_TOKEN=<your_zalo_bot_token>',
260
260
  credSteps: [
261
+ { textVi: '<span style="color: #fbbf24; font-weight: 500;">⚠️ LƯU Ý: Bot OA Zalo đòi hỏi bạn phải thiết lập Webhook Public (qua VPS/ngrok có HTTPS). Hãy cân nhắc dùng Zalo Personal nếu bạn chưa có Webhook.</span>', textEn: '<span style="color: #fbbf24; font-weight: 500;">⚠️ NOTE: Zalo OA Bot requires setting up a Public Webhook (using VPS/ngrok with HTTPS). Consider using Zalo Personal if you do not have a webhook.</span>' },
261
262
  { textVi: 'Vào <a href="https://developers.zalo.me" target="_blank">Zalo Bot Platform</a> → Tạo bot mới → copy Bot Token', textEn: 'Go to <a href="https://developers.zalo.me" target="_blank">Zalo Bot Platform</a> → Create new bot → copy Bot Token' },
262
263
  ],
263
264
  channelConfig: {
@@ -345,7 +346,7 @@
345
346
  - ✅ Chỉ mount đúng thư mục cần thiết (config + workspace)
346
347
  - ❌ KHÔNG mount nguyên ổ đĩa (C:/ hoặc D:/)
347
348
  - ❌ KHÔNG chạy container với --privileged
348
- - ✅ Giới hạn port expose (chỉ 18789)`,
349
+ - ✅ Giới hạn port expose (chỉ 38789)`,
349
350
  en: `## 🔐 Security Rules — MANDATORY
350
351
 
351
352
  ### System files & directories
@@ -372,7 +373,7 @@
372
373
  - ✅ Only mount required directories (config + workspace)
373
374
  - ❌ DO NOT mount entire drives (C:/ or D:/)
374
375
  - ❌ DO NOT run containers with --privileged
375
- - ✅ Limit exposed ports (only 18789)`,
376
+ - ✅ Limit exposed ports (only 38789)`,
376
377
  };
377
378
 
378
379
  // ========== DOM Ready ==========
@@ -762,8 +763,8 @@
762
763
  // 9Router: simple message (no API key needed - managed via dashboard)
763
764
  pHtml += `<p style="font-size: 13px; color: var(--text-secondary); margin: 0 0 8px;">
764
765
  ${isVi
765
- ? 'Sau khi Docker khởi động xong, mở <a href="http://localhost:20128/dashboard" target="_blank" style="color: var(--accent);">localhost:20128/dashboard</a> để đăng nhập OAuth và kết nối các Provider.'
766
- : 'After Docker starts, open <a href="http://localhost:20128/dashboard" target="_blank" style="color: var(--accent);">localhost:20128/dashboard</a> to OAuth login and connect Providers.'}
766
+ ? 'Sau khi Docker khởi động xong, mở <a href="http://localhost:30128/dashboard" target="_blank" style="color: var(--accent);">localhost:30128/dashboard</a> để đăng nhập OAuth và kết nối các Provider.'
767
+ : 'After Docker starts, open <a href="http://localhost:30128/dashboard" target="_blank" style="color: var(--accent);">localhost:30128/dashboard</a> to OAuth login and connect Providers.'}
767
768
  </p>`;
768
769
  pHtml += `<p style="font-size: 12px; color: var(--text-muted); margin: 0;">
769
770
  ${isVi
@@ -1039,29 +1040,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
1039
1040
  apiKey: 'sk-no-key',
1040
1041
  api: 'openai-completions',
1041
1042
  models: [
1042
- // Free Tier Providers
1043
- { id: 'if/qwen3-coder-plus', name: 'Qwen3 Coder+ (iFlow FREE)', contextWindow: 128000, maxTokens: 8192 },
1044
- { id: 'if/kimi-k2', name: 'Kimi K2 (iFlow FREE)', contextWindow: 128000, maxTokens: 8192 },
1045
- { id: 'if/kimi-k2-thinking', name: 'Kimi K2 Thinking (iFlow FREE)', contextWindow: 128000, maxTokens: 8192 },
1046
- { id: 'if/glm-4.7', name: 'GLM 4.7 (iFlow FREE)', contextWindow: 128000, maxTokens: 8192 },
1047
- { id: 'if/minimax-m2', name: 'MiniMax M2 (iFlow FREE)', contextWindow: 1000000, maxTokens: 8192 },
1048
- { id: 'if/deepseek-r1', name: 'DeepSeek R1 (iFlow FREE)', contextWindow: 128000, maxTokens: 8192 },
1049
- { id: 'qw/qwen3-coder-plus', name: 'Qwen3 Coder+ (Qwen FREE)', contextWindow: 128000, maxTokens: 8192 },
1050
- { id: 'qw/qwen3-coder-flash', name: 'Qwen3 Coder Flash (Qwen FREE)', contextWindow: 128000, maxTokens: 8192 },
1051
- { id: 'kr/claude-sonnet-4.5', name: 'Claude Sonnet 4.5 (Kiro FREE)', contextWindow: 200000, maxTokens: 8192 },
1052
- { id: 'kr/claude-haiku-4.5', name: 'Claude Haiku 4.5 (Kiro FREE)', contextWindow: 200000, maxTokens: 8192 },
1053
- // Ollama Cloud
1054
- { id: 'ollama/qwen3.5', name: 'Qwen 3.5 (Ollama Cloud)', contextWindow: 128000, maxTokens: 8192 },
1055
- { id: 'ollama/kimi-k2.5', name: 'Kimi K2.5 (Ollama Cloud)', contextWindow: 128000, maxTokens: 8192 },
1056
- { id: 'ollama/glm-5', name: 'GLM 5 (Ollama Cloud)', contextWindow: 128000, maxTokens: 8192 },
1057
- { id: 'ollama/glm-4.7-flash', name: 'GLM 4.7 Flash (Ollama Cloud)', contextWindow: 128000, maxTokens: 8192 },
1058
- { id: 'ollama/minimax-m2.5', name: 'MiniMax M2.5 (Ollama Cloud)', contextWindow: 128000, maxTokens: 8192 },
1059
- { id: 'ollama/gpt-oss:120b', name: 'GPT-OSS 120B (Ollama Cloud)', contextWindow: 128000, maxTokens: 8192 },
1060
- // API Key Providers
1061
- { id: 'glm/glm-4.7', name: 'GLM 4.7 ($0.6/1M)', contextWindow: 128000, maxTokens: 8192 },
1062
- { id: 'minimax/MiniMax-M2.1', name: 'MiniMax M2.1 ($0.20/1M)', contextWindow: 1000000, maxTokens: 8192 },
1063
- { id: 'kimi/kimi-latest', name: 'Kimi Latest ($0.90/1M)', contextWindow: 128000, maxTokens: 8192 },
1064
- { id: 'deepseek/deepseek-chat', name: 'DeepSeek V3.2 Chat', contextWindow: 128000, maxTokens: 8192 },
1043
+ { id: 'smart-route', name: 'Smart Proxy (Auto Route)', contextWindow: 200000, maxTokens: 8192 }
1065
1044
  ],
1066
1045
  },
1067
1046
  },
@@ -1184,7 +1163,42 @@ ${finalCmd}`;
1184
1163
  // ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
1185
1164
  // Background loop inside 9Router container every 30s.
1186
1165
  // Queries /api/providers → filters connected+enabled → updates smart-route combo.
1187
- const syncScript = `#!/bin/sh\nROUTER=http://localhost:20128\nINTERVAL=30\necho "[sync-combo] Waiting for 9Router..."\nwhile ! wget -qO- $ROUTER/api/version >/dev/null 2>&1; do sleep 2; done\necho "[sync-combo] 9Router ready. Syncing every \${INTERVAL}s..."\nwhile true; do\n PJ=$(wget -qO- $ROUTER/api/providers 2>/dev/null || echo '{}')\n CJ=$(node -e "\nconst 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/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']};\ntry{const d=$PJ;const a=(d.connections||[]).filter(c=>c.isActive).map(c=>c.provider);if(!a.length)process.exit(1);const m=a.flatMap(p=>PM[p]||[]);if(!m.length)process.exit(1);console.log(JSON.stringify({id:'smart-route',name:'smart-route',alias:'smart-route',models:m}))}catch(e){process.exit(1)}\n " 2>/dev/null)\n if [ -n "$CJ" ]; then\n node -e "\nconst fs=require('fs'),p='/root/.9router/db.json';let d={};try{d=JSON.parse(fs.readFileSync(p,'utf8'))}catch(e){}const c=$CJ;if(!d.combos)d.combos=[];const i=d.combos.findIndex(x=>x.id==='smart-route');if(i>=0){if(JSON.stringify(d.combos[i].models)!==JSON.stringify(c.models)){d.combos[i]=c;fs.writeFileSync(p,JSON.stringify(d,null,2));console.log('[sync-combo] Updated: '+c.models.length+' models')}}else{d.combos.push(c);fs.writeFileSync(p,JSON.stringify(d,null,2));console.log('[sync-combo] Created: '+c.models.length+' models')}\n " 2>/dev/null\n fi\n sleep $INTERVAL\ndone`;
1166
+ const syncScript = `const fs=require('fs');const ROUTER='http://localhost:20128';const INTERVAL=30000;const p='/root/.9router/db.json';
1167
+ 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/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']};
1168
+ console.log('[sync-combo] 9Router sync loop started...');
1169
+ const sync = async () => {
1170
+ try {
1171
+ const res = await fetch(ROUTER + '/api/providers');
1172
+ const d = await res.json();
1173
+ const a = (d.connections || []).filter(c=>(c.isActive !== false && !c.disabled) && (c.isActive || c.connected > 0 || c.tokens?.length > 0)).map(c=>c.provider);
1174
+ if (!a.length) return;
1175
+
1176
+ const PREF = ['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];
1177
+ a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
1178
+
1179
+ const m = a.flatMap(p => PM[p] || []);
1180
+ if (!m.length) return;
1181
+ let db = {};
1182
+ try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e){}
1183
+ if (!db.combos) db.combos = [];
1184
+
1185
+ const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
1186
+ const i = db.combos.findIndex(x => x.id === 'smart-route');
1187
+ if (i >= 0) {
1188
+ if (JSON.stringify(db.combos[i].models) !== JSON.stringify(c.models)) {
1189
+ db.combos[i] = c;
1190
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
1191
+ console.log('[sync-combo] Updated smart-route: ' + c.models.length + ' models');
1192
+ }
1193
+ } else {
1194
+ db.combos.push(c);
1195
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
1196
+ console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
1197
+ }
1198
+ } catch (e) { }
1199
+ };
1200
+ sync();
1201
+ setInterval(sync, INTERVAL);`;
1188
1202
 
1189
1203
  let compose;
1190
1204
  if (is9Router) {
@@ -1201,14 +1215,22 @@ ${extraHostsBlock}
1201
1215
  volumes:
1202
1216
  - ../../.openclaw:/root/.openclaw
1203
1217
  ports:
1204
- - "18789:18789"
1218
+ - "38789:38789"
1205
1219
 
1206
1220
  9router:
1207
1221
  image: node:22-slim
1208
1222
  container_name: 9router
1209
1223
  restart: always
1210
- entrypoint: >
1211
- /bin/sh -c "npm install -g 9router && (echo '${syncScript}' > /tmp/sync.sh && sh /tmp/sync.sh &) && 9router"
1224
+ entrypoint:
1225
+ - /bin/sh
1226
+ - -c
1227
+ - |
1228
+ npm install -g 9router
1229
+ cat << 'CLAWEOF' > /tmp/sync.js
1230
+ ${syncScript.replace(/\$/g, '$$$$').replace(/\n/g, '\n ')}
1231
+ CLAWEOF
1232
+ node /tmp/sync.js > /tmp/sync.log 2>&1 &
1233
+ exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
1212
1234
  environment:
1213
1235
  - PORT=20128
1214
1236
  - HOSTNAME=0.0.0.0
@@ -1232,7 +1254,7 @@ ${extraHostsBlock}
1232
1254
  volumes:
1233
1255
  - ../../.openclaw:/root/.openclaw
1234
1256
  ports:
1235
- - "18789:18789"`;
1257
+ - "38789:38789"`;
1236
1258
  }
1237
1259
 
1238
1260
  setOutput('out-compose', compose);
@@ -1797,8 +1819,10 @@ New-Item -ItemType Directory -Force -Path "$projectDir" | Out-Null
1797
1819
 
1798
1820
  Object.entries(files).forEach(([path, content]) => {
1799
1821
  const winPath = path.replace(/\//g, '\\');
1800
- // Escape content for PowerShell here-string (only issue: content containing "'@" on own line)
1801
- const safeContent = content.replace(/\r\n/g, '\n');
1822
+ // Fix: escape any "'@" at start of line would prematurely terminate PowerShell here-string
1823
+ const safeContent = content
1824
+ .replace(/\r\n/g, '\n')
1825
+ .replace(/^'@/mg, "'`@"); // escape with backtick so PS here-string doesn't terminate early
1802
1826
  ps += `\n[IO.File]::WriteAllText("$projectDir\\${winPath}", @'\n${safeContent}\n'@, $utf8)\n`;
1803
1827
  });
1804
1828
 
@@ -1831,7 +1855,7 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
1831
1855
  // Post-setup notes
1832
1856
  const is9Router = state.config.provider === '9router';
1833
1857
  if (is9Router) {
1834
- ps += `Write-Host " ${isVi ? 'Mở http://localhost:20128/dashboard để login OAuth' : 'Open http://localhost:20128/dashboard to login OAuth'}" -ForegroundColor White\n`;
1858
+ ps += `Write-Host " ${isVi ? 'Mở http://localhost:30128/dashboard để login OAuth' : 'Open http://localhost:30128/dashboard to login OAuth'}" -ForegroundColor White\n`;
1835
1859
  }
1836
1860
  if (state.channel === 'zalo-personal') {
1837
1861
  ps += `Write-Host " ${isVi ? 'Chạy: docker exec -it openclaw-bot openclaw onboard (quét QR)' : 'Run: docker exec -it openclaw-bot openclaw onboard (scan QR)'}" -ForegroundColor White\n`;
@@ -1846,13 +1870,22 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
1846
1870
  Read-Host "${isVi ? 'Nhấn Enter để thoát' : 'Press Enter to exit'}"
1847
1871
  `;
1848
1872
 
1849
- // Wrap in polyglot .bat/.ps1
1850
- const bat = `<# : batch wrapper
1851
- @echo off & chcp 65001>nul
1852
- powershell -ExecutionPolicy Bypass -NoProfile -File "%~f0" %*
1873
+ // Wrap in a .bat that extracts the PS section to a temp .ps1 then runs it.
1874
+ // This avoids 2 issues:
1875
+ // 1. powershell -File refuses .bat extension (hard error, immediate exit)
1876
+ // 2. Zone.Identifier security block on downloaded files affects -File but not -Command
1877
+ // The extraction command uses NO pipes (CMD treats | as special inside ""), and uses
1878
+ // $env:OPENCLAW_SELF / $env:OPENCLAW_TMP to avoid CMD quote issues with paths.
1879
+ const bat = `@echo off
1880
+ chcp 65001>nul
1881
+ set "OPENCLAW_SELF=%~f0"
1882
+ set "OPENCLAW_TMP=%TEMP%\\openclaw_%RANDOM%.ps1"
1883
+ powershell -ep bypass -nop -c "$l=(Select-String -Path $env:OPENCLAW_SELF -Pattern '^:PS_BEGIN$').LineNumber;$a=[io.file]::ReadAllLines($env:OPENCLAW_SELF,[text.encoding]::UTF8);[io.file]::WriteAllText($env:OPENCLAW_TMP,($a[$l..($a.Length-1)] -join \\"\`n\\"),[text.encoding]::UTF8)"
1884
+ powershell -ep bypass -nop -File "%OPENCLAW_TMP%"
1853
1885
  if %errorlevel% neq 0 pause
1886
+ del "%OPENCLAW_TMP%" 2>nul
1854
1887
  exit /b
1855
- #>
1888
+ :PS_BEGIN
1856
1889
  ${ps}`;
1857
1890
 
1858
1891
  return bat;
@@ -1863,7 +1896,8 @@ ${ps}`;
1863
1896
  // Regenerate output first to ensure state._generatedFiles is current
1864
1897
  generateOutput();
1865
1898
  const content = generateAutoSetupBat();
1866
- const blob = new Blob([content], { type: 'application/bat' });
1899
+ const winContent = content.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
1900
+ const blob = new Blob([winContent], { type: 'application/x-bat;charset=utf-8' });
1867
1901
  const url = URL.createObjectURL(blob);
1868
1902
  const a = document.createElement('a');
1869
1903
  a.href = url;