lightbox

Execute commands in a sandbox

sb.exec, sb.shell, streaming, interactive shells, sandbox filesystem.

Once you have a Sandbox handle (from launchSandbox, runInSandbox, or connectToSandbox), you run code in it via the methods below. These are all re-exported from the microsandbox SDK — lightbox doesn’t wrap them, it just hands the handle back.

One-shot exec

sb.exec(cmd, args?) → Promise<ExecOutput>

Direct argv execution — no shell interpretation. Safest when args contain user data or paths with spaces.

const out = await sb.exec("python", ["-c", "import sys; print(sys.version)"]);
console.log(out.code);          // exit code
console.log(out.success);       // shorthand for code === 0
console.log(out.stdout());      // decoded UTF-8
console.log(out.stderr());

sb.shell(script) → Promise<ExecOutput>

Runs the script through sh -lc — pipes, redirects, &&, env expansion all work.

const out = await sb.shell("ls -la /work | head -5 && echo done");
console.log(out.stdout());

ExecOutput

class ExecOutput {
  get code(): number;
  get success(): boolean;       // code === 0
  stdout(): string;             // decoded UTF-8
  stderr(): string;
  stdoutBytes(): Uint8Array;    // raw bytes for binary output
  stderrBytes(): Uint8Array;
}

Streaming exec

For long-running commands you want to watch live (installs, builds, tests), use the stream variants. They return an ExecHandle that’s an AsyncIterable<ExecEvent>.

sb.execStream(cmd, args?) → Promise<ExecHandle>

sb.shellStream(script) → Promise<ExecHandle>

const handle = await sb.shellStream("npm install");
for await (const event of handle) {
  if (event.kind === "stdout") process.stdout.write(event.data);
  else if (event.kind === "stderr") process.stderr.write(event.data);
  else if (event.kind === "exited") console.log(`exited with ${event.code}`);
}

Event kinds: started (with pid), stdout / stderr (with Uint8Array data), exited (with code).

ExecHandle also exposes:

class ExecHandle implements AsyncIterable<ExecEvent> {
  recv(): Promise<ExecEvent | null>;     // pull one event manually
  wait(): Promise<ExitStatus>;           // await final exit code without iterating
  collect(): Promise<ExecOutput>;        // drain to an ExecOutput
  kill(): Promise<void>;                 // SIGKILL the guest process
}

Interactive shell

sb.attachShell() → Promise<number>

Drop into an interactive shell on your terminal, wired to the guest. Returns the exit code when the shell terminates. Useful for debugging — see Debug a failed build for an end-to-end walkthrough.

const code = await sb.attachShell();
console.log("shell exited with", code);

Filesystem access

sb.fs() → SandboxFs

Read and write the guest filesystem from the host, without using cat/echo shell commands.

const fs = sb.fs();

// Whole-file convenience
const config = await fs.readToString("/etc/os-release");
const bytes = await fs.read("/usr/bin/node");      // Uint8Array

// Streaming
const stream = await fs.readStream("/var/log/big.log");
for await (const chunk of stream) {
  // chunk is Uint8Array
}

const sink = await fs.writeStream("/work/output.txt");
await sink.write("hello\n");
await sink.close();

// Metadata + listing
const entries = await fs.list("/work");           // FsEntry[]
const meta = await fs.stat("/work/output.txt");   // FsMetadata

(See the microsandbox SDK source for the full SandboxFs surface — mkdir, remove, rename, etc.)