Install-on-demand bootstrap
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.
The generic contract
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 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..."
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
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:
# 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 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:
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] 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]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 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.