Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Install-on-demand bootstrap

How an adapter ships spt-core with itself. The contract: the canonical install one-liner 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 runs the official script when it’s missing.

The scripts are non-interactive by construction (they run unattended), idempotent (safe to re-run), sha256-verify what they download, and register the user PATH. Served from the permanent canonical URL:

  • https://sabermage.github.io/spt-releases/install.sh
  • https://sabermage.github.io/spt-releases/install.ps1

The generic contract

if `spt` is on PATH        -> done (optionally check `spt --version` ≥ your floor)
else                       -> run the official one-liner for the OS
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), so the bootstrap can leave upgrades to spt-core. The remaining bootstrap step is to register your adapter — see Activate the adapter below.

Check-and-install: POSIX sh

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

if ! command -v spt >/dev/null 2>&1; then
  echo "spt-core not found - installing..."
  curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
  # current shell may not see the PATH update yet:
  SPT="$HOME/.local/bin/spt"
else
  SPT="spt"
fi
"$SPT" --version

Check-and-install: PowerShell

if (-not (Get-Command spt -ErrorAction SilentlyContinue)) {
    Write-Output "spt-core not found - installing..."
    irm https://sabermage.github.io/spt-releases/install.ps1 | iex
    # 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:

# 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] 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 HTTPS + GitHub exactly like the install one-liner’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:

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 x86_64-pc-windows-msvc and x86_64-unknown-linux-gnu; 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] 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_init] runner, the [digest] extractor, and spt adapter digest-proof.

“Install the plugin, get the adapter for free” — include the activation step. The [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 installer 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 installer — 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 installer 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: $HOME/.local/bin/spt absolutely, once.

Pinning and air-gapped installs

The scripts are configured by environment knobs, so the pipe-to-shell form stays canonical:

Env varMeaning
SPT_INSTALL_VERSIONInstall a specific release tag instead of latest
SPT_INSTALL_DIROverride the install directory
SPT_INSTALL_ASSET_BASEA URL or local directory holding the release assets + SHA256SUMS directly (CI, air-gap, mirrors)
SPT_INSTALL_NO_PATH1 = skip PATH registration

Example — pin a version inside a CI job:

SPT_INSTALL_VERSION=v0.1.0 \
  curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh

Trust model

First fetch trusts HTTPS + GitHub and verifies the binary’s sha256 against the release’s SHA256SUMS. From then on, spt update performs full Ed25519 signature verification against the two-key trust anchor embedded in every binary — so the installer is the strong link only once.