> ## 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.

# ADR-30: Install authority is the Node CLI; one chicken-and-egg bootstrap; consumer-grade UX

> Install authority lives in `cli/src/index.ts`. **No Python install code. No repo-internal shell that contains install business logic.** The only shell allowed i

<Info>**Status:** `accepted` · **ADR-30** · Filed 2026-04-29</Info>

## Decision

Install authority lives in `cli/src/index.ts`. **No Python install code. No repo-internal shell that contains install business logic.** The only shell allowed is a thin consumer bootstrap (one per OS family) at `scripts/install.{sh,ps1}` whose sole purpose is solving the chicken-and-egg of "Node not installed, repo not cloned." Quality bar across all three artifacts (`scripts/install.sh`, `scripts/install.ps1`, `cli/src/index.ts`): production-grade — status output, error handling, on-disk log file at `~/.prism/install.log`, professional copy. **`prism update` is the canonical refresh verb after first install — same authority, same quality bar; semantically `install --refresh`.**

### Consumer install flow — one command per OS

```
Mac / Linux:
  curl -fsSL https://github.com/FrankTewksbury/Prism/raw/main/scripts/install.sh | bash

Windows (PowerShell):
  irm https://github.com/FrankTewksbury/Prism/raw/main/scripts/install.ps1 | iex
```

URL form is `github.com/<user>/<repo>/raw/<branch>/<path>` — visible, recognizable github.com domain (no `raw.githubusercontent.com` surprise). GitHub redirects to raw content automatically.

### `scripts/install.{sh,ps1}` responsibilities (and only these)

1. Detect Node; install via OS-native package manager (`brew` on Mac, `winget` on Windows, `apt`/`dnf`/native on Linux). Unrecognized OS → clear error pointing at [https://nodejs.org](https://nodejs.org) and the log file.
2. Prompt for Projects root with OS-convention defaults (Mac/Linux: `~/Projects/`, Windows: `%USERPROFILE%\Projects\`). User can override to anywhere. The repo subdirectory is always named `Prism` (not customizable).
3. `git clone` into `<projects-root>/Prism`.
4. `cd` into it; exec `npm install --prefix cli` (the existing `prepare` hook builds via `tsc`); then `node cli/dist/index.js install` — handing off to the Node CLI.

Quality bar: status at every step; every external command checked, failure prints command + stderr + non-zero exit; `~/.prism/install.log` with timestamps + full command output (append, not overwrite — preserve history across re-runs); idempotent (Node already installed → skip; repo already cloned → skip); calm professional copy (no emoji chatter, no welcome wizards, no jokes).

### `prism install` (Node CLI) responsibilities — what the consumer never types directly

* Detect missing system prereqs (uv, python 3.11, docker, docker-compose, gh).
* Single consent prompt: "Install missing prereqs? \[Y/n]" — then drive `brew`/`winget`/`apt` from Node via `child_process`. Never ask the user to run a second command.
* Configuration prompts (backend mode: local/lan/cloud, API URL, API key).
* Write env vars + JSON/TOML configs into every detected editor (Claude Desktop, Claude Code, Cursor, Codex).
* Wire `~/.claude/statusline-command.sh` → `$PRISM_ROOT/bin/statusline-claude-code.sh` (symlink); JSON-merge `~/.claude/settings.json`'s `statusLine.command` to point at the resolved bash path; preserve other top-level keys; `refreshInterval=1`.
* Install launchers; register `coder` AND `prism` on PATH (managed `~/.zshrc`/`~/.bashrc` block on Mac/Linux; `[Environment]::SetEnvironmentVariable('Path', ..., 'User')` on Windows — same registry mechanism already in cli/src/index.ts).
* `mkdir -p ~/.prism` (cache dir, defensive).
* Run smoke.
* Same quality bar as bootstrap: status, error handling, append to `~/.prism/install.log`, professional copy.

### `prism update` — lifecycle refresh verb

After first install, `prism` is globally on PATH. Operator runs `prism update` from anywhere:

* `git pull --ff-only` in `$PRISM_ROOT` (refuses on conflict; surfaces clear path forward).
* Re-run editor MCP config writes (idempotent — writes only on drift).
* Refresh `coder`/`prism` PATH entries if `$PRISM_ROOT` moved.
* Re-resolve `~/.claude/settings.json`'s `statusLine.command` path.
* `docker compose pull` for in-scope stack profiles — refresh container images so backend/dashboard/SM updates land.
* Detect missing-or-stale system prereqs introduced by recent commits and prompt-with-consent to install.
* Run install-smoke.
* Append to `~/.prism/install.log`.
* Report what changed: editor configs rewritten, images pulled, PATH entries refreshed, smokes passed.

`prism install` and `prism update` are the same code paths under the hood — verb names distinguish intent (first install vs ongoing maintenance) for operator clarity, not for code-path divergence. Idempotent: re-running `prism update` produces no second-time changes.

### Known limitation surfaced post-install

Windows operators using pure PowerShell (no Git Bash) can't execute `bin/statusline-claude-code.sh`. `prism install`/`prism update` post-run output flags this as a one-line note until a PowerShell port lands.

## Rationale

**Why this ADR exists at all.** On 2026-04-15 commit `0590bb4` ("Port installer to TypeScript npm CLI") deleted `install.py` with the explicit message "single source of truth for the CLI." Three days later, commit `d86f9ea` ("Plan #5: prism\_clone/create/destroy/migrate + two-stage install") re-introduced `install/*.py` and demoted `cli/` to "deprecated in README." The reversal was framed as a forward redesign, not flagged as a mandate reversal. Between 2026-04-18 and 2026-04-28 four feature commits landed on the Python path while the Node path got parallel features. Two installers, ten days of drift. Cost surfaced via Candi's Windows upgrade-path failure 2026-04-28: the upgrade-scenario gap was structurally unfixable without first reconciling which installer was canonical.

**Why an ADR not a memory or doc.** Memory loads only for the agent whose memory it is — the `d86f9ea` author had no signal to check the prior commit's mandate. Docs rot and get ignored. ADRs are checked-in, surfaced by `semantic_recall` / `prism_decide`, and carry "we already decided this" weight. A future agent solving "two-stage install" or "first-install bootstrap" hits this ADR before reaching for Python or repo-internal shell.

**Why one shell bootstrap is allowed when the broader rule forbids shell.** The failure mode `d86f9ea` introduced was repo-internal shell that contained install business logic and quietly took over the canonical path. A hosted bootstrap with zero business logic is a different animal — it's a reachability primitive (chicken-and-egg solver), not an installer. The sharp distinction: any line of install business logic in `scripts/install.{sh,ps1}` is a regression to be reverted.

**Why consumer-grade UX is product-imperative, not nice-to-have.** Prism is for users we don't have machine access to. Friction in install is a conversion-killer. The minimum is one command per OS — same pattern Homebrew, Rust, Bun, Deno, uv, pnpm all use because it works. Anything more (multiple commands, manual prereq installs, "now run X then Y") fails the consumer mandate.

**Why `prism update` is a first-class verb.** Operators need a single-command refresh path that handles git pull + editor configs + PATH + statusline + container images + smoke. Without it, "upgrade" devolves into a multi-step manual sequence each time, which is exactly what the consumer mandate forbids.

## Alternatives Considered

**Rejected: keep Python `install/install.py` as Stage 2 under bash bootstrap.** This is the current state. Cost: two parallel installers, perpetual drift, install.py keeps accreting features that have to be mirrored in the Node CLI manually. The Candi failure mode repeats every few weeks until cutover.

**Rejected: keep a repo-internal `bin/install.sh` that wraps git pull + npm + prism install.** Looks innocuous; recreates the failure mode. Once that file exists, future contributors will reach for it as a "convenient place to put just one more thing," and install business logic will accrete there. The ADR forbids this category.

**Rejected: distribute as a self-contained npm package (npm install -g @prism/cli).** Doesn't solve the chicken-and-egg — operator still needs Node installed first, plus npm-global has known cross-platform PATH issues on Windows. The hosted bootstrap pattern is strictly more accessible.

**Rejected: ship a Windows .msi / Mac .pkg / Linux .deb.** Highest UX polish, but requires a build pipeline + signing infrastructure + per-OS testing matrix that doesn't exist today. Right answer eventually; wrong answer for the cutover. The curl|sh / irm|iex pattern reaches the same ergonomic floor with zero pipeline overhead.

**Rejected: separate `prism upgrade` and `prism update` verbs.** Different names suggest different semantics; same code path under the hood means operators just guess which one to type. One verb (`prism update`) following the brew/apt idiom (`update` = refresh local artifacts) is cleaner.
