create-openclaw-bot 5.8.3 → 5.8.5
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/README.md +2 -2
- package/README.vi.md +2 -2
- package/dist/cli.js +11 -15
- package/dist/server/local-server.js +231 -135
- package/dist/setup/data/header.js +1 -1
- package/dist/setup/data/skills.js +2 -4
- package/dist/setup/shared/bot-config-gen.js +21 -16
- package/dist/setup/shared/workspace-gen.js +307 -485
- package/dist/web/app.js +102 -31
- package/dist/web/styles.css +477 -10
- package/node_modules/color-convert/CHANGELOG.md +54 -0
- package/package.json +1 -1
- package/dist/legacy-cli.js +0 -2812
- package/dist/setup.js +0 -6941
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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-v5.8.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.5-0EA5E9?style=for-the-badge" alt="Version 5.8.5" /></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
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>
|
|
@@ -23,7 +23,7 @@ A next-generation **Web UI Setup** and management dashboard that automates 100%
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
## 🆕 What's New in v5.8.
|
|
26
|
+
## 🆕 What's New in v5.8.5
|
|
27
27
|
|
|
28
28
|
- 🔄 **Smart Header Update Button**: Instantly upgrades the setup wizard from the UI! The button queries the npm registry dynamically and only reveals itself when a newer release is published.
|
|
29
29
|
- 📡 **Live Log-Streaming Upgrade**: Kicking off the update automatically executes the migration (running `git pull && npm install && npm run build` for Git clones, or `npm install -g create-openclaw-bot` for NPM installs) while streaming standard outputs in real-time straight to the dashboard's Logs terminal.
|
package/README.vi.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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-v5.8.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.5-0EA5E9?style=for-the-badge" alt="Version 5.8.5" /></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
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>
|
|
@@ -23,7 +23,7 @@ Trình cài đặt và quản trị **Web UI Setup** thế hệ mới giúp tự
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
## 🆕 Có gì mới trong v5.8.
|
|
26
|
+
## 🆕 Có gì mới trong v5.8.5
|
|
27
27
|
|
|
28
28
|
- 🔄 **Nút cập nhật Header thông minh**: Nâng cấp trực tiếp setup wizard từ giao diện! Nút cập nhật tự truy vấn npm registry và chỉ hiển thị khi có phiên bản mới hơn.
|
|
29
29
|
- 📡 **Nâng cấp Stream Log trực tiếp**: Khởi chạy cập nhật sẽ tự động nâng cấp (chạy `git pull && npm install && npm run build` cho bản Git, hoặc `npm install -g create-openclaw-bot` cho bản NPM) và đẩy dòng log theo thời gian thực về tab Logs.
|
package/dist/cli.js
CHANGED
|
@@ -32,21 +32,17 @@ const args = process.argv.slice(2);
|
|
|
32
32
|
if (isLocalRepo()) {
|
|
33
33
|
const { startLocalInstaller } = await import('./server/local-server.js');
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
preferredPort: portArg ? Number(portArg.slice('--port='.length)) : 51789,
|
|
47
|
-
projectDir: projectDirArg ? projectDirArg.slice('--project-dir='.length) : process.cwd(),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
35
|
+
const noOpen = args.includes('--no-open');
|
|
36
|
+
const hostArg = args.find((arg) => arg.startsWith('--host='));
|
|
37
|
+
const portArg = args.find((arg) => arg.startsWith('--port='));
|
|
38
|
+
const projectDirArg = args.find((arg) => arg.startsWith('--project-dir='));
|
|
39
|
+
|
|
40
|
+
await startLocalInstaller({
|
|
41
|
+
openBrowser: !noOpen,
|
|
42
|
+
host: hostArg ? hostArg.slice('--host='.length) : '127.0.0.1',
|
|
43
|
+
preferredPort: portArg ? Number(portArg.slice('--port='.length)) : 51789,
|
|
44
|
+
projectDir: projectDirArg ? projectDirArg.slice('--project-dir='.length) : process.cwd(),
|
|
45
|
+
});
|
|
50
46
|
} else {
|
|
51
47
|
console.log('\n============================================================');
|
|
52
48
|
console.log(' 🦞 OpenClaw Setup — Auto-downloader & Installer');
|
|
@@ -13,7 +13,7 @@ function loadSharedModule(modulePath, globalName) {
|
|
|
13
13
|
if (loaded && Object.keys(loaded).length > 0) return loaded;
|
|
14
14
|
return globalThis[globalName] || loaded || {};
|
|
15
15
|
}
|
|
16
|
-
const { buildWorkspaceFileMap } = loadSharedModule('../setup/shared/workspace-gen.js', '__openclawWorkspace');
|
|
16
|
+
const { buildWorkspaceFileMap, buildCronjobSkillMd, buildInfographicGeneratorSkillMd, buildInfographicGeneratorJs } = loadSharedModule('../setup/shared/workspace-gen.js', '__openclawWorkspace');
|
|
17
17
|
const { buildOpenclawJson, buildEnvFileContent, buildExecApprovalsJson } = loadSharedModule('../setup/shared/bot-config-gen.js', '__openclawBotConfig');
|
|
18
18
|
const { buildDockerArtifacts } = loadSharedModule('../setup/shared/docker-gen.js', '__openclawDockerGen');
|
|
19
19
|
const { OPENCLAW_NPM_SPEC, NINE_ROUTER_NPM_SPEC, build9RouterProviderConfig, get9RouterBaseUrl } = loadSharedModule('../setup/shared/common-gen.js', '__openclawCommon');
|
|
@@ -22,11 +22,33 @@ const dataExport = loadSharedModule('../setup/data/index.js', '__openclawData');
|
|
|
22
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const WEB_DIR = resolve(__dirname, '../web');
|
|
24
24
|
const SETUP_VERSION = (() => { try { return JSON.parse(fs.readFileSync(resolve(__dirname, '../../package.json'), 'utf8')).version || '0.0.0'; } catch { return '0.0.0'; } })();
|
|
25
|
+
let latestSetupVersionCache = SETUP_VERSION;
|
|
26
|
+
let isFetchingLatestSetup = false;
|
|
27
|
+
|
|
28
|
+
async function fetchLatestSetupVersionBg() {
|
|
29
|
+
if (isFetchingLatestSetup) return;
|
|
30
|
+
isFetchingLatestSetup = true;
|
|
31
|
+
try {
|
|
32
|
+
const resp = await fetch('https://registry.npmjs.org/create-openclaw-bot/latest', { signal: AbortSignal.timeout(4000) });
|
|
33
|
+
if (resp.ok) {
|
|
34
|
+
const data = await resp.json();
|
|
35
|
+
if (data.version) {
|
|
36
|
+
latestSetupVersionCache = data.version;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
} finally {
|
|
41
|
+
isFetchingLatestSetup = false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
fetchLatestSetupVersionBg().catch(() => {});
|
|
45
|
+
|
|
25
46
|
const DEFAULT_PROJECT_NAME = 'openclaw-bot';
|
|
26
47
|
const STATE_FILE = '.openclaw-setup-state.json';
|
|
27
48
|
const DEFAULT_MODEL = 'smart-route';
|
|
28
49
|
const logClients = new Set();
|
|
29
50
|
let zaloLoginInFlight = false;
|
|
51
|
+
let activeServerInstance = null;
|
|
30
52
|
const state = {
|
|
31
53
|
installing: false,
|
|
32
54
|
installed: false,
|
|
@@ -901,7 +923,32 @@ async function buildBotStatus() {
|
|
|
901
923
|
resolveProjectRuntimeVersions(state.projectDir, state.mode).catch(() => ({ openclaw: '', nineRouter: '', node: process.version || '' })),
|
|
902
924
|
]);
|
|
903
925
|
const credentials = await readBotCredentials(state.projectDir).catch(() => ({ openclawToken: '', nineRouterApiKey: '' }));
|
|
904
|
-
|
|
926
|
+
|
|
927
|
+
let activeModel = 'smart-route';
|
|
928
|
+
let activeProvider = '9Router';
|
|
929
|
+
if (state.projectDir) {
|
|
930
|
+
const cfgPath = join(state.projectDir, '.openclaw', 'openclaw.json');
|
|
931
|
+
if (existsSync(cfgPath)) {
|
|
932
|
+
try {
|
|
933
|
+
const raw = await fsp.readFile(cfgPath, 'utf8');
|
|
934
|
+
const cfg = JSON.parse(raw);
|
|
935
|
+
const modelStr = cfg.agents?.defaults?.model?.primary || cfg.agents?.list?.[0]?.model?.primary || 'smart-route';
|
|
936
|
+
if (modelStr.includes('/')) {
|
|
937
|
+
const parts = modelStr.split('/');
|
|
938
|
+
activeProvider = parts[0];
|
|
939
|
+
activeModel = parts.slice(1).join('/');
|
|
940
|
+
} else {
|
|
941
|
+
activeModel = modelStr;
|
|
942
|
+
activeProvider = cfg.models?.providers?.openai ? 'openai' : '9router';
|
|
943
|
+
}
|
|
944
|
+
} catch (e) {}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const cap = (s) => String(s).toLowerCase() === 'openai' ? 'OpenAI' : String(s).toLowerCase() === '9router' ? '9Router' : s;
|
|
949
|
+
activeProvider = cap(activeProvider);
|
|
950
|
+
|
|
951
|
+
return { ...state, gatewayStatus, routerStatus, bots, credentials, runtimeVersions, activeModel, activeProvider };
|
|
905
952
|
}
|
|
906
953
|
|
|
907
954
|
async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
@@ -933,10 +980,16 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
|
933
980
|
agentMetas: [],
|
|
934
981
|
}));
|
|
935
982
|
|
|
936
|
-
const existingAgentCount = cfg.agents.list.length;
|
|
937
983
|
const used = new Set(cfg.agents.list.map((a) => a.id));
|
|
938
984
|
const botName = uniqueDisplayName(requestedBotName, new Set(cfg.agents.list.map((a) => a.name || a.id)));
|
|
939
|
-
|
|
985
|
+
let agentId = body.agentId ? String(body.agentId).trim().toLowerCase().replace(/[^a-z0-9-_]+/g, '-') : '';
|
|
986
|
+
if (!agentId) {
|
|
987
|
+
agentId = uniqueSlug(slugify(botName), used);
|
|
988
|
+
} else {
|
|
989
|
+
if (used.has(agentId)) {
|
|
990
|
+
throw httpError(400, `Bot ID "${agentId}" đã tồn tại. Vui lòng chọn ID khác.`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
940
993
|
const workspaceDir = `workspace-${agentId}`;
|
|
941
994
|
const model = cfg.agents.defaults?.model?.primary || cfg.agents.list[0]?.model?.primary || DEFAULT_MODEL;
|
|
942
995
|
cfg.agents.list.push({
|
|
@@ -980,6 +1033,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
|
980
1033
|
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
981
1034
|
|
|
982
1035
|
const hasScheduler = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
|
|
1036
|
+
const hasImageGen = !!(cfg.skills?.entries?.['image-gen']?.enabled);
|
|
983
1037
|
const files = buildWorkspaceFileMap({
|
|
984
1038
|
isVi: true,
|
|
985
1039
|
botName,
|
|
@@ -991,6 +1045,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
|
991
1045
|
workspacePath: `.openclaw/${workspaceDir}`,
|
|
992
1046
|
hasZaloMod: channel === 'zalo-personal',
|
|
993
1047
|
hasScheduler,
|
|
1048
|
+
hasImageGen,
|
|
994
1049
|
});
|
|
995
1050
|
const wsRoot = join(openclawHome, workspaceDir);
|
|
996
1051
|
for (const [name, content] of Object.entries(files)) {
|
|
@@ -1080,6 +1135,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
|
|
|
1080
1135
|
}
|
|
1081
1136
|
|
|
1082
1137
|
const hasScheduler = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
|
|
1138
|
+
const hasImageGen = !!(cfg.skills?.entries?.['image-gen']?.enabled);
|
|
1083
1139
|
const files = buildWorkspaceFileMap({
|
|
1084
1140
|
isVi: true,
|
|
1085
1141
|
botName,
|
|
@@ -1091,6 +1147,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
|
|
|
1091
1147
|
workspacePath: `.openclaw/${workspaceDir}`,
|
|
1092
1148
|
hasZaloMod: channel === 'zalo-personal',
|
|
1093
1149
|
hasScheduler,
|
|
1150
|
+
hasImageGen,
|
|
1094
1151
|
});
|
|
1095
1152
|
const wsRoot = join(projectDir, '.openclaw', workspaceDir);
|
|
1096
1153
|
for (const [name, content] of Object.entries(files)) {
|
|
@@ -1531,10 +1588,9 @@ async function writeCoreProject({ projectDir, osChoice, mode, gatewayPort = 1878
|
|
|
1531
1588
|
await fsp.mkdir(openclawHome, { recursive: true });
|
|
1532
1589
|
await fsp.mkdir(join(openclawHome, 'plugin-runtime-deps'), { recursive: true });
|
|
1533
1590
|
|
|
1534
|
-
const selectedSkills = [];
|
|
1535
|
-
const
|
|
1536
|
-
const
|
|
1537
|
-
const common = { botName, channelKey: 'telegram', providerKey: '9router', model: DEFAULT_MODEL, deployMode: mode, osChoice, selectedSkills, skills: dataExport.SKILLS || [], agentMetas, gatewayPort, routerPort };
|
|
1591
|
+
const selectedSkills = ['memory', 'image-gen', 'web-search'];
|
|
1592
|
+
const agentMetas = [];
|
|
1593
|
+
const common = { channelKey: 'telegram', providerKey: '9router', model: DEFAULT_MODEL, deployMode: mode, osChoice, selectedSkills, skills: dataExport.SKILLS || [], agentMetas, gatewayPort, routerPort };
|
|
1538
1594
|
const cfg = buildOpenclawJson(common);
|
|
1539
1595
|
const env = buildEnvFileContent({ ...common, apiKey: '', botToken: '' });
|
|
1540
1596
|
const approvals = buildExecApprovalsJson({ agentMetas });
|
|
@@ -1543,26 +1599,6 @@ async function writeCoreProject({ projectDir, osChoice, mode, gatewayPort = 1878
|
|
|
1543
1599
|
await fsp.writeFile(join(projectDir, '.env'), env, 'utf8');
|
|
1544
1600
|
await fsp.writeFile(join(openclawHome, 'exec-approvals.json'), JSON.stringify(approvals, null, 2), 'utf8');
|
|
1545
1601
|
|
|
1546
|
-
const workspaceDir = 'workspace-bot';
|
|
1547
|
-
const workspace = buildWorkspaceFileMap({
|
|
1548
|
-
isVi: true,
|
|
1549
|
-
botName,
|
|
1550
|
-
channelKey: 'telegram',
|
|
1551
|
-
providerKey: '9router',
|
|
1552
|
-
selectedSkills,
|
|
1553
|
-
skillsCatalog: dataExport.SKILLS || [],
|
|
1554
|
-
agentMetas,
|
|
1555
|
-
deployMode: mode,
|
|
1556
|
-
osChoice,
|
|
1557
|
-
agentWorkspaceDir: workspaceDir,
|
|
1558
|
-
workspacePath: `.openclaw/${workspaceDir}`,
|
|
1559
|
-
});
|
|
1560
|
-
const wsRoot = join(openclawHome, workspaceDir);
|
|
1561
|
-
for (const [name, content] of Object.entries(workspace)) {
|
|
1562
|
-
await fsp.mkdir(dirname(join(wsRoot, name)), { recursive: true });
|
|
1563
|
-
await fsp.writeFile(join(wsRoot, name), content || '', 'utf8');
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
1602
|
if (mode === 'docker') {
|
|
1567
1603
|
const projectName = slugify(basename(projectDir)) || 'bot';
|
|
1568
1604
|
const docker = buildDockerArtifacts({
|
|
@@ -1616,7 +1652,7 @@ async function installCore({ osChoice, mode, projectDir, gatewayPort = 18789, ro
|
|
|
1616
1652
|
await fsp.mkdir(dockerDir, { recursive: true });
|
|
1617
1653
|
const envContent = existsSync(rootEnvPath)
|
|
1618
1654
|
? await fsp.readFile(rootEnvPath, 'utf8')
|
|
1619
|
-
: buildEnvFileContent({
|
|
1655
|
+
: buildEnvFileContent({ channelKey: 'telegram', providerKey: '9router', deployMode: mode, osChoice, selectedSkills: [], skills: dataExport.SKILLS || [], agentMetas: [], apiKey: '', botToken: '' });
|
|
1620
1656
|
await fsp.writeFile(dockerEnvPath, envContent, 'utf8');
|
|
1621
1657
|
sendLog(`Docker env ready: ${dockerEnvPath}`);
|
|
1622
1658
|
await run('docker', ['compose', 'up', '-d', '--build'], { cwd: dockerDir });
|
|
@@ -1735,6 +1771,29 @@ async function loadSavedState(rootProjectDir) {
|
|
|
1735
1771
|
}
|
|
1736
1772
|
}
|
|
1737
1773
|
|
|
1774
|
+
function isRestrictedSystemDir(dirPath) {
|
|
1775
|
+
if (!dirPath) return true;
|
|
1776
|
+
const lower = resolve(dirPath).toLowerCase();
|
|
1777
|
+
|
|
1778
|
+
if (SYSTEM_DIR_BLACKLIST.has(basename(lower))) return true;
|
|
1779
|
+
|
|
1780
|
+
const winDir = process.env.SystemRoot ? resolve(process.env.SystemRoot).toLowerCase() : 'c:\\windows';
|
|
1781
|
+
const programFiles = process.env.ProgramFiles ? resolve(process.env.ProgramFiles).toLowerCase() : 'c:\\program files';
|
|
1782
|
+
const programFilesX86 = process.env['ProgramFiles(x86)'] ? resolve(process.env['ProgramFiles(x86)']).toLowerCase() : 'c:\\program files (x86)';
|
|
1783
|
+
|
|
1784
|
+
if (lower.startsWith(winDir) || lower.startsWith(programFiles) || lower.startsWith(programFilesX86)) {
|
|
1785
|
+
return true;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
if (lower.includes(':\\users\\') || lower.endsWith(':\\users')) {
|
|
1789
|
+
const home = resolve(os.homedir()).toLowerCase();
|
|
1790
|
+
if (lower !== home && !lower.startsWith(home + '\\') && !lower.startsWith(home + '/')) {
|
|
1791
|
+
return true;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
return false;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1738
1797
|
async function findLatestProject(rootProjectDir) {
|
|
1739
1798
|
const roots = [
|
|
1740
1799
|
process.env.OPENCLAW_PROJECT_DIR,
|
|
@@ -1743,29 +1802,39 @@ async function findLatestProject(rootProjectDir) {
|
|
|
1743
1802
|
join(rootProjectDir, DEFAULT_PROJECT_NAME),
|
|
1744
1803
|
dirname(rootProjectDir),
|
|
1745
1804
|
os.homedir(),
|
|
1746
|
-
|
|
1747
|
-
|
|
1805
|
+
join(os.homedir(), 'Documents'),
|
|
1806
|
+
].filter(Boolean);
|
|
1807
|
+
|
|
1748
1808
|
const drives = await getAvailableDrives();
|
|
1749
1809
|
for (const drive of drives) {
|
|
1750
1810
|
const entries = await fsp.readdir(drive, { withFileTypes: true }).catch(() => []);
|
|
1751
1811
|
for (const e of entries) {
|
|
1752
1812
|
if (e.isDirectory() && !e.name.startsWith('$') && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
|
|
1753
|
-
|
|
1813
|
+
const fullPath = join(drive, e.name);
|
|
1814
|
+
if (!isRestrictedSystemDir(fullPath)) {
|
|
1815
|
+
roots.push(fullPath);
|
|
1816
|
+
}
|
|
1754
1817
|
}
|
|
1755
1818
|
}
|
|
1756
1819
|
}
|
|
1757
1820
|
const candidates = [];
|
|
1821
|
+
const seen = new Set();
|
|
1758
1822
|
async function walk(dir, depth = 0) {
|
|
1759
1823
|
if (!dir || depth > 2 || !existsSync(dir)) return;
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1824
|
+
const full = resolve(dir);
|
|
1825
|
+
if (isRestrictedSystemDir(full)) return;
|
|
1826
|
+
if (seen.has(full)) return;
|
|
1827
|
+
seen.add(full);
|
|
1828
|
+
|
|
1829
|
+
if (existsSync(join(full, '.openclaw', 'openclaw.json'))) {
|
|
1830
|
+
const st = await fsp.stat(join(full, '.openclaw', 'openclaw.json')).catch(() => null);
|
|
1831
|
+
if (st) candidates.push({ dir: full, mtimeMs: st.mtimeMs });
|
|
1763
1832
|
return;
|
|
1764
1833
|
}
|
|
1765
|
-
const entries = await fsp.readdir(
|
|
1834
|
+
const entries = await fsp.readdir(full, { withFileTypes: true }).catch(() => []);
|
|
1766
1835
|
for (const e of entries) {
|
|
1767
1836
|
if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules' && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
|
|
1768
|
-
await walk(join(
|
|
1837
|
+
await walk(join(full, e.name), depth + 1);
|
|
1769
1838
|
}
|
|
1770
1839
|
}
|
|
1771
1840
|
}
|
|
@@ -1780,16 +1849,28 @@ async function discoverProjects(rootProjectDir) {
|
|
|
1780
1849
|
rootProjectDir,
|
|
1781
1850
|
dirname(rootProjectDir),
|
|
1782
1851
|
process.env.OPENCLAW_HOME ? dirname(process.env.OPENCLAW_HOME) : '',
|
|
1783
|
-
|
|
1784
|
-
|
|
1852
|
+
os.homedir(),
|
|
1853
|
+
join(os.homedir(), 'Documents'),
|
|
1854
|
+
].filter(Boolean);
|
|
1855
|
+
|
|
1785
1856
|
const drives = await getAvailableDrives();
|
|
1786
|
-
for (const drive of drives)
|
|
1857
|
+
for (const drive of drives) {
|
|
1858
|
+
const entries = await fsp.readdir(drive, { withFileTypes: true }).catch(() => []);
|
|
1859
|
+
for (const e of entries) {
|
|
1860
|
+
if (e.isDirectory() && !e.name.startsWith('$') && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
|
|
1861
|
+
const fullPath = join(drive, e.name);
|
|
1862
|
+
if (!isRestrictedSystemDir(fullPath)) {
|
|
1863
|
+
roots.push(fullPath);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1787
1868
|
const seen = new Set();
|
|
1788
1869
|
const hits = [];
|
|
1789
1870
|
async function walk(dir, depth = 0) {
|
|
1790
1871
|
if (!dir || depth > 2 || !existsSync(dir)) return;
|
|
1791
1872
|
const full = resolve(dir);
|
|
1792
|
-
if (full
|
|
1873
|
+
if (isRestrictedSystemDir(full)) return;
|
|
1793
1874
|
if (seen.has(full)) return;
|
|
1794
1875
|
seen.add(full);
|
|
1795
1876
|
const cfgPath = join(full, '.openclaw', 'openclaw.json');
|
|
@@ -1817,7 +1898,7 @@ async function discoverProjects(rootProjectDir) {
|
|
|
1817
1898
|
const entries = await fsp.readdir(full, { withFileTypes: true }).catch(() => []);
|
|
1818
1899
|
for (const e of entries) {
|
|
1819
1900
|
if (!e.isDirectory()) continue;
|
|
1820
|
-
if (e.name === 'node_modules' || e.name.startsWith('.
|
|
1901
|
+
if (e.name === 'node_modules' || e.name.startsWith('.') || SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) continue;
|
|
1821
1902
|
await walk(join(full, e.name), depth + 1);
|
|
1822
1903
|
}
|
|
1823
1904
|
}
|
|
@@ -1971,72 +2052,23 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
1971
2052
|
const k = `${kind}:${id}`;
|
|
1972
2053
|
|
|
1973
2054
|
if (kind === 'skill' && id === 'browser') {
|
|
2055
|
+
delete cfg.browser;
|
|
2056
|
+
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2057
|
+
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
2058
|
+
const aliases = ['browser-automation', 'openclaw-browser-automation'];
|
|
2059
|
+
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
2060
|
+
cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
|
|
2061
|
+
cfg.plugins.entries[existingKey].enabled = !!enabled;
|
|
2062
|
+
cfg.plugins.allow = cfg.plugins.allow || [];
|
|
1974
2063
|
if (enabled) {
|
|
1975
|
-
cfg.
|
|
1976
|
-
enabled: true,
|
|
1977
|
-
defaultProfile: 'host-chrome',
|
|
1978
|
-
profiles: { 'host-chrome': { cdpUrl: 'http://127.0.0.1:9222', color: '#4285F4' } },
|
|
1979
|
-
};
|
|
1980
|
-
const isHeadlessServer = process.platform === 'linux';
|
|
1981
|
-
const docVariant = 'cli-server';
|
|
1982
|
-
|
|
1983
|
-
for (const a of cfg.agents.list) {
|
|
1984
|
-
const wm = buildWorkspaceFileMap({
|
|
1985
|
-
isVi: true,
|
|
1986
|
-
botName: a.name || a.id,
|
|
1987
|
-
botDesc: '',
|
|
1988
|
-
hasBrowser: false,
|
|
1989
|
-
hasScheduler: true,
|
|
1990
|
-
workspacePath: `.openclaw/${workspaceRelForAgent(a, cfg, projectDir)}/`,
|
|
1991
|
-
agentWorkspaceDir: workspaceRelForAgent(a, cfg, projectDir),
|
|
1992
|
-
variant: cfg.agents.list.length > 1 ? 'relay' : 'single',
|
|
1993
|
-
browserDocVariant: docVariant,
|
|
1994
|
-
});
|
|
1995
|
-
const browserDoc = wm['BROWSER.md'] || '# BROWSER';
|
|
1996
|
-
const browserTool = wm['browser-tool.js'] || '';
|
|
1997
|
-
const bf = await readWorkspaceText(projectDir, a, 'BROWSER.md');
|
|
1998
|
-
await fsp.writeFile(bf.file, browserDoc, 'utf8');
|
|
1999
|
-
const bt = await readWorkspaceText(projectDir, a, 'browser-tool.js');
|
|
2000
|
-
if (browserTool) await fsp.writeFile(bt.file, browserTool, 'utf8');
|
|
2001
|
-
|
|
2002
|
-
const af = await readWorkspaceText(projectDir, a, 'AGENTS.md');
|
|
2003
|
-
const agentsManaged = upsertManagedBlock(af.content, 'BROWSER_LINK', '- Browser docs: `BROWSER.md`');
|
|
2004
|
-
await fsp.writeFile(af.file, agentsManaged, 'utf8');
|
|
2005
|
-
|
|
2006
|
-
// Add to TOOLS.md
|
|
2007
|
-
const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
|
|
2008
|
-
const browserGuide = isHeadlessServer
|
|
2009
|
-
? `## 🌐 Browser Automation
|
|
2010
|
-
- Xem hướng dẫn chi tiết tại **BROWSER.md**
|
|
2011
|
-
- Script điều khiển: \`browser-tool.js\`
|
|
2012
|
-
- Chế độ hiện tại: Chạy ngầm độc lập qua Docker hoặc Xvfb trên VPS.
|
|
2013
|
-
- **Tìm kiếm Web:** Nếu không có công cụ Web Search (hoặc Web Search không khả dụng/bị lỗi), hãy **luôn sử dụng ngay công cụ terminal (exec/run_command) để chạy lệnh: \`node search-tool.js "<từ khóa>" 5\`**! Lệnh này sẽ tự động chạy ngầm qua DuckDuckGo/Google/Bing bằng trình duyệt ngầm tàng hình của bạn và trả về kết quả JSON sạch ngay lập tức. Tuyệt đối KHÔNG được mở trình duyệt thủ công, chụp ảnh màn hình hay click tìm kiếm bằng tay từng bước!
|
|
2014
|
-
- Nếu browser lỗi, thử lại 1 lần rồi mới báo user với lỗi cụ thể`
|
|
2015
|
-
: `## 🌐 Browser Automation
|
|
2016
|
-
- Xem hướng dẫn chi tiết tại **BROWSER.md**
|
|
2017
|
-
- Script điều khiển: \`browser-tool.js\`
|
|
2018
|
-
- Chế độ hiện tại:
|
|
2019
|
-
- **Mặc định:** Chạy ngầm độc lập qua Docker hoặc Server.
|
|
2020
|
-
- **Chế độ xem Chrome thật:** Chạy file \`start-chrome-debug.bat\` / \`start-chrome-debug.sh\` trên host trước để bot kết nối điều khiển trực quan.
|
|
2021
|
-
- Kết nối mặc định: \`http://127.0.0.1:9222\`
|
|
2022
|
-
- **Tìm kiếm Web:** Nếu không có công cụ Web Search (hoặc Web Search không khả dụng/bị lỗi), hãy **luôn sử dụng ngay công cụ terminal (exec/run_command) để chạy lệnh: \`node search-tool.js "<từ khóa>" 5\`**! Lệnh này sẽ tự động chạy ngầm qua DuckDuckGo/Google/Bing bằng trình duyệt ngầm tàng hình của bạn và trả về kết quả JSON sạch ngay lập tức. Tuyệt đối KHÔNG được mở trình duyệt thủ công, chụp ảnh màn hình hay click tìm kiếm bằng tay từng bước!
|
|
2023
|
-
- Nếu browser lỗi, thử lại 1 lần rồi mới báo user với lỗi cụ thể`;
|
|
2024
|
-
await fsp.writeFile(tf.file, upsertManagedBlock(tf.content, 'BROWSER_GUIDE', browserGuide), 'utf8');
|
|
2025
|
-
}
|
|
2064
|
+
if (!cfg.plugins.allow.includes(existingKey)) cfg.plugins.allow.push(existingKey);
|
|
2026
2065
|
} else {
|
|
2027
|
-
|
|
2066
|
+
cfg.plugins.allow = cfg.plugins.allow.filter((x) => x !== existingKey);
|
|
2028
2067
|
for (const a of cfg.agents.list) {
|
|
2029
2068
|
const bf = await readWorkspaceText(projectDir, a, 'BROWSER.md');
|
|
2030
2069
|
if (existsSync(bf.file)) await fsp.rm(bf.file, { force: true });
|
|
2031
2070
|
const bt = await readWorkspaceText(projectDir, a, 'browser-tool.js');
|
|
2032
2071
|
if (existsSync(bt.file)) await fsp.rm(bt.file, { force: true });
|
|
2033
|
-
|
|
2034
|
-
const af = await readWorkspaceText(projectDir, a, 'AGENTS.md');
|
|
2035
|
-
await fsp.writeFile(af.file, removeManagedBlock(af.content, 'BROWSER_LINK'), 'utf8');
|
|
2036
|
-
|
|
2037
|
-
// Remove from TOOLS.md
|
|
2038
|
-
const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
|
|
2039
|
-
await fsp.writeFile(tf.file, removeManagedBlock(tf.content, 'BROWSER_GUIDE'), 'utf8');
|
|
2040
2072
|
}
|
|
2041
2073
|
}
|
|
2042
2074
|
|
|
@@ -2059,37 +2091,17 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
2059
2091
|
cfg.tools.alsoAllow = Array.from(new Set([...(cfg.tools.alsoAllow || []), 'group:automation']));
|
|
2060
2092
|
cfg.commands = cfg.commands || {};
|
|
2061
2093
|
cfg.commands.ownerAllowFrom = Array.from(new Set([...(cfg.commands.ownerAllowFrom || []), '*']));
|
|
2062
|
-
const cronGuide = `## ⏰ Cron / Lên lịch nhắc nhở (tool: \`cron\`)
|
|
2063
|
-
- **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
|
|
2064
|
-
- **⛔ TUYỆT ĐỐI KHÔNG sửa trực tiếp file JSON** như \`jobs.json\`, \`jobs-state.json\` trong thư mục \`.openclaw/cron/\`. Dữ liệu cron được lưu trong SQLite database, file JSON chỉ là legacy format đã ngưng hỗ trợ. Mọi thao tác PHẢI thông qua tool \`cron\`.
|
|
2065
|
-
- **Khi tạo cronjob mới (action \`add\`):**
|
|
2066
|
-
- **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
|
|
2067
|
-
- Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
|
|
2068
|
-
- **Session:** Luôn dùng \`sessionTarget: "isolated"\` cho các job chạy nền (báo cáo, nhắc nhở, gửi tin nhắn tự động). Chỉ dùng \`"main"\` cho system event/reminder ngắn.
|
|
2069
|
-
- **Timezone:** Luôn chỉ định timezone rõ ràng bằng trường \`tz\` (ví dụ: \`"Asia/Ho_Chi_Minh"\`). Nếu không chỉ định, hệ thống sẽ dùng timezone của Gateway host (thường là UTC) và job sẽ chạy sai giờ.
|
|
2070
|
-
- **Delivery:** Đối với job cần gửi kết quả ra chat, set \`delivery.mode: "announce"\` kèm \`delivery.channel\` và \`delivery.to\`.
|
|
2071
|
-
- **Khi user yêu cầu tắt/bật/xóa cronjob:**
|
|
2072
|
-
1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
|
|
2073
|
-
2. **Bước 2 (Xử lý):**
|
|
2074
|
-
- Để xóa: Gọi action \`remove\` với \`id\` tìm được.
|
|
2075
|
-
- Để tắt/tạm dừng: Gọi action \`update\` với \`id\` và patch \`{"enabled": false}\`.
|
|
2076
|
-
- Để bật lại: Gọi action \`update\` với \`id\` và patch \`{"enabled": true}\`.
|
|
2077
|
-
3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
|
|
2078
|
-
- Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
|
|
2079
|
-
- Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
|
|
2080
|
-
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố thích hợp trước ID của group. Với kênh Telegram/Matrix/Discord/Slack, dùng tiền tố \`group:\` (ví dụ: \`group:123456\`). RIÊNG với kênh Zalo (\`zalouser\`), **bắt buộc** phải sử dụng tiền tố \`g:\` (ví dụ: \`g:3815464776067464419\`) để tránh bị OpenClaw core lược bỏ tiền tố và gửi nhầm vào DM chat cá nhân.
|
|
2081
|
-
- **One-shot job:** Dùng schedule kind \`"at"\` với ISO 8601 timestamp. Job sẽ tự xóa sau khi chạy thành công trừ khi set \`deleteAfterRun: false\`.
|
|
2082
|
-
- Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`;
|
|
2083
2094
|
for (const a of cfg.agents.list) {
|
|
2084
|
-
const
|
|
2085
|
-
await fsp.
|
|
2095
|
+
const sf = await readWorkspaceText(projectDir, a, 'skills/cronjob/SKILL.md');
|
|
2096
|
+
await fsp.mkdir(dirname(sf.file), { recursive: true });
|
|
2097
|
+
await fsp.writeFile(sf.file, buildCronjobSkillMd(true), 'utf8');
|
|
2086
2098
|
}
|
|
2087
2099
|
} else {
|
|
2088
2100
|
if (cfg.tools?.alsoAllow) cfg.tools.alsoAllow = cfg.tools.alsoAllow.filter((x) => x !== 'group:automation');
|
|
2089
2101
|
if (cfg.commands?.ownerAllowFrom) cfg.commands.ownerAllowFrom = cfg.commands.ownerAllowFrom.filter((x) => x !== '*');
|
|
2090
2102
|
for (const a of cfg.agents.list) {
|
|
2091
|
-
const
|
|
2092
|
-
await fsp.
|
|
2103
|
+
const sf = await readWorkspaceText(projectDir, a, 'skills/cronjob/SKILL.md');
|
|
2104
|
+
if (existsSync(sf.file)) await fsp.rm(sf.file, { force: true });
|
|
2093
2105
|
}
|
|
2094
2106
|
}
|
|
2095
2107
|
|
|
@@ -2104,6 +2116,59 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
2104
2116
|
}
|
|
2105
2117
|
}
|
|
2106
2118
|
|
|
2119
|
+
if (kind === 'skill' && id === 'image-gen') {
|
|
2120
|
+
cfg.skills = cfg.skills || { entries: {} };
|
|
2121
|
+
cfg.skills.entries = cfg.skills.entries || {};
|
|
2122
|
+
cfg.skills.entries['image-gen'] = cfg.skills.entries['image-gen'] || {};
|
|
2123
|
+
cfg.skills.entries['image-gen'].enabled = !!enabled;
|
|
2124
|
+
|
|
2125
|
+
for (const a of cfg.agents.list) {
|
|
2126
|
+
const sf = await readWorkspaceText(projectDir, a, 'skills/infographic-generator/SKILL.md');
|
|
2127
|
+
const js = await readWorkspaceText(projectDir, a, 'skills/infographic-generator/image-generator.js');
|
|
2128
|
+
if (enabled) {
|
|
2129
|
+
await fsp.mkdir(dirname(sf.file), { recursive: true });
|
|
2130
|
+
await fsp.writeFile(sf.file, buildInfographicGeneratorSkillMd(), 'utf8');
|
|
2131
|
+
await fsp.writeFile(js.file, buildInfographicGeneratorJs(), 'utf8');
|
|
2132
|
+
} else {
|
|
2133
|
+
if (existsSync(sf.file)) await fsp.rm(sf.file, { force: true });
|
|
2134
|
+
if (existsSync(js.file)) await fsp.rm(js.file, { force: true });
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// Write cfgPath early so recreation reads updated openclaw.json
|
|
2139
|
+
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2140
|
+
|
|
2141
|
+
// Recreate container to apply updated openclaw.json
|
|
2142
|
+
const hasDocker = existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'));
|
|
2143
|
+
if (hasDocker) {
|
|
2144
|
+
sendLog(`[docker] Infographic skill toggled to ${enabled}. Recreating containers...`);
|
|
2145
|
+
await recreateDockerBot(projectDir).catch((err) => sendLog(`[docker] Warning: Failed to recreate container: ${err.message}`));
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
if (kind === 'skill' && id === 'web-search') {
|
|
2150
|
+
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2151
|
+
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
2152
|
+
cfg.plugins.entries['duckduckgo'] = cfg.plugins.entries['duckduckgo'] || {};
|
|
2153
|
+
cfg.plugins.entries['duckduckgo'].enabled = !!enabled;
|
|
2154
|
+
cfg.plugins.allow = cfg.plugins.allow || [];
|
|
2155
|
+
if (enabled) {
|
|
2156
|
+
if (!cfg.plugins.allow.includes('duckduckgo')) cfg.plugins.allow.push('duckduckgo');
|
|
2157
|
+
} else {
|
|
2158
|
+
cfg.plugins.allow = cfg.plugins.allow.filter((x) => x !== 'duckduckgo');
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
// Write cfgPath early so recreation reads updated openclaw.json
|
|
2162
|
+
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2163
|
+
|
|
2164
|
+
// Recreate container to apply updated openclaw.json
|
|
2165
|
+
const hasDocker = existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'));
|
|
2166
|
+
if (hasDocker) {
|
|
2167
|
+
sendLog(`[docker] Web Search skill toggled to ${enabled}. Recreating containers...`);
|
|
2168
|
+
await recreateDockerBot(projectDir).catch((err) => sendLog(`[docker] Warning: Failed to recreate container: ${err.message}`));
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2107
2172
|
if (kind === 'plugin') {
|
|
2108
2173
|
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2109
2174
|
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
@@ -2344,6 +2409,8 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
2344
2409
|
Array.from(installedSpecs).some((spec) => spec.includes(a)) ||
|
|
2345
2410
|
allowSet.has(a)
|
|
2346
2411
|
);
|
|
2412
|
+
const imageGenOn = !!cfg.skills?.entries?.['image-gen']?.enabled;
|
|
2413
|
+
const webSearchOn = isEnabled(['duckduckgo']);
|
|
2347
2414
|
const aliases = {
|
|
2348
2415
|
browser: ['openclaw-browser-automation', 'browser-automation'],
|
|
2349
2416
|
zalo: ['openclaw-zalo-mod', 'zalo-mod'],
|
|
@@ -2353,6 +2420,8 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
2353
2420
|
const flags = {
|
|
2354
2421
|
'skill:browser': browserOn,
|
|
2355
2422
|
'skill:cron': cronOn,
|
|
2423
|
+
'skill:image-gen': imageGenOn,
|
|
2424
|
+
'skill:web-search': webSearchOn,
|
|
2356
2425
|
'plugin:openclaw-browser-automation': isEnabled(aliases.browser),
|
|
2357
2426
|
'plugin:openclaw-zalo-mod': isEnabled(aliases.zalo),
|
|
2358
2427
|
'plugin:openclaw-facebook-crawler': isEnabled(aliases.crawler),
|
|
@@ -2422,14 +2491,8 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2422
2491
|
};
|
|
2423
2492
|
const projects = await discoverProjects(rootProjectDir).catch(() => []);
|
|
2424
2493
|
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
const resp = await fetch('https://registry.npmjs.org/create-openclaw-bot/latest', { signal: AbortSignal.timeout(3000) });
|
|
2428
|
-
if (resp.ok) {
|
|
2429
|
-
const data = await resp.json();
|
|
2430
|
-
if (data.version) latestSetupVersion = data.version;
|
|
2431
|
-
}
|
|
2432
|
-
} catch (e) {}
|
|
2494
|
+
fetchLatestSetupVersionBg().catch(() => {});
|
|
2495
|
+
const latestSetupVersion = latestSetupVersionCache;
|
|
2433
2496
|
|
|
2434
2497
|
return json(res, {
|
|
2435
2498
|
os: osChoice,
|
|
@@ -2544,6 +2607,7 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2544
2607
|
await run('npm', ['install'], { cwd: rootProjectDir });
|
|
2545
2608
|
await run('npm', ['run', 'build'], { cwd: rootProjectDir });
|
|
2546
2609
|
sendLog('[update-setup] Setup Wizard updated successfully! Please restart the installer.');
|
|
2610
|
+
restartInstaller();
|
|
2547
2611
|
} catch (err) {
|
|
2548
2612
|
sendLog(`[update-setup] Error updating: ${err.message}`);
|
|
2549
2613
|
}
|
|
@@ -2555,6 +2619,7 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2555
2619
|
try {
|
|
2556
2620
|
await run('npm', ['install', '-g', 'create-openclaw-bot@latest'], { cwd: rootProjectDir });
|
|
2557
2621
|
sendLog('[update-setup] Setup Wizard updated successfully! Please restart the installer.');
|
|
2622
|
+
restartInstaller();
|
|
2558
2623
|
} catch (err) {
|
|
2559
2624
|
sendLog(`[update-setup] Error updating: ${err.message}`);
|
|
2560
2625
|
}
|
|
@@ -2652,6 +2717,8 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2652
2717
|
skills: [
|
|
2653
2718
|
{ name: 'Browser', slug: 'browser' },
|
|
2654
2719
|
{ name: 'Cron', slug: 'cron' },
|
|
2720
|
+
{ name: 'Tạo ảnh Infographic', slug: 'image-gen' },
|
|
2721
|
+
{ name: 'Web Search', slug: 'web-search' },
|
|
2655
2722
|
],
|
|
2656
2723
|
plugins: [
|
|
2657
2724
|
{ name: 'openclaw-browser-automation', package: 'openclaw-browser-automation' },
|
|
@@ -2700,13 +2767,42 @@ function openUrl(url) {
|
|
|
2700
2767
|
child.unref();
|
|
2701
2768
|
}
|
|
2702
2769
|
|
|
2770
|
+
function restartInstaller() {
|
|
2771
|
+
sendLog('[update-setup] Restarting Setup Wizard to apply update...');
|
|
2772
|
+
setTimeout(() => {
|
|
2773
|
+
try {
|
|
2774
|
+
if (activeServerInstance) {
|
|
2775
|
+
activeServerInstance.close();
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
const entryFile = process.argv[1];
|
|
2779
|
+
const args = process.argv.slice(2);
|
|
2780
|
+
|
|
2781
|
+
if (!args.includes('--no-open')) {
|
|
2782
|
+
args.push('--no-open');
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
const child = spawn(process.argv[0], [entryFile, ...args], {
|
|
2786
|
+
detached: true,
|
|
2787
|
+
stdio: 'inherit',
|
|
2788
|
+
shell: process.platform === 'win32'
|
|
2789
|
+
});
|
|
2790
|
+
child.unref();
|
|
2791
|
+
|
|
2792
|
+
process.exit(0);
|
|
2793
|
+
} catch (err) {
|
|
2794
|
+
sendLog(`[update-setup] Failed to restart: ${err.message}`);
|
|
2795
|
+
}
|
|
2796
|
+
}, 2000);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2703
2799
|
export async function startLocalInstaller({ host = '127.0.0.1', preferredPort = 51789, openBrowser = true, projectDir = process.cwd() } = {}) {
|
|
2704
2800
|
const port = await findPort(host, preferredPort);
|
|
2705
2801
|
const server = http.createServer((req, res) => handler(req, res, projectDir));
|
|
2802
|
+
activeServerInstance = server;
|
|
2706
2803
|
await new Promise((resolve) => server.listen(port, host, resolve));
|
|
2707
2804
|
const url = `http://${host}:${port}`;
|
|
2708
2805
|
console.log(`OpenClaw Setup UI: ${url}`);
|
|
2709
|
-
console.log('Legacy CLI: create-openclaw-bot legacy');
|
|
2710
2806
|
if (openBrowser) openUrl(url);
|
|
2711
2807
|
}
|
|
2712
2808
|
|