@algosuite/vo-mcp 0.2.0-beta.2 → 0.2.0-beta.4

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
@@ -1,153 +1,154 @@
1
- # @algosuite/vo-mcp
2
-
3
- Virtual Office MCP server — the open protocol surface that exposes VO's consensus and ratchet tool family to any MCP-capable LLM client (Claude Code, Claude Desktop, Cursor, Continue, Codex, etc.).
4
-
5
- **Status:** Phase 2. Stdio transport. **20 tools registered** (source of truth: `src/server.ts` `buildToolRegistry()`). The static-ratchet tools (`vo_check_assertion_strength`, `vo_check_ratchets`) run locally today; the consensus-routed tools fall back to `{ verdict: "unimplemented", ... }` until engine credentials are present; the heal-family, PR-admin, and session-state tools forward to the vo-control-plane admin proxy in cloud mode. `vo_review_merge` is a **read-only consensus pre-merge review** (the verify-before-act gate for the Command Center — never merges). The shape is stable; the institutional-knowledge content is loaded from closed packages.
6
-
7
- This package is intentionally **shell-only**. Per `docs/handoffs/vo-mcp-server-2026-05-21.md` §C-1: tool *definitions* (schemas, names, descriptions) ship openly. Tool *implementations* that encode institutional knowledge (specific ratchet thresholds, consensus prompt content, architectural-defaults knowledge base) live behind the cloud or in closed companion packages.
8
-
9
- ## Tools
10
-
11
- | Name | Phase 1 status | Description |
12
- | --- | --- | --- |
13
- | `vo_check_assertion_strength` | Implemented (stub ratchet) | Score a test file's assertions 0-100 with per-assertion findings. |
14
- | `vo_check_hollow_test` | Stub | Detect tests that pass without verifying product truth. |
15
- | `vo_verify_answer` | Stub | Semantic-equivalence comparison of expected vs observed. |
16
- | `vo_consensus_judgment` | Stub + logging skeleton | Submit a prompt to multiple models, return synthesized verdict. **Logs every call** (V1 launch gate #7). |
17
- | `vo_architecture_review` | Stub | Senior-architect review of a diff against project defaults. |
18
-
19
- All tools follow the same response envelope:
20
-
21
- ```jsonc
22
- {
23
- "tool": "vo_check_assertion_strength",
24
- "schema_version": 1,
25
- "cache": { "hit": false, "key": "<sha256 hex>" },
26
- "payload": { /* tool-specific */ }
27
- }
28
- ```
29
-
30
- ## V1 launch gates implemented in Phase 1
31
-
32
- - **Gate #7 — consensus-call logging.** Every tool invocation appends a JSONL line to `~/.claude/vo-mcp-events.jsonl` (override with `$VO_MCP_EVENTS_PATH`). Schema is locked: see `src/types.ts` `ConsensusCallEvent`. `per_model_verdicts` and `synthesized_verdict` are empty/null in the scaffold; the schema shape is the deliverable.
33
- - **Gate #8 — content-hash cache.** Every tool invocation computes `sha256(canonicalize(tool_input))`. Repeat calls with the same canonicalized input return the cached envelope with `cache.hit = true`. Backed by sqlite at `~/.claude/vo-mcp-cache.db` (override with `$VO_MCP_DB_PATH`).
34
-
35
- ## Building & testing
36
-
37
- ```sh
38
- cd packages/vo-mcp
39
- pnpm install
40
- pnpm run build # tsc -p tsconfig.json
41
- pnpm run typecheck # tsc --noEmit
42
- pnpm test # vitest run
43
- ```
44
-
45
- The integration test (`test/integration/stdio-roundtrip.test.ts`) spawns the built CLI, exchanges JSON-RPC over stdio, and asserts:
46
-
47
- - `initialize` + `tools/list` returns the 20 expected tools.
48
- - `vo_check_assertion_strength` runs end-to-end and writes a JSONL event line.
49
- - A repeat call with identical input hits the cache (`cache.hit = true`).
50
- - `vo_consensus_judgment` returns `unimplemented` and still writes its event line.
51
-
52
- ## Running the server
53
-
54
- The CLI is a stdio MCP server. Don't run it interactively; register it in your MCP client.
55
-
56
- ### Claude Desktop / Claude Code
57
-
58
- Add to `~/.claude/mcp_settings.json` (Claude Desktop) or your Claude Code config:
59
-
60
- ```jsonc
61
- {
62
- "mcpServers": {
63
- "vo": {
64
- "command": "node",
65
- "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"],
66
- "env": {
67
- // Optional overrides — defaults shown below
68
- // "VO_MCP_EVENTS_PATH": "/path/to/events.jsonl",
69
- // "VO_MCP_DB_PATH": "/path/to/cache.db"
70
- }
71
- }
72
- }
73
- }
74
- ```
75
-
76
- ### Cursor
77
-
78
- Cursor reads MCP servers from its global config (Settings → Features → MCP). Add an entry:
79
-
80
- ```jsonc
81
- {
82
- "mcpServers": {
83
- "vo": {
84
- "command": "node",
85
- "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"]
86
- }
87
- }
88
- }
89
- ```
90
-
91
- After saving, restart Cursor. The tools appear under the `@vo` namespace in the chat composer's tool picker.
92
-
93
- ### Continue
94
-
95
- Continue reads MCP servers from `~/.continue/config.json` under the `experimental.modelContextProtocolServer` (or v2 `mcpServers`) section, depending on the Continue release on your machine. Verify the current shape against Continue's docs; the launch command itself is the same:
96
-
97
- ```jsonc
98
- {
99
- "command": "node",
100
- "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"]
101
- }
102
- ```
103
-
104
- > **Cross-vendor smoke.** A captured cross-vendor smoke now exists — see
105
- > [`CROSS_VENDOR_SMOKE.md`](./CROSS_VENDOR_SMOKE.md). Run `pnpm run smoke:cross-vendor`
106
- > to drive the built server through the full MCP lifecycle under each vendor's
107
- > real client parameters (protocol-version negotiation, capabilities, client
108
- > identity, schema validity) and reproduce the recorded transcripts. That doc
109
- > also carries the operator checklist for capturing real Cursor/Continue/Codex
110
- > GUI sessions. The integration test in this package exercises the generic
111
- > single-version stdio surface that any MCP-spec-compliant client uses.
112
-
113
- ## Environment variables
114
-
115
- | Var | Default | Purpose |
116
- | --- | --- | --- |
117
- | `VO_MCP_EVENTS_PATH` | `~/.claude/vo-mcp-events.jsonl` | Where consensus-call event lines are appended. |
118
- | `VO_MCP_DB_PATH` | `~/.claude/vo-mcp-cache.db` | sqlite cache file for content-hash cache. |
119
- | `VO_CONTROL_PLANE_URL` | _(unset → cloud off)_ | vo-control-plane base URL. Set together with the admin token to activate **cloud mode** — the admin-proxy tools (concierge dispatch, heal/PR families) forward to vo-control-plane instead of returning `unimplemented` stubs. |
120
- | `VO_CONTROL_PLANE_ADMIN_TOKEN` | _(unset → cloud off)_ | Bearer token vo-control-plane validates. Required with the URL above; setting only one is a configuration error. |
121
- | `VO_ADMIN_CALLABLES_READONLY` | `false` | When truthy (`1`/`true`), cloud mode runs **read-only**: read-only tools (concierge dispatch + the list/get diagnostics) forward, but the heal/PR **write** tools (merge, reject, trigger-heal, ) stay gated to their stubs. Lets you expose concierge without exposing destructive admin actions. |
122
-
123
- ## Adding a new tool (Phase 2+)
124
-
125
- 1. Create `src/tools/<tool>.ts` exporting:
126
- - `TOOL_NAME` constant
127
- - `description` (writes the discovery copy that cross-vendor clients show)
128
- - `inputSchema` (JSON Schema for MCP `tools/list`)
129
- - `handle<Tool>(deps, rawInput)` (validate input, hit cache, call backend, log event, return `jsonContent(envelope)`)
130
- 2. Add it to `buildToolRegistry()` in `src/server.ts`.
131
- 3. Add tests under `test/tools/<tool>.test.ts`.
132
- 4. Update this README's tool table and `EXTRACTION_AUDIT.md`.
133
-
134
- The tool handler MUST:
135
-
136
- - Hash the canonicalized input via `deps.cache.keyFor(TOOL_NAME, rawInput)`.
137
- - Read the cache before any expensive work; return cached envelope with `cache.hit = true` if present.
138
- - Append a `ConsensusCallEvent` via `deps.events.append(buildBaseEvent(...))` on every invocation.
139
- - Sanitize inputs before logging (`sanitizeExcerpt` is applied automatically in `buildBaseEvent`).
140
- - Return a `ToolResultEnvelope<TPayload>` JSON-encoded in a single text content block.
141
-
142
- ## Hard rules (handoff §C)
143
-
144
- This package complies with:
145
-
146
- 1. **Open shell, closed content.** Stub ratchet client lives here; real thresholds load from `@algosuite/ratchets-generic` in Phase 2 (see `EXTRACTION_AUDIT.md`).
147
- 2. **Cross-vendor by design.** No Claude-specific assumptions; tool schemas are MCP-spec compliant.
148
- 3. **Content-hash cache every consensus call.** Implemented at `src/cache/sqlite-cache.ts`.
149
- 4. **Log every consensus call.** Implemented at `src/logging/events-writer.ts`.
150
- 5. **TypeScript strict, zero `any`, zero `@ts-ignore`.**
151
- 6. **No PII in logs.** `sanitizeExcerpt` strips JWTs, sk- keys, bearer tokens, and email addresses before logging.
152
-
153
- See `EXTRACTION_AUDIT.md` for what's stub, what's real, and where the moat lives.
1
+ # @algosuite/vo-mcp
2
+
3
+ Virtual Office MCP server — the open protocol surface that exposes VO's consensus and ratchet tool family to any MCP-capable LLM client (Claude Code, Claude Desktop, Cursor, Continue, Codex, etc.).
4
+
5
+ **Status:** Phase 2. Stdio transport. **20 tools registered** (source of truth: `src/server.ts` `buildToolRegistry()`). The static-ratchet tools (`vo_check_assertion_strength`, `vo_check_ratchets`) run locally today; the consensus-routed tools fall back to `{ verdict: "unimplemented", ... }` until engine credentials are present; the heal-family, PR-admin, and session-state tools forward to the vo-control-plane admin proxy in cloud mode. `vo_review_merge` is a **read-only consensus pre-merge review** (the verify-before-act gate for the Command Center — never merges). The shape is stable; the institutional-knowledge content is loaded from closed packages.
6
+
7
+ This package is intentionally **shell-only**. Per `docs/handoffs/vo-mcp-server-2026-05-21.md` §C-1: tool *definitions* (schemas, names, descriptions) ship openly. Tool *implementations* that encode institutional knowledge (specific ratchet thresholds, consensus prompt content, architectural-defaults knowledge base) live behind the cloud or in closed companion packages.
8
+
9
+ ## Tools
10
+
11
+ | Name | Phase 1 status | Description |
12
+ | --- | --- | --- |
13
+ | `vo_check_assertion_strength` | Implemented (stub ratchet) | Score a test file's assertions 0-100 with per-assertion findings. |
14
+ | `vo_check_hollow_test` | Stub | Detect tests that pass without verifying product truth. |
15
+ | `vo_verify_answer` | Stub | Semantic-equivalence comparison of expected vs observed. |
16
+ | `vo_consensus_judgment` | Stub + logging skeleton | Submit a prompt to multiple models, return synthesized verdict. **Logs every call** (V1 launch gate #7). |
17
+ | `vo_architecture_review` | Stub | Senior-architect review of a diff against project defaults. |
18
+
19
+ All tools follow the same response envelope:
20
+
21
+ ```jsonc
22
+ {
23
+ "tool": "vo_check_assertion_strength",
24
+ "schema_version": 1,
25
+ "cache": { "hit": false, "key": "<sha256 hex>" },
26
+ "payload": { /* tool-specific */ }
27
+ }
28
+ ```
29
+
30
+ ## V1 launch gates implemented in Phase 1
31
+
32
+ - **Gate #7 — consensus-call logging.** Every tool invocation appends a JSONL line to `~/.claude/vo-mcp-events.jsonl` (override with `$VO_MCP_EVENTS_PATH`). Schema is locked: see `src/types.ts` `ConsensusCallEvent`. `per_model_verdicts` and `synthesized_verdict` are empty/null in the scaffold; the schema shape is the deliverable.
33
+ - **Gate #8 — content-hash cache.** Every tool invocation computes `sha256(canonicalize(tool_input))`. Repeat calls with the same canonicalized input return the cached envelope with `cache.hit = true`. Backed by sqlite at `~/.claude/vo-mcp-cache.db` (override with `$VO_MCP_DB_PATH`).
34
+
35
+ ## Building & testing
36
+
37
+ ```sh
38
+ cd packages/vo-mcp
39
+ pnpm install
40
+ pnpm run build # tsc -p tsconfig.json
41
+ pnpm run typecheck # tsc --noEmit
42
+ pnpm test # vitest run
43
+ ```
44
+
45
+ The integration test (`test/integration/stdio-roundtrip.test.ts`) spawns the built CLI, exchanges JSON-RPC over stdio, and asserts:
46
+
47
+ - `initialize` + `tools/list` returns the 20 expected tools.
48
+ - `vo_check_assertion_strength` runs end-to-end and writes a JSONL event line.
49
+ - A repeat call with identical input hits the cache (`cache.hit = true`).
50
+ - `vo_consensus_judgment` returns `unimplemented` and still writes its event line.
51
+
52
+ ## Running the server
53
+
54
+ The CLI is a stdio MCP server. Don't run it interactively; register it in your MCP client.
55
+
56
+ ### Claude Desktop / Claude Code
57
+
58
+ Add to `~/.claude/mcp_settings.json` (Claude Desktop) or your Claude Code config:
59
+
60
+ ```jsonc
61
+ {
62
+ "mcpServers": {
63
+ "vo": {
64
+ "command": "node",
65
+ "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"],
66
+ "env": {
67
+ // Optional overrides — defaults shown below
68
+ // "VO_MCP_EVENTS_PATH": "/path/to/events.jsonl",
69
+ // "VO_MCP_DB_PATH": "/path/to/cache.db"
70
+ }
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Cursor
77
+
78
+ Cursor reads MCP servers from its global config (Settings → Features → MCP). Add an entry:
79
+
80
+ ```jsonc
81
+ {
82
+ "mcpServers": {
83
+ "vo": {
84
+ "command": "node",
85
+ "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"]
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ After saving, restart Cursor. The tools appear under the `@vo` namespace in the chat composer's tool picker.
92
+
93
+ ### Continue
94
+
95
+ Continue reads MCP servers from `~/.continue/config.json` under the `experimental.modelContextProtocolServer` (or v2 `mcpServers`) section, depending on the Continue release on your machine. Verify the current shape against Continue's docs; the launch command itself is the same:
96
+
97
+ ```jsonc
98
+ {
99
+ "command": "node",
100
+ "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"]
101
+ }
102
+ ```
103
+
104
+ > **Cross-vendor smoke.** A captured cross-vendor smoke now exists — see
105
+ > [`CROSS_VENDOR_SMOKE.md`](./CROSS_VENDOR_SMOKE.md). Run `pnpm run smoke:cross-vendor`
106
+ > to drive the built server through the full MCP lifecycle under each vendor's
107
+ > real client parameters (protocol-version negotiation, capabilities, client
108
+ > identity, schema validity) and reproduce the recorded transcripts. That doc
109
+ > also carries the operator checklist for capturing real Cursor/Continue/Codex
110
+ > GUI sessions. The integration test in this package exercises the generic
111
+ > single-version stdio surface that any MCP-spec-compliant client uses.
112
+
113
+ ## Environment variables
114
+
115
+ | Var | Default | Purpose |
116
+ | --- | --- | --- |
117
+ | `VO_MCP_EVENTS_PATH` | `~/.claude/vo-mcp-events.jsonl` | Where consensus-call event lines are appended. |
118
+ | `VO_MCP_DB_PATH` | `~/.claude/vo-mcp-cache.db` | sqlite cache file for content-hash cache. |
119
+ | `VO_CONTROL_PLANE_URL` | _(unset → cloud off)_ | vo-control-plane base URL. Set together with the admin token and tenant ID to activate **cloud mode** — the admin-proxy tools (concierge dispatch, heal/PR families) forward to vo-control-plane instead of returning `unimplemented` stubs. |
120
+ | `VO_CONTROL_PLANE_ADMIN_TOKEN` | _(unset → cloud off)_ | Bearer token vo-control-plane validates. Required with the URL and tenant ID above; setting only some is a configuration error. |
121
+ | `VO_TENANT_ID` | _(unset cloud off)_ | Tenant UUID. Required with the control-plane URL and admin token above to enable cloud mode. Interactive agents (Claude Code, Cursor, Codex, Continue) auto-allocate their session on first report and appear on the live fleet whiteboard. |
122
+ | `VO_ADMIN_CALLABLES_READONLY` | `false` | When truthy (`1`/`true`), cloud mode runs **read-only**: read-only tools (concierge dispatch + the list/get diagnostics) forward, but the heal/PR **write** tools (merge, reject, trigger-heal, …) stay gated to their stubs. Lets you expose concierge without exposing destructive admin actions. |
123
+
124
+ ## Adding a new tool (Phase 2+)
125
+
126
+ 1. Create `src/tools/<tool>.ts` exporting:
127
+ - `TOOL_NAME` constant
128
+ - `description` (writes the discovery copy that cross-vendor clients show)
129
+ - `inputSchema` (JSON Schema for MCP `tools/list`)
130
+ - `handle<Tool>(deps, rawInput)` (validate input, hit cache, call backend, log event, return `jsonContent(envelope)`)
131
+ 2. Add it to `buildToolRegistry()` in `src/server.ts`.
132
+ 3. Add tests under `test/tools/<tool>.test.ts`.
133
+ 4. Update this README's tool table and `EXTRACTION_AUDIT.md`.
134
+
135
+ The tool handler MUST:
136
+
137
+ - Hash the canonicalized input via `deps.cache.keyFor(TOOL_NAME, rawInput)`.
138
+ - Read the cache before any expensive work; return cached envelope with `cache.hit = true` if present.
139
+ - Append a `ConsensusCallEvent` via `deps.events.append(buildBaseEvent(...))` on every invocation.
140
+ - Sanitize inputs before logging (`sanitizeExcerpt` is applied automatically in `buildBaseEvent`).
141
+ - Return a `ToolResultEnvelope<TPayload>` JSON-encoded in a single text content block.
142
+
143
+ ## Hard rules (handoff §C)
144
+
145
+ This package complies with:
146
+
147
+ 1. **Open shell, closed content.** Stub ratchet client lives here; real thresholds load from `@algosuite/ratchets-generic` in Phase 2 (see `EXTRACTION_AUDIT.md`).
148
+ 2. **Cross-vendor by design.** No Claude-specific assumptions; tool schemas are MCP-spec compliant.
149
+ 3. **Content-hash cache every consensus call.** Implemented at `src/cache/sqlite-cache.ts`.
150
+ 4. **Log every consensus call.** Implemented at `src/logging/events-writer.ts`.
151
+ 5. **TypeScript strict, zero `any`, zero `@ts-ignore`.**
152
+ 6. **No PII in logs.** `sanitizeExcerpt` strips JWTs, sk- keys, bearer tokens, and email addresses before logging.
153
+
154
+ See `EXTRACTION_AUDIT.md` for what's stub, what's real, and where the moat lives.
package/bin/vo-mcp CHANGED
@@ -1,38 +1,38 @@
1
- #!/usr/bin/env node
2
- /**
3
- * vo-mcp CLI dispatcher.
4
- *
5
- * Usage:
6
- * vo-mcp # MCP stdio server (default)
7
- * vo-mcp install # one-command installer
8
- * vo-mcp login # credential login (browser loopback)
9
- * vo-mcp pair # device-code pairing (enter a code in the web)
10
- * vo-mcp runner # agent runner daemon
11
- * vo-mcp runner --install-autostart # register runner to start at login
12
- * vo-mcp runner --uninstall-autostart # remove auto-start registration
13
- */
14
-
15
- const command = process.argv[2];
16
- const subcommand = process.argv[3];
17
-
18
- if (command === 'install') {
19
- import('../dist/install-cli.js');
20
- } else if (command === 'login') {
21
- import('../dist/login-cli.js');
22
- } else if (command === 'pair') {
23
- import('../dist/pair-cli.js');
24
- } else if (command === 'set-key') {
25
- import('../dist/set-key-cli.js');
26
- } else if (command === 'runner') {
27
- if (subcommand === '--install-autostart') {
28
- import('../dist/autostart-cli.js').then((m) => m.installAutostartCli());
29
- } else if (subcommand === '--uninstall-autostart') {
30
- import('../dist/autostart-cli.js').then((m) => m.uninstallAutostartCli());
31
- } else {
32
- // Bring-your-own runner daemon (bundled by scripts/bundle.mjs into dist/).
33
- import('../dist/runner-cli.js');
34
- }
35
- } else {
36
- // Default: MCP stdio server
37
- import('../dist/cli.js');
38
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * vo-mcp CLI dispatcher.
4
+ *
5
+ * Usage:
6
+ * vo-mcp # MCP stdio server (default)
7
+ * vo-mcp install # one-command installer
8
+ * vo-mcp login # credential login (browser loopback)
9
+ * vo-mcp pair # device-code pairing (enter a code in the web)
10
+ * vo-mcp runner # agent runner daemon
11
+ * vo-mcp runner --install-autostart # register runner to start at login
12
+ * vo-mcp runner --uninstall-autostart # remove auto-start registration
13
+ */
14
+
15
+ const command = process.argv[2];
16
+ const subcommand = process.argv[3];
17
+
18
+ if (command === 'install') {
19
+ import('../dist/install-cli.js');
20
+ } else if (command === 'login') {
21
+ import('../dist/login-cli.js');
22
+ } else if (command === 'pair') {
23
+ import('../dist/pair-cli.js');
24
+ } else if (command === 'set-key') {
25
+ import('../dist/set-key-cli.js');
26
+ } else if (command === 'runner') {
27
+ if (subcommand === '--install-autostart') {
28
+ import('../dist/autostart-cli.js').then((m) => m.installAutostartCli());
29
+ } else if (subcommand === '--uninstall-autostart') {
30
+ import('../dist/autostart-cli.js').then((m) => m.uninstallAutostartCli());
31
+ } else {
32
+ // Bring-your-own runner daemon (bundled by scripts/bundle.mjs into dist/).
33
+ import('../dist/runner-cli.js');
34
+ }
35
+ } else {
36
+ // Default: MCP stdio server
37
+ import('../dist/cli.js');
38
+ }
@@ -117,6 +117,79 @@ async function uninstallMacAutostart(log) {
117
117
  log(`\u2713 Removed launchd plist`);
118
118
  log(` Path: ${plistPath}`);
119
119
  }
120
+ async function installLinuxAutostart(runnerCommand, log, env) {
121
+ const home = env["HOME"]?.trim() || homedir();
122
+ const unitDir = join(home, ".config", "systemd", "user");
123
+ mkdirSync(unitDir, { recursive: true });
124
+ const unitPath = join(unitDir, "vo-runner.service");
125
+ if (existsSync(unitPath)) {
126
+ const existing = readFileSync(unitPath, "utf8");
127
+ if (existing.includes(runnerCommand) || existing.includes("vo-mcp runner")) {
128
+ log(`\u2713 Auto-start is already configured (systemd user unit)`);
129
+ log(` Path: ${unitPath}`);
130
+ return;
131
+ }
132
+ const backupPath = `${unitPath}.backup-${Date.now()}`;
133
+ copyFileSync(unitPath, backupPath);
134
+ log(` Backed up existing unit to: ${backupPath}`);
135
+ }
136
+ const logFile = join(home, ".claude", "vo-runner.log");
137
+ const errFile = join(home, ".claude", "vo-runner-error.log");
138
+ mkdirSync(join(home, ".claude"), { recursive: true });
139
+ const unit = `[Unit]
140
+ Description=VO Code Runner (vo-mcp)
141
+ After=network-online.target
142
+ Wants=network-online.target
143
+
144
+ [Service]
145
+ Type=simple
146
+ ExecStart=/bin/sh -lc '${runnerCommand}'
147
+ Restart=on-failure
148
+ RestartSec=10
149
+ StandardOutput=append:${logFile}
150
+ StandardError=append:${errFile}
151
+
152
+ [Install]
153
+ WantedBy=default.target
154
+ `;
155
+ writeFileSync(unitPath, unit, "utf8");
156
+ log(`\u2713 Installed systemd user unit`);
157
+ log(` Path: ${unitPath}`);
158
+ if (process.env["VITEST"]) {
159
+ log(` (test mode: skipping systemctl enable)`);
160
+ return;
161
+ }
162
+ try {
163
+ const { execSync } = await import("node:child_process");
164
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
165
+ execSync("systemctl --user enable --now vo-runner.service", { stdio: "ignore" });
166
+ log(`\u2713 Enabled + started vo-runner.service (starts at login)`);
167
+ log(` Logs: ${logFile}`);
168
+ } catch {
169
+ log(`\u26A0 Could not enable via systemctl (enable it manually):`);
170
+ log(` systemctl --user daemon-reload && systemctl --user enable --now vo-runner.service`);
171
+ }
172
+ }
173
+ async function uninstallLinuxAutostart(log, env) {
174
+ const home = env["HOME"]?.trim() || homedir();
175
+ const unitPath = join(home, ".config", "systemd", "user", "vo-runner.service");
176
+ if (!existsSync(unitPath)) {
177
+ log(`\u2713 Auto-start unit not found (already removed)`);
178
+ return;
179
+ }
180
+ if (!process.env["VITEST"]) {
181
+ try {
182
+ const { execSync } = await import("node:child_process");
183
+ execSync("systemctl --user disable --now vo-runner.service", { stdio: "ignore" });
184
+ log(`\u2713 Disabled + stopped vo-runner.service`);
185
+ } catch {
186
+ log(`\u26A0 Could not disable via systemctl (continuing anyway)`);
187
+ }
188
+ }
189
+ unlinkSync(unitPath);
190
+ log(`\u2713 Removed systemd user unit`);
191
+ log(` Path: ${unitPath}`);
192
+ }
120
193
  async function installAutostart(opts = {}) {
121
194
  const log = opts.log ?? ((m) => console.error(m));
122
195
  const env = opts.env ?? process.env;
@@ -126,9 +199,11 @@ async function installAutostart(opts = {}) {
126
199
  installWindowsAutostart(runnerCommand, log, env);
127
200
  } else if (plat === "darwin") {
128
201
  await installMacAutostart(runnerCommand, log);
202
+ } else if (plat === "linux") {
203
+ await installLinuxAutostart(runnerCommand, log, env);
129
204
  } else {
130
205
  log(`\u2717 Auto-start is not supported on platform: ${plat}`);
131
- log(` Supported platforms: win32 (Windows), darwin (macOS)`);
206
+ log(` Supported platforms: win32 (Windows), darwin (macOS), linux (Linux)`);
132
207
  }
133
208
  }
134
209
  async function uninstallAutostart(opts = {}) {
@@ -139,9 +214,11 @@ async function uninstallAutostart(opts = {}) {
139
214
  uninstallWindowsAutostart(log, env);
140
215
  } else if (plat === "darwin") {
141
216
  await uninstallMacAutostart(log);
217
+ } else if (plat === "linux") {
218
+ await uninstallLinuxAutostart(log, env);
142
219
  } else {
143
220
  log(`\u2717 Auto-start is not supported on platform: ${plat}`);
144
- log(` Supported platforms: win32 (Windows), darwin (macOS)`);
221
+ log(` Supported platforms: win32 (Windows), darwin (macOS), linux (Linux)`);
145
222
  }
146
223
  }
147
224
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/autostart.ts", "../src/autostart-cli.ts"],
4
- "sourcesContent": ["/**\r\n * Cross-platform auto-start registration for `vo-mcp runner`.\r\n *\r\n * WINDOWS: Uses the user Startup folder, NOT Task Scheduler. Headless\r\n * `claude -p` (which the runner spawns) HANGS under the Task Scheduler\r\n * service session \u2014 it only runs from an interactive, Explorer-descended\r\n * session. The Startup folder launches minimized at user login.\r\n *\r\n * MACOS: Uses ~/Library/LaunchAgents (launchd) with RunAtLoad + KeepAlive.\r\n *\r\n * All operations are idempotent and create backups where applicable.\r\n */\r\nimport { homedir, platform } from 'node:os';\r\nimport { join } from 'node:path';\r\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, copyFileSync } from 'node:fs';\r\n\r\nexport interface AutostartOptions {\r\n readonly log?: (msg: string) => void;\r\n readonly runnerCommand?: string; // Override for tests\r\n readonly env?: Readonly<Record<string, string | undefined>>;\r\n}\r\n\r\n/**\r\n * Resolve the path to the vo-mcp runner command. Defaults to 'vo-mcp runner'\r\n * (assumes it's in PATH). Can be overridden for tests.\r\n */\r\nfunction resolveRunnerCommand(override?: string): string {\r\n return override ?? 'vo-mcp runner';\r\n}\r\n\r\n/**\r\n * Windows: Install a launcher script in the Startup folder.\r\n *\r\n * Creates a .cmd file that starts `vo-mcp runner` minimized. The Startup\r\n * folder is %APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\.\r\n */\r\nfunction installWindowsAutostart(runnerCommand: string, log: (msg: string) => void, env: Readonly<Record<string, string | undefined>>): void {\r\n const appData = env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');\r\n const startupDir = join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');\r\n mkdirSync(startupDir, { recursive: true });\r\n\r\n const launcherPath = join(startupDir, 'vo-runner.cmd');\r\n\r\n // Idempotent: if the launcher already exists with our content, skip\r\n if (existsSync(launcherPath)) {\r\n const existing = readFileSync(launcherPath, 'utf8');\r\n if (existing.includes('vo-mcp runner')) {\r\n log(`\u2713 Auto-start is already configured (Windows Startup folder)`);\r\n log(` Path: ${launcherPath}`);\r\n return;\r\n }\r\n // Backup if content differs\r\n const backupPath = `${launcherPath}.backup-${Date.now()}`;\r\n copyFileSync(launcherPath, backupPath);\r\n log(` Backed up existing launcher to: ${backupPath}`);\r\n }\r\n\r\n // Write the launcher. Uses `start /min` to launch minimized.\r\n const launcherContent = `@echo off\r\nREM Auto-start launcher for vo-mcp runner\r\nREM Created by vo-mcp autostart installer\r\nstart /min cmd /c \"${runnerCommand}\"\r\n`;\r\n\r\n writeFileSync(launcherPath, launcherContent, 'utf8');\r\n log(`\u2713 Installed Windows auto-start launcher`);\r\n log(` Path: ${launcherPath}`);\r\n log(` The runner will start minimized at next login.`);\r\n}\r\n\r\n/**\r\n * Windows: Uninstall the Startup folder launcher.\r\n */\r\nfunction uninstallWindowsAutostart(log: (msg: string) => void, env: Readonly<Record<string, string | undefined>>): void {\r\n const appData = env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');\r\n const startupDir = join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');\r\n const launcherPath = join(startupDir, 'vo-runner.cmd');\r\n\r\n if (!existsSync(launcherPath)) {\r\n log(`\u2713 Auto-start launcher not found (already removed)`);\r\n return;\r\n }\r\n\r\n unlinkSync(launcherPath);\r\n log(`\u2713 Removed Windows auto-start launcher`);\r\n log(` Path: ${launcherPath}`);\r\n}\r\n\r\n/**\r\n * macOS: Install a launchd plist in ~/Library/LaunchAgents.\r\n *\r\n * The plist runs `vo-mcp runner` at login with RunAtLoad=true and\r\n * KeepAlive=true (restarts if it crashes).\r\n */\r\nasync function installMacAutostart(runnerCommand: string, log: (msg: string) => void): Promise<void> {\r\n const launchAgentsDir = join(homedir(), 'Library', 'LaunchAgents');\r\n mkdirSync(launchAgentsDir, { recursive: true });\r\n\r\n const plistPath = join(launchAgentsDir, 'ai.algosuite.vo-runner.plist');\r\n\r\n // Idempotent: if the plist already exists with our content, skip\r\n if (existsSync(plistPath)) {\r\n const existing = readFileSync(plistPath, 'utf8');\r\n if (existing.includes('vo-mcp runner')) {\r\n log(`\u2713 Auto-start is already configured (launchd)`);\r\n log(` Path: ${plistPath}`);\r\n return;\r\n }\r\n // Backup if content differs\r\n const backupPath = `${plistPath}.backup-${Date.now()}`;\r\n copyFileSync(plistPath, backupPath);\r\n log(` Backed up existing plist to: ${backupPath}`);\r\n }\r\n\r\n // Parse the runner command into program + args for launchd\r\n const parts = runnerCommand.split(/\\s+/);\r\n const program = parts[0] ?? 'vo-mcp';\r\n const args = parts.length > 1 ? parts.slice(1) : ['runner'];\r\n\r\n const plistContent = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n<plist version=\"1.0\">\r\n<dict>\r\n <key>Label</key>\r\n <string>ai.algosuite.vo-runner</string>\r\n <key>ProgramArguments</key>\r\n <array>\r\n <string>${program}</string>\r\n${args.map((a) => ` <string>${a}</string>`).join('\\n')}\r\n </array>\r\n <key>RunAtLoad</key>\r\n <true/>\r\n <key>KeepAlive</key>\r\n <true/>\r\n <key>StandardOutPath</key>\r\n <string>${join(homedir(), '.claude', 'vo-runner.log')}</string>\r\n <key>StandardErrorPath</key>\r\n <string>${join(homedir(), '.claude', 'vo-runner-error.log')}</string>\r\n</dict>\r\n</plist>\r\n`;\r\n\r\n writeFileSync(plistPath, plistContent, 'utf8');\r\n log(`\u2713 Installed launchd plist`);\r\n log(` Path: ${plistPath}`);\r\n\r\n // Load the plist with launchctl\r\n try {\r\n const { execSync } = await import('node:child_process');\r\n execSync(`launchctl load \"${plistPath}\"`, { stdio: 'ignore' });\r\n log(`\u2713 Loaded plist with launchctl (runner will start at next login)`);\r\n log(` Logs: ${join(homedir(), '.claude', 'vo-runner.log')}`);\r\n } catch {\r\n log(`\u26A0 Failed to load plist with launchctl (you may need to load it manually)`);\r\n log(` Run: launchctl load \"${plistPath}\"`);\r\n }\r\n}\r\n\r\n/**\r\n * macOS: Uninstall the launchd plist.\r\n */\r\nasync function uninstallMacAutostart(log: (msg: string) => void): Promise<void> {\r\n const launchAgentsDir = join(homedir(), 'Library', 'LaunchAgents');\r\n const plistPath = join(launchAgentsDir, 'ai.algosuite.vo-runner.plist');\r\n\r\n if (!existsSync(plistPath)) {\r\n log(`\u2713 Auto-start plist not found (already removed)`);\r\n return;\r\n }\r\n\r\n // Unload the plist with launchctl\r\n try {\r\n const { execSync } = await import('node:child_process');\r\n execSync(`launchctl unload \"${plistPath}\"`, { stdio: 'ignore' });\r\n log(`\u2713 Unloaded plist with launchctl`);\r\n } catch {\r\n log(`\u26A0 Failed to unload plist with launchctl (continuing anyway)`);\r\n }\r\n\r\n unlinkSync(plistPath);\r\n log(`\u2713 Removed launchd plist`);\r\n log(` Path: ${plistPath}`);\r\n}\r\n\r\n/**\r\n * Install auto-start for the runner daemon. Cross-platform.\r\n */\r\nexport async function installAutostart(opts: AutostartOptions = {}): Promise<void> {\r\n const log = opts.log ?? ((m: string) => console.error(m));\r\n const env = opts.env ?? process.env;\r\n const runnerCommand = resolveRunnerCommand(opts.runnerCommand);\r\n const plat = platform();\r\n\r\n if (plat === 'win32') {\r\n installWindowsAutostart(runnerCommand, log, env);\r\n } else if (plat === 'darwin') {\r\n await installMacAutostart(runnerCommand, log);\r\n } else {\r\n log(`\u2717 Auto-start is not supported on platform: ${plat}`);\r\n log(` Supported platforms: win32 (Windows), darwin (macOS)`);\r\n }\r\n}\r\n\r\n/**\r\n * Uninstall auto-start for the runner daemon. Cross-platform.\r\n */\r\nexport async function uninstallAutostart(opts: AutostartOptions = {}): Promise<void> {\r\n const log = opts.log ?? ((m: string) => console.error(m));\r\n const env = opts.env ?? process.env;\r\n const plat = platform();\r\n\r\n if (plat === 'win32') {\r\n uninstallWindowsAutostart(log, env);\r\n } else if (plat === 'darwin') {\r\n await uninstallMacAutostart(log);\r\n } else {\r\n log(`\u2717 Auto-start is not supported on platform: ${plat}`);\r\n log(` Supported platforms: win32 (Windows), darwin (macOS)`);\r\n }\r\n}\r\n", "#!/usr/bin/env node\r\n/**\r\n * `vo-mcp runner --install-autostart` / `--uninstall-autostart` CLI entrypoints.\r\n *\r\n * Registers or unregisters the runner daemon to start automatically at user login.\r\n */\r\nimport { installAutostart, uninstallAutostart } from './autostart.js';\r\n\r\nexport async function installAutostartCli(): Promise<void> {\r\n const log = (m: string): void => console.error(m);\r\n\r\n log('\u2501\u2501\u2501 vo-mcp runner auto-start installer \u2501\u2501\u2501\\n');\r\n\r\n await installAutostart({ log });\r\n\r\n log('\\nAuto-start is now configured. The runner will launch automatically at your next login.');\r\n log('To remove: vo-mcp runner --uninstall-autostart');\r\n}\r\n\r\nexport async function uninstallAutostartCli(): Promise<void> {\r\n const log = (m: string): void => console.error(m);\r\n\r\n log('\u2501\u2501\u2501 vo-mcp runner auto-start uninstaller \u2501\u2501\u2501\\n');\r\n\r\n await uninstallAutostart({ log });\r\n\r\n log('\\nAuto-start has been removed. The runner will no longer launch automatically.');\r\n log('To reinstall: vo-mcp runner --install-autostart');\r\n}\r\n"],
5
- "mappings": "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",
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 minimized at user login.\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 { 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\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/**\n * Windows: Install a launcher script in the Startup folder.\n *\n * Creates a .cmd file that starts `vo-mcp runner` minimized. 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.cmd');\n\n // Idempotent: if the launcher already exists with our content, skip\n if (existsSync(launcherPath)) {\n const existing = readFileSync(launcherPath, 'utf8');\n if (existing.includes('vo-mcp runner')) {\n log(`\u2713 Auto-start is already configured (Windows Startup folder)`);\n log(` Path: ${launcherPath}`);\n return;\n }\n // Backup if content differs\n const backupPath = `${launcherPath}.backup-${Date.now()}`;\n copyFileSync(launcherPath, backupPath);\n log(` Backed up existing launcher to: ${backupPath}`);\n }\n\n // Write the launcher. Uses `start /min` to launch minimized.\n const launcherContent = `@echo off\nREM Auto-start launcher for vo-mcp runner\nREM Created by vo-mcp autostart installer\nstart /min cmd /c \"${runnerCommand}\"\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 minimized 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 const launcherPath = join(startupDir, 'vo-runner.cmd');\n\n if (!existsSync(launcherPath)) {\n log(`\u2713 Auto-start launcher not found (already removed)`);\n return;\n }\n\n unlinkSync(launcherPath);\n log(`\u2713 Removed Windows auto-start launcher`);\n log(` Path: ${launcherPath}`);\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(runnerCommand: string, log: (msg: string) => void): Promise<void> {\n const launchAgentsDir = join(homedir(), 'Library', 'LaunchAgents');\n mkdirSync(launchAgentsDir, { recursive: true });\n\n const plistPath = join(launchAgentsDir, 'ai.algosuite.vo-runner.plist');\n\n // Idempotent: if the plist already exists with our content, skip\n if (existsSync(plistPath)) {\n const existing = readFileSync(plistPath, 'utf8');\n if (existing.includes('vo-mcp runner')) {\n log(`\u2713 Auto-start is already configured (launchd)`);\n log(` Path: ${plistPath}`);\n return;\n }\n // Backup if content differs\n const backupPath = `${plistPath}.backup-${Date.now()}`;\n copyFileSync(plistPath, backupPath);\n log(` Backed up existing plist to: ${backupPath}`);\n }\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>StandardOutPath</key>\n <string>${join(homedir(), '.claude', 'vo-runner.log')}</string>\n <key>StandardErrorPath</key>\n <string>${join(homedir(), '.claude', 'vo-runner-error.log')}</string>\n</dict>\n</plist>\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(homedir(), '.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(log: (msg: string) => void): Promise<void> {\n const launchAgentsDir = join(homedir(), '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 ~/.config/systemd/user/vo-runner.service,\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 unitDir = join(home, '.config', 'systemd', 'user');\n mkdirSync(unitDir, { recursive: true });\n const unitPath = join(unitDir, 'vo-runner.service');\n\n if (existsSync(unitPath)) {\n const existing = readFileSync(unitPath, 'utf8');\n if (existing.includes(runnerCommand) || existing.includes('vo-mcp runner')) {\n log(`\u2713 Auto-start is already configured (systemd user unit)`);\n log(` Path: ${unitPath}`);\n return;\n }\n const backupPath = `${unitPath}.backup-${Date.now()}`;\n copyFileSync(unitPath, backupPath);\n log(` Backed up existing unit to: ${backupPath}`);\n }\n\n const logFile = join(home, '.claude', 'vo-runner.log');\n const errFile = join(home, '.claude', 'vo-runner-error.log');\n mkdirSync(join(home, '.claude'), { recursive: true });\n\n const unit = `[Unit]\nDescription=VO Code Runner (vo-mcp)\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=/bin/sh -lc '${runnerCommand}'\nRestart=on-failure\nRestartSec=10\nStandardOutput=append:${logFile}\nStandardError=append:${errFile}\n\n[Install]\nWantedBy=default.target\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 unitPath = join(home, '.config', '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 = platform();\n\n if (plat === 'win32') {\n installWindowsAutostart(runnerCommand, log, env);\n } else if (plat === 'darwin') {\n await installMacAutostart(runnerCommand, log);\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 = platform();\n\n if (plat === 'win32') {\n uninstallWindowsAutostart(log, env);\n } else if (plat === 'darwin') {\n await uninstallMacAutostart(log);\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
+ "mappings": "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",
6
6
  "names": []
7
7
  }