SDK
Headless
createHeadless() boots a VM and returns the terminal's features as an API with no UI: files, catalog installs, commands and server previews.
javascript
import { createHeadless } from "@userland-run/nano-sdk";
const s = await createHeadless({ wasmUrl: "/nano/console.wasm", ramMB: 512, serviceWorkerUrl: "/nano-sw.js" });| Option | Type | Default | Description |
|---|---|---|---|
wasmUrl | string | "/nano.wasm" | The image to boot. Use console.wasm for BusyBox without a catalog install. |
ramMB | number | 1800 | VM memory. 256–512 is enough without Node on the VM. |
serviceWorkerUrl | string | "/nano-sw.js" | Service worker for serve() previews. |
The session
| Option | Type | Default | Description |
|---|---|---|---|
fs | Vfs | — | The filesystem. |
run(command, opts?) | Promise<ExecResult> | — | One BusyBox applet (as nano.run). |
sh(line, opts?) | Promise<ExecResult> | — | A full shell line (as nano.shExec). |
installApp(ref, opts?) | Promise<Manifest> | — | Install a signed catalog app. |
catalog() | Catalog | — | The catalog client. |
serve(launch, opts?) | Promise<{ url(path?), stop() }> | port 8080, /listening/i | Start a server (startServer) and get preview URLs. Options: port, readyPattern, onData. |
vm | Nano | — | The underlying instance. |
dispose() | void | — | Stop servers, detach the preview bridge, destroy the VM. Idempotent; later serve() calls throw. |
Files, commands and a catalog app
javascript
const s = await sdk.createHeadless({ wasmUrl: "/nano/console.wasm", ramMB: 256 });
s.fs.writeFile("/work/list.txt", "b\na\nc\n");
print(JSON.stringify(await s.run("sort /work/list.txt")));
print(JSON.stringify(await s.sh("sort -r /work/list.txt | head -1")));
const m = await s.installApp("hexyl");
print("installed", m.name, "| same Vfs:", s.fs === s.vm.fs);
print((await s.sh("printf AB | hexyl --border none --color never")).stdout.trim());
s.dispose();Expected output
{"exitCode":0,"stdout":"a\nb\nc\n"}
{"exitCode":0,"stdout":"c\n"}
installed hexyl | same Vfs: true
00000000 41 42 ABserve() with a preview URL
serve() starts the server, registers the preview bridge and returns url(path). A hidden iframe loads it through the service worker.
javascript
const s = await sdk.createHeadless({ wasmUrl: "/nano/console.wasm", ramMB: 256, serviceWorkerUrl: "/nano-sw.js" });
s.fs.writeFile("/www/index.html", "<h1>served headless</h1>");
const srv = await s.serve({ command: "echo listening on 8090; exec busybox httpd -f -p 8090 -h /www" }, { port: 8090 });
const frame = Object.assign(document.createElement("iframe"), { hidden: true, src: srv.url("/") });
const loaded = new Promise((r) => (frame.onload = r));
document.body.append(frame);
await loaded;
print(srv.url("/"), "→", frame.contentDocument.body.textContent);
frame.remove();
srv.stop();
s.dispose();
s.dispose(); // idempotent
print(await s.serve({ command: "true" }).catch((e) => e.message));Expected output
/sw/8090/ → served headless nano-sdk: headless session is disposed
See the embeddable terminal feature and the json-api example for the UI side. Display vs. headless: <nano-terminal> renders these features; createHeadless() exposes them as an API. For a VM off the main thread, use the worker transport or provision().