open-codex-computer-use-mcp 0.1.32 → 0.1.35

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.
@@ -12,6 +12,8 @@ function usage() {
12
12
  process.stdout.write(`Usage:
13
13
  node ./scripts/install-config-helper.mjs claude-mcp <config-path> <project-root> <server-name> <command-name>
14
14
  node ./scripts/install-config-helper.mjs codex-mcp <config-path> <server-name> <command-name>
15
+ node ./scripts/install-config-helper.mjs gemini-mcp <config-path> <server-name> <command-name>
16
+ node ./scripts/install-config-helper.mjs opencode-mcp <primary-config-path> <secondary-config-path> <server-name> <command-name>
15
17
  node ./scripts/install-config-helper.mjs codex-plugin-version <plugin-manifest-path>
16
18
  node ./scripts/install-config-helper.mjs codex-plugin-config <config-path> <repo-root> <marketplace-name> <plugin-name>
17
19
  node ./scripts/install-config-helper.mjs copy-into-dir <target-dir> <source-path> [<source-path> ...]
@@ -29,6 +31,53 @@ function ensureParentDir(filePath) {
29
31
  mkdirSync(path.dirname(filePath), { recursive: true });
30
32
  }
31
33
 
34
+ function readJSONObjectConfig(configPath, label) {
35
+ const raw = readTextIfExists(configPath);
36
+ if (raw.trim().length === 0) {
37
+ return {};
38
+ }
39
+
40
+ let data;
41
+ try {
42
+ data = JSON.parse(raw);
43
+ } catch (error) {
44
+ fail(`Existing ${label} is not valid JSON: ${error.message}`);
45
+ }
46
+
47
+ if (data === null || Array.isArray(data) || typeof data !== "object") {
48
+ fail(`Existing ${label} root is not a JSON object; refusing to modify it.`);
49
+ }
50
+
51
+ return data;
52
+ }
53
+
54
+ function ensureObjectField(parent, key, label) {
55
+ const value = parent[key] ?? {};
56
+ if (value === null || Array.isArray(value) || typeof value !== "object") {
57
+ fail(label);
58
+ }
59
+ parent[key] = value;
60
+ return value;
61
+ }
62
+
63
+ function getOptionalObjectField(parent, key, label) {
64
+ if (!(key in parent) || parent[key] === undefined) {
65
+ return undefined;
66
+ }
67
+
68
+ const value = parent[key];
69
+ if (value === null || Array.isArray(value) || typeof value !== "object") {
70
+ fail(label);
71
+ }
72
+
73
+ return value;
74
+ }
75
+
76
+ function writeJSONConfig(configPath, data) {
77
+ ensureParentDir(configPath);
78
+ writeFileSync(configPath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
79
+ }
80
+
32
81
  function normalizeNewlines(text) {
33
82
  return text.replace(/\r\n/g, "\n");
34
83
  }
@@ -168,41 +217,55 @@ function installClaudeMcp(configPath, projectRoot, serverName, commandName) {
168
217
  args: ["mcp"],
169
218
  };
170
219
  const legacyServerName = "open-codex-computer-use";
220
+ const data = readJSONObjectConfig(configPath, `Claude config ${configPath}`);
221
+ const projects = ensureObjectField(data, "projects", 'Existing Claude config has non-object "projects"; refusing to modify it.');
222
+ const projectEntry = ensureObjectField(
223
+ projects,
224
+ projectRoot,
225
+ `Existing Claude project entry for ${projectRoot} is not an object; refusing to modify it.`,
226
+ );
227
+ const mcpServers = ensureObjectField(
228
+ projectEntry,
229
+ "mcpServers",
230
+ `Existing Claude project MCP config for ${projectRoot} is not an object; refusing to modify it.`,
231
+ );
171
232
 
172
- const raw = readTextIfExists(configPath);
173
- let data;
233
+ const target = mcpServers[serverName];
234
+ const legacy = mcpServers[legacyServerName];
235
+ const targetMatches = JSON.stringify(target) === JSON.stringify(desiredEntry);
236
+ const legacyMatches = JSON.stringify(legacy) === JSON.stringify(desiredEntry);
174
237
 
175
- if (raw.trim().length === 0) {
176
- data = {};
177
- } else {
178
- try {
179
- data = JSON.parse(raw);
180
- } catch (error) {
181
- fail(`Existing Claude config is not valid JSON: ${error.message}`);
182
- }
238
+ if (targetMatches && !legacyMatches) {
239
+ process.stdout.write(`Claude MCP server "${serverName}" is already installed for ${projectRoot} in ${configPath}.\n`);
240
+ return;
183
241
  }
184
242
 
185
- if (data === null || Array.isArray(data) || typeof data !== "object") {
186
- fail("Existing Claude config root is not a JSON object; refusing to modify it.");
243
+ mcpServers[serverName] = desiredEntry;
244
+ if (legacyMatches) {
245
+ delete mcpServers[legacyServerName];
187
246
  }
188
247
 
189
- const projects = data.projects ?? {};
190
- if (projects === null || Array.isArray(projects) || typeof projects !== "object") {
191
- fail('Existing Claude config has non-object "projects"; refusing to modify it.');
192
- }
193
- data.projects = projects;
248
+ writeJSONConfig(configPath, data);
194
249
 
195
- const projectEntry = projects[projectRoot] ?? {};
196
- if (projectEntry === null || Array.isArray(projectEntry) || typeof projectEntry !== "object") {
197
- fail(`Existing Claude project entry for ${projectRoot} is not an object; refusing to modify it.`);
250
+ if (targetMatches && legacyMatches) {
251
+ process.stdout.write(`Claude MCP server "${serverName}" was already installed for ${projectRoot}; removed legacy alias "${legacyServerName}" from ${configPath}.\n`);
252
+ } else {
253
+ process.stdout.write(`Installed Claude MCP server "${serverName}" for ${projectRoot} into ${configPath}.\n`);
198
254
  }
199
- projects[projectRoot] = projectEntry;
255
+ }
200
256
 
201
- const mcpServers = projectEntry.mcpServers ?? {};
202
- if (mcpServers === null || Array.isArray(mcpServers) || typeof mcpServers !== "object") {
203
- fail(`Existing Claude project MCP config for ${projectRoot} is not an object; refusing to modify it.`);
204
- }
205
- projectEntry.mcpServers = mcpServers;
257
+ function installGeminiMcp(configPath, serverName, commandName) {
258
+ const desiredEntry = {
259
+ command: commandName,
260
+ args: ["mcp"],
261
+ };
262
+ const legacyServerName = "open-codex-computer-use";
263
+ const data = readJSONObjectConfig(configPath, `Gemini config ${configPath}`);
264
+ const mcpServers = ensureObjectField(
265
+ data,
266
+ "mcpServers",
267
+ `Existing Gemini config has non-object "mcpServers"; refusing to modify it.`,
268
+ );
206
269
 
207
270
  const target = mcpServers[serverName];
208
271
  const legacy = mcpServers[legacyServerName];
@@ -210,7 +273,7 @@ function installClaudeMcp(configPath, projectRoot, serverName, commandName) {
210
273
  const legacyMatches = JSON.stringify(legacy) === JSON.stringify(desiredEntry);
211
274
 
212
275
  if (targetMatches && !legacyMatches) {
213
- process.stdout.write(`Claude MCP server "${serverName}" is already installed for ${projectRoot} in ${configPath}.\n`);
276
+ process.stdout.write(`Gemini MCP server "${serverName}" is already installed in ${configPath}.\n`);
214
277
  return;
215
278
  }
216
279
 
@@ -219,16 +282,98 @@ function installClaudeMcp(configPath, projectRoot, serverName, commandName) {
219
282
  delete mcpServers[legacyServerName];
220
283
  }
221
284
 
222
- ensureParentDir(configPath);
223
- writeFileSync(configPath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
285
+ writeJSONConfig(configPath, data);
224
286
 
225
287
  if (targetMatches && legacyMatches) {
226
- process.stdout.write(`Claude MCP server "${serverName}" was already installed for ${projectRoot}; removed legacy alias "${legacyServerName}" from ${configPath}.\n`);
288
+ process.stdout.write(`Gemini MCP server "${serverName}" was already installed; removed legacy alias "${legacyServerName}" from ${configPath}.\n`);
227
289
  } else {
228
- process.stdout.write(`Installed Claude MCP server "${serverName}" for ${projectRoot} into ${configPath}.\n`);
290
+ process.stdout.write(`Installed Gemini MCP server "${serverName}" into ${configPath}.\n`);
229
291
  }
230
292
  }
231
293
 
294
+ function installOpencodeMcp(primaryConfigPath, secondaryConfigPath, serverName, commandName) {
295
+ const desiredEntry = {
296
+ type: "local",
297
+ command: [commandName, "mcp"],
298
+ };
299
+ const legacyServerName = "open-codex-computer-use";
300
+ const configEntries = [{ path: primaryConfigPath, role: "primary" }];
301
+ if (secondaryConfigPath && secondaryConfigPath !== primaryConfigPath) {
302
+ configEntries.push({ path: secondaryConfigPath, role: "secondary" });
303
+ }
304
+
305
+ const records = configEntries.map((entry) => ({
306
+ ...entry,
307
+ data: readJSONObjectConfig(entry.path, `opencode config ${entry.path}`),
308
+ dirty: false,
309
+ }));
310
+
311
+ const targetMatches = [];
312
+ const extraAliases = [];
313
+ for (const record of records) {
314
+ const mcp = getOptionalObjectField(
315
+ record.data,
316
+ "mcp",
317
+ `Existing opencode config has non-object "mcp" in ${record.path}; refusing to modify it.`,
318
+ );
319
+ if (!mcp) {
320
+ continue;
321
+ }
322
+
323
+ if (JSON.stringify(mcp[serverName]) === JSON.stringify(desiredEntry)) {
324
+ targetMatches.push(record.path);
325
+ }
326
+ if (serverName in mcp || legacyServerName in mcp) {
327
+ extraAliases.push(record.path);
328
+ }
329
+ }
330
+
331
+ if (targetMatches.length === 1 && extraAliases.length === 1 && targetMatches[0] === extraAliases[0]) {
332
+ process.stdout.write(`opencode MCP server "${serverName}" is already installed in ${targetMatches[0]}.\n`);
333
+ return;
334
+ }
335
+
336
+ for (const record of records) {
337
+ const mcp = ensureObjectField(
338
+ record.data,
339
+ "mcp",
340
+ `Existing opencode config has non-object "mcp" in ${record.path}; refusing to modify it.`,
341
+ );
342
+
343
+ if (record.role === "primary") {
344
+ if (JSON.stringify(mcp[serverName]) !== JSON.stringify(desiredEntry)) {
345
+ mcp[serverName] = desiredEntry;
346
+ record.dirty = true;
347
+ }
348
+ if (legacyServerName in mcp) {
349
+ delete mcp[legacyServerName];
350
+ record.dirty = true;
351
+ }
352
+ continue;
353
+ }
354
+
355
+ if (serverName in mcp) {
356
+ delete mcp[serverName];
357
+ record.dirty = true;
358
+ }
359
+ if (legacyServerName in mcp) {
360
+ delete mcp[legacyServerName];
361
+ record.dirty = true;
362
+ }
363
+ if (Object.keys(mcp).length === 0) {
364
+ delete record.data.mcp;
365
+ }
366
+ }
367
+
368
+ for (const record of records) {
369
+ if (record.dirty) {
370
+ writeJSONConfig(record.path, record.data);
371
+ }
372
+ }
373
+
374
+ process.stdout.write(`Installed opencode MCP server "${serverName}" into ${primaryConfigPath}.\n`);
375
+ }
376
+
232
377
  function installCodexMcp(configPath, serverName, commandName) {
233
378
  const desiredBody = `command = ${JSON.stringify(commandName)}\nargs = ["mcp"]`;
234
379
  const targetHeader = `mcp_servers."${serverName}"`;
@@ -347,6 +492,20 @@ function main(argv) {
347
492
  }
348
493
  installCodexMcp(...args);
349
494
  return;
495
+ case "gemini-mcp":
496
+ if (args.length !== 3) {
497
+ usage();
498
+ process.exit(1);
499
+ }
500
+ installGeminiMcp(...args);
501
+ return;
502
+ case "opencode-mcp":
503
+ if (args.length !== 4) {
504
+ usage();
505
+ process.exit(1);
506
+ }
507
+ installOpencodeMcp(...args);
508
+ return;
350
509
  case "codex-plugin-version":
351
510
  if (args.length !== 1) {
352
511
  usage();
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ config_helper="${script_dir}/install-config-helper.mjs"
7
+ server_name="open-computer-use"
8
+ command_name="open-computer-use"
9
+ scope="project"
10
+
11
+ usage() {
12
+ cat <<'EOF'
13
+ Usage: ./scripts/install-gemini-mcp.sh [--scope project|user]
14
+
15
+ Install the open-computer-use stdio MCP entry into Gemini CLI config.
16
+ Defaults to project scope, which writes ./.gemini/settings.json for the current project.
17
+ Set GEMINI_CONFIG_PATH to override the target file directly.
18
+ EOF
19
+ }
20
+
21
+ while [[ $# -gt 0 ]]; do
22
+ case "$1" in
23
+ --scope)
24
+ if [[ $# -lt 2 ]]; then
25
+ echo "--scope requires a value" >&2
26
+ usage >&2
27
+ exit 1
28
+ fi
29
+ scope="$2"
30
+ shift 2
31
+ ;;
32
+ -h|--help)
33
+ usage
34
+ exit 0
35
+ ;;
36
+ *)
37
+ echo "Unknown argument: $1" >&2
38
+ usage >&2
39
+ exit 1
40
+ ;;
41
+ esac
42
+ done
43
+
44
+ case "${scope}" in
45
+ project)
46
+ default_config_path="$(pwd -P)/.gemini/settings.json"
47
+ ;;
48
+ user)
49
+ default_config_path="${HOME}/.gemini/settings.json"
50
+ ;;
51
+ *)
52
+ echo "Unsupported Gemini scope: ${scope}" >&2
53
+ usage >&2
54
+ exit 1
55
+ ;;
56
+ esac
57
+
58
+ config_path="${GEMINI_CONFIG_PATH:-${default_config_path}}"
59
+
60
+ node "${config_helper}" gemini-mcp "${config_path}" "${server_name}" "${command_name}"
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ config_helper="${script_dir}/install-config-helper.mjs"
7
+ server_name="open-computer-use"
8
+ command_name="open-computer-use"
9
+ opencode_config_dir="${XDG_CONFIG_HOME:-${HOME}/.config}/opencode"
10
+
11
+ usage() {
12
+ cat <<'EOF'
13
+ Usage: ./scripts/install-opencode-mcp.sh
14
+
15
+ Install the open-computer-use stdio MCP entry into opencode config.
16
+ Set OPENCODE_CONFIG_PATH to override the primary config file directly.
17
+ EOF
18
+ }
19
+
20
+ while [[ $# -gt 0 ]]; do
21
+ case "$1" in
22
+ -h|--help)
23
+ usage
24
+ exit 0
25
+ ;;
26
+ *)
27
+ echo "Unknown argument: $1" >&2
28
+ usage >&2
29
+ exit 1
30
+ ;;
31
+ esac
32
+ done
33
+
34
+ if [[ -n "${OPENCODE_CONFIG_PATH:-}" ]]; then
35
+ primary_config_path="${OPENCODE_CONFIG_PATH}"
36
+ secondary_config_path=""
37
+ elif [[ -f "${opencode_config_dir}/opencode.json" ]]; then
38
+ primary_config_path="${opencode_config_dir}/opencode.json"
39
+ secondary_config_path="${opencode_config_dir}/config.json"
40
+ elif [[ -f "${opencode_config_dir}/config.json" ]]; then
41
+ primary_config_path="${opencode_config_dir}/config.json"
42
+ secondary_config_path="${opencode_config_dir}/opencode.json"
43
+ else
44
+ primary_config_path="${opencode_config_dir}/opencode.json"
45
+ secondary_config_path="${opencode_config_dir}/config.json"
46
+ fi
47
+
48
+ node "${config_helper}" opencode-mcp "${primary_config_path}" "${secondary_config_path}" "${server_name}" "${command_name}"
@@ -11,16 +11,17 @@ const mcpConfig = {
11
11
  };
12
12
  const lines = [
13
13
  "",
14
- "Installed open-codex-computer-use-mcp@0.1.32.",
14
+ "Installed open-codex-computer-use-mcp@0.1.35.",
15
15
  "Package: https://www.npmjs.com/package/open-codex-computer-use-mcp",
16
16
  "Commands: open-computer-use, open-computer-use-mcp, open-codex-computer-use-mcp",
17
+ "Native runtime will be selected from bundled artifacts for " + process.platform + "-" + process.arch + ".",
17
18
  "",
18
19
  "Next:",
19
- "1. Run open-computer-use doctor",
20
- "2. In macOS System Settings, grant Accessibility and Screen Recording to your host terminal or MCP client",
21
- "3. Run open-computer-use install-claude-mcp for Claude Code, open-computer-use install-codex-mcp for Codex, or open-computer-use install-codex-plugin for the local plugin cache",
20
+ "1. Run open-computer-use --version",
21
+ "2. Add the MCP config below to your host client",
22
+ "3. On macOS, run open-computer-use doctor and grant Accessibility / Screen Recording if prompted",
22
23
  "",
23
- "You can add this to any MCP-capable client:",
24
+ "MCP config:",
24
25
  JSON.stringify(mcpConfig, null, 2),
25
26
  "",
26
27
  ];