create-openclaw-bot 4.0.9 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.0",
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
@@ -345,7 +345,7 @@
345
345
  - ✅ Chỉ mount đúng thư mục cần thiết (config + workspace)
346
346
  - ❌ KHÔNG mount nguyên ổ đĩa (C:/ hoặc D:/)
347
347
  - ❌ KHÔNG chạy container với --privileged
348
- - ✅ Giới hạn port expose (chỉ 18789)`,
348
+ - ✅ Giới hạn port expose (chỉ 38789)`,
349
349
  en: `## 🔐 Security Rules — MANDATORY
350
350
 
351
351
  ### System files & directories
@@ -372,7 +372,7 @@
372
372
  - ✅ Only mount required directories (config + workspace)
373
373
  - ❌ DO NOT mount entire drives (C:/ or D:/)
374
374
  - ❌ DO NOT run containers with --privileged
375
- - ✅ Limit exposed ports (only 18789)`,
375
+ - ✅ Limit exposed ports (only 38789)`,
376
376
  };
377
377
 
378
378
  // ========== DOM Ready ==========
@@ -762,8 +762,8 @@
762
762
  // 9Router: simple message (no API key needed - managed via dashboard)
763
763
  pHtml += `<p style="font-size: 13px; color: var(--text-secondary); margin: 0 0 8px;">
764
764
  ${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.'}
765
+ ? '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.'
766
+ : '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
767
  </p>`;
768
768
  pHtml += `<p style="font-size: 12px; color: var(--text-muted); margin: 0;">
769
769
  ${isVi
@@ -1039,29 +1039,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
1039
1039
  apiKey: 'sk-no-key',
1040
1040
  api: 'openai-completions',
1041
1041
  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 },
1042
+ { id: 'smart-route', name: 'Smart Proxy (Auto Route)', contextWindow: 200000, maxTokens: 8192 }
1065
1043
  ],
1066
1044
  },
1067
1045
  },
@@ -1184,7 +1162,42 @@ ${finalCmd}`;
1184
1162
  // ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
1185
1163
  // Background loop inside 9Router container every 30s.
1186
1164
  // 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`;
1165
+ const syncScript = `const fs=require('fs');const ROUTER='http://localhost:20128';const INTERVAL=30000;const p='/root/.9router/db.json';
1166
+ 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']};
1167
+ console.log('[sync-combo] 9Router sync loop started...');
1168
+ const sync = async () => {
1169
+ try {
1170
+ const res = await fetch(ROUTER + '/api/providers');
1171
+ const d = await res.json();
1172
+ const a = (d.connections || []).filter(c=>(c.isActive !== false && !c.disabled) && (c.isActive || c.connected > 0 || c.tokens?.length > 0)).map(c=>c.provider);
1173
+ if (!a.length) return;
1174
+
1175
+ const PREF = ['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];
1176
+ a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
1177
+
1178
+ const m = a.flatMap(p => PM[p] || []);
1179
+ if (!m.length) return;
1180
+ let db = {};
1181
+ try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e){}
1182
+ if (!db.combos) db.combos = [];
1183
+
1184
+ const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
1185
+ const i = db.combos.findIndex(x => x.id === 'smart-route');
1186
+ if (i >= 0) {
1187
+ if (JSON.stringify(db.combos[i].models) !== JSON.stringify(c.models)) {
1188
+ db.combos[i] = c;
1189
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
1190
+ console.log('[sync-combo] Updated smart-route: ' + c.models.length + ' models');
1191
+ }
1192
+ } else {
1193
+ db.combos.push(c);
1194
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
1195
+ console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
1196
+ }
1197
+ } catch (e) { }
1198
+ };
1199
+ sync();
1200
+ setInterval(sync, INTERVAL);`;
1188
1201
 
1189
1202
  let compose;
1190
1203
  if (is9Router) {
@@ -1201,14 +1214,22 @@ ${extraHostsBlock}
1201
1214
  volumes:
1202
1215
  - ../../.openclaw:/root/.openclaw
1203
1216
  ports:
1204
- - "18789:18789"
1217
+ - "38789:38789"
1205
1218
 
1206
1219
  9router:
1207
1220
  image: node:22-slim
1208
1221
  container_name: 9router
1209
1222
  restart: always
1210
- entrypoint: >
1211
- /bin/sh -c "npm install -g 9router && (echo '${syncScript}' > /tmp/sync.sh && sh /tmp/sync.sh &) && 9router"
1223
+ entrypoint:
1224
+ - /bin/sh
1225
+ - -c
1226
+ - |
1227
+ npm install -g 9router
1228
+ cat << 'CLAWEOF' > /tmp/sync.js
1229
+ ${syncScript.replace(/\$/g, '$$$$').replace(/\n/g, '\n ')}
1230
+ CLAWEOF
1231
+ node /tmp/sync.js > /tmp/sync.log 2>&1 &
1232
+ exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
1212
1233
  environment:
1213
1234
  - PORT=20128
1214
1235
  - HOSTNAME=0.0.0.0
@@ -1232,7 +1253,7 @@ ${extraHostsBlock}
1232
1253
  volumes:
1233
1254
  - ../../.openclaw:/root/.openclaw
1234
1255
  ports:
1235
- - "18789:18789"`;
1256
+ - "38789:38789"`;
1236
1257
  }
1237
1258
 
1238
1259
  setOutput('out-compose', compose);
@@ -1797,8 +1818,10 @@ New-Item -ItemType Directory -Force -Path "$projectDir" | Out-Null
1797
1818
 
1798
1819
  Object.entries(files).forEach(([path, content]) => {
1799
1820
  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');
1821
+ // Fix: escape any "'@" at start of line would prematurely terminate PowerShell here-string
1822
+ const safeContent = content
1823
+ .replace(/\r\n/g, '\n')
1824
+ .replace(/^'@/mg, "'`@"); // escape with backtick so PS here-string doesn't terminate early
1802
1825
  ps += `\n[IO.File]::WriteAllText("$projectDir\\${winPath}", @'\n${safeContent}\n'@, $utf8)\n`;
1803
1826
  });
1804
1827
 
@@ -1831,7 +1854,7 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
1831
1854
  // Post-setup notes
1832
1855
  const is9Router = state.config.provider === '9router';
1833
1856
  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`;
1857
+ ps += `Write-Host " ${isVi ? 'Mở http://localhost:30128/dashboard để login OAuth' : 'Open http://localhost:30128/dashboard to login OAuth'}" -ForegroundColor White\n`;
1835
1858
  }
1836
1859
  if (state.channel === 'zalo-personal') {
1837
1860
  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 +1869,22 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
1846
1869
  Read-Host "${isVi ? 'Nhấn Enter để thoát' : 'Press Enter to exit'}"
1847
1870
  `;
1848
1871
 
1849
- // Wrap in polyglot .bat/.ps1
1850
- const bat = `<# : batch wrapper
1851
- @echo off & chcp 65001>nul
1852
- powershell -ExecutionPolicy Bypass -NoProfile -File "%~f0" %*
1872
+ // Wrap in a .bat that extracts the PS section to a temp .ps1 then runs it.
1873
+ // This avoids 2 issues:
1874
+ // 1. powershell -File refuses .bat extension (hard error, immediate exit)
1875
+ // 2. Zone.Identifier security block on downloaded files affects -File but not -Command
1876
+ // The extraction command uses NO pipes (CMD treats | as special inside ""), and uses
1877
+ // $env:OPENCLAW_SELF / $env:OPENCLAW_TMP to avoid CMD quote issues with paths.
1878
+ const bat = `@echo off
1879
+ chcp 65001>nul
1880
+ set "OPENCLAW_SELF=%~f0"
1881
+ set "OPENCLAW_TMP=%TEMP%\\openclaw_%RANDOM%.ps1"
1882
+ 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)"
1883
+ powershell -ep bypass -nop -File "%OPENCLAW_TMP%"
1853
1884
  if %errorlevel% neq 0 pause
1885
+ del "%OPENCLAW_TMP%" 2>nul
1854
1886
  exit /b
1855
- #>
1887
+ :PS_BEGIN
1856
1888
  ${ps}`;
1857
1889
 
1858
1890
  return bat;
@@ -1863,7 +1895,8 @@ ${ps}`;
1863
1895
  // Regenerate output first to ensure state._generatedFiles is current
1864
1896
  generateOutput();
1865
1897
  const content = generateAutoSetupBat();
1866
- const blob = new Blob([content], { type: 'application/bat' });
1898
+ const winContent = content.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
1899
+ const blob = new Blob([winContent], { type: 'application/x-bat;charset=utf-8' });
1867
1900
  const url = URL.createObjectURL(blob);
1868
1901
  const a = document.createElement('a');
1869
1902
  a.href = url;