A browser-native reimplementation of spore.host spawn:
launch and manage self-terminating AWS EC2 instances entirely from a web page —
no backend, no server-side credentials.
It is API-first: the core is a framework-free TypeScript library
(SpawnClient) that ports spawn's lifecycle model. A GUI and an optional
terminal are bundled as two consumers of that API.
▶ Live demo (mock backend, no credentials needed): https://spore-host.github.io/spawn-ts/
- Architecture — layers and the provider seam
- Lifecycle engine — priority order and invariants
- API reference — the
SpawnClientAPI - Integration with truffle-ts — how the two tools compose
Full index in docs/.
The real spawn (Go) launches EC2 instances that manage their own death — via
TTL, idle detection, cost limit, or a completion signal — so you never get a
forgotten bill. The self-termination guarantee lives in spored, an in-instance
daemon, not the CLI. That means the launcher can be anything — including a web
page. spawn-ts is that web page.
src/
core/ ← the API (no DOM, no framework)
types.ts domain model (LaunchSpec, ManagedInstance, …)
duration.ts Go-compatible duration parse/format ("4h", "1h30m")
tags.ts the spawn:* tag wire-contract (matches the Go tool)
lifecycle.ts pure decision engine — a port of spored's checkAndAct
provider.ts backend interface (compute abstraction)
mock.ts in-memory provider (default; not billable)
client.ts SpawnClient — public API: clock + monitor loop + events
aws/
ec2.ts real EC2 provider via @aws-sdk/client-ec2 (or substrate)
userdata.ts bootstrap that installs spored on the instance
cli/ spawn command parser + handlers (used by the terminal)
ui/ the GUI (dashboard) + optional terminal pane
index.ts library entry / barrel export
core/lifecycle.ts is a faithful, pure port of
spawn/pkg/agent/agent.go:checkAndAct. Priority order is load-bearing and
matches the original exactly:
- completion signal → run the on-complete action
- TTL → always terminate (the hard cost backstop; never just stops)
- cost limit → terminate
- idle → stop, or hibernate with
hibernateOnIdle
TTL uses an absolute deadline anchored at launch, so stop/start cycles can't extend an instance past its deadline — same invariant as the Go tool.
import { SpawnClient } from "spawn-ts";
const spawn = new SpawnClient({ clock: 60 }); // 1 sim-minute per real second
spawn.on((e) => console.log(e)); // typed event stream
spawn.startMonitor(); // begin the lifecycle loop
await spawn.launch({ name: "job", ttl: "4h", onComplete: "terminate", pricePerHour: 0.153 });
// …instance self-terminates when its TTL expires, emitting an "action" event.Swap to real AWS (credentials held in memory only, never persisted):
import { SpawnClient, EC2Provider } from "spawn-ts";
spawn.setProvider(new EC2Provider({ region: "us-east-1", accessKeyId, secretAccessKey }));- mock (default) — in-memory, not billable, accelerated sim clock. Safe for demos and the whole test suite.
- substrate — the substrate AWS
emulator at
http://localhost:4566(deterministic, offline, cost-visible). Verified working over the wire against substrate v0.73.0. Needs opt-in CORS for browser SDK clients, added in v0.72.0 (#346); v0.73.0 also makesRunInstancesecho launch-time tags to match real EC2 (#351). - real AWS —
@aws-sdk/client-ec2v3 direct from the browser. Billable. A safety guard refuses an unbounded real launch (no TTL and no cost limit).
npm install
npm run dev # Vite dev server
npm test # vitest — lifecycle + end-to-end client tests
npm run build # static bundle in dist/src/aws/ec2.integration.test.ts drives the real @aws-sdk/client-ec2 path
against a live substrate emulator. It auto-skips when substrate is unreachable,
so plain npm test stays hermetic. To run it:
# substrate v0.73.0+, CORS enabled (config: server.cors.enabled: true)
substrate server --config substrate-cors.yaml # listens on :4566
SUBSTRATE_ENDPOINT=http://localhost:4566 npm testIt verifies the full launch → list → get → terminate round-trip and that the SpawnClient monitor self-terminates an expired-TTL instance over the wire.
Core lifecycle scope: launch, list, status, connect (surfaces SSH/SSM command),
extend, stop/start, hibernate, terminate + the full TTL/idle/cost/completion
engine. Parameter sweeps (spawn sweep) fan a parameter grid out into many instances
(see docs/sweeps.md), and batch job queues (spawn queue)
launch a DAG of jobs as dependencies complete and capacity allows (see
docs/queues.md) — both over one shared fan-out engine.
The launch form's instance-type field is backed by a truffle instance picker:
type a natural-language query ("h100 efa", "cheapest graviton 32gb") and pick from
matched EC2 instance types, which auto-fills the type and its estimated $/hr. This
is powered by @spore-host/truffle-ts
(a library dependency); spawn-ts owns the picker UI.