> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SPEC-084 v0.2: prism_install_editor_host — AI-FIRST editor-host install verb

> SPEC-084 v0.2 — prism_install_editor_host MCP verb. Closes the editor-host install-verb gap surfaced during P2 mini1 rehearsal: today the editor-host install requires manual scp + npm install + ssh + run-CLI on every target. This verb automates all of it from the operator side. v0.2 incorporates Texi review: discriminated-union target registry, single-shell Windows policy with explicit detection, mutually-exclusive verb argument modes.

<Info>**Status:** `draft` · **Version** `0.2` · Filed 2026-05-04 · Updated 2026-05-04 (Texi review v0.1 → v0.2)</Info>

***

version: 0.2
status: draft
-------------

# Title: SPEC-084 v0.2 — prism\_install\_editor\_host — AI-FIRST editor-host install verb

## Origin

P2 install rehearsal on mini1 (Win 11, Node 22.17, pointing at mini4 LAN backend) surfaced an MCP-verb gap: the editor-host install lane has no MCP wrapper. Available install verbs:

| Verb                        | Shape                                                                               | Editor-host fit        |
| --------------------------- | ----------------------------------------------------------------------------------- | ---------------------- |
| `prism_install_local(...)`  | Local Docker compose stack                                                          | NO — backend installer |
| `prism_install_lan(target)` | Remote backend via SSH + Docker                                                     | NO — backend installer |
| (none)                      | Editor-host install (Node CLI + mcp-node shim, no Docker, points at remote backend) | **GAP**                |

Today the editor-host install path is a manual sequence: SSH to host, scp `cli/dist` + `mcp-node/dist` + each `package.json`, `npm install --ignore-scripts` in each, then run `prism install --mode lan --host <backend> --port <port> --api-key <key>` on the host. This violates `feedback_ai_first_end_to_end.md` and forces the operator to remember the scp recipe.

Validated end-to-end manually (mini1 cycles 1–4, 2026-05-04) — every step is mechanical and SSH-ready.

## §1 — Goals

1. **One verb call → working editor host.** From the operator's machine: `prism_install_editor_host(target=mini1)` produces a Win/Mac/Linux editor host wired with the prism MCP shim talking to the named backend, with no manual SSH or scp.
2. **Re-uses the credential exfiltration chain** shipped in PR #157: the operator's `~/.prism/credentials.<backend_target>.json` is the source of truth for the API key the new editor host needs. Operator never sees or copies the secret.
3. **Same per-invocation log file UX as install\_lan.** Writes `~/.prism/logs/install-editor-host-<target>-<UTC>.log` with timestamped per-stage entries; `tail -f`-compatible.
4. **Idempotent.** Re-running on an already-installed editor host equals an upgrade — refresh dist tarballs, re-run `prism install`, validate handshake.

### Non-goals (v0.2)

* Distribution mechanism beyond scp-from-operator. npm publish / fetch-on-demand / single-binary is the next install-lane SPEC.
* Per-host fan-out / concurrent installs. v0.2 is one host per verb call.
* PowerShell remote shell support. v0.2 requires `cmd.exe` on Windows targets (default for Win OpenSSH 9.x); PowerShell-shell support deferred to v0.3 with separate command builders.

## §2 — Target Registry — Discriminated Union (Texi v0.1 finding 1)

`~/.prism/targets.json` is extended with an explicit `kind` discriminator. Verbs select targets by kind; cross-kind selection is a config error.

### §2.1 — Schema

```jsonc theme={null}
{
  "version": 2,                         // bumped from 1; see §2.4 migration
  "default_target": "<key-name-or-null>",
  "targets": {
    "mini4": {
      "kind": "backend_lan",            // REQUIRED; no implicit default
      "mode": "lan",
      "ssh": { "target": "...", "key": "..." },
      "remote": { "repo_dir": "...", "compose_file": "..." },
      "backend_url": "..."
    },
    "mini1": {
      "kind": "editor_host",            // REQUIRED; no implicit default
      "platform": "windows",            // REQUIRED for editor_host: "windows" | "linux" | "macos"
      "ssh": { "target": "...", "key": "..." },
      "remote": { "install_root": "C:\\Users\\lafonda\\Prism" },
      "backend_target": "mini4"         // REQUIRED for editor_host; MUST name a kind=backend_lan entry
    }
  }
}
```

### §2.2 — Resolver Rules

`resolveTarget(name, expected_kind)` algorithm:

1. Look up entry by name. Missing → `target_not_found`.
2. Read `entry.kind`. If absent (legacy v1 registry), see §2.4.
3. If `entry.kind !== expected_kind`, fail with `kind_mismatch{got: <kind>, expected: <kind>}`. **No silent coercion.**
4. For `kind=editor_host`: also resolve `entry.backend_target`. Missing → `editor_host_missing_backend_target`. Resolved entry's kind must be `backend_lan` → otherwise `backend_target_not_backend_lan`.

### §2.3 — Verb / Kind Matrix (normative)

| Verb                                                              | Accepts kind          | Rejection error |
| ----------------------------------------------------------------- | --------------------- | --------------- |
| `prism_install_lan` / `prism_upgrade_lan` / `prism_configure_lan` | `backend_lan` only    | `kind_mismatch` |
| `prism_install_editor_host`                                       | `editor_host` only    | `kind_mismatch` |
| `prism_list_targets`                                              | all kinds (read-only) | n/a             |

### §2.4 — Migration from registry v1

Pre-existing v1 entries (no `kind` field) are interpreted as `kind=backend_lan` for backward compatibility. The first write through `prism_configure_lan` upgrades the file: writes `version: 2` and adds `kind: "backend_lan"` to existing entries. New `editor_host` entries are written via a new `prism_configure_editor_host` companion verb (out of scope for this SPEC; tracked as a §6 follow-up).

## §3 — Verb Argument Contract (Texi v0.1 finding 3)

Two mutually-exclusive modes, validated at the input boundary:

### §3.1 — Mode A: `target` (registered)

```
prism_install_editor_host(
  target: string                  // REQUIRED; must resolve to kind=editor_host
)
```

All other args MUST be empty. Backend resolution, ssh details, install\_root, platform — all read from the registry entry.

### §3.2 — Mode B: `target_host` (one-shot)

```
prism_install_editor_host(
  target_host: string,            // REQUIRED; ssh target, e.g. "Lafonda@mini1.home.lan"
  ssh_key: string,                // REQUIRED; path to private key
  install_root: string,           // REQUIRED; remote dir
  platform: string,               // REQUIRED; "windows" | "linux" | "macos"
  // Backend identity — exactly one of these two groups:
  backend_target?: string,        // EITHER: name of kind=backend_lan registry entry
  // ── OR ──
  backend_url?: string,           // both required if backend_target absent
  api_key?: string
)
```

`target` MUST be empty in Mode B.

### §3.3 — Validation rules

1. Exactly one of `{target, target_host}` non-empty. Both empty or both set → `arg_mode_ambiguous`.
2. Mode A: all other args MUST be empty → `arg_mode_a_extra_args`.
3. Mode B: `target_host` AND `ssh_key` AND `install_root` AND `platform` all required → `missing_required_arg`.
4. Mode B backend identity: `backend_target` XOR (`backend_url` AND `api_key`). Both groups set → `backend_identity_ambiguous`. Neither set → `backend_identity_missing`.
5. Backend identity precedence: in v0.2 the two backend-identity groups are mutually exclusive. A future v0.x extension may allow both with an explicit precedence flag.
6. Resolved kind in Mode A and resolved backend\_target in either mode MUST satisfy §2.2.

## §4 — Stages

Mirrors `upgrade_lan.ts` flow but adapted for editor-host:

1. **preflight\_local** — local repo `cli/dist` + `mcp-node/dist` exist + are fresh. Fail-fast with actionable error if missing per `feedback_preflight_checks` (no auto-build inside the verb — caller invokes `npm run build` first; verb is side-effect-narrow).
2. **preflight\_remote** — see §5 for the OS-detection + shell-policy detail.
3. **distribution** — `mkdir -p $install_root/{cli,mcp-node}` (per §5 platform branch); `scp cli/dist + cli/package.json` and `mcp-node/dist + mcp-node/package.json` to remote.
4. **prereqs** — `npm install --ignore-scripts --no-audit --no-fund` in `cli/` and `mcp-node/`.
5. **install** — `node $install_root/cli/dist/index.js install --mode lan --host <backend_host> --port <backend_port> --api-key <api_key>`.
6. **verify** — fresh SSH session running JSON-RPC `initialize` handshake against `node $install_root/cli/dist/mcp-launcher.js` with the configured env block; assert version + capabilities; assert `tools/call list_projects` returns clean (real backend round-trip).

Failure at any stage aborts and surfaces a structured error with stage + detail + remediation hint. No partial cleanup — re-running idempotently fixes-forward.

## §5 — Cross-Platform Policy (Texi v0.1 finding 2)

v0.2 selects approach (a) from the v0.1 sketch: **single Windows shell required (cmd.exe), explicit detection at preflight, fail-fast otherwise.** PowerShell support is v0.3 work with separate command builders.

### §5.1 — Platform identification

`entry.platform` (Mode A) or the `platform` arg (Mode B) is normative — operator declares the target's platform explicitly. The verb does NOT auto-detect from `uname` / `ver` because:

* The probe itself differs per shell (cmd `ver` vs PowerShell `$PSVersionTable` vs POSIX `uname -s`).
* Auto-detection masks misconfigured target entries; explicit declaration surfaces them at registration time.

### §5.2 — Shell policy

| platform  | Required remote shell           | Probe at preflight\_remote                       | Failure mode                                                                                                                                |
| --------- | ------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `windows` | `cmd.exe` (Win OpenSSH default) | `ssh ... "ver"` returns "Microsoft Windows" line | If empty / "PowerShell" detected → `windows_powershell_unsupported_v0_2` with remediation: set OpenSSH `DefaultShell` registry to `cmd.exe` |
| `linux`   | POSIX shell (`sh`/`bash`)       | `ssh ... 'uname -s'` returns `Linux`             | non-Linux → `linux_platform_mismatch`                                                                                                       |
| `macos`   | POSIX shell (`zsh`/`bash`)      | `ssh ... 'uname -s'` returns `Darwin`            | non-Darwin → `macos_platform_mismatch`                                                                                                      |

### §5.3 — Command builders per platform

A `RemoteCommands` interface dispatches per platform:

```ts theme={null}
interface RemoteCommands {
  mkdirP(path: string): string;          // Win: 'if not exist "X" mkdir "X"' (cmd.exe); POSIX: `mkdir -p $(shq path)`
  cd(path: string): string;              // Win: `cd /d "path"`; POSIX: `cd $(shq path)`
  envSet(name: string, value: string): string; // Win: `set NAME=value`; POSIX: `NAME=$(shq value)`
  joinAnd(parts: string[]): string;      // Win: `&&`; POSIX: `&&` — same operator but quoting differs
  shq(s: string): string;                // Win: `"<doubled-quote-escaped>"`; POSIX: existing single-quote shq
}
```

Implementations live in `mcp-node/src/ops/_smoke/remote_commands_{windows,posix}.ts` (or similar). The branch is decided once at preflight\_remote; subsequent stages call methods on the dispatched instance.

### §5.4 — scp destination handling

`scp` uses `host:path` syntax. Path semantics differ:

* **POSIX target**: forward slashes, absolute path: `Lafonda@mini1.home.lan:/home/lafonda/Prism/cli/dist/`.
* **Windows target**: forward slashes ALSO work in scp's path argument (translated by Win OpenSSH). Use forward-slash form for the scp arg even when `install_root` in registry is backslash form. Path normalization: `install_root.replace(/\\/g, "/")` for scp args.

Quoting: scp args go through the local shell. `shq` (operator side, POSIX) wraps the destination string. The remote-side path interpretation is by Win OpenSSH, NOT cmd.exe — no double-quoting needed.

## §6 — Open Questions (v0.2)

1. **PowerShell support timeline.** v0.3 candidate. Question: detect at preflight and select builder, or require operator to declare shell in target entry? Lean toward declaration for the same reason as platform — explicit > magic.
2. **`prism_configure_editor_host` companion verb.** Out of scope here but blocks fully programmatic registration of editor\_host entries. File as separate small SPEC after this one ratifies.
3. **Daemon binary distribution.** SPEC-072 daemon source is not part of cli/dist. v0.2 install path leaves `daemon/` absent on editor host (CLI already handles it gracefully per cycle 3 output). Future v0.x SPEC for daemon-included editor-host installs.

## §7 — Validation Plan

1. Greenfield mini1 cycle: tear down `C:\Users\lafonda\Prism\{cli,mcp-node}`, then `prism_install_editor_host(target=mini1)` from operator → all stages green, MCP initialize handshake clean against mini4.
2. Idempotency: re-run immediately → distribution refreshes dist, install reports "already wired", verify still green.
3. Failure surface: register a target with bad ssh key → preflight\_remote fails fast with remediation hint; no partial state on remote.
4. Kind mismatch: call `prism_install_editor_host(target=mini4)` (mini4 is backend\_lan) → `kind_mismatch{got: backend_lan, expected: editor_host}`.
5. PowerShell rejection: deliberately reconfigure mini1 OpenSSH to PowerShell → `windows_powershell_unsupported_v0_2`.
6. Mode-validation: call with both `target` and `target_host` → `arg_mode_ambiguous`. Call with `backend_target` and `backend_url` → `backend_identity_ambiguous`.

## §8 — Implementation Notes

* New file: `mcp-node/src/ops/install_editor_host.ts` (mirrors `upgrade_lan.ts` shape).
* New files: `mcp-node/src/ops/_smoke/remote_commands_{windows,posix}.ts` for the §5.3 command builders.
* Verb wiring: `mcp-node/src/verbs/tier1c.ts` adds `registerVerb({ name: "prism_install_editor_host", ... })`.
* Re-uses POSIX `shq()` (from PR #157) for operator-shell quoting. Adds a Windows `winq()` helper alongside.
* Re-uses log-file helper (`newLogFile` / `logLine`) — refactor into a shared util as a third consumer arrives.
* Targets registry resolver gains `expected_kind` arg; existing callers updated to pass `"backend_lan"` explicitly.

## §9 — Related

* **Implements:** the install-lane verb taxonomy gap surfaced in P2 mini1 rehearsal 2026-05-04.
* **Depends on:** SPEC-055 (target registry) + PR #157 (`stageBootstrapApiKey` for credentials chain) + PR #159 (CLI consumer-install no-src guard).
* **Extends:** SPEC-054 (Node MCP shim distribution model — explicit consumer install path).
* **Out-of-scope follow-up:** distribution mechanism (npm publish vs scp vs single-binary) — separate SPEC TBD.

## §10 — Review history

* v0.1 → Texi review (PR #160 commit dda33a6): 3 findings — discriminated-union resolver rules (blocking), Win/POSIX shell-policy inconsistency (blocking), verb-arg-contract precedence (medium).
* v0.2 → applied: §2.1 schema + §2.2 resolver rules + §2.3 kind/verb matrix + §2.4 migration; §5 single-shell Windows policy with explicit `platform` declaration + §5.3 RemoteCommands dispatch + §5.4 scp path normalization; §3 mutually-exclusive Mode A vs Mode B with normative validation rules.
