@agent-sh/computer-use-linux 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,11 +36,13 @@ The crate was extracted from [`codex-desktop-linux`](https://github.com/avifenes
36
36
  MCP tools exposed by the server:
37
37
 
38
38
  **Diagnostics**
39
+
39
40
  - `doctor` — single-shot JSON readiness report (platform, portals, accessibility, windowing, input, readiness summary, and a capability map of available backends)
40
41
  - `setup_accessibility` — enables GNOME's `org.gnome.desktop.interface toolkit-accessibility` setting so toolkit apps expose AT-SPI trees
41
42
  - `setup_window_targeting` — installs and enables the bundled GNOME Shell extension when `org.gnome.Shell.Introspect` is locked down
42
43
 
43
44
  **Discovery**
45
+
44
46
  - `list_apps` — running desktop apps visible to the AT-SPI registry
45
47
  - `list_windows` — compositor windows with title, app id, wm_class, focus state, client type (Wayland/X11), and bounds
46
48
  - `focused_window` — the window currently holding keyboard focus
@@ -50,6 +52,7 @@ MCP tools exposed by the server:
50
52
  Screenshot payloads are size-bounded by default before they are returned to the MCP host: max 1920 px width/height and 2 MiB image bytes, with hard caps even when callers request more. Agents that need more detail can pass `max_width`, `max_height`, `max_bytes`, `scale`, `format: "jpeg"`, or `quality`, preferably with a window target or crop. PNG remains the default; JPEG lets callers trade lossless pixels for a smaller payload before the byte cap forces further resizing. Returned screenshot metadata includes `coordinate_width`, `coordinate_height`, `scale`, `format`, and `quality` so callers can convert from a downscaled preview to desktop coordinate pixels.
51
53
 
52
54
  **Input**
55
+
53
56
  - `click` — by element index, semantic selector, or desktop coordinate pixels
54
57
  - `drag` — desktop coordinate drag (start / end)
55
58
  - `scroll` — page-based scroll on an element or at a pixel location
@@ -59,10 +62,12 @@ Screenshot payloads are size-bounded by default before they are returned to the
59
62
  Targeted `press_key`/`type_text` results append focused-element feedback from AT-SPI (role, name, editable) and warn when no editable element holds focus. Click/screenshot/input results warn when the target window or coordinate is partially or fully off-screen. `get_app_state` returns a compact readiness block by default; pass `verbose: true` for the full diagnostics report.
60
63
 
61
64
  **Semantic actions**
65
+
62
66
  - `perform_action` — invoke any AT-SPI action exposed by an element (`Press`, `Activate`, `Toggle`, …); defaults to the primary action
63
67
  - `set_value` — write to a settable accessibility element (text fields, sliders, spinners)
64
68
 
65
69
  **Navigation**
70
+
66
71
  - `activate_window` — focus a window by `window_id`, `pid`, `app_id`, `wm_class`, `title`, or terminal selectors
67
72
  - `move_window` / `resize_window` — reposition or resize a window in desktop coordinates (GNOME Shell extension backend); useful to recover windows that are partially off-screen
68
73
 
@@ -223,6 +228,24 @@ Edit `~/.config/Claude/claude_desktop_config.json`:
223
228
 
224
229
  Restart Claude Desktop. The tools should appear in the tools list.
225
230
 
231
+ ### Pi Coding Agent
232
+
233
+ ```bash
234
+ pi install npm:pi-mcp-adapter
235
+ pi install npm:@agent-sh/computer-use-linux
236
+ ```
237
+
238
+ Restart pi or run `/reload`. The MCP proxy tool `mcp()` will have the desktop tools available:
239
+
240
+ ```
241
+ mcp({ server: "computer-use-linux" }) # list all tools
242
+ mcp({ search: "windows" }) # search for window tools
243
+ mcp({ tool: "computer_use_linux_doctor" }) # run readiness check
244
+ mcp({ tool: "computer_use_linux_list_windows" }) # list desktop windows
245
+ ```
246
+
247
+ The extension auto-registers the computer-use-linux MCP server into pi-mcp-adapter's config. If the binary is not found, check the [Pi setup guide](skills/computer-use-linux/references/pi-setup.md).
248
+
226
249
  ### Hermes Agent
227
250
 
228
251
  Install the companion Hermes skill so Hermes has the desktop-specific runbook:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-sh/computer-use-linux",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Linux desktop-control MCP server: AT-SPI accessibility trees, Wayland/X11 input, screenshots, and compositor window targeting.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -19,7 +19,8 @@
19
19
  "wayland",
20
20
  "accessibility",
21
21
  "hermes-agent",
22
- "codex"
22
+ "codex",
23
+ "pi-package"
23
24
  ],
24
25
  "os": [
25
26
  "linux"
@@ -35,10 +36,16 @@
35
36
  "LICENSE",
36
37
  "README.md",
37
38
  "skills/computer-use-linux/SKILL.md",
39
+ "skills/computer-use-linux/references/",
38
40
  "npm/README.md",
39
41
  "npm/bin/computer-use-linux.js",
40
- "npm/install.js"
42
+ "npm/install.js",
43
+ "pi/"
41
44
  ],
45
+ "pi": {
46
+ "extensions": ["./pi/extension/index.ts"],
47
+ "skills": ["./skills/computer-use-linux/SKILL.md"]
48
+ },
42
49
  "scripts": {
43
50
  "postinstall": "node npm/install.js",
44
51
  "pack:check": "npm pack --dry-run",
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Pi coding agent extension for computer-use-linux.
3
+ *
4
+ * Discovers the computer-use-linux binary and registers it as an MCP server
5
+ * for pi-mcp-adapter by writing to ~/.pi/agent/mcp.json.
6
+ *
7
+ * Prerequisite: pi-mcp-adapter (npm:pi-mcp-adapter) must be installed.
8
+ */
9
+
10
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
+ import {
12
+ accessSync,
13
+ constants,
14
+ existsSync,
15
+ mkdirSync,
16
+ readFileSync,
17
+ writeFileSync,
18
+ } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { dirname, join } from "node:path";
21
+ import { env } from "node:process";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Constants
25
+ // ---------------------------------------------------------------------------
26
+
27
+ const PACKAGE_NAME = "@agent-sh/computer-use-linux";
28
+ const MCP_SERVER_NAME = "computer-use-linux";
29
+
30
+ /**
31
+ * Path to pi's agent-level MCP config. pi-mcp-adapter reads from this file
32
+ * when it starts up, alongside ~/.config/mcp/mcp.json and .mcp.json.
33
+ */
34
+ function getPiAgentMcpConfigPath(): string {
35
+ const configured = env.PI_CODING_AGENT_DIR?.trim();
36
+ if (configured) {
37
+ return join(configured, "mcp.json");
38
+ }
39
+ return join(homedir(), ".pi", "agent", "mcp.json");
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Binary discovery
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /**
47
+ * Find the computer-use-linux binary. Cached after first successful lookup.
48
+ */
49
+ let _binaryPath: string | null | undefined;
50
+
51
+ function findBinary(): string | null {
52
+ if (_binaryPath !== undefined) return _binaryPath;
53
+
54
+ // 1) Environment variable override
55
+ const fromEnv = env.COMPUTER_USE_LINUX_BIN;
56
+ if (fromEnv && existsSync(fromEnv)) {
57
+ _binaryPath = fromEnv;
58
+ return fromEnv;
59
+ }
60
+
61
+ // 2) Bundled by the npm wrapper's postinstall
62
+ // The npm wrapper downloads binaries to npm/bin/ next to the JS wrapper.
63
+ // We derive the path relative to our own installed location.
64
+ const candidates = [
65
+ // Bundled binary for this platform
66
+ join(
67
+ __dirname,
68
+ "..",
69
+ "..",
70
+ "npm",
71
+ "bin",
72
+ `computer-use-linux-linux-${process.arch}`,
73
+ ),
74
+ // Direct npm global install
75
+ join(__dirname, "..", "..", "npm", "bin", "computer-use-linux"),
76
+ ];
77
+ for (const candidate of candidates) {
78
+ const resolved = join(candidate);
79
+ if (existsSync(resolved)) {
80
+ _binaryPath = resolved;
81
+ return resolved;
82
+ }
83
+ }
84
+
85
+ // 3) PATH
86
+ const pathDirs = (env.PATH || "").split(":");
87
+ for (const dir of pathDirs) {
88
+ const candidate = join(dir, "computer-use-linux");
89
+ try {
90
+ if (existsSync(candidate)) {
91
+ accessSync(candidate, constants.X_OK);
92
+ _binaryPath = candidate;
93
+ return candidate;
94
+ }
95
+ } catch {
96
+ // Skip inaccessible or non-executable entries
97
+ }
98
+ }
99
+
100
+ _binaryPath = null;
101
+ return null;
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // MCP config management
106
+ // ---------------------------------------------------------------------------
107
+
108
+ interface McpServerEntry {
109
+ command: string;
110
+ args: string[];
111
+ lifecycle?: string;
112
+ }
113
+
114
+ interface McpConfig {
115
+ mcpServers?: Record<string, McpServerEntry>;
116
+ }
117
+
118
+ function isRecord(value: unknown): value is Record<string, unknown> {
119
+ return typeof value === "object" && value !== null && !Array.isArray(value);
120
+ }
121
+
122
+ function readMcpConfig(path: string): McpConfig | null {
123
+ // Only return empty if the file genuinely doesn't exist.
124
+ // If the file exists but can't be read or parsed, return null
125
+ // so the caller knows not to overwrite it.
126
+ if (!existsSync(path)) return {};
127
+
128
+ try {
129
+ const raw = readFileSync(path, "utf-8");
130
+ const parsed = JSON.parse(raw);
131
+ if (!isRecord(parsed)) {
132
+ console.error(
133
+ `${PACKAGE_NAME}: MCP config at ${path} contains a ${typeof parsed}` +
134
+ (Array.isArray(parsed) ? " (array)" : "") +
135
+ ", expected a JSON object. Skipping.",
136
+ );
137
+ return null;
138
+ }
139
+ return parsed as McpConfig;
140
+ } catch (error) {
141
+ console.error(
142
+ `${PACKAGE_NAME}: failed to read existing MCP config at ${path}:`,
143
+ error instanceof Error ? error.message : String(error),
144
+ );
145
+ return null;
146
+ }
147
+ }
148
+
149
+ function writeMcpConfig(path: string, config: McpConfig): void {
150
+ mkdirSync(dirname(path), { recursive: true });
151
+ writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf-8");
152
+ }
153
+
154
+ /**
155
+ * Result of ensuring the computer-use-linux entry in the MCP config.
156
+ * - "updated": entry was written or updated successfully
157
+ * - "unchanged": entry already matches, no write needed
158
+ * - "failed": config could not be read or written
159
+ */
160
+ type EnsureResult = "updated" | "unchanged" | "failed";
161
+
162
+ function ensureServerEntry(
163
+ configPath: string,
164
+ binaryPath: string,
165
+ ): EnsureResult {
166
+ const config = readMcpConfig(configPath);
167
+
168
+ // If config exists but can't be read, don't overwrite it
169
+ if (config === null) return "failed";
170
+
171
+ // Validate that mcpServers is a plain object before mutating
172
+ const servers =
173
+ config.mcpServers != null && isRecord(config.mcpServers)
174
+ ? (config.mcpServers as Record<string, McpServerEntry>)
175
+ : {};
176
+
177
+ const existing = servers[MCP_SERVER_NAME];
178
+
179
+ // Check if entry already exists and matches
180
+ if (
181
+ existing &&
182
+ existing.command === binaryPath &&
183
+ existing.args?.length === 1 &&
184
+ existing.args[0] === "mcp"
185
+ ) {
186
+ return "unchanged"; // No change needed
187
+ }
188
+
189
+ // Add or update the entry
190
+ servers[MCP_SERVER_NAME] = {
191
+ command: binaryPath,
192
+ args: ["mcp"],
193
+ };
194
+
195
+ try {
196
+ writeMcpConfig(configPath, { ...config, mcpServers: servers });
197
+ return "updated"; // Config was updated
198
+ } catch (error) {
199
+ console.error(
200
+ `${PACKAGE_NAME}: failed to write MCP config to ${configPath}:`,
201
+ error instanceof Error ? error.message : String(error),
202
+ );
203
+ return "failed"; // Write failed
204
+ }
205
+ }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // Extension entry point
209
+ // ---------------------------------------------------------------------------
210
+
211
+ export default function (pi: ExtensionAPI) {
212
+ // Resolve binary path eagerly (before session_start) so it's cached.
213
+ const binaryPath = findBinary();
214
+
215
+ pi.on("session_start", async (_event, ctx) => {
216
+ try {
217
+ if (!binaryPath) {
218
+ ctx.ui.notify?.(
219
+ `${PACKAGE_NAME}: computer-use-linux binary not found. ` +
220
+ "Install it with 'npm install -g @agent-sh/computer-use-linux' " +
221
+ "or set COMPUTER_USE_LINUX_BIN.",
222
+ "warning",
223
+ );
224
+ return;
225
+ }
226
+
227
+ // Write/update the MCP config that pi-mcp-adapter reads
228
+ const configPath = getPiAgentMcpConfigPath();
229
+ const result = ensureServerEntry(configPath, binaryPath);
230
+
231
+ if (result === "updated" && ctx.hasUI) {
232
+ ctx.ui.notify?.(
233
+ `${PACKAGE_NAME}: MCP server configured at ${configPath}. ` +
234
+ "Run /reload if pi-mcp-adapter is already installed.",
235
+ "info",
236
+ );
237
+ }
238
+
239
+ if (result === "failed" && ctx.hasUI) {
240
+ ctx.ui.notify?.(
241
+ `${PACKAGE_NAME}: failed to configure MCP server at ${configPath}. ` +
242
+ "Check the console logs and ensure the file is writable.",
243
+ "error",
244
+ );
245
+ }
246
+
247
+ // Check that pi-mcp-adapter is available (its tools are registered)
248
+ if (!pi.getAllTools().some((t) => t.name === "mcp")) {
249
+ ctx.ui.notify?.(
250
+ `${PACKAGE_NAME}: pi-mcp-adapter not detected. ` +
251
+ "Install it with 'pi install npm:pi-mcp-adapter' then /reload.",
252
+ "warning",
253
+ );
254
+ }
255
+ } catch (error) {
256
+ console.error(
257
+ `${PACKAGE_NAME}: unexpected error in session_start handler:`,
258
+ error instanceof Error ? error.message : String(error),
259
+ );
260
+ }
261
+ });
262
+ }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: computer-use-linux
3
- description: "Use when Hermes needs Linux desktop observation or control through the computer-use-linux MCP server."
3
+ description: "Linux desktop observation and control via the computer-use-linux MCP server: accessibility trees, screenshots, window targeting, and input synthesis (click, type, scroll). Works with any MCP host."
4
4
  author: agent-sh
5
5
  license: MIT
6
6
  platforms: [linux]
@@ -8,28 +8,29 @@ platforms: [linux]
8
8
 
9
9
  # computer-use-linux
10
10
 
11
- Use `computer-use-linux` when Hermes needs to observe or operate a local Linux desktop through MCP: inspect the accessibility tree, list/focus windows, take screenshots, click, scroll, type, press keys, or invoke AT-SPI actions.
11
+ Use `computer-use-linux` when an agent needs to observe or operate a local Linux desktop through MCP: inspect the accessibility tree, list/focus windows, take screenshots, click, scroll, type, press keys, or invoke AT-SPI actions.
12
12
 
13
13
  ## When to Use
14
14
 
15
15
  Use this skill when:
16
- - The user wants Hermes to control a Linux GUI app.
16
+
17
+ - The user wants the agent to control a Linux GUI app.
17
18
  - You need desktop state from AT-SPI, screenshots, or compositor window metadata.
18
- - You are configuring the `computer-use-linux` MCP server for Hermes.
19
+ - You are configuring the `computer-use-linux` MCP server for your agent.
19
20
  - A desktop action needs target-aware input instead of blind shell commands.
20
21
 
21
22
  Do not use this for remote browsers, websites, or headless automation when a browser-specific tool is available. Do not assume desktop actions are safe just because the MCP connection works.
22
23
 
23
24
  ## Install
24
25
 
25
- Preferred install for Hermes users:
26
+ Preferred install:
26
27
 
27
28
  ```bash
28
29
  npm install -g @agent-sh/computer-use-linux
29
30
  computer-use-linux doctor | jq .readiness
30
31
  ```
31
32
 
32
- Rust users can install the same server from crates.io:
33
+ Rust users can install from crates.io:
33
34
 
34
35
  ```bash
35
36
  cargo install computer-use-linux
@@ -47,42 +48,23 @@ computer-use-linux doctor | jq .readiness
47
48
 
48
49
  On GNOME Wayland, log out and back in after `setup-window-targeting` if the GNOME Shell extension was newly installed.
49
50
 
50
- ## Configure Hermes
51
+ ## Configure Your Agent
51
52
 
52
- Add the server with the Hermes MCP CLI:
53
+ The `computer-use-linux` binary is an MCP server. Configure it as a stdio MCP server in your agent of choice:
53
54
 
54
- ```bash
55
- hermes mcp add computer-use-linux --command computer-use-linux --args mcp
56
- hermes mcp test computer-use-linux
57
- hermes mcp configure computer-use-linux
55
+ ```json
56
+ {
57
+ "command": "computer-use-linux",
58
+ "args": ["mcp"]
59
+ }
58
60
  ```
59
61
 
60
- `configure` opens Hermes' tool-selection UI for this MCP server.
61
-
62
- The generated config should look like this:
63
-
64
- ```yaml
65
- mcp_servers:
66
- computer-use-linux:
67
- command: computer-use-linux
68
- args: ["mcp"]
69
- timeout: 120
70
- connect_timeout: 30
71
- ```
72
-
73
- If the binary is not on `PATH`, pass the absolute path to `--command`.
74
-
75
- Hermes registers tools using the `mcp_<server>_<tool>` pattern. With this config, tool names are prefixed as `mcp_computer_use_linux_`, for example:
62
+ If the binary is not on `PATH`, use the absolute path (typically `~/.local/bin/computer-use-linux` or the npm global bin directory).
76
63
 
77
- | MCP tool | Hermes tool name |
78
- | --- | --- |
79
- | `doctor` | `mcp_computer_use_linux_doctor` |
80
- | `get_app_state` | `mcp_computer_use_linux_get_app_state` |
81
- | `list_windows` | `mcp_computer_use_linux_list_windows` |
82
- | `click` | `mcp_computer_use_linux_click` |
83
- | `type_text` | `mcp_computer_use_linux_type_text` |
64
+ ### Host-specific guides
84
65
 
85
- Restart Hermes after changing MCP config.
66
+ - [Hermes setup](references/hermes-setup.md)
67
+ - [Pi coding agent setup](references/pi-setup.md)
86
68
 
87
69
  ## Procedure
88
70
 
@@ -110,7 +92,6 @@ Run:
110
92
 
111
93
  ```bash
112
94
  computer-use-linux doctor | jq .readiness
113
- hermes chat --toolsets mcp-computer-use-linux -q "List the current desktop windows."
114
95
  ```
115
96
 
116
97
  Ready output should have:
@@ -121,4 +102,4 @@ Ready output should have:
121
102
  - `can_send_development_input: true`
122
103
  - `blockers: []`
123
104
 
124
- If Hermes does not expose the tools, check startup logs for MCP discovery errors and confirm the server name in `config.yaml` is exactly `computer-use-linux`.
105
+ Then test with your agent by calling the `doctor` tool or asking the agent to list desktop windows.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: hermes-setup
3
+ description: "Hermes agent setup for the computer-use-linux MCP server."
4
+ ---
5
+
6
+ # Hermes Setup
7
+
8
+ Add the server with the Hermes MCP CLI:
9
+
10
+ ```bash
11
+ hermes mcp add computer-use-linux --command computer-use-linux --args mcp
12
+ hermes mcp test computer-use-linux
13
+ hermes mcp configure computer-use-linux
14
+ ```
15
+
16
+ `configure` opens Hermes' tool-selection UI for this MCP server.
17
+
18
+ The generated config should look like this:
19
+
20
+ ```yaml
21
+ mcp_servers:
22
+ computer-use-linux:
23
+ command: computer-use-linux
24
+ args: ["mcp"]
25
+ timeout: 120
26
+ connect_timeout: 30
27
+ ```
28
+
29
+ If the binary is not on `PATH`, pass the absolute path to `--command`.
30
+
31
+ Hermes registers tools using the `mcp_<server>_<tool>` pattern. With this config, tool names are prefixed as `mcp_computer_use_linux_`, for example:
32
+
33
+ | MCP tool | Hermes tool name |
34
+ | --- | --- |
35
+ | `doctor` | `mcp_computer_use_linux_doctor` |
36
+ | `get_app_state` | `mcp_computer_use_linux_get_app_state` |
37
+ | `list_windows` | `mcp_computer_use_linux_list_windows` |
38
+ | `click` | `mcp_computer_use_linux_click` |
39
+ | `type_text` | `mcp_computer_use_linux_type_text` |
40
+
41
+ Restart Hermes after changing MCP config.
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: pi-setup
3
+ description: "Pi coding agent setup for the computer-use-linux MCP server."
4
+ ---
5
+
6
+ # Pi Setup
7
+
8
+ ## Prerequisites
9
+
10
+ You need both of these installed:
11
+
12
+ ```bash
13
+ pi install npm:pi-mcp-adapter
14
+ pi install npm:@agent-sh/computer-use-linux
15
+ ```
16
+
17
+ > **Note:** `pi-mcp-adapter` is a separate extension that provides MCP protocol support in pi. The `@agent-sh/computer-use-linux` package auto-registers the desktop MCP server into pi-mcp-adapter's config.
18
+
19
+ After installing both, restart pi or run `/reload`.
20
+
21
+ ## Verify
22
+
23
+ The `mcp()` proxy tool should now be available. Call it from any prompt:
24
+
25
+ ```bash
26
+ mcp({ search: "doctor" })
27
+ ```
28
+
29
+ Or start with the readiness check (note the server-prefixed tool name):
30
+
31
+ ```bash
32
+ mcp({ tool: "computer_use_linux_doctor" })
33
+ ```
34
+
35
+ Search for available tools:
36
+
37
+ ```bash
38
+ mcp({ server: "computer-use-linux" })
39
+ ```
40
+
41
+ ## If the binary is not found
42
+
43
+ The extension looks for `computer-use-linux` in this order:
44
+
45
+ 1. `COMPUTER_USE_LINUX_BIN` environment variable
46
+ 2. The npm-bundled binary (from `npm install -g @agent-sh/computer-use-linux`)
47
+ 3. `$PATH`
48
+
49
+ If none are found, install it:
50
+
51
+ ```bash
52
+ npm install -g @agent-sh/computer-use-linux
53
+ # or
54
+ cargo install computer-use-linux
55
+ ```
56
+
57
+ Then restart pi or run `/reload`.