create-openclaw-bot 5.15.0 → 5.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README.vi.md +2 -2
- package/dist/server/local-server.js +73 -18
- package/dist/web/app.js +6 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*Run one command → open the dashboard → your bot is live. Windows · macOS · Linux · VPS — Docker-powered, auto-installed for you.*
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.15.
|
|
10
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.15.1-0EA5E9?style=for-the-badge" alt="Version 5.15.1" /></a>
|
|
11
11
|
<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>
|
|
12
12
|
<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>
|
|
13
13
|
<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>
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
## 🆕 What's New in v5.15.
|
|
40
|
+
## 🆕 What's New in v5.15.1
|
|
41
41
|
|
|
42
42
|
- 🖥️ **"Control PC" — let the bot open Chrome & apps on your machine**: a new toggle on each bot card turns on the ability for the bot to open Chrome or an allow-listed app (TeamViewer, Zalo, …) on the computer running it. **Off by default**, token-gated, allow-list only, desktop-only.
|
|
43
43
|
- 🌐 **Browsing works out of the box on every OS**: the bot drives your real Chrome on a desktop (your logged-in profile, so sites don't flag it) and starts its own headless Chromium on a server — no more "no browser available" just because Chrome wasn't running.
|
package/README.vi.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*Chạy một lệnh → mở dashboard → bot lên sóng. Windows · macOS · Linux · VPS — chạy trên Docker, tự cài giúp bạn.*
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.15.
|
|
10
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.15.1-0EA5E9?style=for-the-badge" alt="Version 5.15.1" /></a>
|
|
11
11
|
<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>
|
|
12
12
|
<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>
|
|
13
13
|
<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>
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
## 🆕 Có gì mới trong v5.15.
|
|
40
|
+
## 🆕 Có gì mới trong v5.15.1
|
|
41
41
|
|
|
42
42
|
- 🖥️ **"Điều khiển máy" — cho bot mở Chrome & ứng dụng trên máy bạn**: nút mới trên thẻ mỗi bot, bật lên là bot mở được Chrome hoặc app trong danh sách cho phép (TeamViewer, Zalo, …) trên máy đang chạy bot. **Mặc định TẮT**, cần token, chỉ giới hạn danh sách bạn khai, chỉ dùng trên máy có màn hình.
|
|
43
43
|
- 🌐 **Duyệt web chạy ngay trên mọi hệ điều hành**: máy để bàn thì bot dùng **Chrome thật** (profile đã đăng nhập, web ít nghi là bot), server thì tự mở Chromium headless — hết cảnh "không có trình duyệt" chỉ vì chưa mở Chrome.
|
|
@@ -61,6 +61,49 @@ async function syncExecApprovals(projectDir, cfg) {
|
|
|
61
61
|
await fsp.writeFile(path2, JSON.stringify(approvals, null, 2), 'utf8');
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Write files into the bot container's plugin folder. Needed when `.openclaw/extensions` is a
|
|
66
|
+
* Docker named volume: the host sees an empty directory, so there is nothing on disk to patch.
|
|
67
|
+
* Best-effort — a stopped container or a project without Docker just yields 0.
|
|
68
|
+
*/
|
|
69
|
+
async function pushBrowserScriptsIntoContainer(projectDir, aliases, files, sendLog = () => {}) {
|
|
70
|
+
if (isNativeProject(projectDir)) return 0;
|
|
71
|
+
const container = getBotContainerName(projectDir);
|
|
72
|
+
if (!container) return 0;
|
|
73
|
+
const running = await runCapture('docker', ['inspect', '-f', '{{.State.Running}}', container], { shell: false, timeout: 8000 }).catch(() => null);
|
|
74
|
+
if (String(running?.stdout || '').trim() !== 'true') return 0;
|
|
75
|
+
|
|
76
|
+
const homeOut = await runCapture('docker', ['exec', container, 'sh', '-c', 'echo "${OPENCLAW_HOME:-/home/node/project/.openclaw}"'], { shell: false, timeout: 8000 }).catch(() => null);
|
|
77
|
+
const openclawHome = String(homeOut?.stdout || '').trim() || '/home/node/project/.openclaw';
|
|
78
|
+
|
|
79
|
+
let pushed = 0;
|
|
80
|
+
for (const alias of aliases) {
|
|
81
|
+
const dir = `${openclawHome}/extensions/${alias}`;
|
|
82
|
+
const check = await runCapture('docker', ['exec', container, 'sh', '-c', `[ -d "${dir}" ] && echo yes || echo no`], { shell: false, timeout: 8000 }).catch(() => null);
|
|
83
|
+
if (String(check?.stdout || '').trim() !== 'yes') continue;
|
|
84
|
+
for (const [name, content, mode] of files) {
|
|
85
|
+
const tmp = join(os.tmpdir(), `openclaw-${Date.now()}-${name}`);
|
|
86
|
+
try {
|
|
87
|
+
await fsp.writeFile(tmp, content, 'utf8');
|
|
88
|
+
// runCapture, not run: run() forces a shell on Windows, and the temp path goes through
|
|
89
|
+
// a home directory that usually has a space in it ("VT 2025") — cmd then splits it and
|
|
90
|
+
// docker cp fails with a usage error.
|
|
91
|
+
const cp = await runCapture('docker', ['cp', tmp, `${container}:${dir}/${name}`], { shell: false, timeout: 20000 });
|
|
92
|
+
if (cp.code !== 0) throw new Error(String(cp.stderr || cp.stdout || 'docker cp failed').trim());
|
|
93
|
+
// A world-writable file makes OpenClaw refuse to load the plugin, and a copy landing
|
|
94
|
+
// from a Windows host is exactly that.
|
|
95
|
+
await runCapture('docker', ['exec', container, 'sh', '-c', `chmod ${mode} "${dir}/${name}"`], { shell: false, timeout: 20000 });
|
|
96
|
+
pushed += 1;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
sendLog(`[browser] Could not push ${name} into ${container}: ${err.message}`);
|
|
99
|
+
} finally {
|
|
100
|
+
await fsp.rm(tmp, { force: true }).catch(() => {});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return pushed;
|
|
105
|
+
}
|
|
106
|
+
|
|
64
107
|
async function patchBrowserAutomationHostPreference(projectDir, aliases = [], sendLog = () => {}) {
|
|
65
108
|
const preferredCdpBlock = `const dns = require('dns').promises;
|
|
66
109
|
const DEFAULT_CDP_URLS = [
|
|
@@ -300,9 +343,10 @@ async function connectPreferredChrome() {
|
|
|
300
343
|
|
|
301
344
|
// Patch the plugin's own copies too: the plugin re-syncs its skill folder into every
|
|
302
345
|
// workspace on startup, so an unpatched source would undo the profile change below.
|
|
346
|
+
const sourceScriptNames = ['start-chrome-debug.bat', 'start-chrome-debug.sh'];
|
|
303
347
|
let scriptsPatched = 0;
|
|
304
348
|
for (const dir of extensionDirs) {
|
|
305
|
-
for (const name of
|
|
349
|
+
for (const name of sourceScriptNames) {
|
|
306
350
|
const file = join(dir, name);
|
|
307
351
|
if (!existsSync(file)) continue;
|
|
308
352
|
const content = await fsp.readFile(file, 'utf8');
|
|
@@ -314,7 +358,22 @@ async function connectPreferredChrome() {
|
|
|
314
358
|
}
|
|
315
359
|
}
|
|
316
360
|
if (scriptsPatched > 0) {
|
|
317
|
-
sendLog(`[browser] Patched ${scriptsPatched} start-chrome script(s) to use
|
|
361
|
+
sendLog(`[browser] Patched ${scriptsPatched} start-chrome script(s) to use a dedicated Chrome profile seeded from yours (set OPENCLAW_CHROME_PROFILE_DIR to override).`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Some Docker projects mount `.openclaw/extensions` as a named volume, so the plugin's own
|
|
365
|
+
// files exist only inside the container. Every host path above then quietly finds nothing,
|
|
366
|
+
// the plugin re-syncs its stale script into the workspaces on each boot, and the operator
|
|
367
|
+
// keeps running the version that cannot open the debug port. Push the script in through the
|
|
368
|
+
// container instead.
|
|
369
|
+
if (!extensionDirs.some((dir) => existsSync(join(dir, sourceScriptNames[0])) || existsSync(join(dir, sourceScriptNames[1])))) {
|
|
370
|
+
const pushed = await pushBrowserScriptsIntoContainer(projectDir, aliases, [
|
|
371
|
+
['start-chrome-debug.bat', startChromeBat, '644'],
|
|
372
|
+
['start-chrome-debug.sh', startChromeSh, '755'],
|
|
373
|
+
], sendLog);
|
|
374
|
+
if (pushed > 0) {
|
|
375
|
+
sendLog(`[browser] Extensions live in a Docker volume — pushed ${pushed} start-chrome script(s) into the container so the plugin delivers the current one.`);
|
|
376
|
+
}
|
|
318
377
|
}
|
|
319
378
|
|
|
320
379
|
const browserMd = `# Browser Automation
|
|
@@ -366,12 +425,10 @@ Do not call \`search-tool.js\`; browser-automation does not own search. Use \`we
|
|
|
366
425
|
const hostOs = normalizeHostOs(await resolveProjectHostOs(projectDir));
|
|
367
426
|
const shouldKeepBat = hostOs === 'win';
|
|
368
427
|
// Shipped as start-chrome-debug.* by the plugin; delivered to the workspace as
|
|
369
|
-
// start-chrome.* — it launches
|
|
370
|
-
//
|
|
371
|
-
const sourceName = shouldKeepBat ? 'start-chrome-debug.bat' : 'start-chrome-debug.sh';
|
|
428
|
+
// start-chrome.* — it launches Chrome with the debug port open, so "debug" in the name only
|
|
429
|
+
// ever made people think it was a developer-only thing.
|
|
372
430
|
const scriptToKeep = shouldKeepBat ? 'start-chrome.bat' : 'start-chrome.sh';
|
|
373
431
|
const legacyScripts = ['start-chrome-debug.bat', 'start-chrome-debug.sh', shouldKeepBat ? 'start-chrome.sh' : 'start-chrome.bat'];
|
|
374
|
-
const sourceScript = extensionDirs.map((dir) => join(dir, sourceName)).find((file) => existsSync(file));
|
|
375
432
|
const sourceBrowserTool = extensionDirs.map((dir) => join(dir, 'browser-tool.js')).find((file) => existsSync(file));
|
|
376
433
|
|
|
377
434
|
let sanitized = 0;
|
|
@@ -396,16 +453,12 @@ Do not call \`search-tool.js\`; browser-automation does not own search. Use \`we
|
|
|
396
453
|
if (next !== content) await fsp.writeFile(targetBrowserTool, next, 'utf8');
|
|
397
454
|
}
|
|
398
455
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (nextScript !== scriptContent) await fsp.writeFile(targetScript, nextScript, 'utf8');
|
|
406
|
-
}
|
|
407
|
-
if (scriptToKeep.endsWith('.sh')) await fsp.chmod(targetScript, 0o755).catch(() => {});
|
|
408
|
-
}
|
|
456
|
+
// Written from the generator, not copied from the plugin: on a project whose extensions
|
|
457
|
+
// folder is a Docker volume there is no host copy to read, and the workspace would be left
|
|
458
|
+
// without a starter at all.
|
|
459
|
+
const targetScript = join(pluginSkillPath, scriptToKeep);
|
|
460
|
+
await fsp.writeFile(targetScript, scriptToKeep.endsWith('.bat') ? startChromeBat : startChromeSh, 'utf8');
|
|
461
|
+
if (scriptToKeep.endsWith('.sh')) await fsp.chmod(targetScript, 0o755).catch(() => {});
|
|
409
462
|
await fsp.writeFile(join(pluginSkillPath, 'BROWSER.md'), browserMd, 'utf8');
|
|
410
463
|
for (const dirName of ['cl-stealth-search', 'openclaw-smart-search']) {
|
|
411
464
|
await fsp.rm(join(workspacePath, 'plugin-skills', dirName), { recursive: true, force: true }).catch(() => {});
|
|
@@ -3847,8 +3900,10 @@ async function startChromeDebug() {
|
|
|
3847
3900
|
ok: true,
|
|
3848
3901
|
headless: true,
|
|
3849
3902
|
port: 9222,
|
|
3850
|
-
|
|
3851
|
-
|
|
3903
|
+
// Same dedicated profile directories the local button and the generated scripts use, so
|
|
3904
|
+
// an operator who has already run one of those keeps the session they signed in with.
|
|
3905
|
+
chromeCmdMac: `"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222 --user-data-dir="$HOME/${CHROME_DEBUG_PROFILE_LEAF_MAC}" --profile-directory=Default --remote-allow-origins='*'`,
|
|
3906
|
+
chromeCmdWin: `"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" --remote-debugging-port=9222 --user-data-dir="%LOCALAPPDATA%\\${CHROME_DEBUG_PROFILE_LEAF_WIN}" --profile-directory=Default --remote-allow-origins=*`,
|
|
3852
3907
|
tunnelCmd: `ssh -N -R 9222:127.0.0.1:9222 ${user}@${ip}`,
|
|
3853
3908
|
};
|
|
3854
3909
|
}
|
package/dist/web/app.js
CHANGED
|
@@ -623,7 +623,7 @@ function wireSkillsHandlers(scope = document) {
|
|
|
623
623
|
scope.querySelectorAll('[data-chrome-debug]').forEach(btn => btn.onclick = async () => {
|
|
624
624
|
btn.disabled = true;
|
|
625
625
|
try {
|
|
626
|
-
const r = await api('/api/browser/start-chrome
|
|
626
|
+
const r = await api('/api/browser/start-chrome', { method: 'POST', body: {} });
|
|
627
627
|
if (r.headless) {
|
|
628
628
|
// VPS has no display — guide the user to run Chrome on THEIR machine + a reverse tunnel.
|
|
629
629
|
state.confirmModal = {
|
|
@@ -639,8 +639,10 @@ function wireSkillsHandlers(scope = document) {
|
|
|
639
639
|
};
|
|
640
640
|
render();
|
|
641
641
|
} else {
|
|
642
|
-
|
|
643
|
-
|
|
642
|
+
// The profile is a copy of the operator's own, so their normal Chrome can stay open —
|
|
643
|
+
// say so, because the old flow required closing every Chrome window first.
|
|
644
|
+
showToast(t('Đã mở Chrome cho bot', 'Chrome opened for the bot'),
|
|
645
|
+
`${t('Cổng', 'Port')} ${r.port} — ${t('cửa sổ Chrome mới này dùng bản sao profile của bạn (đã đăng nhập sẵn); Chrome thường của bạn cứ để nguyên.', 'the new window runs a copy of your profile (already signed in); your normal Chrome can stay open.')}`, 'success');
|
|
644
646
|
}
|
|
645
647
|
} catch (err) {
|
|
646
648
|
showToast(t('Thất bại', 'Failed'), err.message, 'error');
|
|
@@ -1166,7 +1168,7 @@ function botSkillsPanel() {
|
|
|
1166
1168
|
secs.push(`<button class="secondary icon-btn2 update-plugin-btn" type="button" data-feature-install="${key}" ${loading ? 'disabled' : ''} title="${t('Cập nhật lên bản mới nhất','Update to latest version')}" style="padding: 4px 8px; font-size: 11px; height: 28px; border-width: 1px; color:#ffb020; border-color: rgba(255,176,32,0.25); background: rgba(255,176,32,0.05);">${actionIcon('refresh')}<span>${t('Cập nhật','Update')}</span></button>`);
|
|
1167
1169
|
}
|
|
1168
1170
|
if (item.id === 'browser-automation' || item.id === 'openclaw-browser-automation') {
|
|
1169
|
-
secs.push(`<button class="secondary icon-btn2" type="button" data-chrome-debug title="${t('Mở Chrome
|
|
1171
|
+
secs.push(`<button class="secondary icon-btn2" type="button" data-chrome-debug title="${t('Mở Chrome trên máy này cho bot dùng. Chạy bản sao profile của bạn nên vẫn đăng nhập sẵn, và Chrome thường của bạn không bị đóng.','Open Chrome on this machine for the bot. It runs a copy of your profile, so it stays signed in, and your normal Chrome is left alone.')}" style="padding: 4px 8px; font-size: 11px; height: 28px; border-width: 1px; color:#4285F4; border-color: rgba(66,133,244,0.3); background: rgba(66,133,244,0.06);">${actionIcon('link')}<span>${t('Mở Chrome cho bot','Open Chrome for bot')}</span></button>`);
|
|
1170
1172
|
}
|
|
1171
1173
|
} else {
|
|
1172
1174
|
secs.push(`<button class="secondary icon-btn2" type="button" data-feature-install="${key}" ${loading ? 'disabled' : ''}>${actionIcon('download')} ${ui('installVerb')}</button>`);
|