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.shhttps://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 isgh_release(since v0.8.0): declareavenue = "gh_release", repo = "your-org/your-adapter"andspt adapter updateships the latest release.sptto the node — fetched, optionally verified against yoursigning_key, re-extracted, and re-registered. The other avenues:delegated(your harness’s own updater installs the content — setself_verifies = trueto attest it verifies what it installs), andfile_pull(its automatic network-pull transport is on the roadmap). Deliver the manifest withadapter add --release(or--github, or a packed local dir) and letgh_releasecarry 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 var | Meaning |
|---|---|
SPT_INSTALL_VERSION | Install a specific release tag instead of latest |
SPT_INSTALL_DIR | Override the install directory |
SPT_INSTALL_ASSET_BASE | A URL or local directory holding the release assets + SHA256SUMS directly (CI, air-gap, mirrors) |
SPT_INSTALL_NO_PATH | 1 = 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.