@algosuite/vo-mcp 0.2.0-beta.68 → 0.2.0-beta.69

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.
@@ -637,26 +637,15 @@ var ClaudeRunner = class {
637
637
  if (process.platform !== "win32") return { bin, args, spawnOptions: spawnOptions ?? this.getSpawnOptions() };
638
638
  return buildWindowsClaudeLaunch({ bin, args, env });
639
639
  }
640
- /**
641
- * Fill ANTHROPIC_API_KEY from the OS keychain when not already set (M4 BYO),
642
- * so a friend who ran `vo-mcp set-key` authenticates without an env var.
643
- * Explicit env wins; no key stored → unchanged (Claude Code login as before).
644
- */
645
640
  applyAuthEnv(env = process.env) {
646
641
  return withAnthropicKey(env);
647
642
  }
648
643
  costBasis(env = process.env) {
649
644
  return claudeCostBasis(env);
650
645
  }
651
- /** Describe which Anthropic auth source the spawn will use (for runner logs). */
652
646
  describeAuth(env = process.env) {
653
647
  return describeAnthropicAuthSource(env);
654
648
  }
655
- /**
656
- * Best-effort auth check: is `claude` on PATH and can we verify login?
657
- * Never throws. If we can't cheaply detect auth, we return installed:true
658
- * and let the real spawn fail with a clearer error from the CLI itself.
659
- */
660
649
  async checkAuth() {
661
650
  return checkClaudeAuth();
662
651
  }
@@ -774,6 +763,43 @@ function extractFlatTokenUsage(usage) {
774
763
  return Object.values(out).some((value) => value > 0) ? out : null;
775
764
  }
776
765
 
766
+ // ../../scripts/virtual-office/code-runner/error-message.mjs
767
+ var FAILURE_SCHEMA = "vo.runner_failure.v1";
768
+ var FAILURE_DEFINITIONS = {
769
+ model_unsupported_for_account: {
770
+ source: "terminal_event",
771
+ operator_next_action: "Create a new owner-reviewed task with an account-supported Codex model; keep the preserved draft PR for review rather than resuming this task."
772
+ },
773
+ codex_runtime_launch_logon_session: {
774
+ source: "stderr",
775
+ win32_error: 1312,
776
+ operator_next_action: "Repair the Codex Windows logon/session configuration on the serving host, then rerun the runner."
777
+ }
778
+ };
779
+ function codexFailure(code, fields = {}) {
780
+ const definition = FAILURE_DEFINITIONS[code];
781
+ return {
782
+ schema: FAILURE_SCHEMA,
783
+ agent: "codex",
784
+ source: definition.source,
785
+ code,
786
+ ...fields,
787
+ operator_next_action: definition.operator_next_action
788
+ };
789
+ }
790
+ function classifyCodexTerminalFailure(evt = {}) {
791
+ const error = evt && typeof evt.error === "object" && evt.error !== null ? evt.error : null;
792
+ if (!/^The ['"][^'"]+['"] model is not supported when using Codex with a ChatGPT account\.$/u.test(
793
+ typeof error?.message === "string" ? error.message : ""
794
+ )) return null;
795
+ const status = Number(evt.status ?? error.status);
796
+ return codexFailure("model_unsupported_for_account", Number.isInteger(status) ? { provider_status: status } : {});
797
+ }
798
+ function classifyCodexStderr(stderr = "") {
799
+ if (!/(?:^|\n)\s*windows sandbox(?: failed)?: runner error:.*\bCreateProcessAsUserW failed:\s*1312\b/imu.test(String(stderr))) return null;
800
+ return codexFailure("codex_runtime_launch_logon_session", { win32_error: 1312 });
801
+ }
802
+
777
803
  // ../../scripts/virtual-office/code-runner/codex-runner.mjs
778
804
  var CODEX_PREFER_LOGIN_ENV = "VO_RUNNER_CODEX_PREFER_LOGIN";
779
805
  var LEGACY_PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
@@ -873,7 +899,8 @@ function parseCodexEvent(line) {
873
899
  }
874
900
  if (type === "turn.failed" || type === "error") {
875
901
  const msg = evt.error && (evt.error.message || evt.error) || evt.message || "codex run failed";
876
- return { kind: "result", isError: true, costUsd: null, summary: String(msg), numTurns: null };
902
+ const failure = classifyCodexTerminalFailure(evt);
903
+ return { kind: "result", isError: true, costUsd: null, summary: String(msg), numTurns: null, ...failure ? { failure } : {} };
877
904
  }
878
905
  return null;
879
906
  }
@@ -892,6 +919,9 @@ var CodexRunner = class {
892
919
  parseEvent(line) {
893
920
  return parseCodexEvent(line);
894
921
  }
922
+ classifyStderr(stderr) {
923
+ return classifyCodexStderr(stderr);
924
+ }
895
925
  /**
896
926
  * SECURITY: never `shell: true` — same RCE class as cursor-runner. The old
897
927
  * `shell: win32 && !/\.exe$/` fell back to shell mode whenever
@@ -913,12 +943,6 @@ var CodexRunner = class {
913
943
  windowsVerbatimArguments: false
914
944
  };
915
945
  }
916
- /**
917
- * Fill the OpenAI credential env var(s) (OPENAI_API_KEY / CODEX_API_KEY) from
918
- * the OS keychain when not already set, so a BYO friend who ran
919
- * `vo-mcp set-key --provider codex` authenticates without an env var. Explicit
920
- * env wins; no key stored → unchanged (a prior `codex login` still works).
921
- */
922
946
  applyAuthEnv(env = process.env) {
923
947
  if (isTruthyFlag2(env[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag2(env[LEGACY_PREFER_LOGIN_ENV])) {
924
948
  const out = { ...env };
@@ -931,20 +955,9 @@ var CodexRunner = class {
931
955
  costBasis(env = process.env) {
932
956
  return String(env.OPENAI_API_KEY || env.CODEX_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
933
957
  }
934
- /**
935
- * Dispatch-time billing tier for the NEXT codex spawn, from the SAME facts
936
- * checkAuth() already gathered — no extra subprocess. applyAuthEnv() is a
937
- * keychain read in this process (@napi-rs/keyring), not a spawn.
938
- *
939
- * Codex is the agent where this signal was already computed and then thrown
940
- * away: checkAuth() distinguished "API key available (no persisted ChatGPT
941
- * login)" from a real login, but only inside a `message` string that the
942
- * heartbeat schema strips before storage. This gives that fact a typed home.
943
- */
944
958
  authTier(env = this.env) {
945
959
  return safeAuthTier(() => authTierFromCostBasis(this.costBasis(this.applyAuthEnv(env))));
946
960
  }
947
- /** Best-effort binary + persisted-login probe. Never throws or spends tokens. */
948
961
  async checkAuth() {
949
962
  try {
950
963
  const bin = this.binary;
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/autostart.ts", "../src/autostart-cli.ts"],
4
- "sourcesContent": ["/**\n * Cross-platform auto-start registration for `vo-mcp runner`.\n *\n * WINDOWS: Uses the user Startup folder, NOT Task Scheduler. Headless\n * `claude -p` (which the runner spawns) HANGS under the Task Scheduler\n * service session \u2014 it only runs from an interactive, Explorer-descended\n * session. The Startup folder launches the runner at user login via a .vbs\n * supervisor shim.\n *\n * The shim runs the runner in a MINIMIZED-not-activated window (WScript.Shell.Run\n * window style 7), NOT a hidden window (style 0). Style 0 is deliberately banned:\n * a script host (wscript) launching a hidden subprocess is the exact behavioural\n * signature of Windows Defender's `Trojan:Win32/PowhidSubExec.B` heuristic\n * (\"Pow[ershell]-hid[den]-Sub[process]-Exec\"), which blocked this launch pattern on\n * a fleet box on 2026-08-29 (see docs/vo/roadmap-log/2026-08-29-defender-safe-autostart.md).\n * A Defender block at logon silently prevents the runner from ever starting, which is\n * strictly worse than a labelled minimized console the operator can see. Style 7 keeps\n * the console off the foreground (never steals focus) while staying VISIBLE, so it does\n * not match the hidden-exec heuristic \u2014 the same visibility class as the pre-2026-07\n * `start /min` launcher, which was Defender-clean for months.\n *\n * MACOS: Uses ~/Library/LaunchAgents (launchd) with RunAtLoad + KeepAlive.\n *\n * LINUX: Uses ~/.config/systemd/user (systemd user unit) with Restart=on-failure;\n * ExecStart runs via a login shell so the npm-global `vo-mcp` resolves on PATH.\n *\n * All operations are idempotent and create backups where applicable.\n */\nimport { homedir, platform } from 'node:os';\nimport { isAbsolute, join } from 'node:path';\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, copyFileSync } from 'node:fs';\n\nexport interface AutostartOptions {\n readonly log?: (msg: string) => void;\n readonly runnerCommand?: string; // Override for tests\n readonly env?: Readonly<Record<string, string | undefined>>;\n /**\n * Override the detected platform. Injectable ONLY so the Windows branch can be\n * exercised on a Linux CI runner \u2014 every Windows assertion in autostart.test.ts\n * used to early-return on ubuntu-latest, so no Windows autostart behaviour was\n * verified anywhere.\n */\n readonly platform?: NodeJS.Platform;\n}\n\n/**\n * Base restart backoff for the Windows keepalive loop \u2014 short enough that a real\n * crash recovers well within a heartbeat interval.\n */\nconst WINDOWS_RESTART_BACKOFF_MS = 10_000;\n\n/**\n * A run lasting at least this long counts as HEALTHY and resets the backoff.\n * Anything shorter is treated as a fast-fail and doubles the wait.\n */\nconst WINDOWS_HEALTHY_RUN_MS = 60_000;\n\n/**\n * Ceiling for the escalating backoff. Without one, a permanently broken runner\n * (missing credential, bad install) would relaunch every 10s forever \u2014 ~8,600\n * restarts a day, each appending to vo-runner.log. That is a disk-fill risk on a\n * machine nobody is watching, which is the whole premise here. launchd throttles\n * to a 10s floor and systemd has StartLimitBurst for the same reason.\n */\nconst WINDOWS_MAX_BACKOFF_MS = 300_000;\n\n/**\n * Resolve the path to the vo-mcp runner command. Defaults to 'vo-mcp runner'\n * (assumes it's in PATH). Can be overridden for tests.\n */\nfunction resolveRunnerCommand(override?: string): string {\n return override ?? 'vo-mcp runner';\n}\n\n/** Preserve one command string as a single POSIX shell argument. */\nfunction quotePosixShellArgument(value: string): string {\n if (value.includes('\\u0000') || value.includes('\\r') || value.includes('\\n')) {\n throw new Error('Runner command must not contain NUL, carriage return, or newline characters.');\n }\n return `'${value.replace(/'/gu, `'\"'\"'`)}'`;\n}\n\n/**\n * Resolve the freedesktop config root used by both the systemd user unit and\n * the runner's managed clone directory. XDG_CONFIG_HOME is valid only when it\n * is absolute; a relative value falls back to the documented ~/.config root.\n */\nfunction resolveLinuxConfigHome(\n home: string,\n env: Readonly<Record<string, string | undefined>>,\n): string {\n const configured = env['XDG_CONFIG_HOME']?.trim();\n return configured && isAbsolute(configured) ? configured : join(home, '.config');\n}\n\n/**\n * A managed launcher is current only when every byte matches the template.\n * Matching the command substring cannot distinguish an up-to-date launcher\n * from an older broken version that happens to invoke the same command.\n */\nfunction launcherIsCurrent(\n path: string,\n desiredContent: string,\n label: string,\n log: (msg: string) => void,\n): boolean {\n if (!existsSync(path)) return false;\n if (readFileSync(path, 'utf8') === desiredContent) return true;\n const backupPath = `${path}.backup-${Date.now()}`;\n copyFileSync(path, backupPath);\n log(` Backed up existing ${label} to: ${backupPath}`);\n return false;\n}\n\n/**\n * Windows: Install a launcher script in the Startup folder.\n *\n * Creates a .vbs supervisor that starts `vo-mcp runner` in a minimized,\n * non-activated window (WScript.Shell.Run window style 7). Style 0 (hidden) is\n * banned \u2014 see the Defender PowhidSubExec note in the module header. The Startup\n * folder is %APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\.\n */\nfunction installWindowsAutostart(runnerCommand: string, log: (msg: string) => void, env: Readonly<Record<string, string | undefined>>): void {\n const appData = env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');\n const startupDir = join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');\n mkdirSync(startupDir, { recursive: true });\n\n const launcherPath = join(startupDir, 'vo-runner.vbs');\n const legacyCmdPath = join(startupDir, 'vo-runner.cmd');\n\n // Migrate: drop the pre-.vbs minimized launcher so logon can't start two runners.\n // Best-effort \u2014 an ACL-blocked/locked .cmd must not prevent installing the .vbs.\n if (existsSync(legacyCmdPath)) {\n try {\n unlinkSync(legacyCmdPath);\n log(` Removed legacy minimized .cmd launcher: ${legacyCmdPath}`);\n } catch (error) {\n log(`\u26A0 Could not remove legacy launcher ${legacyCmdPath} (${error instanceof Error ? error.message : String(error)}); remove it manually to avoid a double start`);\n }\n }\n\n // Write the launcher. VBS escapes embedded double quotes by doubling them.\n // The minimized window discards stdio, so redirect the runner's output to the\n // same log file the macOS plist and Linux unit use \u2014 otherwise every\n // [orphan-sweep]/[orphan-reaper] warning on Windows lands nowhere.\n const runnerConsoleCommand = `cmd /c ${runnerCommand} >> \"%USERPROFILE%\\\\.claude\\\\vo-runner.log\" 2>&1`.replace(/\"/g, '\"\"');\n // KEEPALIVE. Windows was the ONLY platform with no restart supervision: macOS\n // sets launchd `KeepAlive`, Linux sets systemd `Restart=on-failure`, and this\n // was `.Run(..., 0, False)` \u2014 fire-and-forget. If the runner died mid-session\n // nothing restarted it until the next logon, which is why a Windows host could\n // sit dead for hours (2026-07-25) with no way to reach it.\n //\n // `True` makes .Run WAIT for the process, turning this into a supervisor loop.\n // The sleep is a restart backoff so a runner failing instantly cannot spin hot.\n //\n // Window style 7 = MINIMIZED, not activated (NOT 0 = hidden). Style 0 triggers\n // Windows Defender's Trojan:Win32/PowhidSubExec.B heuristic (script host spawning\n // a hidden subprocess) \u2014 it blocked this pattern on a fleet box 2026-08-29, and a\n // Defender block at logon silently kills the runner. Style 7 is visible (so it\n // does not match the hidden-exec heuristic) but never steals foreground focus.\n //\n // The stop-sentinel is the escape hatch: to stop the loop cleanly, create the\n // sentinel file (or end wscript.exe). The file is CONSUMED (deleted) once\n // honoured, so a later logon starts clean instead of silently refusing.\n const launcherContent = `' Auto-start launcher for vo-mcp runner\n' Created by vo-mcp autostart installer\n' Keepalive supervisor: restarts the runner if it exits (parity with launchd\n' KeepAlive on macOS and systemd Restart=on-failure on Linux).\n' Runs the runner in a MINIMIZED (style 7), never a hidden (style 0) window:\n' hidden script-host exec trips Defender's PowhidSubExec.B heuristic and gets\n' blocked at logon. To stop: create %USERPROFILE%\\\\.claude\\\\vo-runner.stop, or\n' end wscript.exe.\nDim sh, fso, stopFile, backoff, startedAt, ranMs\nSet sh = CreateObject(\"WScript.Shell\")\nSet fso = CreateObject(\"Scripting.FileSystemObject\")\nsh.CurrentDirectory = sh.ExpandEnvironmentStrings(\"%USERPROFILE%\")\nsh.Environment(\"Process\")(\"VO_CODE_RUNNER_CLONES_ROOT\") = sh.ExpandEnvironmentStrings(\"%APPDATA%\\\\ai.algosuite.vo-runner\\\\clones\")\nstopFile = sh.ExpandEnvironmentStrings(\"%USERPROFILE%\\\\.claude\\\\vo-runner.stop\")\nbackoff = ${WINDOWS_RESTART_BACKOFF_MS}\nDo\n If fso.FileExists(stopFile) Then\n fso.DeleteFile stopFile\n WScript.Quit 0\n End If\n startedAt = Timer\n sh.Run \"${runnerConsoleCommand}\", 7, True\n ranMs = (Timer - startedAt) * 1000\n If ranMs < 0 Then ranMs = ${WINDOWS_HEALTHY_RUN_MS}\n If ranMs >= ${WINDOWS_HEALTHY_RUN_MS} Then\n backoff = ${WINDOWS_RESTART_BACKOFF_MS}\n ElseIf backoff < ${WINDOWS_MAX_BACKOFF_MS} Then\n backoff = backoff * 2\n End If\n WScript.Sleep backoff\nLoop\n`;\n\n if (launcherIsCurrent(launcherPath, launcherContent, 'launcher', log)) {\n log(`\u2713 Auto-start is already configured (Windows Startup folder)`);\n log(` Path: ${launcherPath}`);\n return;\n }\n\n writeFileSync(launcherPath, launcherContent, 'utf8');\n log(`\u2713 Installed Windows auto-start launcher`);\n log(` Path: ${launcherPath}`);\n log(` The runner will start hidden at next login.`);\n}\n\n/**\n * Windows: Uninstall the Startup folder launcher.\n */\nfunction uninstallWindowsAutostart(log: (msg: string) => void, env: Readonly<Record<string, string | undefined>>): void {\n const appData = env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');\n const startupDir = join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');\n // Remove the current .vbs launcher AND any legacy .cmd from older installs.\n const launcherPaths = [join(startupDir, 'vo-runner.vbs'), join(startupDir, 'vo-runner.cmd')];\n\n let removedAny = false;\n for (const launcherPath of launcherPaths) {\n if (!existsSync(launcherPath)) continue;\n unlinkSync(launcherPath);\n removedAny = true;\n log(`\u2713 Removed Windows auto-start launcher`);\n log(` Path: ${launcherPath}`);\n }\n if (!removedAny) {\n log(`\u2713 Auto-start launcher not found (already removed)`);\n }\n}\n\n/**\n * macOS: Install a launchd plist in ~/Library/LaunchAgents.\n *\n * The plist runs `vo-mcp runner` at login with RunAtLoad=true and\n * KeepAlive=true (restarts if it crashes).\n */\nasync function installMacAutostart(\n runnerCommand: string,\n log: (msg: string) => void,\n env: Readonly<Record<string, string | undefined>>,\n): Promise<void> {\n const home = env['HOME']?.trim() || homedir();\n const launchAgentsDir = join(home, 'Library', 'LaunchAgents');\n mkdirSync(launchAgentsDir, { recursive: true });\n\n const plistPath = join(launchAgentsDir, 'ai.algosuite.vo-runner.plist');\n\n // Parse the runner command into program + args for launchd\n const parts = runnerCommand.split(/\\s+/);\n const program = parts[0] ?? 'vo-mcp';\n const args = parts.length > 1 ? parts.slice(1) : ['runner'];\n\n const plistContent = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>ai.algosuite.vo-runner</string>\n <key>ProgramArguments</key>\n <array>\n <string>${program}</string>\n${args.map((a) => ` <string>${a}</string>`).join('\\n')}\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>WorkingDirectory</key>\n <string>${home}</string>\n <key>EnvironmentVariables</key>\n <dict>\n <key>VO_CODE_RUNNER_CLONES_ROOT</key>\n <string>${join(home, 'Library', 'Application Support', 'ai.algosuite.vo-runner', 'clones')}</string>\n </dict>\n <key>StandardOutPath</key>\n <string>${join(home, '.claude', 'vo-runner.log')}</string>\n <key>StandardErrorPath</key>\n <string>${join(home, '.claude', 'vo-runner-error.log')}</string>\n</dict>\n</plist>\n`;\n\n if (launcherIsCurrent(plistPath, plistContent, 'plist', log)) {\n log(`\u2713 Auto-start is already configured (launchd)`);\n log(` Path: ${plistPath}`);\n return;\n }\n\n writeFileSync(plistPath, plistContent, 'utf8');\n log(`\u2713 Installed launchd plist`);\n log(` Path: ${plistPath}`);\n\n // Load the plist with launchctl\n try {\n const { execSync } = await import('node:child_process');\n execSync(`launchctl load \"${plistPath}\"`, { stdio: 'ignore' });\n log(`\u2713 Loaded plist with launchctl (runner will start at next login)`);\n log(` Logs: ${join(home, '.claude', 'vo-runner.log')}`);\n } catch {\n log(`\u26A0 Failed to load plist with launchctl (you may need to load it manually)`);\n log(` Run: launchctl load \"${plistPath}\"`);\n }\n}\n\n/**\n * macOS: Uninstall the launchd plist.\n */\nasync function uninstallMacAutostart(\n log: (msg: string) => void,\n env: Readonly<Record<string, string | undefined>>,\n): Promise<void> {\n const home = env['HOME']?.trim() || homedir();\n const launchAgentsDir = join(home, 'Library', 'LaunchAgents');\n const plistPath = join(launchAgentsDir, 'ai.algosuite.vo-runner.plist');\n\n if (!existsSync(plistPath)) {\n log(`\u2713 Auto-start plist not found (already removed)`);\n return;\n }\n\n // Unload the plist with launchctl\n try {\n const { execSync } = await import('node:child_process');\n execSync(`launchctl unload \"${plistPath}\"`, { stdio: 'ignore' });\n log(`\u2713 Unloaded plist with launchctl`);\n } catch {\n log(`\u26A0 Failed to unload plist with launchctl (continuing anyway)`);\n }\n\n unlinkSync(plistPath);\n log(`\u2713 Removed launchd plist`);\n log(` Path: ${plistPath}`);\n}\n\n/**\n * Linux: Install a systemd USER unit at\n * $XDG_CONFIG_HOME/systemd/user/vo-runner.service (default ~/.config),\n * then enable + start it (`systemctl --user enable --now`). Restart=on-failure\n * mirrors the macOS KeepAlive. ExecStart runs via a login shell so the npm-global\n * `vo-mcp` resolves under systemd's minimal PATH.\n */\nasync function installLinuxAutostart(\n runnerCommand: string,\n log: (msg: string) => void,\n env: Readonly<Record<string, string | undefined>>,\n): Promise<void> {\n const home = env['HOME']?.trim() || homedir();\n const configHome = resolveLinuxConfigHome(home, env);\n const unitDir = join(configHome, 'systemd', 'user');\n mkdirSync(unitDir, { recursive: true });\n const unitPath = join(unitDir, 'vo-runner.service');\n\n const logFile = join(home, '.claude', 'vo-runner.log');\n const errFile = join(home, '.claude', 'vo-runner-error.log');\n const quotedRunnerCommand = quotePosixShellArgument(runnerCommand);\n mkdirSync(join(home, '.claude'), { recursive: true });\n\n const unit = `[Unit]\nDescription=AlgoHQ Code Runner (vo-mcp)\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=${home}\nEnvironment=\"VO_CODE_RUNNER_CLONES_ROOT=${join(configHome, 'ai.algosuite.vo-runner', 'clones')}\"\nExecStart=/bin/sh -lc ${quotedRunnerCommand}\nRestart=on-failure\nRestartSec=10\nStandardOutput=append:${logFile}\nStandardError=append:${errFile}\n\n[Install]\nWantedBy=default.target\n`;\n if (launcherIsCurrent(unitPath, unit, 'unit', log)) {\n log(`\u2713 Auto-start is already configured (systemd user unit)`);\n log(` Path: ${unitPath}`);\n return;\n }\n writeFileSync(unitPath, unit, 'utf8');\n log(`\u2713 Installed systemd user unit`);\n log(` Path: ${unitPath}`);\n\n // In tests, write the unit but NEVER actually enable/start a real service.\n if (process.env['VITEST']) {\n log(` (test mode: skipping systemctl enable)`);\n return;\n }\n try {\n const { execSync } = await import('node:child_process');\n execSync('systemctl --user daemon-reload', { stdio: 'ignore' });\n execSync('systemctl --user enable --now vo-runner.service', { stdio: 'ignore' });\n log(`\u2713 Enabled + started vo-runner.service (starts at login)`);\n log(` Logs: ${logFile}`);\n } catch {\n log(`\u26A0 Could not enable via systemctl (enable it manually):`);\n log(` systemctl --user daemon-reload && systemctl --user enable --now vo-runner.service`);\n }\n}\n\n/**\n * Linux: Uninstall the systemd user unit.\n */\nasync function uninstallLinuxAutostart(\n log: (msg: string) => void,\n env: Readonly<Record<string, string | undefined>>,\n): Promise<void> {\n const home = env['HOME']?.trim() || homedir();\n const configHome = resolveLinuxConfigHome(home, env);\n const unitPath = join(configHome, 'systemd', 'user', 'vo-runner.service');\n if (!existsSync(unitPath)) {\n log(`\u2713 Auto-start unit not found (already removed)`);\n return;\n }\n if (!process.env['VITEST']) {\n try {\n const { execSync } = await import('node:child_process');\n execSync('systemctl --user disable --now vo-runner.service', { stdio: 'ignore' });\n log(`\u2713 Disabled + stopped vo-runner.service`);\n } catch {\n log(`\u26A0 Could not disable via systemctl (continuing anyway)`);\n }\n }\n unlinkSync(unitPath);\n log(`\u2713 Removed systemd user unit`);\n log(` Path: ${unitPath}`);\n}\n\n/**\n * Install auto-start for the runner daemon. Cross-platform.\n */\nexport async function installAutostart(opts: AutostartOptions = {}): Promise<void> {\n const log = opts.log ?? ((m: string) => console.error(m));\n const env = opts.env ?? process.env;\n const runnerCommand = resolveRunnerCommand(opts.runnerCommand);\n const plat = opts.platform ?? platform();\n\n if (plat === 'win32') {\n installWindowsAutostart(runnerCommand, log, env);\n } else if (plat === 'darwin') {\n await installMacAutostart(runnerCommand, log, env);\n } else if (plat === 'linux') {\n await installLinuxAutostart(runnerCommand, log, env);\n } else {\n log(`\u2717 Auto-start is not supported on platform: ${plat}`);\n log(` Supported platforms: win32 (Windows), darwin (macOS), linux (Linux)`);\n }\n}\n\n/**\n * Uninstall auto-start for the runner daemon. Cross-platform.\n */\nexport async function uninstallAutostart(opts: AutostartOptions = {}): Promise<void> {\n const log = opts.log ?? ((m: string) => console.error(m));\n const env = opts.env ?? process.env;\n const plat = opts.platform ?? platform();\n\n if (plat === 'win32') {\n uninstallWindowsAutostart(log, env);\n } else if (plat === 'darwin') {\n await uninstallMacAutostart(log, env);\n } else if (plat === 'linux') {\n await uninstallLinuxAutostart(log, env);\n } else {\n log(`\u2717 Auto-start is not supported on platform: ${plat}`);\n log(` Supported platforms: win32 (Windows), darwin (macOS), linux (Linux)`);\n }\n}\n", "#!/usr/bin/env node\n/**\n * `vo-mcp runner --install-autostart` / `--uninstall-autostart` CLI entrypoints.\n *\n * Registers or unregisters the runner daemon to start automatically at user login.\n */\nimport { installAutostart, uninstallAutostart } from './autostart.js';\n\nexport async function installAutostartCli(): Promise<void> {\n const log = (m: string): void => console.error(m);\n\n log('\u2501\u2501\u2501 vo-mcp runner auto-start installer \u2501\u2501\u2501\\n');\n\n await installAutostart({ log });\n\n log('\\nAuto-start is now configured. The runner will launch automatically at your next login.');\n log('To remove: vo-mcp runner --uninstall-autostart');\n}\n\nexport async function uninstallAutostartCli(): Promise<void> {\n const log = (m: string): void => console.error(m);\n\n log('\u2501\u2501\u2501 vo-mcp runner auto-start uninstaller \u2501\u2501\u2501\\n');\n\n await uninstallAutostart({ log });\n\n log('\\nAuto-start has been removed. The runner will no longer launch automatically.');\n log('To reinstall: vo-mcp runner --install-autostart');\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }