create-openclaw-bot 4.0.8 → 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/CHANGELOG.md +171 -133
- package/CHANGELOG.vi.md +165 -128
- package/README.md +10 -10
- package/README.vi.md +8 -9
- package/cli.js +522 -458
- package/index.html +9 -4
- package/package.json +28 -28
- package/setup.js +91 -43
package/index.html
CHANGED
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
<!-- ─── 3. Bot Identity (Name, Role, Emoji, Vibe) ─── -->
|
|
120
120
|
<div class="identity-grid">
|
|
121
121
|
<div class="form-group">
|
|
122
|
-
<label class="form-group__label" for="cfg-name" data-vi="Tên Bot" data-en="Bot Name">Tên Bot
|
|
122
|
+
<label class="form-group__label" for="cfg-name" data-vi="Tên Bot" data-en="Bot Name">Tên Bot <span style="color: var(--danger, #ef4444);">*</span></label>
|
|
123
123
|
<input type="text" class="form-input" id="cfg-name" placeholder="VD: Williams, Jarvis, Luna..." oninput="window.__validateKeys()">
|
|
124
124
|
</div>
|
|
125
125
|
<div class="form-group">
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
|
|
146
146
|
<!-- ─── 4b. User Info (→ USER.md) ─── -->
|
|
147
147
|
<div class="form-group">
|
|
148
|
-
<label class="form-group__label" for="cfg-user-info" data-vi="👤 Thông tin về bạn (để bot hiểu bạn hơn)" data-en="👤 About You (so the bot replies better)">👤 Thông tin về bạn (để bot hiểu bạn hơn)
|
|
148
|
+
<label class="form-group__label" for="cfg-user-info" data-vi="👤 Thông tin về bạn (để bot hiểu bạn hơn)" data-en="👤 About You (so the bot replies better)">👤 Thông tin về bạn (để bot hiểu bạn hơn) <span style="color: var(--danger, #ef4444);">*</span></label>
|
|
149
149
|
<textarea class="form-textarea" id="cfg-user-info" placeholder="VD: Mình là dev, thích code Python, múi giờ UTC+7. Thích câu trả lời đi thẳng vào vấn đề, không rào trước đón sau..." style="min-height: 100px;"></textarea>
|
|
150
150
|
<div class="prompt-notice">
|
|
151
151
|
<span class="prompt-notice__icon">👤</span>
|
|
@@ -201,7 +201,8 @@
|
|
|
201
201
|
<!-- ===== Step 3: API Keys & Thư Mục ===== -->
|
|
202
202
|
<section class="step" data-step="3">
|
|
203
203
|
<h2 class="step__title" data-vi="Nhập API Keys & Thư mục" data-en="Enter API Keys & Folder">Nhập API Keys & Thư mục</h2>
|
|
204
|
-
<p class="step__description" data-vi="Nhập key trực tiếp + chọn nơi lưu project — wizard lo phần còn lại." data-en="Enter keys + choose project folder — the wizard handles the rest.">Nhập key trực tiếp + chọn nơi lưu project — wizard lo phần còn lại.</p>
|
|
204
|
+
<p class="step__description" style="margin-bottom: 4px;" data-vi="Nhập key trực tiếp + chọn nơi lưu project — wizard lo phần còn lại." data-en="Enter keys + choose project folder — the wizard handles the rest.">Nhập key trực tiếp + chọn nơi lưu project — wizard lo phần còn lại.</p>
|
|
205
|
+
<p style="font-size: 13px; color: var(--danger, #ef4444); margin-top: 0; margin-bottom: 20px;" data-vi="Các trường có dấu <span style='color:var(--danger,#ef4444)'>*</span> là bắt buộc điền." data-en="Fields marked with <span style='color:var(--danger,#ef4444)'>*</span> are required.">Các trường có dấu <span style="color:var(--danger,#ef4444)">*</span> là bắt buộc điền.</p>
|
|
205
206
|
|
|
206
207
|
<!-- Section 1: AI Provider (9Router / Direct API / Ollama) -->
|
|
207
208
|
<div id="key-section-provider" style="margin-bottom: 20px;"></div>
|
|
@@ -214,7 +215,7 @@
|
|
|
214
215
|
|
|
215
216
|
<!-- Project Path Input -->
|
|
216
217
|
<div class="form-group" style="margin-top: 24px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.06);">
|
|
217
|
-
<label class="form-group__label" data-vi="Thư mục lưu trữ Bot (Trên máy chứa Docker)" data-en="Bot Directory (On Docker host)">Thư mục lưu trữ Bot (Trên máy chứa Docker)
|
|
218
|
+
<label class="form-group__label" data-vi="Thư mục lưu trữ Bot (Trên máy chứa Docker) <span style='color:var(--danger,#ef4444)'>*</span>" data-en="Bot Directory (On Docker host) <span style='color:var(--danger,#ef4444)'>*</span>">Thư mục lưu trữ Bot (Trên máy chứa Docker) <span style='color:var(--danger,#ef4444)'>*</span></label>
|
|
218
219
|
<input type="text" class="form-input" id="cfg-project-path" value="D:\openclaw-setup" placeholder="VD: D:\openclaw-setup hoặc /home/user/openclaw-setup" style="font-family: monospace;">
|
|
219
220
|
<p class="form-group__hint" data-vi="Script sẽ tự động tạo thư mục này và sinh các file config bên trong." data-en="The script will auto-create this folder and generate config files inside.">Script sẽ tự động tạo thư mục này và sinh các file config bên trong.</p>
|
|
220
221
|
</div>
|
|
@@ -246,6 +247,10 @@
|
|
|
246
247
|
<span data-vi="📥 Download setup-openclaw.bat" data-en="📥 Download setup-openclaw.bat">📥 Download setup-openclaw.bat</span>
|
|
247
248
|
</button>
|
|
248
249
|
</div>
|
|
250
|
+
<div style="font-size: 12.5px; color: var(--warning, #ffc107); margin: 10px 0 8px; text-align: center; padding: 6px; background: rgba(255, 193, 7, 0.08); border-radius: 6px; border: 1px solid rgba(255, 193, 7, 0.2);">
|
|
251
|
+
⚠️ <strong data-vi="Lưu ý quan trọng:" data-en="Important notice:">Lưu ý quan trọng:</strong>
|
|
252
|
+
<span data-vi="Click chuột phải vào file .bat → Properties (Thuộc tính) → tick Unblock (Mở khóa) trước khi chạy!" data-en="Right-click the .bat file → Properties → check Unblock before running!">Click chuột phải vào file .bat → Properties (Thuộc tính) → tick Unblock (Mở khóa) trước khi chạy!</span>
|
|
253
|
+
</div>
|
|
249
254
|
<p style="font-size: 12px; color: var(--text-muted); margin: 0; text-align: center;" data-vi="Yêu cầu: Docker Desktop đã cài và đang chạy" data-en="Requires: Docker Desktop installed and running">Yêu cầu: Docker Desktop đã cài và đang chạy</p>
|
|
250
255
|
</div>
|
|
251
256
|
|
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-openclaw-bot",
|
|
3
|
-
"version": "4.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
|
-
}
|
|
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ỉ
|
|
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
|
|
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:
|
|
766
|
-
: 'After Docker starts, open <a href="http://localhost:
|
|
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
|
|
@@ -778,7 +778,7 @@
|
|
|
778
778
|
} else {
|
|
779
779
|
// Direct API provider: show key input
|
|
780
780
|
pHtml += `<div class="form-group" style="margin: 0;">
|
|
781
|
-
<label class="form-group__label" for="key-api-key">🔑 ${provider.envLabel}
|
|
781
|
+
<label class="form-group__label" for="key-api-key">🔑 ${provider.envLabel} <span style="color: var(--danger, #ef4444);">*</span></label>
|
|
782
782
|
<input type="text" class="form-input" id="key-api-key" placeholder="${provider.envKey}=..." style="font-family: monospace; font-size: 13px;" oninput="window.__validateKeys()">
|
|
783
783
|
<p class="form-group__hint">${isVi ? 'Lấy từ' : 'Get from'} <a href="${provider.envLink}" target="_blank">${provider.envLink.replace('https://', '')}</a></p>
|
|
784
784
|
</div>`;
|
|
@@ -800,13 +800,13 @@
|
|
|
800
800
|
|
|
801
801
|
if (state.channel === 'telegram') {
|
|
802
802
|
cHtml += `<div class="form-group" style="margin: 0;">
|
|
803
|
-
<label class="form-group__label" for="key-bot-token">🤖 Telegram Bot Token
|
|
803
|
+
<label class="form-group__label" for="key-bot-token">🤖 Telegram Bot Token <span style="color: var(--danger, #ef4444);">*</span></label>
|
|
804
804
|
<input type="text" class="form-input" id="key-bot-token" placeholder="VD: 1234567890:ABCdefGHIjklMNOpqrsTUVwxyz" style="font-family: monospace; font-size: 13px;" oninput="window.__validateKeys()">
|
|
805
805
|
<p class="form-group__hint">${isVi ? 'Lấy từ <a href="https://t.me/BotFather" target="_blank">@BotFather</a> trên Telegram' : 'Get from <a href="https://t.me/BotFather" target="_blank">@BotFather</a> on Telegram'}</p>
|
|
806
806
|
</div>`;
|
|
807
807
|
} else if (state.channel === 'zalo-bot') {
|
|
808
808
|
cHtml += `<div class="form-group" style="margin: 0;">
|
|
809
|
-
<label class="form-group__label" for="key-bot-token">🔑 Zalo Bot Token
|
|
809
|
+
<label class="form-group__label" for="key-bot-token">🔑 Zalo Bot Token <span style="color: var(--danger, #ef4444);">*</span></label>
|
|
810
810
|
<input type="text" class="form-input" id="key-bot-token" placeholder="Zalo Bot Token" style="font-family: monospace; font-size: 13px;" oninput="window.__validateKeys()">
|
|
811
811
|
<p class="form-group__hint">${isVi ? 'Lấy từ <a href="https://developers.zalo.me" target="_blank">Zalo Bot Platform</a>' : 'Get from <a href="https://developers.zalo.me" target="_blank">Zalo Bot Platform</a>'}</p>
|
|
812
812
|
</div>`;
|
|
@@ -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
|
-
|
|
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
|
},
|
|
@@ -1181,6 +1159,46 @@ ${finalCmd}`;
|
|
|
1181
1159
|
// extra_hosts always needed for browser (socat → host Chrome)
|
|
1182
1160
|
const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
|
|
1183
1161
|
|
|
1162
|
+
// ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
|
|
1163
|
+
// Background loop inside 9Router container every 30s.
|
|
1164
|
+
// Queries /api/providers → filters connected+enabled → updates smart-route combo.
|
|
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);`;
|
|
1201
|
+
|
|
1184
1202
|
let compose;
|
|
1185
1203
|
if (is9Router) {
|
|
1186
1204
|
compose = `services:
|
|
@@ -1196,14 +1214,22 @@ ${extraHostsBlock}
|
|
|
1196
1214
|
volumes:
|
|
1197
1215
|
- ../../.openclaw:/root/.openclaw
|
|
1198
1216
|
ports:
|
|
1199
|
-
- "
|
|
1217
|
+
- "38789:38789"
|
|
1200
1218
|
|
|
1201
1219
|
9router:
|
|
1202
1220
|
image: node:22-slim
|
|
1203
1221
|
container_name: 9router
|
|
1204
1222
|
restart: always
|
|
1205
|
-
entrypoint:
|
|
1206
|
-
/bin/sh
|
|
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
|
|
1207
1233
|
environment:
|
|
1208
1234
|
- PORT=20128
|
|
1209
1235
|
- HOSTNAME=0.0.0.0
|
|
@@ -1227,7 +1253,7 @@ ${extraHostsBlock}
|
|
|
1227
1253
|
volumes:
|
|
1228
1254
|
- ../../.openclaw:/root/.openclaw
|
|
1229
1255
|
ports:
|
|
1230
|
-
- "
|
|
1256
|
+
- "38789:38789"`;
|
|
1231
1257
|
}
|
|
1232
1258
|
|
|
1233
1259
|
setOutput('out-compose', compose);
|
|
@@ -1767,8 +1793,12 @@ Write-Host " 🦞 OpenClaw Auto Setup" -ForegroundColor Cyan
|
|
|
1767
1793
|
Write-Host " Project: $projectDir" -ForegroundColor White
|
|
1768
1794
|
Write-Host ""
|
|
1769
1795
|
|
|
1796
|
+
try {
|
|
1770
1797
|
# [1/4] Create directories
|
|
1771
1798
|
Write-Host "[1/4] ${isVi ? 'Tạo thư mục...' : 'Creating directories...'}" -ForegroundColor Yellow
|
|
1799
|
+
|
|
1800
|
+
# Ensure root directory exists first
|
|
1801
|
+
New-Item -ItemType Directory -Force -Path "$projectDir" | Out-Null
|
|
1772
1802
|
`;
|
|
1773
1803
|
|
|
1774
1804
|
// Collect unique directories
|
|
@@ -1788,8 +1818,10 @@ Write-Host "[1/4] ${isVi ? 'Tạo thư mục...' : 'Creating directories...'}" -
|
|
|
1788
1818
|
|
|
1789
1819
|
Object.entries(files).forEach(([path, content]) => {
|
|
1790
1820
|
const winPath = path.replace(/\//g, '\\');
|
|
1791
|
-
//
|
|
1792
|
-
const safeContent = content
|
|
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
|
|
1793
1825
|
ps += `\n[IO.File]::WriteAllText("$projectDir\\${winPath}", @'\n${safeContent}\n'@, $utf8)\n`;
|
|
1794
1826
|
});
|
|
1795
1827
|
|
|
@@ -1822,22 +1854,37 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
|
|
|
1822
1854
|
// Post-setup notes
|
|
1823
1855
|
const is9Router = state.config.provider === '9router';
|
|
1824
1856
|
if (is9Router) {
|
|
1825
|
-
ps += `Write-Host " ${isVi ? 'Mở http://localhost:
|
|
1857
|
+
ps += `Write-Host " ${isVi ? 'Mở http://localhost:30128/dashboard để login OAuth' : 'Open http://localhost:30128/dashboard to login OAuth'}" -ForegroundColor White\n`;
|
|
1826
1858
|
}
|
|
1827
1859
|
if (state.channel === 'zalo-personal') {
|
|
1828
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`;
|
|
1829
1861
|
}
|
|
1830
1862
|
|
|
1831
1863
|
ps += `Write-Host ""
|
|
1864
|
+
} catch {
|
|
1865
|
+
Write-Host ""
|
|
1866
|
+
Write-Host " ❌ LỖI / ERROR: $($_.Exception.Message)" -ForegroundColor Red
|
|
1867
|
+
Write-Host ""
|
|
1868
|
+
}
|
|
1832
1869
|
Read-Host "${isVi ? 'Nhấn Enter để thoát' : 'Press Enter to exit'}"
|
|
1833
1870
|
`;
|
|
1834
1871
|
|
|
1835
|
-
// Wrap in
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
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%"
|
|
1884
|
+
if %errorlevel% neq 0 pause
|
|
1885
|
+
del "%OPENCLAW_TMP%" 2>nul
|
|
1839
1886
|
exit /b
|
|
1840
|
-
|
|
1887
|
+
:PS_BEGIN
|
|
1841
1888
|
${ps}`;
|
|
1842
1889
|
|
|
1843
1890
|
return bat;
|
|
@@ -1848,7 +1895,8 @@ ${ps}`;
|
|
|
1848
1895
|
// Regenerate output first to ensure state._generatedFiles is current
|
|
1849
1896
|
generateOutput();
|
|
1850
1897
|
const content = generateAutoSetupBat();
|
|
1851
|
-
const
|
|
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' });
|
|
1852
1900
|
const url = URL.createObjectURL(blob);
|
|
1853
1901
|
const a = document.createElement('a');
|
|
1854
1902
|
a.href = url;
|