siltrun
v0.2.0
Published
The Silt CLI — run an authoritative multiplayer room locally with `siltrun dev`.
Downloads
37
Maintainers
Readme
siltrun — the Silt CLI
Run an authoritative multiplayer room on your machine with one command. A room is a
reducer (tick(state, inputs)); the server owns the truth. The full quickstart is at
https://silt.run/docs/quickstart/.
npx siltrun dev room.tsYour room is live at http://localhost:4000 — point a client at it and go:
import { useRoom } from "@siltrun/react";
const { state, send } = useRoom("http://localhost:4000", { id: crypto.randomUUID() });What siltrun dev does
siltrun dev <contract.ts> stands up the whole local room and keeps it live as you edit:
- Builds the Go room-server binary if it is missing or stale (
go build). - Bundles your contract and its imports into a single module (Bun's bundler).
- Runs the determinism doctor if
@siltrun/room-hostis resolvable — it replays the tick sequence and reports drift at the exact tick/field. If the doctor is not present, dev prints a notice and keeps running. - Boots the room-server in compute mode (
--contract <bundle> --host <host.ts>) and scrapes the per-boot dev certificate hash it prints. - Serves room-info on
:4000—GET /.well-known/siltreturns{ wtEndpoint, certHash, mode }with permissive CORS, so a Vite app on another port can discover the room and dial WebTransport with the cert pinned. You never touchserverCertificateHashes. - Watches the contract. On save it rebundles, re-runs the doctor, and restarts the room (the simplest correct reload — live state resets in dev; the new cert flows straight into room-info). It prints exactly what happened.
- Ctrl-C tears everything down — the room-server child and the room-info server.
The room-server is supervised: an unexpected crash triggers an exponential-backoff restart (250ms → 5s cap), clearly logged, so a transient failure never ends your session.
Options
siltrun dev <contract.ts> [options]
--wt-port <n> WebTransport port (default 4433, env SILT_WT_PORT)
--info-port <n> room-info port (default 4000, env SILT_INFO_PORT)
--wt-host <host> advertised WebTransport host (default 127.0.0.1, env SILT_WT_HOST)
--wt-bind <addr> WebTransport bind address (default: --wt-host, env SILT_WT_BIND)
--no-doctor skip the determinism check on (re)load--wt-host is what the room advertises; --wt-bind is the address it listens on.
They are the same by default. To reach the room from a second device, bind wide and let
discovery answer for itself:
npx siltrun dev room.ts --wt-bind 0.0.0.0room-info binds wherever WebTransport binds — a device that can reach the room can also
discover it — and the advertised wtEndpoint follows the host each request arrived on, so
a loopback fetch learns a loopback endpoint and a LAN or tailnet fetch learns that one.
Path overrides (mainly for tests and for running against a local checkout):
SILT_ROOM_SERVER_DIR, SILT_ROOM_HOST_ENTRY, SILT_DOCTOR_ENTRY.
siltrun deploy
npx siltrun deploy room.tsShips a contract to the hosted beta: bundle → local determinism doctor → upload
(SILT_DEPLOY_TOKEN required). Prints the room endpoint and the check page on success.
Deployed room names are capped at 12 chars, charset [A-Za-z0-9._-]. The CLI
enforces this before upload, so an over-long name fails on your machine in a second
instead of after a round trip. Local siltrun dev has no length cap.
What gets installed
npm install siltrun works from an empty directory with Node as the only
prerequisite. Three runtimes arrive as packages, and you do not install any of them
yourself:
- The CLI runs on Bun, but its
binis a plain-Node shim that locates Bun and re-execs under it. Bun arrives through the officialbunnpm package, a dependency. SetSILT_BUNto point at a specific Bun binary if you need to; otherwise the shim finds one, and every downstream spawn reuses the same runtime. - The room-server ships prebuilt in per-platform packages
(
@siltrun/room-server-<os>-<arch>), declared asoptionalDependencieswithosandcpufields, so npm installs exactly the one matching your machine. - The room-host (the contract runtime, the doctor, and the deterministic realm)
ships as
@siltrun/room-host, plain TypeScript source with zero runtime deps.
To start a project rather than add the CLI to one, npm create siltrun@latest my-game
scaffolds room.ts, a React client, and a dev script that runs the room and Vite
together.
Where this is heading
npx siltrun dev is the standalone path. The intended future is a Vite plugin, so
the room runs inside your existing npm run dev: one project, one terminal, one reload
story, the way API routes feel in a web framework. room.ts should feel like adding a
route rather than running a game server. The plugin would host the room-info endpoint on
the Vite dev server itself and own the same supervise, bundle, and doctor pipeline this
CLI implements today, so the standalone path stays the reference implementation.
Notes
- Runtime: Bun. The room-host runs on Bun and the contract is bundled with Bun, so
Bun is the toolchain either way. The CLI's own code runs under it through the Node
shim above, and never assumes
bunis on your PATH. - Room route:
/room/<name>, named by--room,SILT_ROOM, or derived from the contract path (generic basenames likeroom.tsdefer to the parent directory). You never type this path yourself, only the room-info URL.
Docs
Full guides and API reference: https://silt.run/docs/
