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 git@github.com:rashidtvmr/opencode-computer-use.git
cd opencode-computer-use
npm installAdd 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": [
["/absolute/path/to/opencode-computer-use", { "backend": "auto" }]
]
}Restart OpenCode or reopen the TUI, then verify the dynamically registered server:
opencode mcp listUse the V2 plugins shape when installing the checkout with OpenCode2.
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "/absolute/path/to/opencode-computer-use",
"options": { "backend": "auto" }
}
]
}Confirm that cua_repl is connected before starting a desktop task.
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 });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.
- OpenCode plugin
Loads the local package and registers the MCP server without exposing raw desktop tools. - cua_repl MCP server
Provides a persistent JavaScript evaluation surface, output bounds, request validation, and worker isolation. - Native runtime
Runsopen-computer-useas a separate child process. OS accessibility, screen capture, and input permissions remain there.
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.
Make risky capabilities explicit.
{
"backend": "auto",
"serverName": "cua_repl",
"timeoutMs": 30000,
"maxTextChars": 200000,
"maxImageBytes": 2097152,
"allowGlobalPointerFallbacks": false
}- backend
auto,native, or a fixedcustomcommand. - serverNameRename the generated MCP server when a project needs isolation.
- 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.
- Read
getAXState()before selecting an element. - Prefer a fresh element index over a remembered coordinate.
- Batch deterministic actions and the next state read in one
jscall. - After navigation, dialogs, resizing, or rerendering, read state again.
- Use
js_resetwhen 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.
| Target | Packaged runtime | Live status here |
|---|---|---|
| Linux x64 | Included | Read-only smoke passed |
| Linux arm64 | Included | Needs arm64 host |
| macOS arm64 and x64 | Included | Needs live permissions |
| Windows arm64 and x64 | Included | Needs 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.
Small surface. Strong boundaries.
- No arbitrary shellThe model-facing API does not expose
run_shellor 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.
- Bounded transportInput frames, output text, images, content items, and native responses are bounded.
- 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.
cua_repl is not listed
Check that the plugin path is in the active OpenCode generation's configuration, restart the TUI, 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 list is empty
Run open-computer-use list-apps in the same graphical session. Confirm the app is already running and that the desktop accessibility session is available.
Screenshots are unavailable
Screen capture depends on the OS compositor and permissions. The plugin can still provide bounded accessibility text when capture is unavailable.
Linux input returns success but nothing changes
Check the desktop session type and native runtime prerequisites. A pseudo-window or Wayland compositor can expose state without accepting synthetic input. Observe again before retrying a mutating action.
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.