CLI and raw JS API
Run RISC-V Linux binaries from the command line with test/run.mjs in the nano repository, or drive the low-level NanoVM class that the SDK wraps (reachable as nano.raw).
The command-line runner
test/run.mjs loads wasm/nano.wasm into Node.js, loads one ELF and runs it, printing the guest's output and diagnostics (instruction count, block-cache statistics) to stderr. It is a development tool for the emulator, not a product CLI.
node test/run.mjs [elf] [--load local:/guest/path]… [--trace] [--stdin] [--tty] [--verbose] [--cmd <applet> args…]
elftest/hello.elf), e.g. runners/riscv/images/busybox--cmd <program> args…--cmd); for BusyBox the first word is the applet--load local:/guest--tracetrace feature: make build-minimal or make build-trace)--stdin--tty--verbose / -vNANOVM_WASMwasm/nano.wasm)NANOVM_RAM_MB2000 − wasmMB − 20)$ node test/run.mjs runners/riscv/images/busybox --cmd echo "Hello from RISC-V" Hello from RISC-V Exit code 0 (~100000 insns) $ node test/run.mjs runners/riscv/images/node --cmd node -e "console.log(process.arch, process.version)" riscv64 v25.4.0 Exit code 0 (~501600000 insns)
Output above is from a run of nano v0.2.1 on an Apple M3 Max (Node.js 24.11.1); stderr lines other than the exit line are omitted. runners/riscv/images/ holds the BusyBox (1,149,392 bytes) and Node.js ELFs via Git LFS.
The raw NanoVM API
runners/riscv/host/nanovm.mjs is the browser wrapper around the WebAssembly module: it owns the shared memory, the in-memory filesystem, the virtual server and the run loop. The SDK vendors it and exposes the instance as nano.raw. Prefer the SDK: it adds types, safe defaults, the VM lock and the higher-level modes. Wrap direct raw runs in nano.withVmLock().
| Option | Type | Default | Description |
|---|---|---|---|
NanoVM.create({ wasm, ramMB, busyboxUrl, nodeUrl }) | Promise<NanoVM> | ramMB 512 | Instantiate the module with a shared memory of ramMB. |
run(command, { onStdout, maxSteps }) | Promise<ExecResult> | maxSteps 2e6 | Run a BusyBox command line (whitespace-split). |
node(...args, opts) | Promise<ExecResult> | — | Run the Node.js ELF. |
addFile(path, content, mode?) | void | — | Create a file (parents created). |
readFileString(path) / listDir(path) | string | null / DirEntry[] | null | — | Read a file / list a directory. |
makeDir / removePath / renamePath / cwd() | | — | Direct MemFS operations. |
loadTarGz(bytes) | Promise<void> | — | Unpack an archive. |
writeStdin / setInteractiveStdin / closeStdin / cancelRun | | — | Standard input and cancellation. |
setNetwork({ corsProxyUrl, disabled }) | void | — | Configure the outbound network device (see Networking). |
snapshot() / restoreAndRun(snap, script, opts) | | — | Snapshot primitives used by NodeRuntime. |
instructionCount() | number | — | Guest instructions retired so far. |
virtualServer.injectConnection(port, request) | Promise<Uint8Array> | — | Inject one HTTP request (see Virtual server). |
nano.raw: the NanoVM instance
Direct filesystem calls and the instruction counter. The counter shows how much guest code a shell pipeline executes.
const raw = nano.raw;
raw.addFile("/work/raw.txt", "via raw\n");
print(raw.readFileString("/work/raw.txt").trim(), JSON.stringify(raw.listDir("/work").map((e) => e.name)));
const before = raw.instructionCount();
const r = await nano.withVmLock(() => raw.run("echo counted"));
print(r.stdout.trim(), "| guest instructions:", raw.instructionCount() - before > 0);via raw [".keep","raw.txt"] counted | guest instructions: true
Prefer the SDK for apps
For a tour of the emulator itself see RISC-V emulator. Everything above is reachable through @userland-run/nano-sdk with types: createNano, Running commands, Filesystem. The raw class has no compatibility guarantees between releases.