@frontendxlab/opencode-computer-use 0.1.0
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/LICENSE +25 -0
- package/README.md +285 -0
- package/SECURITY.md +36 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/opencode-computer-use-repl.js +12 -0
- package/docs/_headers +12 -0
- package/docs/favicon.svg +4 -0
- package/docs/index.html +733 -0
- package/docs/llms-full.txt +146 -0
- package/docs/llms.txt +30 -0
- package/docs/og-card.png +0 -0
- package/docs/robots.txt +7 -0
- package/docs/site.webmanifest +10 -0
- package/docs/sitemap.xml +9 -0
- package/docs/webmcp.js +135 -0
- package/index.js +78 -0
- package/lib/backend.js +190 -0
- package/lib/json-line-peer.mjs +182 -0
- package/lib/options.js +111 -0
- package/lib/output.mjs +212 -0
- package/lib/repl-kernel.mjs +41 -0
- package/lib/repl.mjs +615 -0
- package/lib/sandbox.mjs +291 -0
- package/package.json +57 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
OpenCode Computer Use - full documentation summary
|
|
2
|
+
Canonical site: https://opencode-computer-use.pages.dev/
|
|
3
|
+
Repository: https://github.com/rashidtvmr/opencode-computer-use
|
|
4
|
+
License: MIT
|
|
5
|
+
|
|
6
|
+
1. What it is
|
|
7
|
+
OpenCode Computer Use is a dual-generation OpenCode plugin that provides a
|
|
8
|
+
Codex-style Computer Use session. Instead of exposing many low-level desktop
|
|
9
|
+
MCP tools, it exposes one persistent JavaScript surface through a local MCP
|
|
10
|
+
server named cua_repl. The model receives two tools only: js (evaluate
|
|
11
|
+
JavaScript in the persistent session) and js_reset (discard bindings and
|
|
12
|
+
start a clean session). App bindings and variables survive across model
|
|
13
|
+
turns. Native desktop calls are made by the underlying open-source
|
|
14
|
+
open-computer-use runtime (optional dependency open-computer-use@0.3.5),
|
|
15
|
+
kept separate from the Codex-style session adapter. The adapter is adapted
|
|
16
|
+
from the MIT-licensed iFurySt/open-codex-computer-use project; see
|
|
17
|
+
THIRD_PARTY_NOTICES.md in the repository.
|
|
18
|
+
|
|
19
|
+
2. Architecture (three layers)
|
|
20
|
+
- OpenCode plugin: loads the local package and registers cua_repl without
|
|
21
|
+
exposing raw desktop tools.
|
|
22
|
+
- cua_repl MCP server: persistent JavaScript evaluation, output bounds,
|
|
23
|
+
request validation, and worker isolation so a timed-out loop cannot wedge
|
|
24
|
+
the server.
|
|
25
|
+
- Native runtime: open-computer-use runs as a separate child process. OS
|
|
26
|
+
accessibility, screen capture, and input permissions are enforced there.
|
|
27
|
+
The JavaScript VM is a capability guard, not a complete security boundary.
|
|
28
|
+
|
|
29
|
+
3. Model workflow
|
|
30
|
+
The js tool accepts code (required JavaScript source), timeout_ms (optional,
|
|
31
|
+
capped at 300 seconds), and title (optional short action title).
|
|
32
|
+
Typical first call:
|
|
33
|
+
var apps = await cua.listApps({ emit: false });
|
|
34
|
+
var app = await cua.getApp(apps[0].id);
|
|
35
|
+
await app.getAXState();
|
|
36
|
+
Typical action and verification:
|
|
37
|
+
var save = await app.click(12);
|
|
38
|
+
await app.getAXState();
|
|
39
|
+
State and action rules: read getAXState() or getAXStateAndScreenshot()
|
|
40
|
+
before acting on a new layout; element indexes belong to the most recent
|
|
41
|
+
state and must be re-derived after navigation, dialogs, scroll, resize, or
|
|
42
|
+
rerender; prefer element indexes and semantic actions over coordinates; use
|
|
43
|
+
coordinates only when no useful accessibility data exists; batch
|
|
44
|
+
deterministic actions with the next state read in one js call; never replay
|
|
45
|
+
a mutating action after a timeout or transport failure, observe again and
|
|
46
|
+
decide anew; js_reset discards JavaScript bindings only, not native app
|
|
47
|
+
state.
|
|
48
|
+
|
|
49
|
+
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.
|
|
54
|
+
- app.getAXState(options?): fresh semantic accessibility tree.
|
|
55
|
+
- app.getScreenshot(options?): local image bytes, optionally emitted.
|
|
56
|
+
- app.getAXStateAndScreenshot(options?): combined state and image read.
|
|
57
|
+
- app.click(target, options?): element index first, bounded coordinate when
|
|
58
|
+
necessary.
|
|
59
|
+
- app.scroll(elementIndex, direction, pages?)
|
|
60
|
+
- app.drag([fromX, fromY], [toX, toY])
|
|
61
|
+
- app.typeText(text)
|
|
62
|
+
- app.pressKey(key)
|
|
63
|
+
- app.setValue(elementIndex, value)
|
|
64
|
+
- app.performSecondaryAction(elementIndex, action)
|
|
65
|
+
- nodeRepl.write(value): extra bounded text output.
|
|
66
|
+
- nodeRepl.emitImage(bytesOrDataUrl): bounded local image output.
|
|
67
|
+
|
|
68
|
+
5. Options
|
|
69
|
+
backend (default auto: auto, native, or custom); command (required for
|
|
70
|
+
custom: fixed native MCP executable plus argv, no shell syntax); serverName
|
|
71
|
+
(default cua_repl); override (default false); timeoutMs (default 30000);
|
|
72
|
+
disabled (default false); maxTextChars (default 200000); maxImageBytes
|
|
73
|
+
(default 2097152); allowGlobalPointerFallbacks (default false, off because
|
|
74
|
+
it moves the real pointer and changes focus; enable only for an explicit
|
|
75
|
+
desktop automation test where that input is intended). The proxy also reads
|
|
76
|
+
matching OPENCODE_COMPUTER_USE_* environment variables. Custom commands
|
|
77
|
+
must implement the native MCP operations used by cua: initialize,
|
|
78
|
+
tools/list, tools/call, and notifications.
|
|
79
|
+
|
|
80
|
+
6. Install
|
|
81
|
+
Published package:
|
|
82
|
+
npm install @frontendxlab/opencode-computer-use
|
|
83
|
+
OpenCode 1 (opencode.json or opencode.jsonc, plugin list):
|
|
84
|
+
{ "$schema": "https://opencode.ai/config.json",
|
|
85
|
+
"plugin": [["@frontendxlab/opencode-computer-use", { "backend": "auto" }]] }
|
|
86
|
+
OpenCode2 V2 (plugins shape):
|
|
87
|
+
{ "$schema": "https://opencode.ai/config.json",
|
|
88
|
+
"plugins": [{ "package": "@frontendxlab/opencode-computer-use",
|
|
89
|
+
"options": { "backend": "auto" } }] }
|
|
90
|
+
A local checkout can be used by replacing the package name with its absolute
|
|
91
|
+
path. Do not configure the same package both as an explicit plugin and through
|
|
92
|
+
automatic plugin discovery. If Node is not on PATH, set OPENCODE_NODE to an
|
|
93
|
+
absolute Node executable. Verify with: opencode mcp list.
|
|
94
|
+
|
|
95
|
+
7. Requirements and OS support
|
|
96
|
+
Requirements: Node.js 18 or newer; OpenCode 1.18.29 or newer (V1) or
|
|
97
|
+
OpenCode2 V2; a signed-in graphical desktop session; macOS Accessibility
|
|
98
|
+
and Screen Recording permissions; Linux AT-SPI plus required input and
|
|
99
|
+
capture services; Windows UI Automation in the interactive desktop session.
|
|
100
|
+
The plugin cannot control lock screens, login screens, UAC secure desktops,
|
|
101
|
+
or other OS-owned consent surfaces, and does not bypass permissions.
|
|
102
|
+
Packaged native targets: darwin-arm64, darwin-x64, linux-arm64, linux-x64,
|
|
103
|
+
win32-arm64, win32-x64. Validation status: Linux x64 read-only discovery,
|
|
104
|
+
app listing, MCP registration, and persistent session passed local smoke
|
|
105
|
+
tests; one Ubuntu/GNOME Wayland session exposed a Chrome pseudo-window with
|
|
106
|
+
no actionable AT-SPI children and synthetic input did not change it (the
|
|
107
|
+
adapter reports the native result honestly); macOS and Windows have not
|
|
108
|
+
been live-tested in this environment and need on-host permission and
|
|
109
|
+
desktop validation.
|
|
110
|
+
|
|
111
|
+
8. Safety
|
|
112
|
+
High-privilege desktop-control surface. The js tool is marked conservatively
|
|
113
|
+
as mutating. Keep OpenCode approval prompts enabled for consequential
|
|
114
|
+
actions (sending, deleting, purchasing, uploading, changing permissions).
|
|
115
|
+
Do not expose the session to an untrusted model or run it unattended
|
|
116
|
+
against password managers, banking, administrator, or other high-value
|
|
117
|
+
applications. Screenshots, accessibility trees, and typed values can contain
|
|
118
|
+
sensitive data and should stay local. Read SECURITY.md before high-risk
|
|
119
|
+
use. Manual read-only checks before acting:
|
|
120
|
+
open-computer-use doctor
|
|
121
|
+
open-computer-use list-apps
|
|
122
|
+
|
|
123
|
+
9. Troubleshooting (from the docs page)
|
|
124
|
+
- cua_repl not listed: check the active generation config, restart the TUI,
|
|
125
|
+
run opencode mcp list, avoid double registration.
|
|
126
|
+
- Node not found: set OPENCODE_NODE or fix server PATH.
|
|
127
|
+
- Empty app list: run open-computer-use list-apps in the same graphical
|
|
128
|
+
session; confirm the app runs and desktop accessibility is available.
|
|
129
|
+
- Screenshots unavailable: OS compositor or permission gap; bounded
|
|
130
|
+
accessibility text may still work.
|
|
131
|
+
- Linux input returns success but nothing changes: session type or missing
|
|
132
|
+
runtime prerequisite; observe again before retrying mutating actions.
|
|
133
|
+
- Global pointer fallback rejected: expected safety gate.
|
|
134
|
+
|
|
135
|
+
10. WebMCP preview
|
|
136
|
+
The documentation page includes a progressive, read-only WebMCP enhancement
|
|
137
|
+
based on the current document.modelContext.registerTool draft. It registers
|
|
138
|
+
search_docs, get_install_config, and get_api_reference only when the browser
|
|
139
|
+
supports the API and the tools Permissions Policy. It does not expose desktop
|
|
140
|
+
control, shell, network mutation, or credentials. WebMCP is experimental and
|
|
141
|
+
should not be treated as stable browser support.
|
|
142
|
+
|
|
143
|
+
11. Development
|
|
144
|
+
npm install; npm test; npm run lint. Adapter tests use a fake native MCP
|
|
145
|
+
server and need no real desktop. Live desktop validation must run
|
|
146
|
+
separately per OS.
|
package/docs/llms.txt
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# OpenCode Computer Use
|
|
2
|
+
|
|
3
|
+
> A dual-generation OpenCode plugin that exposes a Codex-style persistent JavaScript session (cua_repl) for local desktop control. The model sees two tools, js and js_reset, backed by the open-source open-computer-use native runtime. OS permissions remain enforced.
|
|
4
|
+
|
|
5
|
+
## Docs
|
|
6
|
+
|
|
7
|
+
- [Documentation home](https://opencode-computer-use.pages.dev/)
|
|
8
|
+
- [Install](https://opencode-computer-use.pages.dev/#install)
|
|
9
|
+
- [First run](https://opencode-computer-use.pages.dev/#first-run)
|
|
10
|
+
- [API surface](https://opencode-computer-use.pages.dev/#api)
|
|
11
|
+
- [OS support](https://opencode-computer-use.pages.dev/#os)
|
|
12
|
+
- [WebMCP preview](https://opencode-computer-use.pages.dev/#webmcp)
|
|
13
|
+
- [Security](https://opencode-computer-use.pages.dev/#security)
|
|
14
|
+
- [FAQ](https://opencode-computer-use.pages.dev/#faq)
|
|
15
|
+
|
|
16
|
+
## Repo
|
|
17
|
+
|
|
18
|
+
- [Source](https://github.com/rashidtvmr/opencode-computer-use)
|
|
19
|
+
- [README](https://github.com/rashidtvmr/opencode-computer-use/blob/main/README.md)
|
|
20
|
+
- [Issues](https://github.com/rashidtvmr/opencode-computer-use/issues)
|
|
21
|
+
|
|
22
|
+
## Key facts
|
|
23
|
+
|
|
24
|
+
- MCP server name: cua_repl. Model-facing tools: js (persistent evaluation) and js_reset (clean session).
|
|
25
|
+
- Async cua API: listApps, getApp, getState, getAXState, getScreenshot, getAXStateAndScreenshot, click, scroll, drag, typeText, pressKey, setValue, performSecondaryAction. Output helpers: nodeRepl.write, nodeRepl.emitImage.
|
|
26
|
+
- 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
|
+
- No arbitrary shell escape. No run_shell in the model-facing API. Restricted VM blocks require, process, fetch, filesystem, dynamic import, and code generation.
|
|
28
|
+
- Published package: @frontendxlab/opencode-computer-use.
|
|
29
|
+
- 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.
|
|
30
|
+
- 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.
|
package/docs/og-card.png
ADDED
|
Binary file
|
package/docs/robots.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "OpenCode Computer Use",
|
|
3
|
+
"short_name": "Computer Use",
|
|
4
|
+
"description": "Install and use OpenCode Computer Use, a Codex-style cua_repl plugin for OpenCode and OpenCode2.",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"scope": "/",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"background_color": "#0a0a0a",
|
|
9
|
+
"theme_color": "#0a0a0a"
|
|
10
|
+
}
|
package/docs/sitemap.xml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
|
+
<url>
|
|
4
|
+
<loc>https://opencode-computer-use.pages.dev/</loc>
|
|
5
|
+
<lastmod>2026-09-25</lastmod>
|
|
6
|
+
<changefreq>weekly</changefreq>
|
|
7
|
+
<priority>1.0</priority>
|
|
8
|
+
</url>
|
|
9
|
+
</urlset>
|
package/docs/webmcp.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var state = { enabled: false, tools: [], preview: true };
|
|
5
|
+
if (typeof window !== "undefined") window.__opencodeComputerUseWebMCP = state;
|
|
6
|
+
if (typeof document === "undefined" || typeof document.querySelectorAll !== "function") return;
|
|
7
|
+
|
|
8
|
+
var modelContext = null;
|
|
9
|
+
try {
|
|
10
|
+
modelContext = document.modelContext;
|
|
11
|
+
} catch (_) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!modelContext || typeof modelContext.registerTool !== "function") return;
|
|
15
|
+
|
|
16
|
+
function plainText(element, limit) {
|
|
17
|
+
var text = String(element && element.textContent || "")
|
|
18
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "")
|
|
19
|
+
.replace(/[ \t]+/g, " ")
|
|
20
|
+
.replace(/\n\s*\n+/g, "\n\n")
|
|
21
|
+
.trim();
|
|
22
|
+
return text.slice(0, limit);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function result(value) {
|
|
26
|
+
return String(value).slice(0, 8000);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function queryValue(value, fallback) {
|
|
30
|
+
return typeof value === "string" ? value.trim() : fallback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function searchDocs(args) {
|
|
34
|
+
var query = queryValue(args && args.query, "");
|
|
35
|
+
if (!query || query.length > 200) return "Invalid query. Provide 1 to 200 characters.";
|
|
36
|
+
var needle = query.toLowerCase();
|
|
37
|
+
var sections = document.querySelectorAll("#content [data-section]");
|
|
38
|
+
var matches = [];
|
|
39
|
+
for (var index = 0; index < sections.length; index += 1) {
|
|
40
|
+
var section = sections[index];
|
|
41
|
+
var heading = section.querySelector("h2, h3");
|
|
42
|
+
var text = plainText(section, 12000);
|
|
43
|
+
var position = text.toLowerCase().indexOf(needle);
|
|
44
|
+
if (position === -1) continue;
|
|
45
|
+
var start = Math.max(0, position - 200);
|
|
46
|
+
var end = Math.min(text.length, position + query.length + 600);
|
|
47
|
+
matches.push((heading ? heading.textContent.trim() + "\n" : "") + text.slice(start, end));
|
|
48
|
+
if (matches.join("\n\n").length >= 8000) break;
|
|
49
|
+
}
|
|
50
|
+
return result(matches.length ? matches.join("\n\n") : "No documentation matches found for: " + query);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getInstallConfig(args) {
|
|
54
|
+
var generation = args && args.generation;
|
|
55
|
+
if (generation !== "v1" && generation !== "v2") return "Invalid generation. Use v1 or v2.";
|
|
56
|
+
var block = document.getElementById(generation === "v1" ? "config-v1" : "config-v2");
|
|
57
|
+
return block ? result(plainText(block, 4000)) : "Install configuration is unavailable on this page.";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getApiReference(args) {
|
|
61
|
+
var requested = queryValue(args && args.name, "").toLowerCase();
|
|
62
|
+
if (!requested || requested.length > 100) return "Invalid name. Provide 1 to 100 characters.";
|
|
63
|
+
var entries = document.querySelectorAll("#api .cards li");
|
|
64
|
+
for (var index = 0; index < entries.length; index += 1) {
|
|
65
|
+
var label = entries[index].querySelector("strong");
|
|
66
|
+
if (!label) continue;
|
|
67
|
+
var documented = String(label.textContent || "").trim().toLowerCase();
|
|
68
|
+
var method = documented.split(".").pop();
|
|
69
|
+
if (documented === requested || documented.endsWith("." + requested) || (requested.indexOf(".") === -1 && method === requested)) {
|
|
70
|
+
return result(plainText(entries[index], 2000));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return "No API entry found for: " + requested;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var tools = [
|
|
77
|
+
{
|
|
78
|
+
name: "search_docs",
|
|
79
|
+
title: "Search documentation",
|
|
80
|
+
description: "Search this documentation page and return bounded matching excerpts.",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
query: { type: "string", minLength: 1, maxLength: 200, description: "Text to find in the documentation." }
|
|
85
|
+
},
|
|
86
|
+
required: ["query"],
|
|
87
|
+
additionalProperties: false
|
|
88
|
+
},
|
|
89
|
+
annotations: { readOnlyHint: true, untrustedContentHint: true, consequentialHint: false, debugging: false },
|
|
90
|
+
execute: searchDocs
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "get_install_config",
|
|
94
|
+
title: "Get installation configuration",
|
|
95
|
+
description: "Return the documented OpenCode installation configuration for one generation.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
generation: { type: "string", enum: ["v1", "v2"], description: "OpenCode configuration generation." }
|
|
100
|
+
},
|
|
101
|
+
required: ["generation"],
|
|
102
|
+
additionalProperties: false
|
|
103
|
+
},
|
|
104
|
+
annotations: { readOnlyHint: true, untrustedContentHint: true, consequentialHint: false, debugging: false },
|
|
105
|
+
execute: getInstallConfig
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "get_api_reference",
|
|
109
|
+
title: "Get API reference",
|
|
110
|
+
description: "Return one documented CUA API entry by method name.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
name: { type: "string", minLength: 1, maxLength: 100, description: "Method name, with or without its cua or app prefix." }
|
|
115
|
+
},
|
|
116
|
+
required: ["name"],
|
|
117
|
+
additionalProperties: false
|
|
118
|
+
},
|
|
119
|
+
annotations: { readOnlyHint: true, untrustedContentHint: true, consequentialHint: false, debugging: false },
|
|
120
|
+
execute: getApiReference
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
Promise.all(tools.map(function (tool) {
|
|
125
|
+
return Promise.resolve().then(function () {
|
|
126
|
+
return modelContext.registerTool(tool);
|
|
127
|
+
}).then(function () {
|
|
128
|
+
state.tools.push(tool.name);
|
|
129
|
+
}).catch(function () {
|
|
130
|
+
// Experimental registration failures must not affect normal documentation.
|
|
131
|
+
});
|
|
132
|
+
})).then(function () {
|
|
133
|
+
state.enabled = state.tools.length > 0;
|
|
134
|
+
});
|
|
135
|
+
}());
|
package/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { normalizeOptions, optionsToEnvironment } from "./lib/options.js";
|
|
5
|
+
|
|
6
|
+
const packageRoot = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const proxyPath = join(packageRoot, "bin", "opencode-computer-use-repl.js");
|
|
8
|
+
|
|
9
|
+
function proxyCommand() {
|
|
10
|
+
return [process.env.OPENCODE_NODE || "node", proxyPath];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function baseServerConfig(options) {
|
|
14
|
+
return {
|
|
15
|
+
type: "local",
|
|
16
|
+
command: proxyCommand(),
|
|
17
|
+
cwd: packageRoot,
|
|
18
|
+
environment: optionsToEnvironment(options),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function v1ServerConfig(options) {
|
|
23
|
+
return {
|
|
24
|
+
...baseServerConfig(options),
|
|
25
|
+
enabled: !options.disabled,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function v2ServerConfig(options) {
|
|
30
|
+
return {
|
|
31
|
+
...baseServerConfig(options),
|
|
32
|
+
disabled: options.disabled,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function shouldReplace(existing, options) {
|
|
37
|
+
return options.override || !existing;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function setup(ctx) {
|
|
41
|
+
const options = normalizeOptions(ctx.options);
|
|
42
|
+
if (!ctx.mcp?.transform) {
|
|
43
|
+
throw new Error("This OpenCode2 runtime does not expose ctx.mcp.transform");
|
|
44
|
+
}
|
|
45
|
+
const registration = await ctx.mcp.transform((editor) => {
|
|
46
|
+
const existing = editor.get(options.serverName);
|
|
47
|
+
if (shouldReplace(existing, options)) {
|
|
48
|
+
editor.set(options.serverName, v2ServerConfig(options));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return async () => {
|
|
52
|
+
await registration?.dispose?.();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function server(_input, inputOptions) {
|
|
57
|
+
const options = normalizeOptions(inputOptions);
|
|
58
|
+
return {
|
|
59
|
+
config: async (config) => {
|
|
60
|
+
if (!config.mcp || typeof config.mcp !== "object" || Array.isArray(config.mcp)) {
|
|
61
|
+
config.mcp = {};
|
|
62
|
+
}
|
|
63
|
+
const existing = config.mcp[options.serverName];
|
|
64
|
+
if (shouldReplace(existing, options)) {
|
|
65
|
+
config.mcp[options.serverName] = v1ServerConfig(options);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const plugin = {
|
|
72
|
+
id: "opencode-computer-use",
|
|
73
|
+
setup,
|
|
74
|
+
server,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const ComputerUsePlugin = server;
|
|
78
|
+
export default plugin;
|
package/lib/backend.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { constants, accessSync, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { delimiter, dirname, join } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { normalizeOptions, optionsFromEnvironment } from "./options.js";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
const PLATFORM_PATHS = {
|
|
10
|
+
"darwin-arm64": ["dist", "Open Computer Use.app", "Contents", "MacOS", "OpenComputerUse"],
|
|
11
|
+
"darwin-x64": ["dist", "Open Computer Use.app", "Contents", "MacOS", "OpenComputerUse"],
|
|
12
|
+
"linux-arm64": ["dist", "linux", "arm64", "open-computer-use"],
|
|
13
|
+
"linux-x64": ["dist", "linux", "amd64", "open-computer-use"],
|
|
14
|
+
"win32-arm64": ["dist", "windows", "arm64", "open-computer-use.exe"],
|
|
15
|
+
"win32-x64": ["dist", "windows", "amd64", "open-computer-use.exe"],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function isExecutable(path) {
|
|
19
|
+
try {
|
|
20
|
+
accessSync(path, constants.X_OK);
|
|
21
|
+
return statSync(path).isFile();
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function commandOnPath(name, environment = process.env) {
|
|
28
|
+
const pathValue = environment.PATH;
|
|
29
|
+
if (!pathValue) return undefined;
|
|
30
|
+
const extensions =
|
|
31
|
+
process.platform === "win32"
|
|
32
|
+
? (environment.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";")
|
|
33
|
+
: [""];
|
|
34
|
+
for (const directory of pathValue.split(delimiter)) {
|
|
35
|
+
if (!directory) continue;
|
|
36
|
+
for (const extension of extensions) {
|
|
37
|
+
const candidate = join(directory, `${name}${extension}`);
|
|
38
|
+
if (isExecutable(candidate)) return candidate;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function packageRoot() {
|
|
45
|
+
try {
|
|
46
|
+
return dirname(require.resolve("open-computer-use/package.json"));
|
|
47
|
+
} catch {
|
|
48
|
+
try {
|
|
49
|
+
let current = dirname(require.resolve("open-computer-use"));
|
|
50
|
+
while (current !== dirname(current)) {
|
|
51
|
+
if (existsSync(join(current, "package.json"))) return current;
|
|
52
|
+
current = dirname(current);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function nativeCommand(environment) {
|
|
62
|
+
const override = environment.OPEN_COMPUTER_USE_NATIVE_COMMAND;
|
|
63
|
+
if (override) {
|
|
64
|
+
const args = environment.OPEN_COMPUTER_USE_NATIVE_ARGS
|
|
65
|
+
? JSON.parse(environment.OPEN_COMPUTER_USE_NATIVE_ARGS)
|
|
66
|
+
: ["mcp"];
|
|
67
|
+
if (!Array.isArray(args) || args.some((value) => typeof value !== "string")) {
|
|
68
|
+
throw new Error("OPEN_COMPUTER_USE_NATIVE_ARGS must be a JSON string array");
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
name: "open-computer-use",
|
|
72
|
+
source: "OPEN_COMPUTER_USE_NATIVE_COMMAND",
|
|
73
|
+
command: override,
|
|
74
|
+
args,
|
|
75
|
+
platform: process.platform,
|
|
76
|
+
arch: process.arch,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const root = packageRoot();
|
|
81
|
+
const relative = PLATFORM_PATHS[`${process.platform}-${process.arch}`];
|
|
82
|
+
if (root && relative) {
|
|
83
|
+
const executable = join(root, ...relative);
|
|
84
|
+
if (isExecutable(executable)) {
|
|
85
|
+
return {
|
|
86
|
+
name: "open-computer-use",
|
|
87
|
+
source: "open-computer-use",
|
|
88
|
+
command: executable,
|
|
89
|
+
args: ["mcp"],
|
|
90
|
+
platform: process.platform,
|
|
91
|
+
arch: process.arch,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const fromPath = commandOnPath("open-computer-use", environment);
|
|
97
|
+
if (fromPath) {
|
|
98
|
+
return {
|
|
99
|
+
name: "open-computer-use",
|
|
100
|
+
source: "PATH:open-computer-use",
|
|
101
|
+
command: fromPath,
|
|
102
|
+
args: ["mcp"],
|
|
103
|
+
platform: process.platform,
|
|
104
|
+
arch: process.arch,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Open Computer Use native runtime is not installed. Install open-computer-use or set OPEN_COMPUTER_USE_NATIVE_COMMAND.",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function resolveBackend(input = {}, environment = process.env) {
|
|
114
|
+
const fromEnvironment = optionsFromEnvironment(environment);
|
|
115
|
+
const options = normalizeOptions({
|
|
116
|
+
...fromEnvironment,
|
|
117
|
+
...input,
|
|
118
|
+
command: input.command ?? fromEnvironment.command,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (options.backend === "custom") {
|
|
122
|
+
const [command, ...args] = options.command;
|
|
123
|
+
return {
|
|
124
|
+
name: "custom",
|
|
125
|
+
source: "options.command",
|
|
126
|
+
command,
|
|
127
|
+
args,
|
|
128
|
+
platform: process.platform,
|
|
129
|
+
arch: process.arch,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return nativeCommand(environment);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function backendEnvironment(environment = process.env) {
|
|
136
|
+
const allowed = new Set([
|
|
137
|
+
"PATH",
|
|
138
|
+
"HOME",
|
|
139
|
+
"USER",
|
|
140
|
+
"LOGNAME",
|
|
141
|
+
"SHELL",
|
|
142
|
+
"LANG",
|
|
143
|
+
"LANGUAGE",
|
|
144
|
+
"LC_ALL",
|
|
145
|
+
"LC_CTYPE",
|
|
146
|
+
"TERM",
|
|
147
|
+
"TMPDIR",
|
|
148
|
+
"TEMP",
|
|
149
|
+
"TMP",
|
|
150
|
+
"DISPLAY",
|
|
151
|
+
"WAYLAND_DISPLAY",
|
|
152
|
+
"XAUTHORITY",
|
|
153
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
154
|
+
"XDG_RUNTIME_DIR",
|
|
155
|
+
"XDG_CURRENT_DESKTOP",
|
|
156
|
+
"XDG_SESSION_TYPE",
|
|
157
|
+
"XDG_SESSION_DESKTOP",
|
|
158
|
+
"XDG_DATA_DIRS",
|
|
159
|
+
"XDG_CONFIG_HOME",
|
|
160
|
+
"XDG_CACHE_HOME",
|
|
161
|
+
"XDG_STATE_HOME",
|
|
162
|
+
"APPDATA",
|
|
163
|
+
"LOCALAPPDATA",
|
|
164
|
+
"USERPROFILE",
|
|
165
|
+
"ProgramData",
|
|
166
|
+
"SystemRoot",
|
|
167
|
+
"SYSTEMROOT",
|
|
168
|
+
"windir",
|
|
169
|
+
"ComSpec",
|
|
170
|
+
"PATHEXT",
|
|
171
|
+
"RUST_LOG",
|
|
172
|
+
"OPEN_COMPUTER_USE_ACTION_READ_BACK",
|
|
173
|
+
"OPEN_COMPUTER_USE_ALLOW_GLOBAL_POINTER_FALLBACKS",
|
|
174
|
+
"OPEN_COMPUTER_USE_WINDOWS_ALLOW_APP_LAUNCH",
|
|
175
|
+
"OPEN_COMPUTER_USE_WINDOWS_ALLOW_FOCUS_ACTIONS",
|
|
176
|
+
"OPEN_COMPUTER_USE_WINDOWS_ALLOW_UIA_TEXT_FALLBACK",
|
|
177
|
+
"OPEN_COMPUTER_USE_IMAGE_CAPTURE_TIMEOUT",
|
|
178
|
+
"OPEN_COMPUTER_USE_IMAGE_MAX_BYTES",
|
|
179
|
+
"OPEN_COMPUTER_USE_IMAGE_MAX_DIMENSION",
|
|
180
|
+
"OPEN_COMPUTER_USE_IMAGE_MIN_SCALE",
|
|
181
|
+
"COMPUTER_USE_LINUX_COSMIC_HELPER",
|
|
182
|
+
"COMPUTER_USE_LINUX_SCREENSHOT_BACKEND",
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
return Object.fromEntries(
|
|
186
|
+
Object.entries(environment).filter(
|
|
187
|
+
([key, value]) => allowed.has(key) && typeof value === "string",
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
}
|