An npm-package CLI (commander + chalk) on the real Node.js
A small command-line tool on two everyday npm packages: commander parses the arguments and chalk colours the report. The dependency tree is pinned by the example's package-lock.json; npm run demos:build installs it with npm ci and ships the installed node_modules/ as bundle.json.
The live sample is the integration code you would write yourself: fetch the bundle, write every file into the VFS with nano.fs.writeFile(), install the riscv64 node binary from the signed catalog, and run cli.js with it. Node's own require() resolves commander and chalk from node_modules/ next to the script.
Why the RISC-V VM and not the faster host engine? commander 12 starts with const process = require("node:process"), and the host-engine tier (nodert, as shipped in nano-sdk 0.2.6) passes process into its module wrapper as a parameter, so that line is a SyntaxError there. The unmodified Node binary on the VM has no such gap — which is what the VM tier is for. Expect the first run to take a while: it downloads Node and boots V8 on an emulated CPU.
Run it
Install the bundle and run the CLI on the VM
An async function body with nano (the page's booted Nano, 1.8 GB guest) and print() in scope.
const dir = "/work/cli-toolkit"; // 1 · install: the node_modules/ that `npm ci` produced from package-lock.json const bundle = await (await fetch("/demo-files/cli-toolkit/bundle.json")).json(); for (const f of bundle) nano.fs.writeFile(`${dir}/${f.path}`, f.content); nano.fs.writeFile(`${dir}/cli.js`, await (await fetch("/demo-files/cli-toolkit/cli.js")).text()); print(`wrote ${bundle.length} files into ${dir}/node_modules`); // 2 · the real Node.js binary (riscv64) from the signed catalog const node = await nano.installApp("node"); print(`installed ${node.name} ${node.version}`); // 3 · run it on the RISC-V VM const r = await nano.shExec(`cd ${dir} && node cli.js`, { maxSteps: 1e12 }); print(r.stdout);
wrote 42 files into /work/cli-toolkit/node_modules installed node 25.4.0 nano cli-toolkit · npm packages, running in NanoVM PACKAGE VERSION ROLE ──────────────────────────────────────── commander 12.1.0 args chalk 4.1.2 color ansi-styles 4.3.0 color supports-color 7.2.0 tty color-convert 2.0.1 color ✓ 5 packages resolved by require() from node_modules
How it runs
- Fetch
/demo-files/cli-toolkit/bundle.json(the installednode_modules/, 42 files) andcli.js, and write them under/work/cli-toolkit/withnano.fs.writeFile(). nano.installApp("node")installs the riscv64 Node.js v25.4.0 ELF from the signed catalog (verified, cached in OPFS).nano.shExec("cd /work/cli-toolkit && node cli.js")runs it on the RISC-V VM; the output is printed.
Source
From demos/cli-toolkit/.
cli.js
// A CLI built on npm packages — commander for arg parsing, chalk for color — // resolved from package-lock.json by `npm run demos:build` and running // unmodified inside NanoVM. We pass an explicit argv to program.parse() so the // demo is self-contained. const { Command } = require("commander"); const chalk = require("chalk"); const fs = require("fs"); const path = require("path"); const versionOf = (name) => JSON.parse(fs.readFileSync(path.join(__dirname, "node_modules", name, "package.json"), "utf8")).version; const program = new Command(); program.name("nano").description("a tiny build-report CLI on commander + chalk").version("1.0.0"); program .command("report") .description("print a colorized dependency report") .option("-l, --limit <n>", "rows to show", "5") .action((opts) => { // Versions are read from each installed package's own package.json. const deps = [ ["commander", "args"], ["chalk", "color"], ["ansi-styles", "color"], ["supports-color", "tty"], ["color-convert", "color"], ].map(([name, role]) => [name, versionOf(name), role]); const limit = parseInt(opts.limit, 10) || deps.length; console.log(chalk.cyan.bold("\n nano cli-toolkit") + chalk.gray(" · npm packages, running in NanoVM\n")); console.log( " " + chalk.gray("PACKAGE".padEnd(16)) + chalk.gray("VERSION".padEnd(12)) + chalk.gray("ROLE"), ); console.log(" " + chalk.gray("─".repeat(40))); for (const [name, version, role] of deps.slice(0, limit)) { console.log( " " + chalk.white(name.padEnd(16)) + chalk.yellow(version.padEnd(12)) + chalk.magenta(role), ); } console.log(chalk.green("\n ✓ ") + chalk.gray(deps.length + " packages resolved by require() from node_modules\n")); }); program.parse(["node", "cli.js", "report"]);
package.json
{
"name": "nano-cli-toolkit",
"version": "1.0.0",
"private": true,
"dependencies": {
"commander": "^12.1.0",
"chalk": "^4.1.2"
}
}