create-openclaw-bot 5.17.0 → 5.17.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.
@@ -58,7 +58,32 @@ function build9RouterCmd({ projectDir, routerPort }) {
58
58
  );
59
59
  }
60
60
 
61
- /** Starts the gateway. Calls node directly — see the --task-supervisor note below. */
61
+ /**
62
+ * The node host that gives the bot screen control.
63
+ *
64
+ * Two things here are not obvious and both cost a day:
65
+ * - It runs against `<project>\.openclaw-node`, NOT the bot's state dir. Pointed at the bot's, it
66
+ * loads the bot's plugins too, and zalo-mod's dashboard port is already held by the gateway, so
67
+ * the node dies on `listen EADDRINUSE 127.0.0.1:18790` before publishing computer.act.
68
+ * - It is launched as a .cmd through wscript like everything else on Windows. Spawning the
69
+ * `openclaw.cmd` shim from Node with detached+shell fails outright with `spawn EINVAL`.
70
+ */
71
+ function buildNodeHostCmd({ projectDir, gatewayPort, gatewayToken }) {
72
+ return CRLF(
73
+ '@echo off\n' +
74
+ 'rem Node host cho tinh nang dieu khien may (tool computer/screen cua OpenClaw).\n' +
75
+ `cd /d ${projectDir}\n` +
76
+ 'set "HOME=%USERPROFILE%"\n' +
77
+ `set "OPENCLAW_HOME=${projectDir}\\.openclaw-node"\n` +
78
+ `set "OPENCLAW_STATE_DIR=${projectDir}\\.openclaw-node"\n` +
79
+ (gatewayToken ? `set "OPENCLAW_GATEWAY_TOKEN=${gatewayToken}"\n` : '') +
80
+ '"%ProgramFiles%\\nodejs\\node.exe" ' +
81
+ '"%APPDATA%\\npm\\node_modules\\openclaw\\dist\\index.js" ' +
82
+ `node run --host 127.0.0.1 --port ${gatewayPort} --no-tls\n`,
83
+ );
84
+ }
85
+
86
+ /** Starts the gateway. Calls node directly - see the --task-supervisor note below. */
62
87
  function buildGatewayCmd({ projectDir, gatewayPort }) {
63
88
  return CRLF(
64
89
  '@echo off\n' +
@@ -177,12 +202,13 @@ function buildReadme({ projectDir, gatewayPort, routerPort, setupPort }) {
177
202
  }
178
203
 
179
204
  /** All launcher files for a native Windows project, as { relativeName: content }. */
180
- function buildWindowsLaunchers({ projectDir, gatewayPort = 18789, routerPort = 20128, setupPort = 51789 }) {
181
- const opts = { projectDir, gatewayPort, routerPort, setupPort };
205
+ function buildWindowsLaunchers({ projectDir, gatewayPort = 18789, routerPort = 20128, setupPort = 51789, gatewayToken = '' }) {
206
+ const opts = { projectDir, gatewayPort, routerPort, setupPort, gatewayToken };
182
207
  return {
183
208
  'run-hidden.vbs': buildRunHiddenVbs(),
184
209
  'start-9router.cmd': build9RouterCmd(opts),
185
210
  'gateway-start.cmd': buildGatewayCmd(opts),
211
+ 'node-host.cmd': buildNodeHostCmd(opts),
186
212
  'setup-ui.cmd': buildSetupUiCmd(opts),
187
213
  '0 - DOC TRUOC.txt': buildReadme(opts),
188
214
  '1 - KHOI DONG BOT.cmd': buildStartBotCmd(opts),
package/dist/web/app.js CHANGED
@@ -324,47 +324,53 @@ function openPathModal({ title, message, value = '', placeholder = '', field2 =
324
324
  * one thing only the operator can do — grant the OS screen permissions from the system settings.
325
325
  */
326
326
  function openComputerUseModal(r = {}) {
327
- const codex = r.codex || {};
328
- const appOk = codex.app && codex.app.present;
329
- const cli = (r.commands || []).includes('codex');
327
+ const cu = r.computerUse || {};
328
+ const isMac = /mac/i.test(state.system?.os || state.os || '');
330
329
  const line = (icon, text) => `<li><span aria-hidden="true">${icon}</span><div>${text}</div></li>`;
330
+
331
+ // OpenClaw's own `computer` tool is the real thing here: screenshot, move, click, type, drag.
332
+ // Codex is only a fallback for machines that have the ChatGPT app, so it must not lead - the
333
+ // old copy said "mouse/keyboard control is macOS-only", which stopped being true once the
334
+ // cua-computer plugin shipped for Windows.
331
335
  const statusItems = [
332
- cli
333
- ? line('✅', t('Bot giao được việc cho <code>codex</code> Codex tự nhìn màn hình, click, gõ phím rồi trả kết quả về.', 'The bot can hand jobs to <code>codex</code> Codex looks at the screen, clicks and types, then reports back.'))
334
- : line('⚠️', t('Không tìm thấy CLI <code>codex</code> cài app ChatGPT/Codex rồi bật lại quyền này.', 'No <code>codex</code> CLI found install the ChatGPT/Codex app, then re-enable this.')),
335
- codex.pluginInstalled
336
- ? line('✅', t(`Computer Use trong app Codex đã ${codex.installedNow ? 'được cài' : 'sẵn sàng'}${codex.mcpRepaired ? ' (đã sửa khai báo MCP cũ)' : ''}.`, `Computer Use in the Codex app is ${codex.installedNow ? 'installed' : 'ready'}${codex.mcpRepaired ? ' (stale MCP entry repaired)' : ''}.`))
337
- : line('⚠️', t(`Chưa bật được Computer Use trong app Codex: ${escapeHtml(codex.error || 'không rõ lý do')}`, `Could not enable Computer Use in the Codex app: ${escapeHtml(codex.error || 'unknown reason')}`)),
338
- appOk
339
- ? line('✅', t('Đã thấy ứng dụng ChatGPT/Codex — <b>nhớ để app đang chạy</b>.', 'Found the ChatGPT/Codex desktop app — <b>keep it running</b>.'))
340
- : line('⚠️', t('CHƯA thấy ứng dụng ChatGPT/Codex — cài rồi mở lên, không có nó thì không điều khiển GUI được.', 'No ChatGPT/Codex desktop app found — install and open it, GUI control needs it.')),
341
- (r.granted && r.granted.length)
342
- ? line('✅', t(`Đã cấp quyền chạy: ${escapeHtml(r.granted.join(', '))}.`, `Granted: ${escapeHtml(r.granted.join(', '))}.`))
336
+ cu.ok && cu.enabled
337
+ ? line('✅', t('Bot <b>nhìn điều khiển được máy này</b>: chụp màn hình, rê chuột, bấm chuột, gõ phím, kéo thả.', 'Your bot <b>can see and drive this machine</b>: screenshot, move, click, type, drag.'))
338
+ : line('⚠️', t(`Chưa bật được điều khiển máy: ${escapeHtml(cu.error || 'không do')}`, `Could not enable computer control: ${escapeHtml(cu.error || 'unknown reason')}`)),
339
+ // No app list any more: the bot opens things the way a person does - Start menu, type the
340
+ // name, Enter - so it is not limited to whatever we managed to enumerate.
341
+ cu.ok && cu.enabled
342
+ ? line('✅', t('Mở được <b>mọi ứng dụng đang cài</b> trên máy, kể cả app mới cài hôm qua.', 'Can open <b>any installed app</b>, including one installed yesterday.'))
343
343
  : '',
344
344
  ].filter(Boolean).join('');
345
+
346
+ // Windows needs no privacy prompts; macOS does, and only from System Settings.
347
+ const permBlock = isMac ? `
348
+ <h4>${t('Cấp quyền màn hình cho máy','Grant the OS screen permissions')}</h4>
349
+ <p>${t('macOS chỉ cấp <b>Screen Recording</b> và <b>Accessibility</b> từ System Settings. Bấm nút dưới rồi bật cho <code>node</code>.','macOS only grants <b>Screen Recording</b> and <b>Accessibility</b> from System Settings. Click below, then tick <code>node</code>.')}</p>
350
+ <p class="cu-perm-row">
351
+ <button class="secondary cu-btn" type="button" data-host-perm="screen">${t('Chụp/quay màn hình','Screen recording')}</button>
352
+ <button class="secondary cu-btn" type="button" data-host-perm="accessibility">${t('Accessibility (chuột/bàn phím)','Accessibility (mouse/keys)')}</button>
353
+ </p>` : '';
354
+
345
355
  state.confirmModal = {
346
356
  icon: '🖥️',
347
357
  eyebrow: t('Điều khiển máy','PC control'),
348
- title: t('Xong còn 1 bước bạn tự làm','Done one step left for you'),
358
+ title: cu.ok && cu.enabled ? t('Đã bật điều khiển máy','Computer control is on') : t('Bật chưa xong','Not fully enabled'),
349
359
  message: t('Model chính của bot vẫn là smart-route, không đổi.','Your bot keeps smart-route as its primary model.'),
350
360
  bodyHtml: `
351
361
  <ul class="cu-status">${statusItems}</ul>
352
- <h4>${t('Cấp quyền màn hình cho máy','Grant the OS screen permissions')}</h4>
353
- <p>${t('macOS chỉ cấp <b>Screen Recording</b> / <b>Accessibility</b> từ System Settings bấm nút dưới, rồi bật cho <code>node</code> app Codex.','macOS only grants <b>Screen Recording</b> / <b>Accessibility</b> from System Settings click below, then tick <code>node</code> and the Codex app.')}</p>
354
- <p class="cu-perm-row">
355
- <button class="secondary cu-btn" type="button" data-host-perm="screen">${t('Chụp/quay màn hình','Screen recording')}</button>
356
- <button class="secondary cu-btn" type="button" data-host-perm="accessibility">${t('Accessibility (chuột/bàn phím)','Accessibility (mouse/keys)')}</button>
357
- </p>
358
- <p class="cu-note">${t('Cách dùng: nhắn bot bình thường, ví dụ “mở TeamViewer và đọc giúp mật khẩu trên màn hình” — bot tự giao cho Codex rồi báo kết quả về.','How to use it: just ask your bot normally, e.g. “open TeamViewer and read the password on screen” — it hands the job to Codex and reports back.')}</p>
359
- <p class="cu-note">${t('Việc giao cho Codex chạy bằng gói ChatGPT đã đăng nhập (tốn quota gói đó); chat thường vẫn đi qua các model free của <code>smart-route</code>. Điều khiển chuột/bàn phím hiện chỉ có trên macOS.','Jobs handed to Codex run on the signed-in ChatGPT plan (they spend that quota); ordinary chat still uses the free <code>smart-route</code> models. Mouse/keyboard control is macOS-only for now.')}</p>
362
+ ${permBlock}
363
+ <p class="cu-note">${t('Cách dùng: nhắn bot bình thường, dụ “chụp màn hình cho anh xem” hoặc “mở TeamViewer rồi đọc mật khẩu trên màn hình”.','How to use it: just ask your bot normally, e.g. "take a screenshot" or "open TeamViewer and read the password on screen".')}</p>
364
+ <p class="cu-note">${t('Chỉ dùng được trên máy có màn hình. Máy chủ VPS không màn hình thì không áp dụng. Tắt nút này là thu hồi lại toàn bộ quyền trên.','Only works on a machine with a screen. A headless VPS cannot use this. Turning the switch off revokes all of it.')}</p>
365
+ <p class="cu-note">${t('<b>Sau khi khởi động lại máy hoặc đăng xuất Windows, phần điều khiển máy sẽ tắt.</b> Mở lại giao diện này và bật lại nút là xong. Bot vẫn chat bình thường, chỉ riêng phần nhìn và điều khiển màn hình là cần bật lại.','<b>Restarting or signing out of Windows turns computer control off.</b> Open this screen and switch it back on. Chat keeps working meanwhile; only seeing and driving the screen needs re-enabling.')}</p>
360
366
  `,
361
367
  okText: t('Đã hiểu','Got it'),
362
368
  okDanger: false,
363
369
  hideCancel: true,
364
370
  onConfirm: () => { state.confirmModal = null; render(); },
365
371
  };
366
- render();
367
372
  }
373
+
368
374
  async function pickFolderPathShared() {
369
375
  try {
370
376
  const picked = await api('/api/project/pick-folder', { method: 'POST', body: {} });
@@ -1256,8 +1262,8 @@ function wireTab() {
1256
1262
  const granted = r.screen && r.screen.supported ? r.screen.granted : null;
1257
1263
  showToast(t('Đã mở cài đặt quyền','Opened settings'),
1258
1264
  granted === true ? t('Quyền chụp/quay màn hình: đã có. Bật thêm Accessibility nếu cần gõ/click.','Screen recording: already granted. Also enable Accessibility for typing/clicking.')
1259
- : granted === false ? t('Chưa có quyền bật cho "node" (và app Codex) trong danh sách vừa mở, rồi restart bot.','Not granted yet tick "node" (and the Codex app) in the list that just opened, then restart the bot.')
1260
- : t('Bật quyền cho "node" và app Codex trong danh sách vừa mở.','Tick "node" and the Codex app in the list that just opened.'),
1265
+ : granted === false ? t('Chưa có quyền. Bật cho "node" trong danh sách vừa mở, rồi khởi động lại bot.','Not granted yet. Tick "node" in the list that just opened, then restart the bot.')
1266
+ : t('Bật quyền cho "node" trong danh sách vừa mở.','Tick "node" in the list that just opened.'),
1261
1267
  granted === false ? 'error' : 'success', 8000);
1262
1268
  } catch (err) { showToast(t('Thất bại','Failed'), err.message, 'error'); }
1263
1269
  }));
@@ -1410,23 +1416,18 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
1410
1416
  try { cur = await api('/api/host/control' + projectQuery({})); }
1411
1417
  catch (err) { return showToast(t('Thất bại','Failed'), err.message, 'error'); }
1412
1418
  const on = !!cur.enabled;
1413
- const apps = cur.apps || [];
1414
- const grants = cur.grants || [];
1415
- const chips = (items) => items.map((i) => `<code>${escapeHtml(i)}</code>`).join(' ');
1416
- const scripts = grants.filter((g) => ['node', 'npx', 'codex'].includes(g));
1417
- // One row per capability instead of a wall of prose — the operator is granting real access to
1419
+ // One row per capability instead of a wall of prose - the operator is granting real access to
1418
1420
  // their machine and should be able to see, at a glance, exactly what each row means.
1419
- const rows = [
1420
- { icon: '🗂', title: t('Mở ứng dụng','Open apps'), desc: apps.length ? chips(apps) : t('chưa được app nào','no apps detected') },
1421
- ...(cur.native ? [
1422
- { icon: '📸', title: t('Chụp & quay màn hình','Screen capture & recording'), desc: t('bot nhìn được màn hình khi bạn nhờ','the bot can see your screen when you ask') },
1423
- { icon: '🖱', title: t('Điều khiển chuột/bàn phím','Mouse & keyboard control'), desc: t('bot giao việc cho Codex CLI (dùng gói ChatGPT đã đăng nhập); macOS','the bot hands the job to the Codex CLI on your ChatGPT plan; macOS only') },
1424
- { icon: '⚙️', title: t('Chạy script','Run scripts'), desc: `${chips(scripts.length ? scripts : ['node'])} <b>${t('chạy được tuỳ ý trên máy này','arbitrary code on this machine')}</b>` },
1425
- ] : []),
1426
- ];
1421
+ // There is no app allow-list to show: the bot drives the screen, so it reaches anything the
1422
+ // person sitting at this machine could reach. Say that plainly rather than implying limits.
1423
+ const rows = cur.native ? [
1424
+ { icon: '📸', title: t('Nhìn màn hình','See the screen'), desc: t('bot chụp được màn hình khi bạn nhờ','the bot can screenshot your screen when you ask') },
1425
+ { icon: '🖱', title: t('Điều khiển chuột/bàn phím','Mouse & keyboard control'), desc: t('bot chuột, bấm, phím kéo thả trên máy này','the bot moves the mouse, clicks, types and drags on this machine') },
1426
+ { icon: '🗂', title: t('Mở ứng dụng','Open apps'), desc: t('mọi app đang cài, bot thao tác như người ngồi trước máy','any installed app, because it operates the machine like a person would') },
1427
+ ] : [];
1427
1428
  const bodyHtml = on ? '' : `
1428
1429
  <ul class="cu-status grant-list">${rows.map((r) => `<li><span aria-hidden="true">${r.icon}</span><div><b>${r.title}</b><br>${r.desc}</div></li>`).join('')}</ul>
1429
- <p class="cu-note">${t('Danh sách sửa trong <code>.openclaw/host-control.json</code>. Chỉ dùng trên máy có màn hình (không áp dụng VPS headless).','Edit the list in <code>.openclaw/host-control.json</code>. Desktop only (not a headless VPS).')}</p>`;
1430
+ <p class="cu-note">${t('Chỉ dùng trên máy có màn hình (không áp dụng VPS headless).','Desktop only (not a headless VPS).')}</p>`;
1430
1431
  state.confirmModal = {
1431
1432
  icon: '🖥️',
1432
1433
  eyebrow: t('Điều khiển máy','PC control'),
@@ -1439,7 +1440,7 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
1439
1440
  okDanger: on,
1440
1441
  onConfirm: async () => {
1441
1442
  state.confirmModal = null; render();
1442
- if (!on) showToast(t('Đang bật','Enabling'), t('Đang cấp quyền & cài Computer Use (có thể mất ~30s)…','Granting access & installing Computer Use (may take ~30s)…'), 'success');
1443
+ if (!on) showToast(t('Đang bật','Enabling'), t('Đang cấp quyền khởi động node điều khiển (có thể mất ~30s)…','Granting access and starting the control node (may take ~30s)…'), 'success');
1443
1444
  try {
1444
1445
  const r = await api('/api/host/control', { method: 'POST', body: { enabled: !on, projectDir: activeProjectDir() } });
1445
1446
  if (!on && r.started && r.started.ok === false && r.started.reason) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.17.0",
3
+ "version": "5.17.1",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {