@frontendxlab/opencode-computer-use 0.1.3 → 0.1.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.
@@ -2,6 +2,7 @@ OpenCode Computer Use - full documentation summary
2
2
  Canonical site: https://opencode-computer-use.pages.dev/
3
3
  Repository: https://github.com/rashidtvmr/opencode-computer-use
4
4
  License: MIT
5
+ Documentation updated: 2026-10-30
5
6
 
6
7
  1. What it is
7
8
  OpenCode Computer Use is a dual-generation OpenCode plugin that provides a
@@ -26,6 +27,17 @@ THIRD_PARTY_NOTICES.md in the repository.
26
27
  accessibility, screen capture, and input permissions are enforced there.
27
28
  The JavaScript VM is a capability guard, not a complete security boundary.
28
29
 
30
+ Device scope: the controlled desktop is the signed-in desktop on the computer
31
+ running the OpenCode server and its local native MCP child. A browser or
32
+ attached terminal on a second laptop or phone does not switch that target.
33
+ There is no built-in remote-device discovery, pairing service, or network
34
+ transport. Install dependencies, configure the plugin, and grant desktop
35
+ permissions separately on each target computer. Regenerate checkout paths
36
+ with --source after moving them; do not copy node_modules between OS targets.
37
+ Services, containers, WSL, and detached SSH sessions do not automatically
38
+ inherit access to the host user's graphical desktop. A custom backend is an
39
+ explicit compatible executable plus argv, not automatic remote-device support.
40
+
29
41
  3. Model workflow
30
42
  The js tool accepts code (required JavaScript source), timeout_ms (optional,
31
43
  capped at 300 seconds), and title (optional short action title).
@@ -34,7 +46,7 @@ Typical first call:
34
46
  var app = await cua.getApp(apps[0].id);
35
47
  await app.getAXState();
36
48
  Typical action and verification:
37
- var save = await app.click(12);
49
+ await app.click(12);
38
50
  await app.getAXState();
39
51
  State and action rules: read getAXState() or getAXStateAndScreenshot()
40
52
  before acting on a new layout; element indexes belong to the most recent
@@ -47,10 +59,12 @@ decide anew; js_reset discards JavaScript bindings only, not native app
47
59
  state.
48
60
 
49
61
  4. API
50
- - cua.getState(options?): list current desktop apps with bounded output.
51
- - cua.listApps(options?): structured app records without emitting by
52
- default.
53
- - cua.getApp(nameOrPid): bind an app and emit initial accessibility state.
62
+ - cua.getState(options?): return permitted app inventory as { apps }; emits
63
+ inventory by default unless options.emit is false.
64
+ - cua.listApps(options?): return permitted app records as an array; emits
65
+ inventory by default unless options.emit is false. Both inventory methods
66
+ apply appAccess rules before returning or emitting app records.
67
+ - cua.getApp(nameOrID): bind an app and emit initial accessibility state.
54
68
  - app.getAXState(options?): fresh semantic accessibility tree.
55
69
  - app.getScreenshot(options?): local image bytes, optionally emitted.
56
70
  - app.getAXStateAndScreenshot(options?): combined state and image read.
@@ -81,16 +95,45 @@ the native MCP operations used by cua: initialize, tools/list, tools/call,
81
95
  and notifications.
82
96
 
83
97
  6. Install
98
+ Documentation version: 0.1.5. Run npm view @frontendxlab/opencode-computer-use
99
+ version to check the registry release. Use the checkout instructions
100
+ for the changes documented here. Published-package commands use the
101
+ registry release until a new version is published.
102
+
103
+ Cloned repository (development dependency installation does not register it):
104
+ npm ci --ignore-scripts
105
+ node bin/opencode-computer-use-setup.js --local --v1 --source .
106
+ node bin/opencode-computer-use-permissions.js --strict
107
+ opencode mcp list
108
+ Run these commands from the checkout. Use --v2 for OpenCode2 or --global to
109
+ use the checkout from other projects. Setup resolves the source to an absolute
110
+ entrypoint; moving the checkout requires rerunning setup.
111
+
84
112
  Published package:
85
113
  npm install @frontendxlab/opencode-computer-use
86
114
  The package postinstall step detects OpenCode 1 versus OpenCode2 and local
87
115
  versus global installation, edits JSONC without dropping comments, and is
88
- idempotent. If both generations are ambiguous it makes no change and prints
89
- manual instructions. Run it explicitly with npx opencode-computer-use-setup;
90
- use --local, --global, --v1, or --v2 to choose explicitly. If pnpm reports
116
+ idempotent. Global setup also edits the current user's selected JSONC directly;
117
+ it does not depend on an OpenCode plugin-add subcommand. If generation detection
118
+ is ambiguous, automatic installation makes no change and prints manual
119
+ instructions. Explicit manual failures return a nonzero exit status. From
120
+ any directory, select the scoped package and generation explicitly:
121
+ npm exec --package=@frontendxlab/opencode-computer-use -- opencode-computer-use-setup --local --v1
122
+ npm exec --package=@frontendxlab/opencode-computer-use -- opencode-computer-use-setup --global --v1
123
+ Use --v2 for OpenCode2 and --dry-run to preview. The shorthand npx
124
+ opencode-computer-use-setup is only suitable when its binary is already
125
+ available in the installed project; it does not name the scoped package for
126
+ download. In a checkout use node bin/opencode-computer-use-setup.js instead.
127
+ If pnpm reports
91
128
  ERR_PNPM_IGNORED_BUILDS, approve the package once with pnpm approve-builds
92
129
  @frontendxlab/opencode-computer-use, or run pnpm exec
93
- opencode-computer-use-setup manually. CI environments skip setup.
130
+ opencode-computer-use-setup manually. CI environments skip setup. From v0.1.5, desktop onboarding runs during postinstall when lifecycle scripts are allowed.
131
+ The installer invokes opencode-computer-use-permissions --install. macOS runs
132
+ doctor and opens native Accessibility/Screen Recording onboarding when needed;
133
+ the user must still approve protected TCC toggles. GNOME Linux best-effort
134
+ enables toolkit accessibility before AT-SPI/session diagnostics. Windows checks
135
+ UI Automation and the signed-in interactive desktop. Rerun manually with
136
+ npm exec --package=@frontendxlab/opencode-computer-use -- opencode-computer-use-permissions --strict.
94
137
  OpenCode 1 (opencode.json or opencode.jsonc, plugin list):
95
138
  { "$schema": "https://opencode.ai/config.json",
96
139
  "plugin": [["@frontendxlab/opencode-computer-use", { "backend": "auto" }]] }
@@ -98,10 +141,11 @@ OpenCode2 V2 (plugins shape):
98
141
  { "$schema": "https://opencode.ai/config.json",
99
142
  "plugins": [{ "package": "@frontendxlab/opencode-computer-use",
100
143
  "options": { "backend": "auto" } }] }
101
- A local checkout can be used by replacing the package name with its absolute
102
- path. Do not configure the same package both as an explicit plugin and through
144
+ For a checkout, prefer --source . so setup selects its actual entrypoint.
145
+ Do not configure the same package both as an explicit plugin and through
103
146
  automatic plugin discovery. If Node is not on PATH, set OPENCODE_NODE to an
104
- absolute Node executable. Verify with: opencode mcp list.
147
+ absolute Node executable. Restart the relevant OpenCode server/TUI after
148
+ changing its configuration, then verify from the same project with opencode mcp list.
105
149
 
106
150
  7. Requirements and OS support
107
151
  Requirements: Node.js 18 or newer; OpenCode 1.18.29 or newer (V1) or
@@ -110,14 +154,40 @@ and Screen Recording permissions; Linux AT-SPI plus required input and
110
154
  capture services; Windows UI Automation in the interactive desktop session.
111
155
  The plugin cannot control lock screens, login screens, UAC secure desktops,
112
156
  or other OS-owned consent surfaces, and does not bypass permissions.
157
+ Install-time desktop onboarding:
158
+ - macOS 14+: postinstall runs doctor. If Accessibility or Screen Recording is
159
+ missing, Open Computer Use.app opens its onboarding and System Settings.
160
+ Manual panes: open x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility
161
+ and open x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture.
162
+ Apple still requires the user to approve the protected toggles.
163
+ - Linux: postinstall checks the signed-in AT-SPI2/D-Bus desktop session. On
164
+ GNOME it best-effort runs: gsettings set org.gnome.desktop.interface
165
+ toolkit-accessibility true. Wayland capture/coordinate input is compositor-dependent.
166
+ - Windows: there is no separate UI Automation permission toggle. Run in the
167
+ signed-in interactive desktop session, then verify with open-computer-use call list_apps.
168
+ Rerun the helper with node bin/opencode-computer-use-permissions.js --strict
169
+ from a checkout, or the scoped npm exec helper command above from elsewhere.
170
+ Native open-computer-use commands below assume that CLI is on PATH; otherwise
171
+ prefix them with npm exec --package=open-computer-use@0.3.5 --.
113
172
  Packaged native targets: darwin-arm64, darwin-x64, linux-arm64, linux-x64,
114
- win32-arm64, win32-x64. Validation status: Linux x64 read-only discovery,
115
- app listing, MCP registration, and persistent session passed local smoke
116
- tests; one Ubuntu/GNOME Wayland session exposed a Chrome pseudo-window with
117
- no actionable AT-SPI children and synthetic input did not change it (the
118
- adapter reports the native result honestly); macOS and Windows have not
119
- been live-tested in this environment and need on-host permission and
120
- desktop validation.
173
+ win32-arm64, win32-x64. Android and iOS are not native targets. CI exercises
174
+ the adapter with a fake native backend on Linux, macOS, and Windows without
175
+ a graphical desktop. Packaged binaries and passing headless tests do not
176
+ certify live accessibility, screenshots, focus, or input. Native smoke evidence
177
+ on 2026-10-30: Ubuntu GNOME Wayland Linux x64, Node 26.13.0, OpenCode 1.19.3,
178
+ and native 0.3.5 passed MCP registration, app inventory, persistent bindings,
179
+ and reset checks. In a disposable GTK3 app, semantic app.click changed a
180
+ counter from 0 to 1 and app.setValue changed an entry; fresh accessibility
181
+ snapshots verified both changes. app.getScreenshot returned Screenshot
182
+ unavailable. Keyboard and coordinate input were not validated. Other
183
+ app/session combinations may expose incomplete trees; the GTK result does
184
+ not certify every Linux app or capability. Observe fresh state after actions.
185
+ macOS, Windows, and Linux arm64 native behavior still needs validation on
186
+ real hosts using docs/NATIVE_VALIDATION.md. Permission checks alone are
187
+ insufficient to certify every native capability.
188
+ The --strict helper invokes the native doctor and reports command failures.
189
+ The current Linux doctor prints session guidance; exit status zero does not
190
+ prove that app discovery, accessibility, screenshots, or input will work.
121
191
 
122
192
  8. Safety
123
193
  High-privilege desktop-control surface. The js tool is marked conservatively
@@ -129,23 +199,67 @@ uploading, changing permissions). Do not expose the session to an untrusted
129
199
  model or run it unattended against password managers, banking, administrator,
130
200
  or other high-value applications. Screenshots, accessibility trees, and typed
131
201
  values can contain sensitive data and should stay local. Read SECURITY.md
132
- before high-risk use. Manual read-only checks before acting:
202
+ before high-risk use. Manual permission and visibility checks before acting:
133
203
  open-computer-use doctor
134
204
  open-computer-use list-apps
135
205
 
136
206
  9. Troubleshooting (from the docs page)
137
- - cua_repl not listed: check the active generation config, restart the TUI or
138
- background service after installation, run opencode mcp list, and avoid
139
- double registration. For global OpenCode2, run opencode2 plugin add
140
- @frontendxlab/opencode-computer-use if the package was installed before
141
- automatic setup was available.
207
+ - cua_repl not listed: cloning or installing development dependencies does
208
+ not register the package. Run the checkout setup sequence with --source .
209
+ above, or the scoped npm exec setup command for the published package.
210
+ Choose --local/--global and --v1/--v2 explicitly if needed. Check the
211
+ configuration path printed by setup, including OPENCODE_CONFIG overrides.
212
+ Restart the OpenCode server/TUI that uses that configuration and run
213
+ opencode mcp list from the same project. The plugin registers MCP tools,
214
+ not a slash command. An existing disabled or stale cua_repl entry is
215
+ preserved by default; inspect it. Avoid double registration.
216
+ - Works on one device only: install and configure the package on each target
217
+ computer and grant that desktop's permissions. Attached clients still
218
+ control the server host. Local paths and graphical sessions are not
219
+ portable device connections; remote pairing/transport is not built in.
220
+ - Windows PATH launcher error: standard npm global .cmd/.bat launchers are
221
+ resolved to the adjacent package's native executable without a shell.
222
+ Other launchers require the runtime installed locally or
223
+ OPEN_COMPUTER_USE_NATIVE_COMMAND set to the absolute open-computer-use.exe.
142
224
  - Node not found: set OPENCODE_NODE or fix server PATH.
143
- - Empty app list: run open-computer-use list-apps in the same graphical
144
- session; confirm the app runs and desktop accessibility is available.
145
- - Screenshots unavailable: OS compositor or permission gap; bounded
225
+ - macOS Accessibility missing: grant Accessibility to Open Computer Use.app,
226
+ not Terminal/OpenCode, restart the app-agent, then rerun doctor. Open the pane
227
+ with: open x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility.
228
+ - macOS Screen Recording missing: grant Open Computer Use.app in Screen &
229
+ System Audio Recording, restart the app-agent, then rerun doctor. Open the
230
+ pane with: open x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture.
231
+ - macOS stale permission state: first restart Open Computer Use and verify the
232
+ correct release app is approved. Last-resort resets are:
233
+ tccutil reset Accessibility com.ifuryst.opencomputeruse
234
+ tccutil reset ScreenCapture com.ifuryst.opencomputeruse
235
+ These reset consent and require the user to approve it again; they do not grant it.
236
+ - macOS native binary will not start: verify sw_vers -productVersion; current
237
+ native macOS runtime requires macOS 14+ and permissions cannot fix an older OS.
238
+ - Linux empty app list / AT-SPI unavailable: run from the logged-in graphical
239
+ desktop session, enable GNOME toolkit accessibility with
240
+ gsettings set org.gnome.desktop.interface toolkit-accessibility true, verify
241
+ D-Bus/session variables, then rerun open-computer-use call list_apps.
242
+ - Linux Wayland screenshot black/omitted: compositor capture restriction; use
243
+ semantic accessibility state/actions or test the workflow in an X11 session
244
+ when reliable coordinate/screenshot automation is required.
245
+ - Linux action reports success but nothing changes: fetch a fresh state and
246
+ prefer element-index AT-SPI actions/set-value/secondary actions. Coordinate
247
+ and key synthesis are best-effort under Wayland.
248
+ - Linux Accessible.is_text/is_editable_text error: upgrade/reinstall the current
249
+ open-computer-use native runtime, which uses standard AT-SPI interfaces.
250
+ - Windows empty app list: run inside the signed-in interactive desktop, not a
251
+ Windows service, session 0, detached SSH context, or unrelated user session.
252
+ - Windows elevated target: UI Automation/input can be blocked by integrity
253
+ boundaries. Prefer a normal-user target; match integrity level only when the
254
+ explicit task truly requires elevation.
255
+ - Windows UAC/lock/sign-in screen: intentionally unsupported secure-desktop/OS
256
+ surfaces; approve/dismiss manually and return to the normal desktop.
257
+ - Windows app found but action does nothing: keep a visible non-minimized window,
258
+ refresh state, prefer semantic UIA actions, and verify the result after input.
259
+ - Empty app list generally: run open-computer-use call list_apps in the same
260
+ graphical session; confirm the app is visible and desktop accessibility is available.
261
+ - Screenshots unavailable generally: OS compositor or permission gap; bounded
146
262
  accessibility text may still work.
147
- - Linux input returns success but nothing changes: session type or missing
148
- runtime prerequisite; observe again before retrying mutating actions.
149
263
  - Global pointer fallback rejected: expected safety gate.
150
264
 
151
265
  10. WebMCP preview
@@ -157,6 +271,7 @@ control, shell, network mutation, or credentials. WebMCP is experimental and
157
271
  should not be treated as stable browser support.
158
272
 
159
273
  11. Development
160
- npm install; npm test; npm run lint. Adapter tests use a fake native MCP
274
+ npm ci --ignore-scripts; npm test; npm run lint. Register a development
275
+ checkout explicitly with the setup --source . command above. Adapter tests use a fake native MCP
161
276
  server and need no real desktop. Live desktop validation must run
162
277
  separately per OS.
package/docs/llms.txt CHANGED
@@ -11,6 +11,7 @@
11
11
  - [OS support](https://opencode-computer-use.pages.dev/#os)
12
12
  - [WebMCP preview](https://opencode-computer-use.pages.dev/#webmcp)
13
13
  - [Security](https://opencode-computer-use.pages.dev/#security)
14
+ - [Troubleshooting](https://opencode-computer-use.pages.dev/#troubleshooting)
14
15
  - [FAQ](https://opencode-computer-use.pages.dev/#faq)
15
16
 
16
17
  ## Repo
@@ -21,12 +22,17 @@
21
22
 
22
23
  ## Key facts
23
24
 
25
+ - Documentation version: 0.1.5. Check npm view @frontendxlab/opencode-computer-use version for the registry release. Checkout setup can load source changes before publication; scoped npm exec commands use the registry release.
24
26
  - MCP server name: cua_repl. Model-facing tools: js (persistent evaluation) and js_reset (clean session).
25
27
  - Async cua API: listApps, getApp, getState, getAXState, getScreenshot, getAXStateAndScreenshot, click, scroll, drag, typeText, pressKey, setValue, performSecondaryAction. Output helpers: nodeRepl.write, nodeRepl.emitImage.
26
28
  - WebMCP preview: the docs page registers only read-only search_docs, get_install_config, and get_api_reference tools when document.modelContext is available. It is progressive enhancement, not stable browser support.
27
29
  - Safety profiles: safetyMode=read-only blocks mutating CUA methods and changes js annotations; appAccess supports exact-name and ID allow/deny rules.
28
30
  - Reliability: app bindings and output are bounded; native timeouts send cancellation; shutdown sends a best-effort turn-ended notification.
29
31
  - No arbitrary shell escape. No run_shell in the model-facing API. Restricted VM blocks require, process, fetch, filesystem, dynamic import, and code generation.
30
- - Published package: @frontendxlab/opencode-computer-use. Its postinstall setup detects OpenCode generation and local/global scope, preserves JSONC, and prints manual instructions when ambiguous. Explicit setup: npx opencode-computer-use-setup --local|--global --v1|--v2.
32
+ - Published package: @frontendxlab/opencode-computer-use. Its postinstall setup detects OpenCode generation and local/global scope and preserves JSONC; global setup edits JSONC directly. Ambiguous automatic setup leaves configuration unchanged; explicit manual failures return nonzero. From any directory: npm exec --package=@frontendxlab/opencode-computer-use -- opencode-computer-use-setup --local --v1. Select --global or --v2 as appropriate. A bare npx helper name only works when the binary is already available; it does not identify this scoped package for download.
33
+ - Cloned repository: npm ci --ignore-scripts, then node bin/opencode-computer-use-setup.js --local --v1 --source . and node bin/opencode-computer-use-permissions.js --strict. Use --v2 for OpenCode2 or --global for other projects. Dependency installation alone does not register a development checkout. Restart OpenCode after setup and check opencode mcp list.
34
+ - Desktop onboarding runs in postinstall when lifecycle scripts are allowed: macOS doctor/onboarding, best-effort GNOME accessibility enablement, and Windows interactive-session diagnostics. Outside a checkout: npm exec --package=@frontendxlab/opencode-computer-use -- opencode-computer-use-permissions --strict.
35
+ - Device scope: the controlled desktop belongs to the OpenCode/MCP server's computer. Attaching a browser or terminal from another device does not change that target. Each computer needs its own dependencies, configuration, permissions, and interactive desktop session; automatic remote-device pairing and network transport are not built in.
31
36
  - Requirements: Node.js 18 or newer, OpenCode 1.18.29 or newer (V1) or OpenCode2 V2, plus a signed-in graphical desktop session with OS accessibility and capture permissions.
32
- - Native runtimes packaged for darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64. Live smoke tests passed on Linux x64 only; macOS and Windows need on-host validation.
37
+ - Native runtimes packaged for darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64. Android/iOS are not native targets. CI tests the adapter against a fake native backend; it does not certify desktop input or capture. macOS, Windows, and Linux arm64 require native validation on real hosts.
38
+ - Native smoke evidence on 2026-10-30: Ubuntu GNOME Wayland Linux x64, Node 26.13.0, OpenCode 1.19.3, native 0.3.5. MCP registration, app inventory, persistent bindings/reset, and semantic click/setValue in a disposable GTK3 app passed with fresh accessibility-state verification. Screenshot capture returned unavailable. Keyboard/coordinate input was not validated; other apps and Wayland capabilities need separate checks.
package/docs/sitemap.xml CHANGED
@@ -2,7 +2,7 @@
2
2
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
3
  <url>
4
4
  <loc>https://opencode-computer-use.pages.dev/</loc>
5
- <lastmod>2026-09-25</lastmod>
5
+ <lastmod>2026-09-27</lastmod>
6
6
  <changefreq>weekly</changefreq>
7
7
  <priority>1.0</priority>
8
8
  </url>
package/docs/webmcp.js CHANGED
@@ -64,7 +64,7 @@
64
64
  for (var index = 0; index < entries.length; index += 1) {
65
65
  var label = entries[index].querySelector("strong");
66
66
  if (!label) continue;
67
- var documented = String(label.textContent || "").trim().toLowerCase();
67
+ var documented = String(label.textContent || "").split("(")[0].trim().toLowerCase();
68
68
  var method = documented.split(".").pop();
69
69
  if (documented === requested || documented.endsWith("." + requested) || (requested.indexOf(".") === -1 && method === requested)) {
70
70
  return result(plainText(entries[index], 2000));
@@ -92,7 +92,7 @@
92
92
  {
93
93
  name: "get_install_config",
94
94
  title: "Get installation configuration",
95
- description: "Return the documented OpenCode installation configuration for one generation.",
95
+ description: "Return the published-package OpenCode configuration for one generation. For a cloned repository, use the documented setup --source command instead. The local MCP server controls the OpenCode server host's desktop; this configuration does not pair remote devices.",
96
96
  inputSchema: {
97
97
  type: "object",
98
98
  properties: {
package/lib/backend.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { constants, accessSync, existsSync, statSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
- import { delimiter, dirname, join } from "node:path";
3
+ import { dirname, join } from "node:path";
4
4
  import process from "node:process";
5
5
  import { normalizeOptions, optionsFromEnvironment } from "./options.js";
6
6
 
@@ -15,6 +15,8 @@ const PLATFORM_PATHS = {
15
15
  "win32-x64": ["dist", "windows", "amd64", "open-computer-use.exe"],
16
16
  };
17
17
 
18
+ const SUPPORTED_TARGETS = Object.freeze(Object.keys(PLATFORM_PATHS));
19
+
18
20
  function isExecutable(path) {
19
21
  try {
20
22
  accessSync(path, constants.X_OK);
@@ -24,14 +26,22 @@ function isExecutable(path) {
24
26
  }
25
27
  }
26
28
 
27
- function commandOnPath(name, environment = process.env) {
28
- const pathValue = environment.PATH;
29
+ function environmentValue(environment, name, platform) {
30
+ if (platform !== "win32") return environment[name];
31
+ // Windows environment names are case-insensitive. Match Node's sorted-key
32
+ // choice when a supplied object contains more than one spelling of a name.
33
+ const key = Object.keys(environment).sort().find((key) => key.toUpperCase() === name.toUpperCase());
34
+ return key === undefined ? undefined : environment[key];
35
+ }
36
+
37
+ export function commandOnPath(name, environment = process.env, platform = process.platform) {
38
+ const pathValue = environmentValue(environment, "PATH", platform);
29
39
  if (!pathValue) return undefined;
30
40
  const extensions =
31
- process.platform === "win32"
32
- ? (environment.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";")
41
+ platform === "win32"
42
+ ? (environmentValue(environment, "PATHEXT", platform) || ".EXE;.CMD;.BAT;.COM").split(";")
33
43
  : [""];
34
- for (const directory of pathValue.split(delimiter)) {
44
+ for (const directory of pathValue.split(platform === "win32" ? ";" : ":")) {
35
45
  if (!directory) continue;
36
46
  for (const extension of extensions) {
37
47
  const candidate = join(directory, `${name}${extension}`);
@@ -41,6 +51,21 @@ function commandOnPath(name, environment = process.env) {
41
51
  return undefined;
42
52
  }
43
53
 
54
+ export function resolvePathExecutable(command, platform = process.platform, arch = process.arch) {
55
+ if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(command)) return command;
56
+ // npm's global Windows shim cannot be spawned with shell:false. Resolve the
57
+ // known package's native executable beside that shim instead of invoking a
58
+ // command shell or interpreting arbitrary shim contents.
59
+ const relative = PLATFORM_PATHS[`${platform}-${arch}`];
60
+ if (relative) {
61
+ const executable = join(dirname(command), "node_modules", "open-computer-use", ...relative);
62
+ if (isExecutable(executable)) return executable;
63
+ }
64
+ throw new Error(
65
+ "The Windows open-computer-use PATH entry is a .cmd/.bat launcher without an adjacent native runtime. Install open-computer-use@0.3.5 in this package or set OPEN_COMPUTER_USE_NATIVE_COMMAND to the absolute open-computer-use.exe path. Shell launchers are not executed.",
66
+ );
67
+ }
68
+
44
69
  function packageRoot() {
45
70
  try {
46
71
  return dirname(require.resolve("open-computer-use/package.json"));
@@ -79,6 +104,11 @@ function nativeCommand(environment) {
79
104
 
80
105
  const root = packageRoot();
81
106
  const relative = PLATFORM_PATHS[`${process.platform}-${process.arch}`];
107
+ if (!relative) {
108
+ throw new Error(
109
+ `Open Computer Use does not provide a native runtime for ${process.platform}-${process.arch}. Supported targets: ${SUPPORTED_TARGETS.join(", ")}. Configure backend 'custom' or OPEN_COMPUTER_USE_NATIVE_COMMAND if you provide a compatible runtime yourself.`,
110
+ );
111
+ }
82
112
  if (root && relative) {
83
113
  const executable = join(root, ...relative);
84
114
  if (isExecutable(executable)) {
@@ -98,7 +128,7 @@ function nativeCommand(environment) {
98
128
  return {
99
129
  name: "open-computer-use",
100
130
  source: "PATH:open-computer-use",
101
- command: fromPath,
131
+ command: resolvePathExecutable(fromPath),
102
132
  args: ["mcp"],
103
133
  platform: process.platform,
104
134
  arch: process.arch,
@@ -106,7 +136,7 @@ function nativeCommand(environment) {
106
136
  }
107
137
 
108
138
  throw new Error(
109
- "Open Computer Use native runtime is not installed. Install open-computer-use or set OPEN_COMPUTER_USE_NATIVE_COMMAND.",
139
+ `Open Computer Use native runtime for ${process.platform}-${process.arch} is not installed or executable. Install open-computer-use@0.3.5, ensure its install scripts were allowed, or set OPEN_COMPUTER_USE_NATIVE_COMMAND.`,
110
140
  );
111
141
  }
112
142
 
@@ -132,7 +162,7 @@ export function resolveBackend(input = {}, environment = process.env) {
132
162
  return nativeCommand(environment);
133
163
  }
134
164
 
135
- export function backendEnvironment(environment = process.env) {
165
+ export function backendEnvironment(environment = process.env, platform = process.platform) {
136
166
  const allowed = new Set([
137
167
  "PATH",
138
168
  "HOME",
@@ -182,9 +212,15 @@ export function backendEnvironment(environment = process.env) {
182
212
  "COMPUTER_USE_LINUX_SCREENSHOT_BACKEND",
183
213
  ]);
184
214
 
185
- return Object.fromEntries(
186
- Object.entries(environment).filter(
187
- ([key, value]) => allowed.has(key) && typeof value === "string",
188
- ),
189
- );
215
+ const normalizeName = platform === "win32" ? (name) => name.toUpperCase() : (name) => name;
216
+ const allowedNames = new Set([...allowed].map(normalizeName));
217
+ const seen = new Set();
218
+ const keys = Object.keys(environment);
219
+ if (platform === "win32") keys.sort();
220
+ return Object.fromEntries(keys.flatMap((key) => {
221
+ const name = normalizeName(key);
222
+ if (!allowedNames.has(name) || seen.has(name) || typeof environment[key] !== "string") return [];
223
+ seen.add(name);
224
+ return [[key, environment[key]]];
225
+ }));
190
226
  }
@@ -37,6 +37,9 @@ export class JsonLinePeer extends EventEmitter {
37
37
  this.child.stderr.setEncoding("utf8");
38
38
  this.child.stdout.on("data", (chunk) => this.#onData(chunk));
39
39
  this.child.stderr.on("data", (chunk) => { this.stderr = (this.stderr + chunk).slice(-8192); });
40
+ for (const stream of [this.child.stdin, this.child.stdout, this.child.stderr]) {
41
+ stream.on("error", (error) => this.#close(error, true));
42
+ }
40
43
  this.child.once("error", (error) => this.#close(error, true));
41
44
  this.child.once("exit", (code, signal) => {
42
45
  this.exited = true;
package/lib/sandbox.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { runInContext } from "node:vm";
2
+ import { parse } from "acorn";
2
3
 
3
4
  const MAX_FRAME_BYTES = 32 * 1024 * 1024;
4
5
  const MAX_APP_BINDINGS = 128;
@@ -219,8 +220,45 @@ export function hardenSandboxContext(context) {
219
220
  }
220
221
 
221
222
  export function assertSandboxCode(code) {
222
- if (/(^|[^A-Za-z0-9_$])import([^A-Za-z0-9_$]|$)/u.test(code)) {
223
- throw new Error("dynamic imports are disabled in the CUA sandbox");
223
+ const options = {
224
+ ecmaVersion: "latest",
225
+ allowAwaitOutsideFunction: true,
226
+ allowReturnOutsideFunction: true,
227
+ allowImportExportEverywhere: true,
228
+ };
229
+ let ast;
230
+ try {
231
+ ast = parse(code, { ...options, sourceType: "script" });
232
+ } catch (scriptError) {
233
+ try {
234
+ // Module grammar also covers top-level resource declarations. The REPL
235
+ // remains responsible for whether otherwise-safe syntax can execute.
236
+ ast = parse(code, { ...options, sourceType: "module" });
237
+ } catch {
238
+ try {
239
+ // Like Node's REPL, accept an object literal entered as one expression.
240
+ ast = parse(`(${code}\n)`, { ...options, sourceType: "script" });
241
+ } catch {
242
+ throw scriptError;
243
+ }
244
+ }
245
+ }
246
+ const nodes = [ast];
247
+ while (nodes.length > 0) {
248
+ const node = nodes.pop();
249
+ if (node.type === "ImportExpression" || node.type === "ImportDeclaration"
250
+ || (node.type === "MetaProperty" && node.meta?.name === "import")) {
251
+ throw new Error("dynamic imports are disabled in the CUA sandbox");
252
+ }
253
+ for (const value of Object.values(node)) {
254
+ if (Array.isArray(value)) {
255
+ for (const child of value) {
256
+ if (child && typeof child.type === "string") nodes.push(child);
257
+ }
258
+ } else if (value && typeof value.type === "string") {
259
+ nodes.push(value);
260
+ }
261
+ }
224
262
  }
225
263
  }
226
264
 
@@ -294,6 +332,9 @@ export function createSandboxBridge(
294
332
  return accessPolicy.default === "allow";
295
333
  };
296
334
  const assertNamedAppAllowed = async (name) => {
335
+ if (accessPolicy.default === "deny" && accessPolicy.allow.size === 0) {
336
+ throw new Error(`app access denied by policy: ${name}`);
337
+ }
297
338
  if (accessPolicy.allow.size === 0 && accessPolicy.deny.size === 0) return;
298
339
  const apps = await api.listApps({ emit: false });
299
340
  const matches = Array.isArray(apps) ? apps.filter((app) => appNameMatches(app, name)) : [];
@@ -313,9 +354,15 @@ export function createSandboxBridge(
313
354
  if (!isActive()) throw new Error("CUA calls are only available while js is executing");
314
355
  if (method === "getState") {
315
356
  const state = await api.getState({ emit: false });
316
- return encodeSandboxResult({ apps: filterApps(state.apps) });
357
+ const apps = filterApps(state.apps);
358
+ if (args[0]?.emit !== false) activeOutput().write(apps, "cua.state");
359
+ return encodeSandboxResult({ apps });
360
+ }
361
+ if (method === "listApps") {
362
+ const apps = filterApps(await api.listApps({ emit: false }));
363
+ if (args[0]?.emit !== false) activeOutput().write(apps, "cua.state");
364
+ return encodeSandboxResult(apps);
317
365
  }
318
- if (method === "listApps") return encodeSandboxResult(filterApps(await api.listApps({ emit: false })));
319
366
  if (method === "getApp") {
320
367
  const app = args[0];
321
368
  if (typeof app !== "string" || !app.trim()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontendxlab/opencode-computer-use",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "A dual OpenCode and OpenCode2 plugin for local cross-platform desktop control.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,27 +29,29 @@
29
29
  },
30
30
  "bin": {
31
31
  "opencode-computer-use-repl": "bin/opencode-computer-use-repl.js",
32
- "opencode-computer-use-setup": "bin/opencode-computer-use-setup.js"
32
+ "opencode-computer-use-setup": "bin/opencode-computer-use-setup.js",
33
+ "opencode-computer-use-permissions": "bin/opencode-computer-use-permissions.js"
33
34
  },
34
35
  "files": [
35
36
  "bin/",
36
37
  "lib/",
37
38
  "docs/",
38
39
  "index.js",
40
+ "CHANGELOG.md",
39
41
  "README.md",
40
42
  "SECURITY.md",
41
43
  "LICENSE",
42
44
  "THIRD_PARTY_NOTICES.md"
43
45
  ],
44
46
  "engines": {
45
- "node": ">=18",
46
- "opencode": ">=0.0.0-beta-18684"
47
+ "node": ">=18"
47
48
  },
48
49
  "scripts": {
49
- "postinstall": "node bin/opencode-computer-use-setup.js --auto",
50
- "lint": "node --check index.js && node --check bin/opencode-computer-use-repl.js && node --check bin/opencode-computer-use-setup.js && node --check lib/backend.js && node --check lib/json-line-peer.mjs && node --check lib/options.js && node --check lib/output.mjs && node --check lib/repl.mjs && node --check lib/repl-kernel.mjs && node --check lib/sandbox.mjs",
51
- "test": "node --test",
52
- "prepack": "node --check index.js && node --check bin/opencode-computer-use-repl.js && node --check bin/opencode-computer-use-setup.js && node -e \"JSON.parse(require('fs').readFileSync('package.json','utf8'))\"",
50
+ "postinstall": "node bin/opencode-computer-use-setup.js --auto && node bin/opencode-computer-use-permissions.js --install",
51
+ "lint": "node --check index.js && node --check bin/opencode-computer-use-repl.js && node --check bin/opencode-computer-use-setup.js && node --check bin/opencode-computer-use-permissions.js && node --check lib/backend.js && node --check lib/json-line-peer.mjs && node --check lib/options.js && node --check lib/output.mjs && node --check lib/repl.mjs && node --check lib/repl-kernel.mjs && node --check lib/sandbox.mjs",
52
+ "test": "node --test test/run.mjs",
53
+ "test:opencode": "node test/opencode-smoke.mjs",
54
+ "prepack": "node --check index.js && node --check bin/opencode-computer-use-repl.js && node --check bin/opencode-computer-use-setup.js && node --check bin/opencode-computer-use-permissions.js && node -e \"JSON.parse(require('fs').readFileSync('package.json','utf8'))\"",
53
55
  "prepublishOnly": "npm run lint && npm test"
54
56
  },
55
57
  "optionalDependencies": {
@@ -57,6 +59,7 @@
57
59
  },
58
60
  "sideEffects": false,
59
61
  "dependencies": {
62
+ "acorn": "^8.19.0",
60
63
  "jsonc-parser": "^3.3.1"
61
64
  }
62
65
  }