# Install-on-demand bootstrap

<!-- [doc->REQ-INSTALL-BOOTSTRAP-VERB] -->
How an adapter ships spt-core *with itself*. The contract: **the canonical
install path is also every adapter's pack-in installer** — there is no second
mechanism, no vendored binary, no bespoke fetch logic to maintain. Your
adapter checks for `spt`, and when it's missing runs the official bootstrap:
`gh release download` the platform binary from the release channel, then the
binary's own **`spt install` verb** — it places itself at the canonical
install path, registers the user PATH, refuses a wrong-platform binary, and
is non-interactive and idempotent by construction.

The release channel is private, so the node needs an authenticated GitHub CLI
(`gh auth login`, once per machine, with an account that can read the
channel) — see [Installing](../reference/install.md).

## The generic contract

```text
if `spt` is on PATH        -> done (optionally check `spt --version` ≥ your floor)
else                       -> gh release download the platform binary + run its `install` verb
then                       -> first invocation may need the absolute path (Windows)
then                       -> register your manifest: spt adapter add --github <org>/<repo>
```

After first install, spt-core keeps itself current (signed self-update over
the same gh channel), so the bootstrap can leave upgrades to spt-core. The
remaining bootstrap step is to register your adapter — see
[Activate the adapter](#activate-the-adapter--register-your-manifest) below.

## Check-and-install: POSIX sh

Drop this into your adapter's bootstrap (plugin install step, postinstall
script, first-run guard):

```sh
if ! command -v spt >/dev/null 2>&1; then
  echo "spt-core not found - installing..."
  gh release download --repo BigscreenVR/spt-bs-releases \
    --pattern 'spt-x86_64-linux' --dir /tmp
  chmod +x /tmp/spt-x86_64-linux
  /tmp/spt-x86_64-linux install
  # current shell may not see the PATH update yet:
  SPT="${SPT_HOME:-$HOME/.spt-core}/bin/spt"
else
  SPT="spt"
fi
"$SPT" --version
```

## Check-and-install: PowerShell

```powershell
if (-not (Get-Command spt -ErrorAction SilentlyContinue)) {
    Write-Output "spt-core not found - installing..."
    gh release download --repo BigscreenVR/spt-bs-releases `
      --pattern 'spt-x86_64-windows.exe' --dir $env:TEMP
    & (Join-Path $env:TEMP 'spt-x86_64-windows.exe') install
    # The user-PATH registration only reaches NEW terminals -- use the
    # absolute install path for everything in THIS process:
    $spt = Join-Path $env:LOCALAPPDATA 'spt-core\bin\spt.exe'
} else {
    $spt = 'spt'
}
& $spt --version
```

## Activate the adapter — register your manifest

Installing the binary is the first half of a pack-in; registering your manifest
is the second. Installing the binary makes `spt` available; **`spt adapter add`
activates your adapter** — registration is what lights up its profiles,
`[strings]` bodies, `[digest]` extractor, and hooks and makes it show in
`spt adapter list`. So the step right after the binary check is registering the
manifest:

```sh
# after `spt` is confirmed present (above):
# from a GitHub release — ships built binaries, source-free, versioned:
"$SPT" adapter add --release <your-org>/<your-adapter-repo>               # latest
"$SPT" adapter add --release <your-org>/<your-adapter-repo> --tag v1.0.0  # pinned
# ...or clone a repo whose ROOT holds manifest.toml:
"$SPT" adapter add --github <your-org>/<your-adapter-repo>
# ...or a local directory your harness ships:
"$SPT" adapter add ./adapter
```

`adapter add` is **manifest-first** — a clean add proves the cross-field manifest
shape — and it conducts your
[`[update]`](manifest.md#update--adapter-self-update) avenue once (install is the
first update). Confirm with `spt adapter list`: your adapter and its version
appear. Keep this idempotent in your bootstrap the same way the binary check is —
register when `adapter list` shows your adapter missing or below the expected
version.

**`--release` is the recommended distribution.** It fetches a `.spt` archive
asset — a tar whose root holds `manifest.toml` + `strings/` + the binaries the
manifest points at — from the named GitHub release, extracts it to the durable
registry home, and registers the root. That ships your **built binaries**,
source-free and **versioned by tag** (`--tag`, default the latest release), and
first-acquisition trusts gh's authenticated TLS + GitHub exactly like the
bootstrap's first binary fetch. A development **monorepo stays a monorepo**: your
release CI packs the archive (`tar -czf adapter.spt manifest.toml strings/ bin/…`)
and uploads it as a release asset, so the adapter ships straight from your
existing repo. Override the asset name with `--asset` (default `adapter.spt`).

**Cover several platforms in one `.spt` (since v0.13.2).** To ship binaries for
more than one OS/arch in a single asset, add a **target-triple subdirectory** at
the archive root per platform and put that platform's binaries inside it, leaving
the shared `manifest.toml` + `strings/` at the root:

```text
adapter.spt
├── manifest.toml                # shared — at the root
├── strings/                     # shared — at the root
├── x86_64-pc-windows-msvc/      # one platform's binaries…
│   └── bin/…                    #   …in the same relative layout a flat .spt uses
└── x86_64-unknown-linux-gnu/
    └── bin/…
```

On install, spt-core extracts the shared root plus **only the current node's
triple**, flattened into the install dir — so the bare-name
`<install_dir>/<program>` resolution above is unchanged; mirror, under each
triple, exactly the per-platform tree a flat `.spt` would place at the root. The
recognized triples are the platforms spt-core itself ships for — today
`x86_64-pc-windows-msvc`, `x86_64-unknown-linux-gnu`, and (since v0.30.0)
`x86_64-unknown-linux-musl` — one set shared with platform-targeted update
sets, so a new spt-core platform tier extends this list automatically; a
root subdirectory whose name is **not** a recognized triple is treated as a shared
root entry (so binaries for other platforms still ship as **separate
single-platform assets**, one selected per node with `--asset`). A multi-platform
archive that lacks the recipient's triple is refused with a clear
`NoArtifactForPlatform` error — never a silent partial install — and requires
`min_spt_core_version >= 0.13.2`. A flat archive (no triple subdirectories)
installs exactly as before.

`--github` is the alternative for an adapter whose **repo root already holds
`manifest.toml`**: it clones the repo and registers the clone root (`adapter add`
resolves a directory source to `<dir>/manifest.toml` at the root). Local
development uses the directory form, which takes any path or filename:
`spt adapter add ./adapters/my-adapter.toml`.

What registration holds under `adapters/<name>/` follows your
[`[update]`](manifest.md#update--adapter-self-update) avenue: a `delegated` or
`gh_release` adapter is **pointer-mode** (the manifest and `strings/` are read
live from the durable home), and a `file_pull` (or avenue-less) adapter is
**copy-mode** (the `manifest.toml` and `strings/` are copied in). Publish the
binaries your manifest references in the `.spt` (or repo) too, and reference them
**by bare name**: since v0.8.0, a command template's program token resolves
against the adapter's install dir before `PATH`, so a `.spt` that ships its
binaries is **self-contained** — the shipped binary is found without any PATH
placement. (Absolute paths still work; an unshipped tool still falls back to
`PATH`.) This applies to the `[session.psyche_resume]` per-event turn, the
`[digest]` extractor, and `spt adapter digest-proof`.

> **"Install the plugin, get the adapter for free" — include the activation
> step.** The [`[update]`](manifest.md#update--adapter-self-update) avenues
> keep a *registered* adapter current. The straightforward path for a
> `--release`-distributed adapter is **`gh_release`** (since v0.8.0): declare
> `avenue = "gh_release", repo = "your-org/your-adapter"` and
> `spt adapter update` ships the latest release `.spt` to the node — fetched,
> optionally verified against your `signing_key`, re-extracted, and
> re-registered. The other avenues: `delegated` (your harness's own updater
> installs the content — set `self_verifies = true` to attest it verifies what
> it installs), and `file_pull` (its automatic network-pull transport is **on
> the roadmap**). Deliver the manifest with `adapter add --release` (or
> `--github`, or a packed local dir) and let `gh_release` carry updates.

## The Windows PATH-refresh gotcha

The install verb registers the binary directory on the **user** PATH via the
registry. Registry PATH changes reach **new** processes; an already-running
process — including the terminal (and *your bootstrap*) that just ran the
install — keeps the PATH it started with.

So: **the first invocation after an install must use the absolute path**
(`%LOCALAPPDATA%\spt-core\bin\spt.exe`; the verb prints it). Every new
terminal after that finds `spt` normally. The snippets above bake this in.
On Linux the equivalent (a `~/.profile` entry the current shell hasn't
sourced) is handled the same way: the printed absolute path, once.

## Pinning and install-dir overrides

Pin a release by giving `gh release download` an explicit tag
(`gh release download v0.32.0 --repo … --pattern …`). The verb's knobs:

| Flag | Meaning |
|---|---|
| `--dir <path>` | Override the install directory |
| `--no-path` | Skip user-PATH registration |

## Trust model

First fetch trusts gh's authenticated TLS + the private channel's access
control (check the download against the release's `SHA256SUMS` for a belt on
the braces). From then on, `spt update` performs full Ed25519 signature
verification against the two-key trust anchor embedded in every binary — so
the bootstrap is the strong link only once.
