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

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,178 @@
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
+ AlgoHQ MCP server — the open protocol surface that exposes HQ's consensus and ratchet tool family to any MCP-capable LLM client (Claude Code, Claude Desktop, Cursor, Continue, Codex, etc.).
4
+
5
+ The live AlgoHQ whiteboard is available to every MCP client through
6
+ `hq_whiteboard_post` and `hq_whiteboard_read`. Both use the scoped credential
7
+ created by `vo-mcp login`; tenant/operator ownership is derived by the control
8
+ plane and cannot be widened by tool input. The control plane admits scoped
9
+ credentials only for operator IDs on its internal `HQ_WHITEBOARD_OPERATOR_IDS`
10
+ allowlist; arbitrary self-serve tenants cannot read or write the shared fleet board.
11
+
12
+ **Status:** Phase 2. Stdio transport. **26 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, session-state, and AlgoHQ whiteboard tools forward to the control plane 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.
13
+
14
+ 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.
15
+
16
+ ## Install and refresh client configuration
17
+
18
+ `vo-mcp install` safely registers the same required `algohq` MCP server in
19
+ Claude Desktop, Claude Code, and Codex (`~/.codex/config.toml`), then offers
20
+ pairing and runner auto-start. Existing client settings and comments are
21
+ preserved, and every changed config is backed up first.
22
+
23
+ To refresh client configuration on an already-paired runner without touching
24
+ its keychain credential or auto-start service, use the noninteractive form:
25
+
26
+ ```sh
27
+ vo-mcp install --config-only
28
+ ```
29
+
30
+ Restart the MCP clients and runner after updating, then use
31
+ [AlgoHQ](https://algosuite.ai/algohq) to dispatch work.
32
+
33
+ ## Tools
34
+
35
+ | Name | Phase 1 status | Description |
36
+ | --- | --- | --- |
37
+ | `vo_check_assertion_strength` | Implemented (stub ratchet) | Score a test file's assertions 0-100 with per-assertion findings. |
38
+ | `vo_check_hollow_test` | Stub | Detect tests that pass without verifying product truth. |
39
+ | `vo_verify_answer` | Stub | Semantic-equivalence comparison of expected vs observed. |
40
+ | `vo_consensus_judgment` | Stub + logging skeleton | Submit a prompt to multiple models, return synthesized verdict. **Logs every call** (V1 launch gate #7). |
41
+ | `vo_architecture_review` | Stub | Senior-architect review of a diff against project defaults. |
42
+
43
+ All tools follow the same response envelope:
44
+
45
+ ```jsonc
46
+ {
47
+ "tool": "vo_check_assertion_strength",
48
+ "schema_version": 1,
49
+ "cache": { "hit": false, "key": "<sha256 hex>" },
50
+ "payload": { /* tool-specific */ }
51
+ }
52
+ ```
53
+
54
+ ## V1 launch gates implemented in Phase 1
55
+
56
+ - **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.
57
+ - **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`).
58
+
59
+ ## Building & testing
60
+
61
+ ```sh
62
+ cd packages/vo-mcp
63
+ pnpm install
64
+ pnpm run build # tsc -p tsconfig.json
65
+ pnpm run typecheck # tsc --noEmit
66
+ pnpm test # vitest run
67
+ ```
68
+
69
+ The integration test (`test/integration/stdio-roundtrip.test.ts`) spawns the built CLI, exchanges JSON-RPC over stdio, and asserts:
70
+
71
+ - `initialize` + `tools/list` returns the 26 expected tools.
72
+ - `vo_check_assertion_strength` runs end-to-end and writes a JSONL event line.
73
+ - A repeat call with identical input hits the cache (`cache.hit = true`).
74
+ - `vo_consensus_judgment` returns `unimplemented` and still writes its event line.
75
+
76
+ ## Running the server
77
+
78
+ The CLI is a stdio MCP server. Don't run it interactively; register it in your MCP client.
79
+
80
+ ### Claude Desktop / Claude Code
81
+
82
+ Add to `~/.claude/mcp_settings.json` (Claude Desktop) or your Claude Code config:
83
+
84
+ ```jsonc
85
+ {
86
+ "mcpServers": {
87
+ "vo": {
88
+ "command": "node",
89
+ "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"],
90
+ "env": {
91
+ // Optional overrides defaults shown below
92
+ // "VO_MCP_EVENTS_PATH": "/path/to/events.jsonl",
93
+ // "VO_MCP_DB_PATH": "/path/to/cache.db"
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### Cursor
101
+
102
+ Cursor reads MCP servers from its global config (Settings → Features → MCP). Add an entry:
103
+
104
+ ```jsonc
105
+ {
106
+ "mcpServers": {
107
+ "vo": {
108
+ "command": "node",
109
+ "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"]
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ After saving, restart Cursor. The tools appear under the `@vo` namespace in the chat composer's tool picker.
116
+
117
+ ### Continue
118
+
119
+ 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:
120
+
121
+ ```jsonc
122
+ {
123
+ "command": "node",
124
+ "args": ["<absolute-path>/packages/vo-mcp/dist/cli.js"]
125
+ }
126
+ ```
127
+
128
+ > **Cross-vendor smoke.** A captured cross-vendor smoke now exists — see
129
+ > [`CROSS_VENDOR_SMOKE.md`](./CROSS_VENDOR_SMOKE.md). Run `pnpm run smoke:cross-vendor`
130
+ > to drive the built server through the full MCP lifecycle under each vendor's
131
+ > real client parameters (protocol-version negotiation, capabilities, client
132
+ > identity, schema validity) and reproduce the recorded transcripts. That doc
133
+ > also carries the operator checklist for capturing real Cursor/Continue/Codex
134
+ > GUI sessions. The integration test in this package exercises the generic
135
+ > single-version stdio surface that any MCP-spec-compliant client uses.
136
+
137
+ ## Environment variables
138
+
139
+ | Var | Default | Purpose |
140
+ | --- | --- | --- |
141
+ | `VO_MCP_EVENTS_PATH` | `~/.claude/vo-mcp-events.jsonl` | Where consensus-call event lines are appended. |
142
+ | `VO_MCP_DB_PATH` | `~/.claude/vo-mcp-cache.db` | sqlite cache file for content-hash cache. |
143
+ | `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. |
144
+ | `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. |
145
+ | `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. |
146
+ | `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. |
147
+
148
+ ## Adding a new tool (Phase 2+)
149
+
150
+ 1. Create `src/tools/<tool>.ts` exporting:
151
+ - `TOOL_NAME` constant
152
+ - `description` (writes the discovery copy that cross-vendor clients show)
153
+ - `inputSchema` (JSON Schema for MCP `tools/list`)
154
+ - `handle<Tool>(deps, rawInput)` (validate input, hit cache, call backend, log event, return `jsonContent(envelope)`)
155
+ 2. Add it to `buildToolRegistry()` in `src/server.ts`.
156
+ 3. Add tests under `test/tools/<tool>.test.ts`.
157
+ 4. Update this README's tool table and `EXTRACTION_AUDIT.md`.
158
+
159
+ The tool handler MUST:
160
+
161
+ - Hash the canonicalized input via `deps.cache.keyFor(TOOL_NAME, rawInput)`.
162
+ - Read the cache before any expensive work; return cached envelope with `cache.hit = true` if present.
163
+ - Append a `ConsensusCallEvent` via `deps.events.append(buildBaseEvent(...))` on every invocation.
164
+ - Sanitize inputs before logging (`sanitizeExcerpt` is applied automatically in `buildBaseEvent`).
165
+ - Return a `ToolResultEnvelope<TPayload>` JSON-encoded in a single text content block.
166
+
167
+ ## Hard rules (handoff §C)
168
+
169
+ This package complies with:
170
+
171
+ 1. **Open shell, closed content.** Stub ratchet client lives here; real thresholds load from `@algosuite/ratchets-generic` in Phase 2 (see `EXTRACTION_AUDIT.md`).
172
+ 2. **Cross-vendor by design.** No Claude-specific assumptions; tool schemas are MCP-spec compliant.
173
+ 3. **Content-hash cache every consensus call.** Implemented at `src/cache/sqlite-cache.ts`.
174
+ 4. **Log every consensus call.** Implemented at `src/logging/events-writer.ts`.
175
+ 5. **TypeScript strict, zero `any`, zero `@ts-ignore`.**
176
+ 6. **No PII in logs.** `sanitizeExcerpt` strips JWTs, sk- keys, bearer tokens, and email addresses before logging.
177
+
178
+ See `EXTRACTION_AUDIT.md` for what's stub, what's real, and where the moat lives.
package/bin/vo-mcp CHANGED
@@ -1,38 +1,44 @@
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 install --config-only # refresh Claude + Codex MCP config only
9
+ * vo-mcp update # update MCP + runner package
10
+ * vo-mcp login # credential login (browser loopback)
11
+ * vo-mcp pair # device-code pairing (enter a code in the web)
12
+ * vo-mcp runner # supervised agent runner
13
+ * vo-mcp runner --install-autostart # register runner to start at login
14
+ * vo-mcp runner --uninstall-autostart # remove auto-start registration
15
+ */
16
+
17
+ const command = process.argv[2];
18
+ const subcommand = process.argv[3];
19
+
20
+ if (command === 'install') {
21
+ import('../dist/install-cli.js');
22
+ } else if (command === 'update') {
23
+ import('../dist/update-cli.js');
24
+ } else if (command === 'login') {
25
+ import('../dist/login-cli.js');
26
+ } else if (command === 'pair') {
27
+ import('../dist/pair-cli.js');
28
+ } else if (command === 'set-key') {
29
+ import('../dist/set-key-cli.js');
30
+ } else if (command === 'runner') {
31
+ if (subcommand === '--install-autostart') {
32
+ import('../dist/autostart-cli.js').then((m) => m.installAutostartCli());
33
+ } else if (subcommand === '--uninstall-autostart') {
34
+ import('../dist/autostart-cli.js').then((m) => m.uninstallAutostartCli());
35
+ } else if (process.argv.includes('--once') || process.argv.includes('--status') || process.argv.includes('--version') || process.argv.includes('-v')) {
36
+ // Direct diagnostic/one-shot mode; remote maintenance uses the supervisor.
37
+ import('../dist/runner-cli.js');
38
+ } else {
39
+ import('../dist/runner-supervisor.js');
40
+ }
41
+ } else {
42
+ // Default: MCP stdio server
43
+ import('../dist/cli.js');
44
+ }
@@ -12,7 +12,16 @@ function installWindowsAutostart(runnerCommand, log, env) {
12
12
  const appData = env["APPDATA"] ?? join(homedir(), "AppData", "Roaming");
13
13
  const startupDir = join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
14
14
  mkdirSync(startupDir, { recursive: true });
15
- const launcherPath = join(startupDir, "vo-runner.cmd");
15
+ const launcherPath = join(startupDir, "vo-runner.vbs");
16
+ const legacyCmdPath = join(startupDir, "vo-runner.cmd");
17
+ if (existsSync(legacyCmdPath)) {
18
+ try {
19
+ unlinkSync(legacyCmdPath);
20
+ log(` Removed legacy minimized .cmd launcher: ${legacyCmdPath}`);
21
+ } catch (error) {
22
+ log(`\u26A0 Could not remove legacy launcher ${legacyCmdPath} (${error instanceof Error ? error.message : String(error)}); remove it manually to avoid a double start`);
23
+ }
24
+ }
16
25
  if (existsSync(launcherPath)) {
17
26
  const existing = readFileSync(launcherPath, "utf8");
18
27
  if (existing.includes("vo-mcp runner")) {
@@ -24,27 +33,31 @@ function installWindowsAutostart(runnerCommand, log, env) {
24
33
  copyFileSync(launcherPath, backupPath);
25
34
  log(` Backed up existing launcher to: ${backupPath}`);
26
35
  }
27
- const launcherContent = `@echo off
28
- REM Auto-start launcher for vo-mcp runner
29
- REM Created by vo-mcp autostart installer
30
- start /min cmd /c "${runnerCommand}"
36
+ const hiddenCommand = `cmd /c ${runnerCommand} >> "%USERPROFILE%\\.claude\\vo-runner.log" 2>&1`.replace(/"/g, '""');
37
+ const launcherContent = `' Auto-start launcher for vo-mcp runner
38
+ ' Created by vo-mcp autostart installer
39
+ CreateObject("WScript.Shell").Run "${hiddenCommand}", 0, False
31
40
  `;
32
41
  writeFileSync(launcherPath, launcherContent, "utf8");
33
42
  log(`\u2713 Installed Windows auto-start launcher`);
34
43
  log(` Path: ${launcherPath}`);
35
- log(` The runner will start minimized at next login.`);
44
+ log(` The runner will start hidden at next login.`);
36
45
  }
37
46
  function uninstallWindowsAutostart(log, env) {
38
47
  const appData = env["APPDATA"] ?? join(homedir(), "AppData", "Roaming");
39
48
  const startupDir = join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
40
- const launcherPath = join(startupDir, "vo-runner.cmd");
41
- if (!existsSync(launcherPath)) {
49
+ const launcherPaths = [join(startupDir, "vo-runner.vbs"), join(startupDir, "vo-runner.cmd")];
50
+ let removedAny = false;
51
+ for (const launcherPath of launcherPaths) {
52
+ if (!existsSync(launcherPath)) continue;
53
+ unlinkSync(launcherPath);
54
+ removedAny = true;
55
+ log(`\u2713 Removed Windows auto-start launcher`);
56
+ log(` Path: ${launcherPath}`);
57
+ }
58
+ if (!removedAny) {
42
59
  log(`\u2713 Auto-start launcher not found (already removed)`);
43
- return;
44
60
  }
45
- unlinkSync(launcherPath);
46
- log(`\u2713 Removed Windows auto-start launcher`);
47
- log(` Path: ${launcherPath}`);
48
61
  }
49
62
  async function installMacAutostart(runnerCommand, log) {
50
63
  const launchAgentsDir = join(homedir(), "Library", "LaunchAgents");
@@ -117,6 +130,79 @@ async function uninstallMacAutostart(log) {
117
130
  log(`\u2713 Removed launchd plist`);
118
131
  log(` Path: ${plistPath}`);
119
132
  }
133
+ async function installLinuxAutostart(runnerCommand, log, env) {
134
+ const home = env["HOME"]?.trim() || homedir();
135
+ const unitDir = join(home, ".config", "systemd", "user");
136
+ mkdirSync(unitDir, { recursive: true });
137
+ const unitPath = join(unitDir, "vo-runner.service");
138
+ if (existsSync(unitPath)) {
139
+ const existing = readFileSync(unitPath, "utf8");
140
+ if (existing.includes(runnerCommand) || existing.includes("vo-mcp runner")) {
141
+ log(`\u2713 Auto-start is already configured (systemd user unit)`);
142
+ log(` Path: ${unitPath}`);
143
+ return;
144
+ }
145
+ const backupPath = `${unitPath}.backup-${Date.now()}`;
146
+ copyFileSync(unitPath, backupPath);
147
+ log(` Backed up existing unit to: ${backupPath}`);
148
+ }
149
+ const logFile = join(home, ".claude", "vo-runner.log");
150
+ const errFile = join(home, ".claude", "vo-runner-error.log");
151
+ mkdirSync(join(home, ".claude"), { recursive: true });
152
+ const unit = `[Unit]
153
+ Description=AlgoHQ Code Runner (vo-mcp)
154
+ After=network-online.target
155
+ Wants=network-online.target
156
+
157
+ [Service]
158
+ Type=simple
159
+ ExecStart=/bin/sh -lc '${runnerCommand}'
160
+ Restart=on-failure
161
+ RestartSec=10
162
+ StandardOutput=append:${logFile}
163
+ StandardError=append:${errFile}
164
+
165
+ [Install]
166
+ WantedBy=default.target
167
+ `;
168
+ writeFileSync(unitPath, unit, "utf8");
169
+ log(`\u2713 Installed systemd user unit`);
170
+ log(` Path: ${unitPath}`);
171
+ if (process.env["VITEST"]) {
172
+ log(` (test mode: skipping systemctl enable)`);
173
+ return;
174
+ }
175
+ try {
176
+ const { execSync } = await import("node:child_process");
177
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
178
+ execSync("systemctl --user enable --now vo-runner.service", { stdio: "ignore" });
179
+ log(`\u2713 Enabled + started vo-runner.service (starts at login)`);
180
+ log(` Logs: ${logFile}`);
181
+ } catch {
182
+ log(`\u26A0 Could not enable via systemctl (enable it manually):`);
183
+ log(` systemctl --user daemon-reload && systemctl --user enable --now vo-runner.service`);
184
+ }
185
+ }
186
+ async function uninstallLinuxAutostart(log, env) {
187
+ const home = env["HOME"]?.trim() || homedir();
188
+ const unitPath = join(home, ".config", "systemd", "user", "vo-runner.service");
189
+ if (!existsSync(unitPath)) {
190
+ log(`\u2713 Auto-start unit not found (already removed)`);
191
+ return;
192
+ }
193
+ if (!process.env["VITEST"]) {
194
+ try {
195
+ const { execSync } = await import("node:child_process");
196
+ execSync("systemctl --user disable --now vo-runner.service", { stdio: "ignore" });
197
+ log(`\u2713 Disabled + stopped vo-runner.service`);
198
+ } catch {
199
+ log(`\u26A0 Could not disable via systemctl (continuing anyway)`);
200
+ }
201
+ }
202
+ unlinkSync(unitPath);
203
+ log(`\u2713 Removed systemd user unit`);
204
+ log(` Path: ${unitPath}`);
205
+ }
120
206
  async function installAutostart(opts = {}) {
121
207
  const log = opts.log ?? ((m) => console.error(m));
122
208
  const env = opts.env ?? process.env;
@@ -126,9 +212,11 @@ async function installAutostart(opts = {}) {
126
212
  installWindowsAutostart(runnerCommand, log, env);
127
213
  } else if (plat === "darwin") {
128
214
  await installMacAutostart(runnerCommand, log);
215
+ } else if (plat === "linux") {
216
+ await installLinuxAutostart(runnerCommand, log, env);
129
217
  } else {
130
218
  log(`\u2717 Auto-start is not supported on platform: ${plat}`);
131
- log(` Supported platforms: win32 (Windows), darwin (macOS)`);
219
+ log(` Supported platforms: win32 (Windows), darwin (macOS), linux (Linux)`);
132
220
  }
133
221
  }
134
222
  async function uninstallAutostart(opts = {}) {
@@ -139,9 +227,11 @@ async function uninstallAutostart(opts = {}) {
139
227
  uninstallWindowsAutostart(log, env);
140
228
  } else if (plat === "darwin") {
141
229
  await uninstallMacAutostart(log);
230
+ } else if (plat === "linux") {
231
+ await uninstallLinuxAutostart(log, env);
142
232
  } else {
143
233
  log(`\u2717 Auto-start is not supported on platform: ${plat}`);
144
- log(` Supported platforms: win32 (Windows), darwin (macOS)`);
234
+ log(` Supported platforms: win32 (Windows), darwin (macOS), linux (Linux)`);
145
235
  }
146
236
  }
147
237