@algosuite/vo-mcp 0.2.0-beta.7 → 0.2.0-beta.9

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.
@@ -3,8 +3,8 @@ import { createRequire as __cr } from 'module'; const require = __cr(import.meta
3
3
 
4
4
  // src/install.ts
5
5
  import { homedir as homedir3, platform as platform3 } from "node:os";
6
- import { join as join3, dirname as dirname2 } from "node:path";
7
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2 } from "node:fs";
6
+ import { join as join4, dirname as dirname3 } from "node:path";
7
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, copyFileSync as copyFileSync3 } from "node:fs";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
10
10
  // src/cloud/pairing.ts
@@ -197,27 +197,156 @@ async function runPairing(deps = {}) {
197
197
  }
198
198
  }
199
199
 
200
+ // src/codex-mcp-config.ts
201
+ import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
202
+ import { dirname as dirname2, join as join2 } from "node:path";
203
+ var MANAGED_BEGIN = "# BEGIN AlgoHQ MCP (managed by vo-mcp install)";
204
+ var MANAGED_END = "# END AlgoHQ MCP (managed by vo-mcp install)";
205
+ var MANAGED_SERVER_NAMES = /* @__PURE__ */ new Set(["algohq", "vo", "vo-mcp", "vo_mcp"]);
206
+ function resolveCodexConfigPath(home) {
207
+ return join2(home, ".codex", "config.toml");
208
+ }
209
+ function normalizeKey(value) {
210
+ const trimmed = value.trim();
211
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
212
+ return trimmed.slice(1, -1);
213
+ }
214
+ return trimmed;
215
+ }
216
+ function tablePath(line) {
217
+ const match = /^\s*\[([^\r\n]+)\]\s*(?:#.*)?$/.exec(line);
218
+ const rawPath = match?.[1];
219
+ if (!rawPath || rawPath.includes("[") || rawPath.includes("]")) return null;
220
+ return rawPath.split(".").map(normalizeKey);
221
+ }
222
+ function tableSections(lines) {
223
+ const starts = [];
224
+ for (let index = 0; index < lines.length; index += 1) {
225
+ const path = tablePath(lines[index] ?? "");
226
+ if (path) starts.push({ path, start: index });
227
+ }
228
+ return starts.map((section, index) => ({
229
+ ...section,
230
+ end: starts[index + 1]?.start ?? lines.length
231
+ }));
232
+ }
233
+ function isManagedSection(section) {
234
+ return section.path[0] === "mcp_servers" && MANAGED_SERVER_NAMES.has(section.path[1] ?? "");
235
+ }
236
+ function assignmentKey(line) {
237
+ const match = /^\s*((?:[A-Za-z0-9_-]+)|(?:"[^"]+")|(?:'[^']+'))\s*=/.exec(line);
238
+ return match?.[1] ? normalizeKey(match[1]) : null;
239
+ }
240
+ function isStructurallySafeToml(raw) {
241
+ const lines = raw.split(/\r?\n/);
242
+ const beginIndexes = lines.flatMap((line, index) => line.trim() === MANAGED_BEGIN ? [index] : []);
243
+ const endIndexes = lines.flatMap((line, index) => line.trim() === MANAGED_END ? [index] : []);
244
+ if (beginIndexes.length !== endIndexes.length || beginIndexes.length > 1) return false;
245
+ if (beginIndexes[0] !== void 0 && (endIndexes[0] ?? -1) <= beginIndexes[0]) return false;
246
+ for (const line of lines) {
247
+ const trimmed = line.trim();
248
+ if (/^\[\[?mcp_servers(?:\.|\s|$)/.test(trimmed) && tablePath(line) === null) return false;
249
+ }
250
+ return true;
251
+ }
252
+ function tomlString(value) {
253
+ return JSON.stringify(value);
254
+ }
255
+ function preservedSectionLines(lines, section, managedKeys) {
256
+ if (!section) return [];
257
+ return lines.slice(section.start + 1, section.end).filter((line) => {
258
+ if (line.trim() === MANAGED_BEGIN || line.trim() === MANAGED_END) return false;
259
+ const key = assignmentKey(line);
260
+ return key === null || !managedKeys.has(key);
261
+ }).filter((line, index, all) => line.trim() !== "" || index > 0 && index < all.length - 1);
262
+ }
263
+ function renderCodexMcpConfig(raw, cliPath, controlPlaneUrl) {
264
+ if (!isStructurallySafeToml(raw)) {
265
+ throw new Error("Codex config is malformed; refusing to overwrite it");
266
+ }
267
+ const eol = raw.includes("\r\n") ? "\r\n" : "\n";
268
+ const lines = raw.split(/\r?\n/);
269
+ const sections = tableSections(lines);
270
+ const rootSections = sections.filter((section) => isManagedSection(section) && section.path.length === 2);
271
+ const preferredRoot = rootSections.find((section) => section.path[1] === "algohq") ?? rootSections[0];
272
+ const preferredName = preferredRoot?.path[1];
273
+ const envSection = sections.find((section) => isManagedSection(section) && section.path[1] === preferredName && section.path[2] === "env");
274
+ const rootExtras = preservedSectionLines(lines, preferredRoot, /* @__PURE__ */ new Set(["command", "args", "required"]));
275
+ const envExtras = preservedSectionLines(lines, envSection, /* @__PURE__ */ new Set(["VO_CONTROL_PLANE_URL"]));
276
+ const removed = /* @__PURE__ */ new Set();
277
+ const managedBegin = lines.findIndex((line) => line.trim() === MANAGED_BEGIN);
278
+ const managedEnd = lines.findIndex((line) => line.trim() === MANAGED_END);
279
+ if (managedBegin >= 0 && managedEnd >= managedBegin) {
280
+ for (let index = managedBegin; index <= managedEnd; index += 1) removed.add(index);
281
+ }
282
+ for (const section of sections.filter(isManagedSection)) {
283
+ for (let index = section.start; index < section.end; index += 1) removed.add(index);
284
+ }
285
+ const base = lines.filter((_line, index) => !removed.has(index)).join(eol).trimEnd();
286
+ const block = [
287
+ MANAGED_BEGIN,
288
+ "[mcp_servers.algohq]",
289
+ 'command = "node"',
290
+ `args = [${tomlString(cliPath)}]`,
291
+ "required = true",
292
+ ...rootExtras,
293
+ "",
294
+ "[mcp_servers.algohq.env]",
295
+ `VO_CONTROL_PLANE_URL = ${tomlString(controlPlaneUrl)}`,
296
+ ...envExtras,
297
+ MANAGED_END
298
+ ].join(eol);
299
+ return `${base}${base ? `${eol}${eol}` : ""}${block}${eol}`;
300
+ }
301
+ function installCodexMcpConfigAt(configPath, cliPath, controlPlaneUrl, log) {
302
+ const exists = existsSync2(configPath);
303
+ const raw = exists ? readFileSync2(configPath, "utf8") : "";
304
+ let rendered;
305
+ try {
306
+ rendered = renderCodexMcpConfig(raw, cliPath, controlPlaneUrl);
307
+ } catch (error) {
308
+ if (exists) {
309
+ const backupPath = `${configPath}.backup-${Date.now()}`;
310
+ copyFileSync(configPath, backupPath);
311
+ log(` Backed up malformed Codex config \u2192 ${backupPath}`);
312
+ }
313
+ throw error;
314
+ }
315
+ if (rendered === raw) {
316
+ log(` Codex already current: ${configPath}`);
317
+ return;
318
+ }
319
+ if (exists) {
320
+ const backupPath = `${configPath}.backup-${Date.now()}`;
321
+ copyFileSync(configPath, backupPath);
322
+ log(` Backed up Codex config \u2192 ${backupPath}`);
323
+ }
324
+ mkdirSync2(dirname2(configPath), { recursive: true });
325
+ writeFileSync2(configPath, rendered, "utf8");
326
+ log(`\u2713 Wrote AlgoHQ MCP to Codex: ${configPath}`);
327
+ }
328
+
200
329
  // src/autostart.ts
201
330
  import { homedir as homedir2, platform as platform2 } from "node:os";
202
- import { join as join2 } from "node:path";
203
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2, unlinkSync, copyFileSync } from "node:fs";
331
+ import { join as join3 } from "node:path";
332
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync, copyFileSync as copyFileSync2 } from "node:fs";
204
333
  function resolveRunnerCommand(override) {
205
334
  return override ?? "vo-mcp runner";
206
335
  }
207
336
  function installWindowsAutostart(runnerCommand, log, env) {
208
- const appData = env["APPDATA"] ?? join2(homedir2(), "AppData", "Roaming");
209
- const startupDir = join2(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
210
- mkdirSync2(startupDir, { recursive: true });
211
- const launcherPath = join2(startupDir, "vo-runner.cmd");
212
- if (existsSync2(launcherPath)) {
213
- const existing = readFileSync2(launcherPath, "utf8");
337
+ const appData = env["APPDATA"] ?? join3(homedir2(), "AppData", "Roaming");
338
+ const startupDir = join3(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
339
+ mkdirSync3(startupDir, { recursive: true });
340
+ const launcherPath = join3(startupDir, "vo-runner.cmd");
341
+ if (existsSync3(launcherPath)) {
342
+ const existing = readFileSync3(launcherPath, "utf8");
214
343
  if (existing.includes("vo-mcp runner")) {
215
344
  log(`\u2713 Auto-start is already configured (Windows Startup folder)`);
216
345
  log(` Path: ${launcherPath}`);
217
346
  return;
218
347
  }
219
348
  const backupPath = `${launcherPath}.backup-${Date.now()}`;
220
- copyFileSync(launcherPath, backupPath);
349
+ copyFileSync2(launcherPath, backupPath);
221
350
  log(` Backed up existing launcher to: ${backupPath}`);
222
351
  }
223
352
  const launcherContent = `@echo off
@@ -225,24 +354,24 @@ REM Auto-start launcher for vo-mcp runner
225
354
  REM Created by vo-mcp autostart installer
226
355
  start /min cmd /c "${runnerCommand}"
227
356
  `;
228
- writeFileSync2(launcherPath, launcherContent, "utf8");
357
+ writeFileSync3(launcherPath, launcherContent, "utf8");
229
358
  log(`\u2713 Installed Windows auto-start launcher`);
230
359
  log(` Path: ${launcherPath}`);
231
360
  log(` The runner will start minimized at next login.`);
232
361
  }
233
362
  async function installMacAutostart(runnerCommand, log) {
234
- const launchAgentsDir = join2(homedir2(), "Library", "LaunchAgents");
235
- mkdirSync2(launchAgentsDir, { recursive: true });
236
- const plistPath = join2(launchAgentsDir, "ai.algosuite.vo-runner.plist");
237
- if (existsSync2(plistPath)) {
238
- const existing = readFileSync2(plistPath, "utf8");
363
+ const launchAgentsDir = join3(homedir2(), "Library", "LaunchAgents");
364
+ mkdirSync3(launchAgentsDir, { recursive: true });
365
+ const plistPath = join3(launchAgentsDir, "ai.algosuite.vo-runner.plist");
366
+ if (existsSync3(plistPath)) {
367
+ const existing = readFileSync3(plistPath, "utf8");
239
368
  if (existing.includes("vo-mcp runner")) {
240
369
  log(`\u2713 Auto-start is already configured (launchd)`);
241
370
  log(` Path: ${plistPath}`);
242
371
  return;
243
372
  }
244
373
  const backupPath = `${plistPath}.backup-${Date.now()}`;
245
- copyFileSync(plistPath, backupPath);
374
+ copyFileSync2(plistPath, backupPath);
246
375
  log(` Backed up existing plist to: ${backupPath}`);
247
376
  }
248
377
  const parts = runnerCommand.split(/\s+/);
@@ -264,20 +393,20 @@ ${args.map((a) => ` <string>${a}</string>`).join("\n")}
264
393
  <key>KeepAlive</key>
265
394
  <true/>
266
395
  <key>StandardOutPath</key>
267
- <string>${join2(homedir2(), ".claude", "vo-runner.log")}</string>
396
+ <string>${join3(homedir2(), ".claude", "vo-runner.log")}</string>
268
397
  <key>StandardErrorPath</key>
269
- <string>${join2(homedir2(), ".claude", "vo-runner-error.log")}</string>
398
+ <string>${join3(homedir2(), ".claude", "vo-runner-error.log")}</string>
270
399
  </dict>
271
400
  </plist>
272
401
  `;
273
- writeFileSync2(plistPath, plistContent, "utf8");
402
+ writeFileSync3(plistPath, plistContent, "utf8");
274
403
  log(`\u2713 Installed launchd plist`);
275
404
  log(` Path: ${plistPath}`);
276
405
  try {
277
406
  const { execSync } = await import("node:child_process");
278
407
  execSync(`launchctl load "${plistPath}"`, { stdio: "ignore" });
279
408
  log(`\u2713 Loaded plist with launchctl (runner will start at next login)`);
280
- log(` Logs: ${join2(homedir2(), ".claude", "vo-runner.log")}`);
409
+ log(` Logs: ${join3(homedir2(), ".claude", "vo-runner.log")}`);
281
410
  } catch {
282
411
  log(`\u26A0 Failed to load plist with launchctl (you may need to load it manually)`);
283
412
  log(` Run: launchctl load "${plistPath}"`);
@@ -285,23 +414,23 @@ ${args.map((a) => ` <string>${a}</string>`).join("\n")}
285
414
  }
286
415
  async function installLinuxAutostart(runnerCommand, log, env) {
287
416
  const home = env["HOME"]?.trim() || homedir2();
288
- const unitDir = join2(home, ".config", "systemd", "user");
289
- mkdirSync2(unitDir, { recursive: true });
290
- const unitPath = join2(unitDir, "vo-runner.service");
291
- if (existsSync2(unitPath)) {
292
- const existing = readFileSync2(unitPath, "utf8");
417
+ const unitDir = join3(home, ".config", "systemd", "user");
418
+ mkdirSync3(unitDir, { recursive: true });
419
+ const unitPath = join3(unitDir, "vo-runner.service");
420
+ if (existsSync3(unitPath)) {
421
+ const existing = readFileSync3(unitPath, "utf8");
293
422
  if (existing.includes(runnerCommand) || existing.includes("vo-mcp runner")) {
294
423
  log(`\u2713 Auto-start is already configured (systemd user unit)`);
295
424
  log(` Path: ${unitPath}`);
296
425
  return;
297
426
  }
298
427
  const backupPath = `${unitPath}.backup-${Date.now()}`;
299
- copyFileSync(unitPath, backupPath);
428
+ copyFileSync2(unitPath, backupPath);
300
429
  log(` Backed up existing unit to: ${backupPath}`);
301
430
  }
302
- const logFile = join2(home, ".claude", "vo-runner.log");
303
- const errFile = join2(home, ".claude", "vo-runner-error.log");
304
- mkdirSync2(join2(home, ".claude"), { recursive: true });
431
+ const logFile = join3(home, ".claude", "vo-runner.log");
432
+ const errFile = join3(home, ".claude", "vo-runner-error.log");
433
+ mkdirSync3(join3(home, ".claude"), { recursive: true });
305
434
  const unit = `[Unit]
306
435
  Description=VO Code Runner (vo-mcp)
307
436
  After=network-online.target
@@ -318,7 +447,7 @@ StandardError=append:${errFile}
318
447
  [Install]
319
448
  WantedBy=default.target
320
449
  `;
321
- writeFileSync2(unitPath, unit, "utf8");
450
+ writeFileSync3(unitPath, unit, "utf8");
322
451
  log(`\u2713 Installed systemd user unit`);
323
452
  log(` Path: ${unitPath}`);
324
453
  if (process.env["VITEST"]) {
@@ -356,21 +485,21 @@ async function installAutostart(opts = {}) {
356
485
  // src/install.ts
357
486
  var DEFAULT_CONTROL_PLANE_URL2 = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
358
487
  function resolveCodeConfigPath(home) {
359
- return join3(home, ".claude.json");
488
+ return join4(home, ".claude.json");
360
489
  }
361
490
  function resolveDesktopConfigPath(home, plat, appData) {
362
491
  if (plat === "win32") {
363
- return join3(appData ?? join3(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
492
+ return join4(appData ?? join4(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
364
493
  }
365
494
  if (plat === "darwin") {
366
- return join3(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
495
+ return join4(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
367
496
  }
368
- return join3(home, ".config", "Claude", "claude_desktop_config.json");
497
+ return join4(home, ".config", "Claude", "claude_desktop_config.json");
369
498
  }
370
499
  function readClaudeConfig(path) {
371
500
  try {
372
- if (!existsSync3(path)) return {};
373
- const raw = readFileSync3(path, "utf8");
501
+ if (!existsSync4(path)) return {};
502
+ const raw = readFileSync4(path, "utf8");
374
503
  const parsed = JSON.parse(raw);
375
504
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
376
505
  } catch {
@@ -378,12 +507,12 @@ function readClaudeConfig(path) {
378
507
  }
379
508
  }
380
509
  function writeClaudeConfig(path, config) {
381
- mkdirSync3(dirname2(path), { recursive: true });
382
- writeFileSync3(path, `${JSON.stringify(config, null, 2)}
510
+ mkdirSync4(dirname3(path), { recursive: true });
511
+ writeFileSync4(path, `${JSON.stringify(config, null, 2)}
383
512
  `, "utf8");
384
513
  }
385
514
  function resolveVoMcpCliPath() {
386
- return join3(dirname2(fileURLToPath(import.meta.url)), "cli.js");
515
+ return join4(dirname3(fileURLToPath(import.meta.url)), "cli.js");
387
516
  }
388
517
  function installMcpConfigAt(configPath, cliPath, controlPlaneUrl, log, label) {
389
518
  const existing = readClaudeConfig(configPath);
@@ -393,9 +522,9 @@ function installMcpConfigAt(configPath, cliPath, controlPlaneUrl, log, label) {
393
522
  log(` ${label} already current: ${configPath}`);
394
523
  return;
395
524
  }
396
- if (existsSync3(configPath)) {
525
+ if (existsSync4(configPath)) {
397
526
  const backupPath = `${configPath}.backup-${Date.now()}`;
398
- copyFileSync2(configPath, backupPath);
527
+ copyFileSync3(configPath, backupPath);
399
528
  log(` Backed up ${label} config \u2192 ${backupPath}`);
400
529
  }
401
530
  const merged = {
@@ -422,6 +551,7 @@ function installMcpConfig(log, env) {
422
551
  const controlPlaneUrl = env["VO_CONTROL_PLANE_URL"]?.trim() || DEFAULT_CONTROL_PLANE_URL2;
423
552
  installMcpConfigAt(resolveCodeConfigPath(home), cliPath, controlPlaneUrl, log, "Claude Code CLI");
424
553
  installMcpConfigAt(resolveDesktopConfigPath(home, plat, appData), cliPath, controlPlaneUrl, log, "Claude Desktop");
554
+ installCodexMcpConfigAt(resolveCodexConfigPath(home), cliPath, controlPlaneUrl, log);
425
555
  }
426
556
  async function runPairFlow(log, env) {
427
557
  log("\n\u2501\u2501\u2501 Step 2: Link your VO account (device code) \u2501\u2501\u2501");
@@ -437,27 +567,33 @@ async function runPairFlow(log, env) {
437
567
  log(" No problem \u2014 the rest will finish; pair anytime with: vo-mcp pair");
438
568
  }
439
569
  }
440
- function printNextSteps(log, autostartInstalled) {
570
+ function printNextSteps(log, autostartInstalled, configOnly) {
441
571
  log("\n\u2501\u2501\u2501 Installation complete! \u2501\u2501\u2501\n");
442
572
  log("What's configured:");
443
- log(" \u2713 Claude Desktop / Claude Code will load vo-mcp on next restart");
444
- log(" \u2713 Your scoped credential is stored (revocable via the dashboard)");
573
+ log(" \u2713 Claude Desktop, Claude Code, and Codex will load AlgoHQ MCP on next restart");
574
+ if (configOnly) {
575
+ log(" \u2713 Existing pairing and runner auto-start settings were left unchanged");
576
+ } else {
577
+ log(" \u2713 Your scoped credential is stored (revocable via the dashboard)");
578
+ }
445
579
  if (autostartInstalled) {
446
580
  log(" \u2713 Runner daemon will start automatically at login\n");
447
581
  } else {
448
582
  log("\n");
449
583
  }
450
584
  log("Next steps:");
451
- log(" 1. Restart Claude Desktop / Claude Code (if running).");
452
- if (autostartInstalled) {
585
+ log(" 1. Restart Claude Desktop / Claude Code / Codex (if running).");
586
+ if (configOnly) {
587
+ log(" 2. Restart the existing AlgoHQ runner service or runner terminal.");
588
+ } else if (autostartInstalled) {
453
589
  log(" 2. Log out and back in (or start the runner manually now: vo-mcp runner)");
454
590
  } else {
455
591
  log(" 2. Start the agent runner in a terminal (keep it running):");
456
592
  log(" vo-mcp runner");
457
593
  log(" (To set up auto-start at login: vo-mcp runner --install-autostart)");
458
594
  }
459
- log(" 3. Visit the VO Command Center to dispatch your first agent:");
460
- log(" https://algosuite.ai/virtualoffice\n");
595
+ log(" 3. Visit AlgoHQ to dispatch your first agent:");
596
+ log(" https://algosuite.ai/algohq\n");
461
597
  log("The runner watches for tasks you dispatch and spins up agents in fresh worktrees.");
462
598
  log("Agents only run while the runner is connected. Ctrl+C to stop it anytime.\n");
463
599
  }
@@ -465,11 +601,13 @@ async function install(opts = {}) {
465
601
  const log = opts.log ?? ((m) => console.error(m));
466
602
  const env = opts.env ?? process.env;
467
603
  log("\u2501\u2501\u2501 vo-mcp installer \u2501\u2501\u2501");
468
- log("This will set up your machine to dispatch VO agents from anywhere.\n");
469
- log("\u2501\u2501\u2501 Step 1: Configure Claude Desktop / Claude Code \u2501\u2501\u2501");
604
+ log("This will set up your machine to dispatch AlgoHQ agents from anywhere.\n");
605
+ log("\u2501\u2501\u2501 Step 1: Configure Claude Desktop / Claude Code / Codex \u2501\u2501\u2501");
470
606
  installMcpConfig(log, env);
471
607
  if (!opts.skipLogin) {
472
608
  await runPairFlow(log, env);
609
+ } else if (opts.configOnly) {
610
+ log("\n(Config-only refresh \u2014 existing pairing left unchanged.)");
473
611
  } else {
474
612
  log("\n(Pairing skipped \u2014 run `vo-mcp pair` when ready.)");
475
613
  }
@@ -484,11 +622,15 @@ async function install(opts = {}) {
484
622
  autostartInstalled = true;
485
623
  }
486
624
  }
487
- printNextSteps(log, autostartInstalled);
625
+ printNextSteps(log, autostartInstalled, opts.configOnly === true);
626
+ }
627
+ function installOptionsFromArgs(args) {
628
+ const configOnly = args.includes("--config-only");
629
+ return configOnly ? { configOnly: true, skipLogin: true, skipAutostart: true } : {};
488
630
  }
489
631
 
490
632
  // src/install-cli.ts
491
- install().catch((err) => {
633
+ install(installOptionsFromArgs(process.argv.slice(2))).catch((err) => {
492
634
  console.error("[vo-mcp install] fatal:", err);
493
635
  process.exit(1);
494
636
  });