create-openclaw-bot 4.0.8 → 4.0.9
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 +12 -1
- package/CHANGELOG.vi.md +12 -1
- package/README.md +5 -5
- package/README.vi.md +3 -4
- package/cli.js +136 -2
- package/index.html +9 -4
- package/package.json +1 -1
- package/setup.js +19 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [4.0.
|
|
5
|
+
## [4.0.9] — 2026-04-01
|
|
6
|
+
|
|
7
|
+
### 🔄 Dynamic Smart Route (Real-time Provider Sync)
|
|
8
|
+
- **Zero-Waste Routing**: The `smart-route` combo is no longer a static list of 100+ models. A background sync loop now queries 9Router's `/api/providers` every 30 seconds and dynamically builds the combo from **only connected + enabled providers**. This eliminates `404 No active credentials` errors entirely.
|
|
9
|
+
- **Instant Provider Toggle**: Toggle providers on/off in the 9Router Dashboard — the combo updates automatically within 30s. No restart required.
|
|
10
|
+
- **Smart Mapping**: Full provider-to-model mapping covering 25+ providers (Codex, Claude Code, GitHub Copilot, Cursor, Kilo, Cline, Gemini CLI, iFlow, Qwen, Kiro, Ollama, GLM, MiniMax, DeepSeek, xAI, Mistral, Groq, etc.).
|
|
11
|
+
|
|
12
|
+
### 🐳 Docker Auto-Install
|
|
13
|
+
- **Zero-Prerequisite Setup**: `npx create-openclaw-bot` now detects if Docker is installed. If missing, it offers to install automatically via `winget` (Windows), `brew` (macOS), or the official Docker install script (Linux).
|
|
14
|
+
- **Guided Recovery**: Clear instructions and download links if automatic installation fails.
|
|
15
|
+
|
|
16
|
+
## [4.0.8] — 2026-03-31
|
|
6
17
|
|
|
7
18
|
### ✨ 9Router Stability & Ollama Cloud
|
|
8
19
|
- **Stable 9Router Integration (Zero Config)**: The 9Router proxy is now fully stabilized and runs securely within the Docker network via `sk-no-key`. External configuration (API keys, manual routing) is removed from `.env` and elegantly managed via the [9Router Dashboard](http://localhost:20128/dashboard).
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -2,7 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Tất cả những thay đổi nổi bật của dự án sẽ được ghi chép trong file này.
|
|
4
4
|
|
|
5
|
-
## [4.0.
|
|
5
|
+
## [4.0.9] — 2026-04-01
|
|
6
|
+
|
|
7
|
+
### 🔄 Dynamic Smart Route (Đồng bộ Provider Realtime)
|
|
8
|
+
- **Routing Thông Minh**: Combo `smart-route` không còn là danh sách cứng 100+ model. Script đồng bộ chạy ngầm mỗi 30 giây sẽ tự động quét `/api/providers` của 9Router và chỉ đưa vào combo **những provider đã kết nối VÀ đang bật**. Triệt tiêu hoàn toàn lỗi `404 No active credentials`.
|
|
9
|
+
- **Bật/Tắt Tức Thì**: Bật hoặc tắt provider trên Dashboard 9Router — combo tự cập nhật trong vòng 30 giây, không cần restart container.
|
|
10
|
+
- **Mapping Đầy Đủ**: Hỗ trợ 25+ provider (Codex, Claude Code, GitHub Copilot, Cursor, Kilo, Cline, Gemini CLI, iFlow, Qwen, Kiro, Ollama, GLM, MiniMax, DeepSeek, xAI, Mistral, Groq...).
|
|
11
|
+
|
|
12
|
+
### 🐳 Tự Động Cài Docker
|
|
13
|
+
- **Zero-Prerequisite**: `npx create-openclaw-bot` tự phát hiện Docker chưa cài → tự tải + cài qua `winget` (Windows), `brew` (macOS), hoặc script chính thức Docker (Linux).
|
|
14
|
+
- **Hướng Dẫn Rõ Ràng**: Nếu cài tự động thất bại, hiển thị link tải trực tiếp kèm hướng dẫn chi tiết.
|
|
15
|
+
|
|
16
|
+
## [4.0.8] — 2026-03-31
|
|
6
17
|
|
|
7
18
|
### ✨ Tối ưu 9Router & Mở rộng Ollama Cloud
|
|
8
19
|
- **Tích hợp 9Router cực kỳ Ổn định (Zero Config)**: Proxy 9Router hiện được tự động kích hoạt bảo mật bên trong mạng Docker network qua cổng `sk-no-key`. Toàn bộ thiết đặt API keys thủ công và định tuyến models được gỡ bỏ khỏi `.env` để nhường chỗ cho hệ thống quản lý tập trung và thông minh hơn qua [9Router Dashboard](http://localhost:20128/dashboard).
|
package/README.md
CHANGED
|
@@ -3,17 +3,16 @@
|
|
|
3
3
|
# 🦞 OpenClaw Setup
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v4.0.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v4.0.9-0EA5E9?style=for-the-badge" alt="Version 4.0.9" /></a>
|
|
7
7
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
8
8
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
9
|
-
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/dm/create-openclaw-bot?style=for-the-badge&color=22c55e" alt="NPM Downloads" /></a>
|
|
10
9
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
11
10
|
</p>
|
|
12
11
|
|
|
13
12
|
An interactive <strong>CLI tool</strong> and <strong>Setup Wizard</strong> to deploy your own free AI Bot on Telegram or Zalo in minutes.
|
|
14
13
|
|
|
15
14
|
<a href="https://github.com/tuanminhhole/openclaw-setup">
|
|
16
|
-
<img src="docs/preview.png" alt="OpenClaw Setup Hero Image" width="100%" style="border-radius: 8px; margin: 16px 0; border: 1px solid #333;" />
|
|
15
|
+
<img src="https://raw.githubusercontent.com/tuanminhhole/openclaw-setup/main/docs/preview.png" alt="OpenClaw Setup Hero Image" width="100%" style="border-radius: 8px; margin: 16px 0; border: 1px solid #333;" />
|
|
17
16
|
</a>
|
|
18
17
|
|
|
19
18
|
<p style="margin-top: 16px;">
|
|
@@ -34,7 +33,6 @@ An interactive <strong>CLI tool</strong> and <strong>Setup Wizard</strong> to de
|
|
|
34
33
|
- 🌐 **Instant Browser Automation**: Deploying Web Search and automated Browser skills is now fully handled during setup. We added built-in support for both Windows (`.bat` files) and macOS/Linux (`.sh` files) to instantly attach your local Chrome instances.
|
|
35
34
|
- 🧹 **Zero-Clutter Repository**: Eliminated dummy `.env.example` templates and static docker-compose files. The setup now generates precisely what you need, minimizing security risks and permission errors on native OS setups.
|
|
36
35
|
|
|
37
|
-
|
|
38
36
|
---
|
|
39
37
|
|
|
40
38
|
## ✨ Features
|
|
@@ -102,6 +100,7 @@ The fastest way to install OpenClaw is using the interactive NPM package.
|
|
|
102
100
|
<br>
|
|
103
101
|
|
|
104
102
|
1. **Clone this repo:**
|
|
103
|
+
|
|
105
104
|
```bash
|
|
106
105
|
git clone https://github.com/tuanminhhole/openclaw-setup.git
|
|
107
106
|
cd openclaw-setup
|
|
@@ -120,7 +119,7 @@ The fastest way to install OpenClaw is using the interactive NPM package.
|
|
|
120
119
|
2. Open this repo as workspace
|
|
121
120
|
3. Paste into chat:
|
|
122
121
|
```text
|
|
123
|
-
Read SETUP.md and set up OpenClaw v4.0.
|
|
122
|
+
Read SETUP.md and set up OpenClaw v4.0.9 for me.
|
|
124
123
|
My bot token is X, my 9Router proxy doesn't need a key.
|
|
125
124
|
My project folder: <YOUR_PATH>
|
|
126
125
|
```
|
|
@@ -161,6 +160,7 @@ On your computer inside a Docker container. When your PC is off, the bot is off.
|
|
|
161
160
|
docker compose down # Stop
|
|
162
161
|
docker compose up -d # Start
|
|
163
162
|
```
|
|
163
|
+
|
|
164
164
|
</details>
|
|
165
165
|
|
|
166
166
|
<details>
|
package/README.vi.md
CHANGED
|
@@ -3,17 +3,16 @@
|
|
|
3
3
|
# 🦞 OpenClaw Setup
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v4.0.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v4.0.9-0EA5E9?style=for-the-badge" alt="Version 4.0.9" /></a>
|
|
7
7
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
8
8
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
9
|
-
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/dm/create-openclaw-bot?style=for-the-badge&color=22c55e" alt="NPM Downloads" /></a>
|
|
10
9
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
11
10
|
</p>
|
|
12
11
|
|
|
13
12
|
Một công cụ trực quan <strong>Setup Wizard (UI)</strong> & <strong>CLI</strong> để tự tay build Bot AI trên Telegram và Zalo chỉ trong vài phút.
|
|
14
13
|
|
|
15
14
|
<a href="https://github.com/tuanminhhole/openclaw-setup">
|
|
16
|
-
<img src="docs/preview.png" alt="OpenClaw Setup Hero Image" width="100%" style="border-radius: 8px; margin: 16px 0; border: 1px solid #333;" />
|
|
15
|
+
<img src="https://raw.githubusercontent.com/tuanminhhole/openclaw-setup/main/docs/preview.png" alt="OpenClaw Setup Hero Image" width="100%" style="border-radius: 8px; margin: 16px 0; border: 1px solid #333;" />
|
|
17
16
|
</a>
|
|
18
17
|
|
|
19
18
|
<p style="margin-top: 16px;">
|
|
@@ -119,7 +118,7 @@ Dùng NPX là cách cài chuẩn nhất:
|
|
|
119
118
|
2. Mở repo này làm workspace
|
|
120
119
|
3. Paste vào chat:
|
|
121
120
|
```text
|
|
122
|
-
Read SETUP.md and install OpenClaw 4.0.
|
|
121
|
+
Read SETUP.md and install OpenClaw 4.0.9 for me.
|
|
123
122
|
My bot token is X, my 9Router proxy doesn't need a key.
|
|
124
123
|
My project folder: <THƯ_MỤC_CỦA_BẠN>
|
|
125
124
|
```
|
package/cli.js
CHANGED
|
@@ -4,7 +4,57 @@ import { input, select, checkbox, confirm } from '@inquirer/prompts';
|
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import { spawn } from 'child_process';
|
|
7
|
+
import { spawn, execSync } from 'child_process';
|
|
8
|
+
|
|
9
|
+
// ─── Docker Auto-Detection ───────────────────────────────────────────────────
|
|
10
|
+
function isDockerInstalled() {
|
|
11
|
+
try {
|
|
12
|
+
execSync('docker --version', { stdio: 'ignore' });
|
|
13
|
+
return true;
|
|
14
|
+
} catch { return false; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function ensureDocker(isVi) {
|
|
18
|
+
if (isDockerInstalled()) return true;
|
|
19
|
+
|
|
20
|
+
console.log(chalk.yellow(`\n⚠️ ${isVi ? 'Docker chưa được cài đặt trên máy!' : 'Docker is not installed on this machine!'}`));
|
|
21
|
+
|
|
22
|
+
const shouldInstall = await confirm({
|
|
23
|
+
message: isVi ? 'Bạn có muốn tự động cài Docker không?' : 'Do you want to install Docker automatically?',
|
|
24
|
+
default: true
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (!shouldInstall) {
|
|
28
|
+
console.log(chalk.cyan(isVi
|
|
29
|
+
? '👉 Tải Docker Desktop tại: https://www.docker.com/products/docker-desktop/'
|
|
30
|
+
: '👉 Download Docker Desktop at: https://www.docker.com/products/docker-desktop/'));
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const platform = process.platform;
|
|
35
|
+
try {
|
|
36
|
+
if (platform === 'win32') {
|
|
37
|
+
console.log(chalk.cyan(isVi ? '🐳 Đang tải Docker Desktop cho Windows (qua winget)...' : '🐳 Downloading Docker Desktop for Windows (via winget)...'));
|
|
38
|
+
execSync('winget install -e --id Docker.DockerDesktop --accept-source-agreements --accept-package-agreements', { stdio: 'inherit' });
|
|
39
|
+
} else if (platform === 'darwin') {
|
|
40
|
+
console.log(chalk.cyan(isVi ? '🐳 Đang tải Docker Desktop cho macOS (qua Homebrew)...' : '🐳 Downloading Docker Desktop for macOS (via Homebrew)...'));
|
|
41
|
+
execSync('brew install --cask docker', { stdio: 'inherit' });
|
|
42
|
+
} else {
|
|
43
|
+
console.log(chalk.cyan(isVi ? '🐳 Đang cài Docker cho Linux...' : '🐳 Installing Docker for Linux...'));
|
|
44
|
+
execSync('curl -fsSL https://get.docker.com | sh', { stdio: 'inherit' });
|
|
45
|
+
}
|
|
46
|
+
console.log(chalk.green(isVi ? '✅ Docker đã cài xong! Vui lòng khởi động Docker Desktop rồi chạy lại lệnh này.' : '✅ Docker installed! Please start Docker Desktop and re-run this command.'));
|
|
47
|
+
if (platform === 'win32' || platform === 'darwin') {
|
|
48
|
+
console.log(chalk.yellow(isVi ? '⚠️ Bạn cần mở Docker Desktop và đợi nó khởi động xong trước khi tiếp tục.' : '⚠️ Please open Docker Desktop and wait for it to finish starting.'));
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.log(chalk.red(isVi ? '❌ Không thể tự cài Docker. Vui lòng tải thủ công:' : '❌ Could not install Docker automatically. Please download manually:'));
|
|
54
|
+
console.log(chalk.cyan(' https://www.docker.com/products/docker-desktop/'));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
8
58
|
|
|
9
59
|
const LOGO = `
|
|
10
60
|
████████╗██╗ ██╗ █████╗ ███╗ ██╗███╗ ███╗██╗███╗ ██╗██╗ ██╗██╗ ██╗ ██████╗ ██╗ ███████╗
|
|
@@ -53,6 +103,9 @@ async function main() {
|
|
|
53
103
|
});
|
|
54
104
|
const isVi = lang === 'vi';
|
|
55
105
|
|
|
106
|
+
// 1b. Docker check
|
|
107
|
+
await ensureDocker(isVi);
|
|
108
|
+
|
|
56
109
|
// 2. Channel
|
|
57
110
|
const channelKey = await select({
|
|
58
111
|
message: isVi ? 'Chọn nền tảng bot:' : 'Select bot platform:',
|
|
@@ -168,6 +221,87 @@ CMD sh -c "node -e \\"eval(Buffer.from('${b64Patch}','base64').toString())\\" &&
|
|
|
168
221
|
|
|
169
222
|
const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-$/, '') || 'chat';
|
|
170
223
|
|
|
224
|
+
// ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
|
|
225
|
+
// This script runs inside the 9Router container as a background loop.
|
|
226
|
+
// Every 30s it queries /api/providers, filters for active+enabled providers,
|
|
227
|
+
// and updates the smart-route combo to ONLY include models from those providers.
|
|
228
|
+
const syncComboScript = `
|
|
229
|
+
#!/bin/sh
|
|
230
|
+
# sync-combo: dynamically builds smart-route combo from connected providers
|
|
231
|
+
ROUTER=http://localhost:20128
|
|
232
|
+
INTERVAL=30
|
|
233
|
+
|
|
234
|
+
# Wait for 9router to be ready
|
|
235
|
+
echo "[sync-combo] Waiting for 9Router to start..."
|
|
236
|
+
while ! wget -qO- \$ROUTER/api/version >/dev/null 2>&1; do sleep 2; done
|
|
237
|
+
echo "[sync-combo] 9Router is ready. Starting sync loop (every \${INTERVAL}s)..."
|
|
238
|
+
|
|
239
|
+
while true; do
|
|
240
|
+
PROVIDERS_JSON=\$(wget -qO- \$ROUTER/api/providers 2>/dev/null || echo '{}')
|
|
241
|
+
COMBO_JSON=\$(node -e "
|
|
242
|
+
const PROVIDER_MODELS = {
|
|
243
|
+
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','cx/gpt-5-codex-mini'],
|
|
244
|
+
'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'],
|
|
245
|
+
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-5-mini','gh/gpt-5-codex','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'],
|
|
246
|
+
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'],
|
|
247
|
+
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/openai/o3','kc/deepseek/deepseek-chat'],
|
|
248
|
+
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'],
|
|
249
|
+
'gemini-cli': ['gc/gemini-3-flash-preview','gc/gemini-3-pro-preview'],
|
|
250
|
+
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'],
|
|
251
|
+
qwen: ['qw/qwen3-coder-plus','qw/qwen3-coder-flash','qw/vision-model','qw/coder-model'],
|
|
252
|
+
kiro: ['kr/claude-sonnet-4.5','kr/claude-haiku-4.5','kr/deepseek-3.2','kr/deepseek-3.1','kr/qwen3-coder-next'],
|
|
253
|
+
ollama: ['ollama/qwen3.5','ollama/kimi-k2.5','ollama/glm-5','ollama/glm-4.7-flash','ollama/minimax-m2.5','ollama/gpt-oss:120b'],
|
|
254
|
+
'kimi-coding': ['kmc/kimi-k2.5','kmc/kimi-k2.5-thinking','kmc/kimi-latest'],
|
|
255
|
+
glm: ['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],
|
|
256
|
+
'glm-cn': ['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],
|
|
257
|
+
minimax: ['minimax/MiniMax-M2.7','minimax/MiniMax-M2.5','minimax/MiniMax-M2.1'],
|
|
258
|
+
kimi: ['kimi/kimi-k2.5','kimi/kimi-k2.5-thinking','kimi/kimi-latest'],
|
|
259
|
+
deepseek: ['deepseek/deepseek-chat','deepseek/deepseek-reasoner'],
|
|
260
|
+
xai: ['xai/grok-4','xai/grok-4-fast-reasoning','xai/grok-code-fast-1'],
|
|
261
|
+
mistral: ['mistral/mistral-large-latest','mistral/codestral-latest'],
|
|
262
|
+
groq: ['groq/llama-3.3-70b-versatile','groq/openai/gpt-oss-120b'],
|
|
263
|
+
cerebras: ['cerebras/gpt-oss-120b'],
|
|
264
|
+
alicode: ['alicode/qwen3.5-plus','alicode/qwen3-coder-plus'],
|
|
265
|
+
openai: ['openai/gpt-4o','openai/gpt-4.1','openai/o3-mini'],
|
|
266
|
+
anthropic: ['anthropic/claude-sonnet-4','anthropic/claude-haiku-3.5'],
|
|
267
|
+
gemini: ['gemini/gemini-2.5-flash','gemini/gemini-2.5-pro'],
|
|
268
|
+
};
|
|
269
|
+
try {
|
|
270
|
+
const data = \$PROVIDERS_JSON;
|
|
271
|
+
const active = (data.connections||[]).filter(c => c.isActive).map(c => c.provider);
|
|
272
|
+
if (active.length === 0) { process.exit(1); }
|
|
273
|
+
const models = active.flatMap(p => PROVIDER_MODELS[p]||[]);
|
|
274
|
+
if (models.length === 0) { process.exit(1); }
|
|
275
|
+
console.log(JSON.stringify({id:'smart-route',name:'smart-route',alias:'smart-route',models:models}));
|
|
276
|
+
} catch(e) { process.exit(1); }
|
|
277
|
+
" 2>/dev/null)
|
|
278
|
+
if [ -n "\$COMBO_JSON" ]; then
|
|
279
|
+
# Read existing db.json, update/add the smart-route combo, write back
|
|
280
|
+
node -e "
|
|
281
|
+
const fs = require('fs');
|
|
282
|
+
const dbPath = '/root/.9router/db.json';
|
|
283
|
+
let db = {};
|
|
284
|
+
try { db = JSON.parse(fs.readFileSync(dbPath,'utf8')); } catch(e) {}
|
|
285
|
+
const newCombo = \$COMBO_JSON;
|
|
286
|
+
if (!db.combos) db.combos = [];
|
|
287
|
+
const idx = db.combos.findIndex(c => c.id === 'smart-route');
|
|
288
|
+
if (idx >= 0) {
|
|
289
|
+
if (JSON.stringify(db.combos[idx].models) !== JSON.stringify(newCombo.models)) {
|
|
290
|
+
db.combos[idx] = newCombo;
|
|
291
|
+
fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
|
|
292
|
+
console.log('[sync-combo] Updated smart-route: ' + newCombo.models.length + ' models from ' + new Set(newCombo.models.map(m=>m.split('/')[0])).size + ' providers');
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
db.combos.push(newCombo);
|
|
296
|
+
fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
|
|
297
|
+
console.log('[sync-combo] Created smart-route: ' + newCombo.models.length + ' models');
|
|
298
|
+
}
|
|
299
|
+
" 2>/dev/null
|
|
300
|
+
fi
|
|
301
|
+
sleep \$INTERVAL
|
|
302
|
+
done
|
|
303
|
+
`.trim().replace(/\n/g, '\\n');
|
|
304
|
+
|
|
171
305
|
let compose = '';
|
|
172
306
|
if (providerKey === '9router') {
|
|
173
307
|
compose = `name: oc-${agentId}
|
|
@@ -190,7 +324,7 @@ ${selectedSkills.includes('browser') ? ` extra_hosts:
|
|
|
190
324
|
container_name: 9router-${agentId}
|
|
191
325
|
restart: always
|
|
192
326
|
entrypoint: >
|
|
193
|
-
/bin/sh -c "npm install -g 9router &&
|
|
327
|
+
/bin/sh -c "npm install -g 9router && (echo '${syncComboScript}' > /tmp/sync-combo.sh && chmod +x /tmp/sync-combo.sh && /bin/sh /tmp/sync-combo.sh &) && 9router"
|
|
194
328
|
environment:
|
|
195
329
|
- PORT=20128
|
|
196
330
|
- HOSTNAME=0.0.0.0
|
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
package/setup.js
CHANGED
|
@@ -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>`;
|
|
@@ -1181,6 +1181,11 @@ ${finalCmd}`;
|
|
|
1181
1181
|
// extra_hosts always needed for browser (socat → host Chrome)
|
|
1182
1182
|
const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
|
|
1183
1183
|
|
|
1184
|
+
// ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
|
|
1185
|
+
// Background loop inside 9Router container every 30s.
|
|
1186
|
+
// 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`;
|
|
1188
|
+
|
|
1184
1189
|
let compose;
|
|
1185
1190
|
if (is9Router) {
|
|
1186
1191
|
compose = `services:
|
|
@@ -1203,7 +1208,7 @@ ${extraHostsBlock}
|
|
|
1203
1208
|
container_name: 9router
|
|
1204
1209
|
restart: always
|
|
1205
1210
|
entrypoint: >
|
|
1206
|
-
/bin/sh -c "npm install -g 9router &&
|
|
1211
|
+
/bin/sh -c "npm install -g 9router && (echo '${syncScript}' > /tmp/sync.sh && sh /tmp/sync.sh &) && 9router"
|
|
1207
1212
|
environment:
|
|
1208
1213
|
- PORT=20128
|
|
1209
1214
|
- HOSTNAME=0.0.0.0
|
|
@@ -1767,8 +1772,12 @@ Write-Host " 🦞 OpenClaw Auto Setup" -ForegroundColor Cyan
|
|
|
1767
1772
|
Write-Host " Project: $projectDir" -ForegroundColor White
|
|
1768
1773
|
Write-Host ""
|
|
1769
1774
|
|
|
1775
|
+
try {
|
|
1770
1776
|
# [1/4] Create directories
|
|
1771
1777
|
Write-Host "[1/4] ${isVi ? 'Tạo thư mục...' : 'Creating directories...'}" -ForegroundColor Yellow
|
|
1778
|
+
|
|
1779
|
+
# Ensure root directory exists first
|
|
1780
|
+
New-Item -ItemType Directory -Force -Path "$projectDir" | Out-Null
|
|
1772
1781
|
`;
|
|
1773
1782
|
|
|
1774
1783
|
// Collect unique directories
|
|
@@ -1829,6 +1838,11 @@ Write-Host " 🎉 ${isVi ? 'Setup hoàn tất!' : 'Setup complete!'}" -Foregrou
|
|
|
1829
1838
|
}
|
|
1830
1839
|
|
|
1831
1840
|
ps += `Write-Host ""
|
|
1841
|
+
} catch {
|
|
1842
|
+
Write-Host ""
|
|
1843
|
+
Write-Host " ❌ LỖI / ERROR: $($_.Exception.Message)" -ForegroundColor Red
|
|
1844
|
+
Write-Host ""
|
|
1845
|
+
}
|
|
1832
1846
|
Read-Host "${isVi ? 'Nhấn Enter để thoát' : 'Press Enter to exit'}"
|
|
1833
1847
|
`;
|
|
1834
1848
|
|
|
@@ -1836,6 +1850,7 @@ Read-Host "${isVi ? 'Nhấn Enter để thoát' : 'Press Enter to exit'}"
|
|
|
1836
1850
|
const bat = `<# : batch wrapper
|
|
1837
1851
|
@echo off & chcp 65001>nul
|
|
1838
1852
|
powershell -ExecutionPolicy Bypass -NoProfile -File "%~f0" %*
|
|
1853
|
+
if %errorlevel% neq 0 pause
|
|
1839
1854
|
exit /b
|
|
1840
1855
|
#>
|
|
1841
1856
|
${ps}`;
|