OpenCode / Computer Use

Codex-style desktop control for OpenCode

Desktop control with a memory.

A small, persistent cua_repl session for OpenCode and OpenCode2. Bind an app, inspect its accessibility state, act, and verify the result without exposing a pile of raw desktop tools.

Local stdio MCP session, with native OS permissions still enforced.

Session / Chrome cua_repl example
var chrome = await cua.getApp("Google Chrome");
await chrome.getAXState();
await chrome.click([530, 20]);
js persistentjs_reset clean slateno shell tool
On this page

Quick answer · Install · First run · API · Capabilities · WebMCP · Security · FAQ

Short answer.

OpenCode Computer Use is an OpenCode plugin that registers a local MCP server named cua_repl. The model receives two tools: js for persistent JavaScript and js_reset for a clean JavaScript session. Native operating-system permissions still control desktop access.

Plugin
The package loaded by OpenCode. It manages registration and lifecycle.
MCP server
The local stdio server named cua_repl.
Model tools
js and js_reset, not raw desktop tools.
Native runtime
open-computer-use, running separately with OS accessibility and capture permissions.
Supported hosts
OpenCode 1 and OpenCode2 on Linux, macOS, and Windows where the native runtime and permissions are available.

Install once. Keep the session.

This repository is a standalone OpenCode plugin. It contains the plugin entrypoint, the local MCP server, and the native runtime integration. The plugin registers cua_repl; the model sees only js and js_reset.

git clone [email protected]:rashidtvmr/opencode-computer-use.git
cd opencode-computer-use
npm install

For a published-package install, use:

npm install @frontendxlab/opencode-computer-use
# pnpm install @frontendxlab/opencode-computer-use also works

The package postinstall step detects OpenCode 1 versus OpenCode2 and local versus global installation. It edits JSONC without discarding comments, avoids duplicate entries, and prints both manual formats when the choice is ambiguous. Run it explicitly with npx opencode-computer-use-setup, or add --local, --global, --v1, or --v2 when you want to choose explicitly.

Recent pnpm releases can require explicit lifecycle-script approval. If pnpm reports ERR_PNPM_IGNORED_BUILDS, run pnpm approve-builds @frontendxlab/opencode-computer-use once, then rerun pnpm install. If scripts are disabled, run pnpm exec opencode-computer-use-setup manually. CI environments skip setup automatically.

Desktop onboarding is part of installation from v0.1.5. When package lifecycle scripts are allowed, postinstall first configures OpenCode and then runs opencode-computer-use-permissions --install. On macOS it runs the native doctor and opens the Open Computer Use permission onboarding when Accessibility or Screen Recording is missing; Apple still requires you to approve the protected toggles. On GNOME Linux it best-effort enables toolkit accessibility before the AT-SPI/session check. Windows validates that UI Automation is running in an interactive signed-in desktop session. Set OPENCODE_COMPUTER_USE_SKIP_PERMISSIONS=1 to opt out.

Add the checkout to the V1 plugin list. An absolute path avoids ambiguity when OpenCode starts from another directory.

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    ["@frontendxlab/opencode-computer-use", { "backend": "auto" }]
  ]
}

Restart OpenCode or reopen the TUI, then verify the dynamically registered server:

opencode mcp list
Why it is not listed as a command? The package is a plugin. During loading, its entrypoint registers a local MCP server named cua_repl. That server then exposes the callable tools cua_repl.js and cua_repl.js_reset.

Start with discovery.

Use the first call to see what the native runtime can actually see. Do not guess an app name or assume that a screenshot is available.

var apps = await cua.listApps({ emit: false });
var chrome = await cua.getApp("Google Chrome");
await chrome.getAXState({ textLimit: 12000 });
Action

The generated snippet is intentionally small. Bindings persist between calls, so use var when a later call may need to reassign the same name.

One surface. Three layers.

  1. OpenCode plugin
    Loads the local package and registers the MCP server without exposing raw desktop tools.
  2. cua_repl MCP server
    Provides a persistent JavaScript evaluation surface, output bounds, request validation, and worker isolation.
  3. Native runtime
    Runs open-computer-use as a separate child process. OS accessibility, screen capture, and input permissions remain there.
The JavaScript VM is a capability guard, not a complete security boundary. OpenCode approvals and operating-system permissions remain the enforcement layer.

The Codex-shaped API.

The model receives a small async API inside persistent JavaScript:

  • cua.getState(options?)List the current desktop apps and emit bounded output.
  • cua.listApps(options?)Return structured app records without emitting by default.
  • cua.getApp(nameOrPid)Bind an app and emit its initial accessibility state.
  • app.getAXState(options?)Read a fresh semantic tree before choosing an element index.
  • app.getScreenshot(options?)Return local image bytes and optionally emit them with nodeRepl.
  • app.click(target, options?)Use an element index first, or a bounded coordinate when necessary.
  • app.typeText(text)Send literal text through the native runtime.
  • app.pressKey(key)Send a named key or supported modifier chord.
  • app.performSecondaryAction(index, action)Invoke an advertised accessibility action such as a context action.

Use nodeRepl.write(value) for extra text and await nodeRepl.emitImage(bytesOrDataUrl) for local images. Both are bounded before crossing the worker boundary.

What it can — and cannot — do.

This is the complete model-facing computer-use surface of the current cua_repl adapter, plus its intentional exclusions. A green check means the adapter/native runtime exposes that path; it does not mean every application or desktop session will accept it.

Supported Conditional Not exposed / blocked
CapabilitymacOSLinuxWindowsWhat that means
Observe & understand
List visible applicationscua.listApps() Discovers apps visible to the native accessibility runtime in the signed-in desktop session.
Read desktop / app inventorycua.getState() Returns the current structured app inventory with bounded output.
Bind an existing applicationcua.getApp(nameOrID) Binds an already running or visible app. It does not launch a new application.
Read accessibility / UI treeapp.getAXState() macOS uses Accessibility, Linux AT-SPI, and Windows UI Automation. Some Wayland/app combinations expose incomplete trees.
Capture an app screenshotapp.getScreenshot() Depends on capture permission plus the active compositor/capture backend.
Read state + screenshot togetherapp.getAXStateAndScreenshot() Combines semantic state and image capture; state can still be useful when capture is unavailable.
Keep variables and app bindingspersistent js session Top-level variables and app bindings survive across model turns until js_reset or a timeout reset.
Emit text and images to the modelnodeRepl.write / emitImage Text and image output are bounded before crossing the worker/MCP boundary.
Interact with an app
Click a semantic elementapp.click(elementIndex) Preferred input path. Requires a current actionable accessibility element.
Click a coordinateapp.click([x, y]) Use when semantic state is insufficient. System/global-pointer fallback is separately gated.
Type literal textapp.typeText(text) Requires a writable/focused target and an input path accepted by the host desktop.
Press keys / supported chordsapp.pressKey(key) Host input restrictions, focus, and compositor behavior still apply.
Scroll an accessibility elementapp.scroll(index, direction, pages) Semantic element scrolling is exposed. Coordinate-only scrolling is intentionally rejected.
Drag between coordinatesapp.drag([x1,y1], [x2,y2]) Coordinate/input-backend dependent; verify the resulting UI state rather than assuming success.
Set an accessibility valueapp.setValue(index, value) Only works when the target element/backend advertises a writable accessibility value.
Run a secondary accessibility actionapp.performSecondaryAction() Only actions advertised by the accessibility element/backend can be invoked.
Use global pointer fallbackallowGlobalPointerFallbacks Off by default. Must be explicitly enabled because it can move the real pointer and change focus.
Session, safety & recovery
Run a read-only desktop sessionsafetyMode: read-only Mutating CUA methods are blocked by the adapter before native execution.
Allow / deny specific applicationsappAccess Exact case-insensitive app name/ID rules are enforced before binding and actions.
Reset JavaScript bindingsjs_reset Clears REPL variables and bindings without closing apps or resetting their native state.
Recover from CPU-bound JavaScriptworker timeout + restart A wedged JS evaluation is terminated in its worker and the JavaScript session is restarted.
Cancel timed-out native requestsnotifications/cancelled Cancellation is best-effort. After an uncertain mutation, observe state before acting again.
Clean up at session endnotifications/turn-ended Shutdown sends best-effort lifecycle cleanup before the native child is closed.
Re-emit CUA usage guidancecua.rewriteDocumentation() Reprints the bounded API guidance inside the persistent JavaScript session.
Use safe JavaScript timerssetTimeout / clearTimeout Timers exist inside the restricted session and are cancelled when the active evaluation ends or resets.
Intentionally unavailable through cua_repl
Launch a new applicationno model-facing method cua.getApp() binds an existing/visible app; the adapter exposes no launch method.
Quit / force-close an applicationno model-facing method No quit, terminate, or force-kill application API is exposed to the model.
Move / resize native windowsno model-facing method The current public CUA facade has no window-management methods.
Coordinate-only scrollingapp.scroll([x,y], ...) The adapter explicitly rejects coordinate scroll; bind a semantic element instead.
Read / write clipboard directlyno clipboard API There is no direct clipboard API. Normal app keyboard shortcuts may still work when the app/OS accepts them.
Use browser DOM / CDP directlyno DOM or CDP API Browser windows are controlled like desktop apps through accessibility/screenshot state, not page DOM or Chrome DevTools Protocol.
Record screen video or audioscreenshot only The model-facing surface can request still screenshots; it does not expose continuous screen, camera, microphone, or audio recording.
Run arbitrary shell commandsrun_shell / exec Deliberately omitted from the model-facing CUA API.
Read / write filesystem from JSfs / require / process Node built-ins, require, process, and filesystem access are removed from the sandbox.
Make network requests from JSfetch / sockets fetch and arbitrary networking are unavailable inside the model-facing VM.
Use dynamic imports / code generationimport() / eval / Function Dynamic imports, string code generation, WebAssembly, and host-realm escapes are disabled.
Control lock / login / secure desktopOS-owned surfaces Lock/login screens, Windows UAC secure desktop, and other OS-owned consent surfaces are outside the supported session.
Bypass operating-system permissionsAccessibility / AT-SPI / UIA / capture The plugin never elevates itself or bypasses native permission boundaries.
Automate password managersnative safety denial The native runtime keeps its password-manager safety denial; secret stores are intentionally out of scope.
Platform reality: macOS needs Accessibility and Screen Recording permissions. Linux needs a signed-in AT-SPI/D-Bus desktop and some Wayland applications may expose incomplete or non-actionable state. Windows needs a signed-in interactive UI Automation session. macOS and Windows are packaged targets but still require the live certification checklist before their native desktop behavior is described as verified.

WebMCP, when available.

The documentation page includes a small, read-only WebMCP enhancement based on the current document.modelContext.registerTool() draft. Browsers without the preview API continue normally; no desktop-control or mutating tool is exposed through the page.

const tools = await document.modelContext.getTools();
// Preview-only discovery: search_docs, get_install_config, get_api_reference

Preview boundary: WebMCP support is experimental and requires a supporting browser, a secure context, and the tools Permissions Policy. Cloudflare Browser Run lab sessions can be used to test discovery. This page does not claim stable or universal browser support.

Make risky capabilities explicit.

{
  "backend": "auto",
  "serverName": "cua_repl",
  "safetyMode": "full",
  "appAccess": {
    "default": "allow",
    "allow": [],
    "deny": []
  },
  "timeoutMs": 30000,
  "maxTextChars": 200000,
  "maxImageBytes": 2097152,
  "allowGlobalPointerFallbacks": false
}
  • backendauto, native, or a fixed custom command.
  • serverNameRename the generated MCP server when a project needs isolation.
  • safetyModefull or read-only. Read-only blocks mutating CUA methods and changes MCP annotations.
  • appAccessOptional exact-name and ID allow or deny rules, evaluated before native access.
  • timeoutMsBounds JavaScript evaluation. A timeout resets the worker session.
  • maxTextCharsAggregate text budget for one tool result.
  • maxImageBytesAggregate image budget for one tool result.
  • allowGlobalPointerFallbacksOff by default. Enable only when system-level pointer movement is explicitly intended.

Observe, act, verify.

  1. Read getAXState() before selecting an element.
  2. Prefer a fresh element index over a remembered coordinate.
  3. Batch deterministic actions and the next state read in one js call.
  4. After navigation, dialogs, resizing, or rerendering, read state again.
  5. Use js_reset when bindings need to be discarded, not when an app needs restarting.
var app = await cua.getApp("Example App");
await app.getAXState();
var state = await app.click(12);
await app.getAXState();
nodeRepl.write(state);

Know what the host can do.

Operating-system runtime targets and current validation status
TargetPackaged runtimeLive status here
Linux x64IncludedRead-only smoke passed
Linux arm64IncludedNeeds arm64 host
macOS arm64 and x64IncludedNeeds live permissions
Windows arm64 and x64IncludedNeeds UIA validation

macOS generally needs Accessibility and Screen Recording. Windows needs UI Automation in the interactive desktop session. Linux needs AT-SPI plus the native runtime's input and capture services. A secure desktop, lock screen, UAC surface, or missing permission is not bypassed.

Installation walks you through desktop access.

The v0.1.5+ npm/pnpm install path runs the same desktop prerequisite helper automatically. It checks the current OS, runs the native doctor, and gives platform-specific guidance. The helper can enable non-protected settings such as GNOME toolkit accessibility, but it never bypasses protected OS consent. On macOS, the native onboarding opens the correct System Settings panes and waits for you to approve Open Computer Use.app — not Terminal or OpenCode.

Why there is still a manual step on macOS: Accessibility and Screen Recording are TCC-protected permissions. Apple does not provide a supported command that silently grants them to an ordinary npm package. Our installer automates detection, opens onboarding, opens the correct Settings destination, and verifies the result; the final privacy toggle remains yours to approve.

Manual permission and verification commands.

macOS 14+ requires Accessibility and Screen Recording for Open Computer Use.app. The first command is the same checker used by installation.

# Run/re-run our guided installer check
npx opencode-computer-use-permissions --strict

# Native status + onboarding UI
open-computer-use doctor

# Open the exact System Settings panes manually if needed
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"

# Verify after approving both toggles
open-computer-use doctor
open-computer-use call list_apps

If doctor still reports a missing permission after you enable it, close/reopen the affected app or Open Computer Use and run the check again. Do not use tccutil as a grant command: it can reset consent, not silently approve it.

How to turn conditional actions into reliable ones.

  • Fix the host firstRun the guided permission check and make sure the native runtime can actually list the target app before debugging clicks or typing.
  • Prefer semantic actionsUse a fresh accessibility element index for click, scroll, set-value, and secondary actions. This is more portable than remembered coordinates.
  • Observe after mutationsRead a new accessibility state after navigation, dialogs, rerenders, or an uncertain timeout. Never assume a reported input event changed the UI.
  • Use screenshots as a fallbackWhen the accessibility tree is incomplete, a screenshot can recover visual context if the OS capture backend permits it.
  • Opt into pointer fallback only when neededallowGlobalPointerFallbacks can recover coordinate-only cases, but it may move the real pointer or change focus and stays off by default.
  • Choose the right Linux sessionAT-SPI works best when the process can reach the logged-in desktop bus. Wayland may restrict capture/input; semantic actions are the preferred path.
Current Linux finding: on a GNOME Wayland session, Chrome exposed a pseudo-window with no actionable AT-SPI children. The native runtime accepted some synthetic events without changing the window. The adapter reports that result honestly; it does not turn a no-op into a success claim.

Small surface. Strong boundaries.

  • No arbitrary shellThe model-facing API does not expose run_shell or a host command escape.
  • Restricted VMFilesystem, process, fetch, dynamic import, code generation, and host built-ins are blocked.
  • Worker isolationCPU-bound JavaScript can be terminated without wedging the MCP server.
  • Read-only modeAn opt-in safety profile blocks clicks, keys, text, scroll, drags, and secondary actions before native execution.
  • App policyExact-name and ID allow or deny rules provide adapter-level defense in depth.
  • Bounded transportInput frames, output text, images, content items, app bindings, and native responses are bounded.
  • Lifecycle cleanupTimeouts send native cancellation, and shutdown sends a best-effort turn-ended notification.
  • Approval preservingConsequential actions still pass through OpenCode and OS permission boundaries.
  • Safe defaultsGlobal pointer fallback is disabled until explicitly enabled.

Read SECURITY.md before using the session against banking, password managers, administrator tools, or unattended workloads.

When the session is quiet.

Start with the same three checks on every platform. They separate plugin registration problems from native desktop permission/session problems before you retry an action.

opencode mcp list
npx opencode-computer-use-permissions --strict
open-computer-use call list_apps

Permission failures by operating system.

doctor says Accessibility is missing

Likely cause: Open Computer Use.app has not been approved in Privacy & Security → Accessibility, or the permission was granted to Terminal/OpenCode instead of the app-agent that performs the real automation.

Fix: run open-computer-use doctor, use the onboarding UI, or open the Accessibility pane directly. Enable Open Computer Use, then restart the Open Computer Use app-agent/OpenCode and rerun doctor.

open-computer-use doctor
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
open-computer-use doctor
Screenshots are missing or screenRecording=missing

Likely cause: Screen Recording / Screen & System Audio Recording has not been granted to Open Computer Use.app, or the already-running app-agent has not picked up the new TCC grant yet.

Fix: approve Open Computer Use in the Screen Recording pane, then quit/reopen the app-agent or restart OpenCode before checking again. Accessibility text can still work while screenshots are unavailable.

open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
open-computer-use doctor
open-computer-use call list_apps
System Settings shows permission granted, but onboarding still says Allow

Likely cause: stale TCC/app-agent state, an old installed copy, or a development build with a different signing identity/path.

Fix: first restart Open Computer Use and rerun doctor. Confirm you approved the current Open Computer Use.app, not Open Computer Use (Dev) or Terminal. As a last-resort reset, remove only this app's stored consent and let doctor request it again. These commands reset permission; they do not grant it.

tccutil reset Accessibility com.ifuryst.opencomputeruse
tccutil reset ScreenCapture com.ifuryst.opencomputeruse
open-computer-use doctor
The native binary will not start even after granting permissions

Likely cause: macOS is older than the native runtime's minimum supported version. Permission changes cannot fix binary compatibility.

Fix: check the OS version. The current macOS runtime requires macOS 14 or newer.

sw_vers -productVersion
open-computer-use --version

General failures.

cua_repl is not listed

Check that the plugin path/package is in the active OpenCode generation's configuration, restart the TUI/service, and run opencode mcp list. Do not register the same plugin and MCP server twice.

Node cannot be found

Set OPENCODE_NODE to an absolute Node executable before starting OpenCode, or make node available on the server PATH.

The app is not found

Run open-computer-use call list_apps, use the exact returned app name or ID, and confirm the app already has a visible, non-minimized window in the same graphical session.

Element index stopped working after navigation

Element indexes belong to a particular accessibility snapshot. Fetch a fresh state after navigation, dialogs, rerenders, or focus changes before acting again.

Global pointer fallback is rejected

This is the expected safety gate. Enable allowGlobalPointerFallbacks only for an explicit diagnostic session where moving the real pointer and changing focus are acceptable.

Frequently asked questions.

Is OpenCode Computer Use a plugin, an MCP server, or both?

Both layers are present. The OpenCode plugin loads the local package and registers an MCP server named cua_repl. That server exposes only js and js_reset to the model.

What do js and js_reset do?

js evaluates JavaScript in the persistent session, so variables and app bindings survive between calls. js_reset discards those bindings but does not reset native application state.

Which operating systems are supported?

Optional native runtime packages target Linux, macOS, and Windows on x64 and arm64. CI is configured to test the adapter on all three operating systems; live capabilities still depend on OS permissions, the desktop session, and the target app, and macOS/Windows native desktop behavior requires on-host validation.

Does the plugin bypass operating-system permissions?

No. It cannot control a lock screen, login screen, or UAC secure desktop. OpenCode approvals and operating-system permissions remain the enforcement layer.

Does the model get raw desktop tools or an arbitrary shell?

No. The model sees only js and js_reset, not raw desktop tools or run_shell. The restricted JavaScript session also blocks filesystem, process, fetch, dynamic import, and code-generation access.

How do I verify the installation?

Restart OpenCode or reopen the TUI, run opencode mcp list, and confirm that cua_repl is connected before starting a desktop task.