Command line
Everything SSPUR does goes through one binary, sspur. This is its usage text, exactly as the current build prints it (CI checks this page against sspur --help and sspur deploy). Commands and flags marked (experimental) may change in a 1.x minor release; Stability and deprecation lists what 1.x keeps:
$ sspur --help
usage:
sspur new web-app NAME | --list create a project from a template: web-app is a notes service with accounts (README.md inside)
sspur init [file.ssp] [--pkg NAME] create a codebase in .sspur/ (optionally import a file; --pkg also writes sspur.toml)
sspur add <name[@^1.2]|scope/name|path|git-url[@rev]> [--as KEY] add a dependency (a published package, a path or a git URL) to sspur.toml and pin it in sspur.lock
sspur deps fetch | update [NAME...] [--force] | tree verify and fetch locked deps; upgrade with a semantic diff (refused if it breaks the build); show the graph
sspur update [NAME...] [--force] the same as deps update: newest matching versions, with the semantic diff
sspur search [QUERY] [--json] published packages; sspur source NAME[@VERSION] [DIR] downloads one, verified, without running it
sspur publish [--dry-run] [--ack net,fs] [--ref TAG] [--no-tests] run the registry checks here, then open the publish request; sspur archive [--rev REV] [-o FILE] builds the source snapshot the registry stores
sspur yank NAME@VERSION [--undo] [--deprecate MSG] request a yank (or deprecation); sspur api [--json] shows the public API and the facts the checks use
sspur edit [file|-] [-e SRC] [--test] replace or add definitions by name (also 'rename A B', 'remove A' lines)
sspur apply [tx.json|-] [-e JSON] [--test] apply a transaction of ops
sspur q <query> [target] [--budget N] (experimental) query the codebase (list [types|fns|tests] sig body callers callees effects find grep pack why impact holes diag log; body and pack take A,B,C)
q find 'a|b*' names matching, with signatures q grep TEXT definitions whose source contains TEXT, with the lines q body A,B several bodies
sspur start [NAME|PATTERN...] (experimental) the agent spec plus this codebase: all of it if small, else counts, q pack of NAMEs, q find of the rest
sspur spec [--more|--full] [src] [QUERY TARGET...] (experimental) the spec (--more: the rest of the builtins, --full: the reference), then optionally src and queries
sspur src | log | export | mcp [--dir PATH] | --version (mcp is experimental)
sspur lsp the language server, over stdio, for VS Code and other editors (docs: Editor setup)
sspur bind header.h [--lib NAME] [-o out.ssp] generate extern declarations from a C header (uses clang)
sspur export-c file.ssp [-o libfoo] [--shared] [--prefix P] build a C library and header from the C-compatible functions
add --json for machine output (apply, edit, q, check)
sspur check|run|test|fuzz|verify|hash|fmt|native [file.ssp] [--json] [--cases N] [--seed N] [--edge] [--write] [--full]
sspur fix [file.ssp|dir...] [--check] rewrite code written for 0.14 so 1.0 reads it the same, and list what to check (--check writes nothing, exit 1 if it would rewrite)
check also lists up to 3 candidates for each typed hole, run against the tests and contracts (--fill-ms N per hole, default 400; --no-fill skips them)
sspur fill [file.ssp] [?hole|fn] [--apply] [--all] [--json] [--fill-ms N] the best expression for each typed hole; --apply writes it in
sspur suggest [file.ssp] [fn] [--apply] [--json] [--cases N] pre and post clauses that held on every test-derived and fuzzed run; --apply adds them
verify proves pre/post/where clauses with z3 and reports proved, counterexample, or unknown per clause
run/test compile to native code by default (cached); --interp forces the interpreter, --native uses the Cranelift JIT (experimental),
--O3 raises the optimization level; fuzz --differential compares native against the interpreter
a failed native build prints one warning and falls back (--quiet hides it); --strict-native or SSPUR_STRICT_NATIVE=1 makes it an error
sspur fuzz --gen N [--seed S] [--cases C] [--group G] [--modes O2,O2-whole,O3] [--keep DIR] [--print] generate N random programs and compare interpreter and native
run/test/build --pgo [--retrain] (experimental) train once, then build with clang profile data; --lto off|thin|full (ADR 0024)
sspur build --backend llvm file.ssp [-o prog] (experimental) direct LLVM IR prototype for the scalar and record subset (ADR 0024)
sspur explain-opt file.ssp [--all] show the proven rewrites applied before native codegen (--all adds rejected candidates)
sspur run --profile file.ssp run in the interpreter and record call counts that guide inlining (ADR 0022)
sspur build --target riscv64-qemu|aarch64-qemu|thumbv7em-mps2 file.ssp [-o kernel.elf] (experimental) build a 'profile bare' kernel or Cortex-M4 firmware (freestanding C, clang, ld.lld)
run/test/build --debug (or SSPUR_DEBUG=1) build native code with DWARF (CodeView on Windows) mapped to the .ssp lines, at -O0
sspur debug [file.ssp] [args] [--debugger lldb|gdb] build with --debug and start lldb (macOS) or gdb (Linux) on it, stopped at main
sspur debug-init [--install] write the lldb and gdb formatters for Str, List, Opt, Bool and sums; --install loads them from ~/.lldbinit and ~/.gdbinit
sspur gpu file.ssp [--emit metal|opencl|spirv|ptx] [-o out] (experimental) emit the kernel fns as Metal, OpenCL C, SPIR-V or PTX (ADR 0020)
sspur deploy plan|local|migrate|replay|swap|promote|rollback|backfill|status (experimental; sspur deploy for details; ADR 0016, 0019; never calls AWS)
sspur sync serve [--port N] [--max N] | push|pull REMOTE [--agent NAME] | status | resolve ours|theirs [PATH] (experimental) replicate by hash (REMOTE: dir or tcp://host:port; ADR 0021)
$ sspur deploy
usage (nothing here calls AWS):
sspur deploy plan <file.ssp> [--out DIR] write template.json, iam/*.json, plan.json, bootstrap.c, build.sh, deploy.sh, rollback.sh, backfill.sh and a host build to DIR (default deploy/)
sspur deploy local <file.ssp> [--port N] [--record FILE] serve on 127.0.0.1 with emulated Lambda and DynamoDB; --record appends requests, responses and db calls as JSON lines
sspur deploy migrate <old.ssp> <new.ssp> [--out DIR] [--json] classify store schema changes, check migrate_/unmigrate_ functions, write migrate.json and backfill.sh
sspur deploy replay <recording> <new.ssp> [--strict] [--json] re-run a recording against new.ssp and report behavior differences (exit 1 if any)
sspur deploy swap <new.ssp> [--weight P] [--port N] hot swap a running deploy local to new.ssp (P% canary, default 100)
sspur deploy promote|rollback|status [--port N] promote the canary, roll back to the previous version, or show versions
sspur deploy backfill [--store S] [--port N] migrate every stored item of the live version's migrated stores in the emulated table
By task
| Task | Commands |
|---|---|
| Start a project | sspur new web-app NAME (the reference web app, see Build a web app), sspur new --list |
| Run and test a file | sspur run FILE, sspur test FILE, sspur check FILE [--json], sspur fmt FILE [--write] |
| Find bugs and prove properties | sspur fuzz FILE (contracts as property tests), sspur fuzz --differential FILE (native against the interpreter), sspur verify FILE (Z3) |
| Write code and contracts from tests | sspur fill [FILE] [?HOLE] [--apply] (typed holes), sspur suggest [FILE] [FN] [--apply] (pre and post from runs) |
| Work on a codebase | sspur init [FILE], sspur start [NAME...], sspur q QUERY TARGET, sspur edit --test -e SRC, sspur log, sspur src |
| Agents and MCP | `sspur spec [–more |
| Editors | sspur lsp (the language server, see Editor setup) |
| Packages | sspur init --pkg NAME, sspur add NAME[@REQ] [--as KEY], sspur add PATH-OR-URL[@REV], sspur deps fetch, sspur update [NAME...] [--force] (also sspur deps update), sspur deps tree |
| The registry | sspur search [QUERY], sspur source NAME[@VERSION] [DIR], sspur publish [--dry-run], sspur yank NAME@VERSION, sspur api [--json], sspur archive (see Publishing a package) |
| Native code and C | sspur native FILE, sspur run --O3, --pgo, --lto, sspur explain-opt FILE, sspur bind HEADER.h, sspur export-c FILE |
| Debug with lldb or gdb | sspur debug FILE [ARGS], sspur build --debug FILE, sspur run --debug FILE, sspur debug-init [--install] (see Debugging) |
| Bare metal and GPU | sspur build --target riscv64-qemu|aarch64-qemu|thumbv7em-mps2 FILE, sspur gpu FILE --emit metal|opencl|spirv|ptx |
| Services | sspur deploy plan, local, migrate, replay, swap, promote, rollback, backfill, status |
| Many agents and replicas | sspur sync serve, push, pull, status, resolve |
| Move code from 0.14 to 1.0 | sspur fix [FILE|DIR...] [--check] (see Migrating to 1.0) |
Flags the usage text leaves out
These are for working on SSPUR itself or for scripts, so the usage text doesn’t list them:
| Flag | Does |
|---|---|
sspur native --emit-c FILE | Prints the generated C instead of building it |
sspur native --release FILE | Builds each definition at -O2 and prints which functions are native and which were skipped, and why |
sspur export-c --keep-c FILE | Keeps the generated .c file next to the library |
sspur q list --tests | Lists the tests only |
sspur fuzz --gen N --from K | Starts the generated programs at index K (to rerun a range) |
sspur fuzz --reduce FILE | Shrinks a program while it still typechecks and still shows the behavior: a mismatch between --modes, --want-skip TEXT (a native skip reason), --want-build TEXT (a build error) or --want-hang "ARGS FILE" (a child sspur ARGS that crashes or hangs) |
sspur q pack NAME --full | Also prints the estimated tokens and what was included and omitted, on stderr |
sspur init FILE --json, sspur sync push|pull|resolve --json | Print the result as JSON |
sspur publish --tests-json | Runs the package’s tests in both tiers and prints them as JSON, for the registry’s test job |
Starting a project
sspur new web-app NAME writes a new directory NAME (which must not exist or be empty; its last component is the package name, so notes or my_notes) with SSPUR’s reference application, a notes service with accounts: main.ssp with the routes and handlers, module packages for the data types, SQLite storage, accounts and HTML views, three reusable packages, tests that sspur run tests/run.ssp runs in both tiers, a backup tool, a systemd unit and a Caddyfile, and a README that covers running, testing, debugging, configuring, backups and deploying. The template is built into sspur, so nothing is downloaded. sspur new --list lists the templates.
Migrating from 0.14
sspur fix takes files or directories (all the .ssp files under them; with none, the package the current directory is in) and rewrites code written for 0.14 so that 1.0 reads it as 0.14 did: string literals whose braces or escapes changed meaning, arms that 0.14 gave to a nested match or catch, and binding annotations that 0.14 ignored and that don’t hold. It edits only those spans, so comments and layout stay, and running it again changes nothing. It also lists, as check: lines, the places whose meaning changed and that it can’t rewrite: == through a hand-written Eq inside an Opt, List, Res, tuple or derived type, round floor ceil trunc on an F64, and comparisons with 0.0. --check writes nothing and exits with 1 if it would rewrite something. Migrating to 1.0 has each change.
Execution tiers
run and test compile to native code through C and clang, cached by content hash under ~/.cache/sspur/native/. A plain sspur run FILE of a program that hasn’t changed since its last run skips parsing and checking and calls the cached library’s main directly (ADR 0034); --no-cache takes the normal path. --interp uses the reference interpreter and --native the Cranelift JIT. If a native build fails, SSPUR prints one warning and falls back to the interpreter; --strict-native or SSPUR_STRICT_NATIVE=1 makes that an error, which is how CI runs.
Debugging
--debug on run, test, build and native, or SSPUR_DEBUG=1, builds native code with debug info that maps to the .ssp lines: DWARF (CodeView on Windows) at -O0, functions, parameters and locals under their SSPUR names where C allows it, cached separately from normal builds, which don’t change. sspur build --debug FILE builds it and prints the library and the source file its lines refer to (for a codebase, a copy of its source under ~/.cache/sspur/debug/src/). sspur debug FILE [ARGS] builds it and starts lldb (macOS) or gdb (Linux, or --debugger gdb) with the formatters loaded and breakpoints on fn main and on sspur_trap, which every trap calls. sspur debug-init writes the lldb and gdb formatters to ~/.cache/sspur/debug/ and prints the lines that load them; --install adds those lines to ~/.lldbinit and ~/.gdbinit. See Debugging and ADR 0036.
Queries
The query API is how an agent reads a large codebase without printing all of it. The graph model describes every query; these are the ones agents use most:
| Query | Prints |
|---|---|
q find 'ship|tax_*' | definitions whose names match (substring, * glob, ^ and $ anchors), with signatures |
q grep TEXT | definitions whose source contains TEXT, with up to 3 matching lines each |
q body A,B | the source of several definitions |
q pack A,B,C | each definition with the types and signatures it uses, its tests and its callers, and a line saying whether that list is complete |
q callers NAME, q callees NAME | the call graph around one definition |
q effects NAME | the declared effect row of one definition |
q diag, q log | every diagnostic, and the history of edits |
Holes and suggested contracts
sspur check lists up to three candidates under each typed hole when the program has no errors, and --json puts them in the hole’s fix as fill ops with an evidence string and ok. The search has 400 ms per hole (--fill-ms N changes it, --no-fill turns it off); files without holes are checked exactly as before.
| Command | Prints |
|---|---|
sspur fill [FILE] [?NAME|FN] | The best candidate for each hole (or the named one, or every hole in FN): ?total: items.map(_.qty * _.price).sum (passes 3 tests, post), with 2 others also fit added when other candidates pass the same checks, so the tests don’t decide between them. --all shows up to 3, --json adds the search size and time. --apply writes the passing ones into the file, in parentheses where precedence needs them, or applies them to the codebase as fill ops. The budget is 1000 ms per hole |
sspur suggest [FILE] [FN] | For each function, the pre and post lines that held on every run, ready to paste: post r.len <= xs.len // proved. --json gives refine fixes, --apply adds them, --cases N sets the number of fuzzed inputs (200) |
The MCP server has both as the tools fill and suggest (MCP).
The registry
| Command | Does |
|---|---|
sspur add NAME[@REQ] | Adds a published package to [deps] as { version = "REQ" } (default ^ the newest version) and locks the newest version that meets it. NAME@1.2.3, a whole version, writes =1.2.3: exactly that version. A scoped name, sspur add acme/money, is added under the key after the scope, money = { package = "acme/money", version = "^1.0.0" }; --as KEY chooses the key, and is needed when the key is taken. A file or directory called NAME is added by path instead |
sspur update [NAME...] [--force] | sspur deps update: moves the named packages (or all) to the newest versions their requirements allow, prints the export diff, and writes the lock only if your code still checks |
sspur search [QUERY] [--json] | Packages whose name, keywords or description match, with their newest version |
sspur source NAME[@VERSION] [DIR] | Downloads a version’s source into DIR (default NAME-VERSION, with / as - for a scoped name), verified against its snapshot SHA-256, without building or running it. @1.2.3 is exactly that version, @^1.2 the newest that matches |
sspur publish | Runs the registry checks on the package at HEAD and every module in it (their tests natively and in the interpreter too, side by side, in a copy of the commit), prints the report, and when no blocking check fails, opens the publish request: with the GitHub CLI if it is signed in, else by printing the prefilled form URL. --dry-run only checks, --ack net,fs acknowledges new capability effects, --ref TAG names the tag (default the tag at HEAD), --repo URL the GitHub repository (default the origin remote), --no-tests skips the tests, --no-gh prints the URL, --json prints the report as JSON |
sspur yank NAME@VERSION | Opens a request to yank the version (acme/money@1.0.0 for a scoped name); --undo unyanks, --deprecate MESSAGE deprecates (with --undo, undeprecates), --reason TEXT explains, --no-gh prints the URL |
sspur api [--json] [--rev REV] | The package’s public API with lines and doc comments; --json gives the facts the checks read (diagnostics, formatting, definitions, effects, export hash, and in a git repository the files of REV) |
sspur archive [--rev REV] [-o FILE] | Builds the source snapshot of a commit (default HEAD) as the registry stores it, a deterministic tar, and prints its SHA-256 and size |
The index is read from SSPUR_INDEX (default sspur-packages on raw.githubusercontent.com) and cached under ~/.cache/sspur/index; see Environment variables.
The language server
sspur lsp speaks the Language Server Protocol over stdin and stdout, for VS Code and any other editor with an LSP client (Editor setup, ADR 0031). It publishes diagnostics as you type, a short pause after the last edit, and then the candidates for each typed hole (600 ms a hole, or SSPUR_FILL_MS). It answers completion, hover, signature help (after ( and ,, with the argument being typed highlighted, on half-typed calls too), inlay hints (the inferred type after a binding whose type isn’t written, and the parameter name before a literal argument), code actions (the compiler’s fixes and the hole fills), formatting, definition, references, document highlights, rename, the outline and semantic tokens, all within one file, and the command sspur.fill fills every hole that has a passing candidate. A file under a directory with sspur.toml is checked with that package’s dependencies. It takes no options, and ignores --stdio, which some clients add. Inlay hints can be narrowed with initializationOptions or the sspur settings section, {"inlayHints": {"types": true, "lambdas": true, "params": false}}, where types covers let, guard, for and var bindings, lambdas lambda parameters and params the names before literal arguments; all three are on by default.