WSVM is a lightweight toolchain manager for the
WebAssembly wasi-sdk. It installs,
selects, discovers, and validates versioned wasi-sdk toolchains and exposes the
active one to your build tooling via WASI_SDK_PATH, so that wasi-sdk becomes an
implementation detail rather than a prerequisite.
wsvm's logic runs as a WebAssembly component, which needs a wasm runtime to
execute. Rather than bundling its own, wsvm borrows one — from
wvm (its sibling, the Wasmtime version
manager), from a user-supplied path, or from PATH — so wsvm and its runtime
stay decoupled. See docs/design.md for the full design.
curl -fsSL https://raw.githubusercontent.com/tegmentum/wsvm/main/install.sh | shThe installer places the wsvm binary and, unless you pass --no-wvm (or set
WSVM_NO_WVM=1), chain-installs wvm plus a default Wasmtime so nothing
extra is needed to run the app component. To bring your own runtime, set
WSVM_WASMTIME=/path/to/wasmtime or add wasmtime to PATH.
wsvm list # all available versions (installed ones marked)
wsvm install latest # download (over wasi:http) + verify + install
wsvm default latest # default toolchain for new shells
eval "$(wsvm env)" # export WASI_SDK_PATH and add bin/ to PATH
clang --target=wasm32-wasip1 \
--sysroot="$WASI_SDK_PATH/share/wasi-sysroot" \
hello.c -o hello.wasmOr run a tool directly through wsvm:
wsvm exec clang --versionLong operations show a progress bar / spinner on stderr when attached to a terminal, and fall back to plain milestone lines when output is piped.
wasi-sdk publishes bare-major releases (33, 32, 27, …); no minor or
patch levels have ever shipped, so install, default, use, path, and
upgrade accept exactly two spec forms:
| Spec | Meaning | Example resolution |
|---|---|---|
latest |
newest available | → 33 |
<major> |
that specific major | → 33 |
Both forms are stored as the spec (not a frozen version), so wsvm default latest keeps tracking new releases as they land. wsvm default 27 sticks
with the 27 line — if wasi-sdk ever adds a 27.1, it would pick that up.
wsvm upgrade [spec] [--all] refreshes a spec now rather than waiting for
activation-time auto-install.
33.0 / 33.0.0 normalize to 33 (for users coming from nvm/rustup
conventions). Anything with a non-zero minor or patch is rejected with a
clear "wasi-sdk uses major-only versions" error.
wsvm default <spec>sets the persistent default used by new shells.wsvm use <spec>switches the toolchain for the current shell only (reverting when you open a new one), via aWSVM_VERSIONenvironment variable.
The installer wires a shell hook into your rc file that auto-manages
WASI_SDK_PATH and its bin/ on PATH: it applies the active toolchain
once at shell startup and re-applies it after any wsvm use, wsvm deactivate, or wsvm default. Tools like clang, wasm-ld, and ar just
work — no manual eval "$(wsvm env)" needed. The hook is idempotent, so
re-evaluating it never accumulates duplicate PATH entries.
If you skipped the installer, wire the hook yourself:
wsvm shell-init >> ~/.zshrc # then restart your shellFor one-shot shells that did not source the hook (e.g. a scratch container),
eval "$(wsvm env)" still works.
| Command | Description |
|---|---|
wsvm install <spec> |
Install a toolchain (spec: latest, 27, 27.0, 27.0.1). --default to set it as default. |
wsvm install <ver> --from <archive> |
Install offline from a local .tar.gz (exact version required). |
wsvm list [--all] |
List all available versions (most recent first); installed/default marked. --all includes prereleases. |
wsvm uninstall <version> |
Remove an installed toolchain. |
wsvm default <spec> |
Set the persistent default (floats: latest, 27, 27.0). |
wsvm use <spec> |
Switch the toolchain for the current shell (needs shell-init). |
wsvm upgrade [spec] [--all] |
Pull the newest match for a floating line now. |
wsvm deactivate |
Clear the per-shell override, reverting to the default. |
wsvm shell-init |
Print the shell hook that enables per-shell use. |
wsvm current |
Print the effective version (session override, else default). |
wsvm path [spec] |
Print a toolchain's filesystem path (its WASI_SDK_PATH). |
wsvm env |
Print shell exports for the active toolchain. |
wsvm exec <tool> [args] |
Run a tool from the active toolchain's bin/. |
wsvm verify [version] |
Validate installation integrity against manifests. |
wsvm gc [--prune] |
Report (or delete) unreferenced store objects. |
wsvm objects |
List stored objects with sizes and the versions referencing them. |
wsvm doctor |
Diagnose install, shell integration, runtime discovery, and detect external wasi-sdks. |
wsvm completions <shell> |
Print a completion script (bash, zsh, or fish). |
wsvm --version |
Print the wsvm version. |
wsvm --upgrade [--check] |
Upgrade the wsvm binary itself to the latest release. --check reports without installing. |
wsvm --upgrade replaces the running wsvm binary with the newest release from
GitHub. It downloads the wsvm-<arch>-<os> asset for this host, verifies the
published .sha256 sidecar, and atomically renames the new binary over the old
one. Add --check to report whether a newer release exists without installing
it. This is distinct from wsvm upgrade <spec>, which upgrades the managed
wasi-sdk toolchains.
On ordinary management commands, wsvm prints a one-line "a newer version is
available" notice at most once per WSVM_REFRESH_INTERVAL seconds (default
3600). Set WSVM_NO_UPDATE_NOTIFIER=1 to disable the notice entirely.
wsvm (native bootstrapper, on PATH)
├─ resolves a wasm runtime (WSVM_WASMTIME → config → wvm → PATH wasmtime)
├─ handles `wsvm exec` natively (resolve toolchain, run bin/<tool>)
├─ finalizes unix file modes after each install
└─ runs the app on that runtime: <runtime> run -S http --dir WSVM_HOME wsvm-app.wasm -- <args>
wsvm-app (wasm32-wasip2 component) — all other commands
├─ explicit wasi:cli command: include wasi:cli/imports + export wasi:cli/run@0.2.6
├─ imports wasi:http (downloads, via waki)
├─ imports wasi:filesystem (internal toolchain symlinks, via symlink-at)
└─ imports sqlite:wasm/high-level (the index; composed in via `wac`)
wsvm-app is a standard wasi:cli command (built as a cdylib that owns its
wasi:cli/run@0.2.6 export), so it also runs directly under any wasi:cli host:
wasmtime run -S http --dir "$WSVM_HOME::$WSVM_HOME" --env WSVM_HOME="$WSVM_HOME" \
--env WSVM_HOST_ARCH="$(uname -m)" --env WSVM_HOST_OS=macos \
target/wsvm-app.composed.wasm -- listThe native binary embeds the composed app component; the wasm runtime is
supplied by the environment (wvm, WSVM_WASMTIME, or PATH).
wsvm picks the first runtime in this order:
WSVM_WASMTIME=/path/to/wasmtime— one-off override.~/.tegmentum/wsvm/config.toml:[runtime] path = "/opt/wasmtime/bin/wasmtime"
wvmonPATH— delegated aswvm exec -- run ..., so wsvm picks up whatever runtimewvmhas active. This is the installer's default.wasmtimeonPATH.
wsvm doctor reports which of these won. Missing a runtime is a hard error
with an actionable hint.
WSVM stores everything under ~/.tegmentum/wsvm (override with WSVM_HOME).
Files are kept once in a content-addressable store and referenced per version,
so multiple toolchains share identical files:
~/.tegmentum/wsvm/
store/sha256/<ab>/<cd>/<digest> # deduplicated file objects
33/ # a materialized toolchain (per version)
bin/clang # hardlinked from the store (default)
bin/clang++ -> clang # archive symlink, reproduced
lib/ … share/wasi-sysroot/ …
manifest.json
32/ # another installed version
default # persistent default spec (plain text)
downloads/
cache/releases.json # bounded remote-release list for spec resolution
index.db # SQLite backlink/metadata index (rebuildable)
wsvm-app.wasm # the app component
config.toml
Version directories live directly at the root — wsvm manages exactly one
toolchain (wasi-sdk), so the earlier toolchains/wasi-sdk/versions/ was three
layers of redundant nesting. Version names are recognized by the presence of a
manifest.json, so they never collide with the fixed siblings above.
The index.db SQLite database tracks object backlinks and version metadata;
it is a derived cache that wsvm gc rebuilds from disk, so a missing or stale
index is never fatal.
Regular files are materialized with the hardlink strategy: the store object
and the version path share an inode, so each unique byte lives on disk once —
du counts the store or the version dirs, not both. wasi-sdk's rpath-relative
library loading (@loader_path/../lib, $ORIGIN/../lib) works because
hardlinks look like ordinary files to the loader (no path canonicalization,
unlike symlinks). On filesystems that don't support hardlinks (cross-device
store, some wasm hosts), each file falls back to copy. Internal toolchain
symlinks (e.g. clang++ -> clang) are reproduced as real symlinks regardless
of strategy. Override in config.toml: [wsvm] materialization = "copy".
Requires the Rust wasm32-wasip2 target and wac
(cargo install wac-cli).
rustup target add wasm32-wasip2
make # builds the app, composes it with the SQLite component,
# then builds the native binary (target/release/wsvm)The vendored SQLite component (vendor/sqlite-core.wasm) provides
sqlite:wasm/high-level. The WASI WIT for the app's wasi:cli command world is
vendored under crates/wsvm-app/wit/deps, so a normal build needs no network for
WIT.
Apache-2.0.