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/CHANGELOG.md +177 -144
- package/CHANGELOG.vi.md +171 -139
- package/README.md +8 -8
- package/README.vi.md +8 -8
- package/cli.js +531 -589
- package/package.json +28 -28
- package/setup.js +75 -41
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-openclaw-bot",
|
|
3
|
-
"version": "4.
|
|
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ỉ
|
|
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
|
|
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:
|
|
766
|
-
: 'After Docker starts, open <a href="http://localhost:
|
|
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
|
-
|
|
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 =
|
|
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
|
-
- "
|
|
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
|
-
|
|
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
|
-
- "
|
|
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
|
-
//
|
|
1801
|
-
const safeContent = content
|
|
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:
|
|
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
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
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
|
|
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;
|