@boxlite-ai/boxlite

中文文档
0.12.0发布于 2026/11/06Apache-2.0TypeScriptES Module零依赖构建来源已验证近 30 天下载 3万

BoxLite - Embeddable micro-VM runtime for secure, isolated code execution

原始使用文档
文档目录34 节
  1. Features
  2. Installation
  3. Quick Start
  4. JavaScript
  5. TypeScript
  6. TypeScript 5.2+ (Async Disposal)
  7. API Reference
  8. Remote BoxLite server (REST)
  9. Runtime Registry Options
  10. SimpleBox
  11. Guest SSH Control
  12. Runtime Image Management
  13. Private Registry Logins
  14. Live Execution Inventory
  15. CodeBox
  16. BrowserBox
  17. ComputerBox
  18. InteractiveBox
  19. Examples
  20. Error Handling
  21. Building from Source
  22. TypeScript Support
  23. Platform Requirements
  24. macOS
  25. Linux
  26. Architecture
  27. Performance
  28. Troubleshooting
  29. Contributing
  30. Agent binding tests
  31. License
  32. Related Projects
  33. Support
  34. Service tunnels

BoxLite Node.js/TypeScript SDK

Embeddable VM runtime for secure, isolated code execution environments.

Access the agent API through JsBox.agents and SimpleBox.agents for agent and turn methods, grouped options and error handling.

Think "SQLite for sandboxing" - a lightweight library embedded directly in your application without requiring a daemon or root privileges.

npm version License: Apache 2.0

Features

  • Hardware-level VM isolation (KVM on Linux, Hypervisor.framework on macOS)
  • OCI container support - Use any Docker/OCI image
  • Async-first API - Built on native async Rust + napi-rs
  • Multiple specialized boxes:
    • SimpleBox - Basic command execution
    • CodeBox - Python code sandbox
    • BrowserBox - Browser automation (Puppeteer/Playwright)
    • ComputerBox - Desktop automation (14 automation functions)
    • InteractiveBox - PTY terminal sessions
  • Zero daemon - No background processes required
  • Cross-platform - macOS ARM64, Linux x86_64/ARM64

Installation

npm install @boxlite-ai/boxlite

Requirements:

  • Node.js 18 or later
  • Platform with hardware virtualization:
    • macOS: Apple Silicon (macOS 12+)
    • Linux: x86_64 or ARM64 with KVM (/dev/kvm accessible)

Not supported: macOS Intel, Windows (use WSL2 with Linux requirements)

Quick Start

JavaScript

import { SimpleBox } from '@boxlite-ai/boxlite';

async function main() {
  const box = new SimpleBox({ image: 'alpine:latest' });

  try {
    const result = await box.exec('echo', 'Hello from BoxLite!');
    console.log(result.stdout);  // Hello from BoxLite!
  } finally {
    await box.stop();
  }
}

main();

TypeScript

import { SimpleBox } from 'boxlite';

async function main() {
  const box = new SimpleBox({ image: 'alpine:latest' });

  try {
    const result = await box.exec('echo', 'Hello from BoxLite!');
    console.log(result.stdout);
  } finally {
    await box.stop();
  }
}

main();

TypeScript 5.2+ (Async Disposal)

import { SimpleBox } from 'boxlite';

async function main() {
  await using box = new SimpleBox({ image: 'alpine:latest' });
  const result = await box.exec('echo', 'Hello!');
  console.log(result.stdout);
  // Box automatically stopped when leaving scope
}

main();

API Reference

Remote BoxLite server (REST)

Connect to a remote BoxLite server instead of the local runtime. Construct an ApiKeyCredential and pass it positionally: Boxlite.rest(url, credential).

import { JsBoxlite, ApiKeyCredential } from '@boxlite-ai/boxlite';

const rt = JsBoxlite.rest(
  'http://localhost:8100',
  new ApiKeyCredential('your-api-key'),
);
const boxes = await rt.listInfo();

// Env discovery — returns null when BOXLITE_API_KEY is unset:
const cred = ApiKeyCredential.fromEnv();
const rt2 = JsBoxlite.rest('http://localhost:8100', cred ?? undefined);

ApiKeyCredential structurally satisfies the exported Credential interface; functions can type their parameter as Credential and accept any future credential kind without a signature change.

Routing prefix (vendor-agnostic)

Box-scoped requests resolve to {url}/v1/{prefix}/…. The v1 segment is hardcoded; path_prefix is an opaque, deployment- defined routing value that the server tells the client to use via Principal.path_prefix from GET /v1/me. The slot's semantics are vendor-specific: boxlite cloud uses it for the organization id; another deployment may use a workspace name, a region+team pair, or any other multi-segment value such as us-east/team-42.

import { JsBoxlite, BoxliteRestOptions, ApiKeyCredential } from '@boxlite-ai/boxlite';

const rt = JsBoxlite.rest(new BoxliteRestOptions({
  url: 'https://api.boxlite.ai',
  credential: new ApiKeyCredential('blk_live_…'),
  pathPrefix: 'acme',   // → requests hit /v1/acme/boxes
}));

When pathPrefix is unset, the client builds URLs without the segment (/v1/boxes/…) — the canonical shape for single-tenant deployments such as the local boxlite serve reference server.

The CLI captures Principal.path_prefix at login and caches it under the active profile, so subsequent boxlite commands route correctly without an extra flag.

Runtime Registry Options

import { JsBoxlite } from 'boxlite';

const runtime = new JsBoxlite({
  imageRegistries: [
    {
      host: 'registry.example.com',
      auth: { username: 'user', password: 'password' },
    },
    {
      host: 'registry.local:5000',
      transport: 'http',
      search: true,
    },
  ],
});

SimpleBox

Basic container for command execution.

import { SimpleBox } from 'boxlite';

const box = new SimpleBox({
  image: 'python:slim',
  memoryMib: 512,    // Memory limit in MiB
  cpus: 2,           // Number of CPU cores
  name: 'my-box',    // Optional name
  autoRemove: true,  // Auto-remove on stop (default: true)
  workingDir: '/app',
  network: {
    outbound: {
      mode: 'enabled',
      allowNet: ['api.openai.com'],
    },
    inbound: {
      mode: 'disabled',
    },
  },
  // The pre-split shape `network: { mode, allowNet }` still works and
  // configures the outbound direction. Passing it together with `outbound`
  // is rejected.
  advanced: {
    capabilities: {
      add: ['NET_ADMIN'],
      drop: ['NET_RAW'],
    },
  },
  env: { FOO: 'bar' },
  volumes: [
    // A host bind (local runtimes only)...
    { hostPath: '/tmp/data', guestPath: '/data', readOnly: false },
    // ...or a managed volume, by id or by name (REST runtimes).
    { managedVolume: 'my-data', guestPath: '/cache' }
  ],
  ports: [
    { hostPort: 8080, guestPort: 80 }
  ],
  secrets: [
    {
      name: 'openai',
      value: process.env.OPENAI_API_KEY!,
      hosts: ['api.openai.com'],
    }
  ]
});

// Execute command
const result = await box.exec('ls', '-la', '/');
console.log(result.exitCode, result.stdout, result.stderr);

// Execute with options (cwd, user, timeout)
const pwdResult = await box.exec('pwd', [], undefined, {
  cwd: '/tmp',
  user: 'nobody',
  timeoutSecs: 30,
});
console.log(pwdResult.stdout); // "/tmp\n"

// Get box info
console.log(box.id);    // ULID
console.log(box.name);  // Optional name
const info = await box.info(); // Metadata
// null means unknown to this handle; [] means no active publications.
console.log(info.network?.publishedPorts);
// Local info reads do not query the live network backend; REST info performs a GET.
const refreshed = await runtime.getInfo(box.id);
console.log(refreshed?.network?.publishedPorts);

// Cleanup
await box.stop();

info(), getInfo(), and listInfo() expose privileged, the guest mode saved at creation. Local metadata reports true or false; a REST server that omits the field produces undefined.

Guest SSH Control

JsBox and SimpleBox expose box.ssh to configure, observe, and disable the guest SSH listener.

import { readFile } from 'node:fs/promises';

const status = await box.ssh.configure({
  listenAddress: '0.0.0.0:2222',
  hostPrivateKey: await readFile('host_key', 'utf8'), // OpenSSH private key
  accounts: [
    {
      login: 'alice',
      authorizedKeys: [await readFile('alice.pub', 'utf8')],
      // Certificate auth instead of authorizedKeys:
      // ca: { publicKey: caPublicKey, principal: 'alice' },
    },
  ],
});
// { enabled: true, generation: 1n, listenAddress, hostPublicKey,
//   hostKeyFingerprint } — generation is a bigint (full u64).

await box.ssh.status();
await box.ssh.disable();
await box.ssh.enable(); // reuse saved configuration; requires a running box

See the Node.js SSH reference for configuration types, return values, and errors.

Runtime Image Management

runtime.images holds the images a runtime can boot from: the local cache on an embedded runtime, the server's catalog on JsBoxlite.rest(...). The same code runs against either; pull is local only (a REST runtime pulls when a box is created) and usage, createAlias and deleteAlias are REST only.

import { JsBoxlite } from "boxlite";

const runtime = JsBoxlite.withDefaultConfig();

const images = await runtime.images.list();
for (const image of images) {
  console.log(image.repository, image.tag, image.id);
}

const detail = await runtime.images.get("docker.io/library/alpine");
console.log(detail.aliases, detail.versions.map((version) => version.digest));
await runtime.images.remove("docker.io/library/alpine");

pull fills a local cache ahead of time. A REST runtime refuses it as unsupported, because creating a box there pulls the image:

const pulled = await runtime.images.pull("alpine:latest");
console.log(pulled.reference, pulled.configDigest, pulled.layerCount);

See the Node.js reference for each method on each runtime.

Private Registry Logins

On JsBoxlite.rest(...), runtime.images.registries manages the logins the server pulls private images with. The password goes up once and is never returned. A local runtime throws unsupported: pass imageRegistries to its options instead.

const login = await runtime.images.registries.create({
  registryHost: "ghcr.io",
  repositoryPrefix: "acme/", // omit for the whole registry
  username: "acme-bot",
  password: process.env.GHCR_TOKEN!,
});
const box = await runtime.create({ image: "ghcr.io/acme/private-app:1" });
// ...
await runtime.images.registries.remove(login.id); // refused while a box pulls through it

See the Node.js reference.

Live Execution Inventory

await box.executions().list() is available on native JsBox and SimpleBox. It returns ExecutionInfo[] from the guest's live inventory, including the main process: executionId (canonical guest ID), argv, startedAt (Unix milliseconds), tty, and isMainSession. Completed processes disappear; this is not execution history. Listing does not attach or consume output, and never boots a stopped box. A stopped/unstarted native box returns an empty list; a paused box returns an invalid-state error. An uncreated SimpleBox also returns an empty list without creating its box.

Use await box.executions().get(executionId) for control-only access while another client owns output, or await box.executions().attach(executionId, false) to recover streams without stdin. Both are available on native JsBox and SimpleBox, never start the box, and return a JsExecution supporting kill(), signal(), and wait(). Only one output reader is permitted.

for (const execution of await box.executions().list()) {
  console.log(execution.executionId, execution.argv, execution.isMainSession);
}

CodeBox

Python code execution sandbox.

import { CodeBox } from 'boxlite';

const codebox = new CodeBox({
  image: 'python:slim',  // default
  memoryMib: 512,
  cpus: 1
});

try {
  // Run Python code
  const result = await codebox.run(`
import math
print(f"Pi is approximately {math.pi}")
  `);
  console.log(result);  // Pi is approximately 3.141592653589793

  // Install packages
  await codebox.installPackage('requests');
  await codebox.installPackages('numpy', 'pandas');

  // Use installed packages
  const result2 = await codebox.run(`
import requests
response = requests.get('https://api.github.com/zen')
print(response.text)
  `);
  console.log(result2);
} finally {
  await codebox.stop();
}

BrowserBox

Browser automation with remote debugging.

import { BrowserBox } from 'boxlite';

const browser = new BrowserBox({
  browser: 'chromium',  // 'chromium', 'firefox', or 'webkit'
  memoryMib: 2048,
  cpus: 2
});

try {
  await browser.start();

  // Get CDP endpoint
  const endpoint = browser.endpoint();
  console.log(endpoint);  // http://localhost:9222

  // Connect with Puppeteer
  import puppeteer from 'puppeteer-core';
  const browserInstance = await puppeteer.connect({ browserURL: endpoint });

  const page = await browserInstance.newPage();
  await page.goto('https://example.com');
  const title = await page.title();
  console.log(title);

  await page.close();
} finally {
  await browser.stop();
}

ComputerBox

Desktop automation with web access.

import { ComputerBox } from 'boxlite';

const desktop = new ComputerBox({
  cpus: 2,
  memoryMib: 2048,
  guiHttpPort: 3000,   // default
  guiHttpsPort: 3001   // default (self-signed cert)
});

try {
  // Wait for desktop to be ready
  await desktop.waitUntilReady(60);

  // Mouse automation
  await desktop.mouseMove(100, 200);
  await desktop.leftClick();
  await desktop.doubleClick();
  await desktop.rightClick();
  await desktop.leftClickDrag(100, 100, 200, 200);

  const [x, y] = await desktop.cursorPosition();
  console.log(`Cursor at: ${x}, ${y}`);

  // Keyboard automation
  await desktop.type('Hello, World!');
  await desktop.key('Return');
  await desktop.key('ctrl+c');

  // Screenshot
  const screenshot = await desktop.screenshot();
  console.log(`${screenshot.width}x${screenshot.height} ${screenshot.format}`);
  // screenshot.data contains base64-encoded PNG

  // Scroll
  await desktop.scroll(500, 300, 'down', 5);

  // Get screen size
  const [width, height] = await desktop.getScreenSize();
  console.log(`Screen: ${width}x${height}`);

  // Access desktop via browser:
  // HTTP:  http://localhost:3000
  // HTTPS: https://localhost:3001 (self-signed certificate)
} finally {
  await desktop.stop();
}

InteractiveBox

Interactive terminal sessions with PTY.

import { InteractiveBox } from 'boxlite';

const box = new InteractiveBox({
  image: 'alpine:latest',
  shell: '/bin/sh',
  tty: true,  // Auto-detected if undefined
  memoryMib: 512,
  cpus: 1
});

try {
  await box.start();
  await box.wait();  // Blocks until shell exits
} finally {
  await box.stop();
}

Examples

See ../../examples/node/ directory for complete examples:

# If installed via npm
npm install @boxlite-ai/boxlite
node simplebox.js

# If working from source
cd ../../sdks/node
npm install && npm run build
npm link
cd ../../examples/node
npm link @boxlite-ai/boxlite
node simplebox.js

Error Handling

import { SimpleBox, ExecError, TimeoutError, ParseError } from 'boxlite';

try {
  const box = new SimpleBox({ image: 'alpine:latest' });
  await box.exec('false');  // Exit code 1
} catch (err) {
  if (err instanceof ExecError) {
    console.error(`Command failed: ${err.command}`);
    console.error(`Exit code: ${err.exitCode}`);
    console.error(`Stderr: ${err.stderr}`);
  }
}

A failure the runtime itself reports carries a code that errorCode(err) returns, such as not_found or invalid_state; it is undefined for any other error. See the Node.js reference.

Building from Source

# Clone repository
git clone https://github.com/boxlite-ai/boxlite.git
cd boxlite/sdks/node

# Initialize submodules (critical!)
git submodule update --init --recursive

# Install dependencies
npm install

# Build (Rust + TypeScript)
npm run build

# Link to examples
npm link
cd ../../examples/node
npm link @boxlite-ai/boxlite

# Run examples
node simplebox.js

TypeScript Support

Full TypeScript support included:

import {
  SimpleBox, CodeBox, BrowserBox, ComputerBox, InteractiveBox,
  type SimpleBoxOptions,
  type CodeBoxOptions,
  type BrowserBoxOptions,
  type ComputerBoxOptions,
  type InteractiveBoxOptions,
  type Secret,
  type ExecResult,
  type Screenshot,
  type BrowserType
} from 'boxlite';

Platform Requirements

macOS

  • Apple Silicon (ARM64)
  • macOS 12+ (Monterey or later)
  • Hypervisor.framework (built-in)

Linux

  • x86_64 or ARM64
  • KVM enabled (/dev/kvm accessible)
  • User in kvm group:
    sudo usermod -aG kvm $USER
    # Logout/login required
    

Architecture

The SDK uses a dual-layer architecture:

  1. Layer 1 (Rust): napi-rs bindings to native BoxLite runtime

    • Direct mapping to Rust boxlite crate
    • Async operations via env.spawn() (non-blocking)
    • Stream handling via async iterators
  2. Layer 2 (TypeScript): Convenience wrappers

    • Specialized box classes (CodeBox, BrowserBox, etc.)
    • Error class hierarchy
    • Output collection helpers
    • TypeScript type definitions

Performance

  • Startup time: < 100ms
  • Overhead vs Python SDK: < 5%
  • Concurrent boxes: Limited by system resources
  • Memory footprint: ~50-100MB per box (depends on image)

Troubleshooting

"BoxLite native extension not found"

  • Run npm run build to compile Rust bindings

"Image not found"

  • BoxLite auto-pulls OCI images on first use
  • Ensure internet connectivity

"Permission denied" (Linux)

  • Check KVM access: ls -l /dev/kvm
  • Add user to kvm group: sudo usermod -aG kvm $USER

"Unsupported engine"

  • Only Apple Silicon Macs supported (not Intel)
  • Windows users: Use WSL2 with Linux requirements

Contributing

See ../../CONTRIBUTING.md for development guidelines.

Agent binding tests

From the repository root, run make test:integration:node FILTER='agent namespace'. The native Box getter test obtains a handle from a loopback REST server and checks namespace access without starting a VM or sending agent requests. Separate tests reject plain-object and prototype-only method receivers before the agent operation runs.

These receiver tests exercise napi-rs callback unwrapping. They do not exercise the generated FromNapiRef and FromNapiMutRef conversions flagged by CodeQL, so passing them does not establish that those pointer alerts are false positives. The conversions are generated in the locked napi-rs backend.

License

Apache 2.0 - See ../../LICENSE

Support


Made with ❤️ by BoxLite Labs

Service tunnels

Use the built-in address from node:net to prepare a service tunnel:

import { SocketAddress } from "node:net";

const tunnel = await box.network.tunnel(
  new SocketAddress({ address: "127.0.0.1", port: 3000 }),
  { isPublic: false },
);

Options default to public; private tunnels require a cloud runtime. IPv6 targets use new SocketAddress({ address: "::1", family: "ipv6", port: 3000 }). The target must have a nonzero port, a loopback or guest IP, and zero flowlabel. Bare ports and the old { type: "tcp", ... } target are rejected. Invalid argument types can throw synchronously on the native network handle. TCP/Unix listener shapes remain available for tunnel.forward(listen). See private tunnels.

版本与发布记录

@boxlite-ai/boxlite 共发布 43 个版本,首次发布于 2026/01/11;过去 12 个月发布了 43 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.12.0latest2026/11/061 天前
0.11.02026/10/309 天前
0.10.82026/10/2315 天前
0.10.72026/10/1821 天前
0.10.62026/10/1226 天前
0.10.52026/09/301 个月前

关于 @boxlite-ai/boxlite 的常见问题

@boxlite-ai/boxlite 是什么?

BoxLite - Embeddable micro-VM runtime for secure, isolated code execution

如何安装 @boxlite-ai/boxlite?

在项目目录运行 npm install @boxlite-ai/boxlite,或使用 pnpm add / yarn add / bun add。

@boxlite-ai/boxlite 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@boxlite-ai/boxlite 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@boxlite-ai/boxlite 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@boxlite-ai/boxlite 可以商用吗?

@boxlite-ai/boxlite 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@boxlite-ai/boxlite 的下载量有多少?

根据 npm 官方下载统计,@boxlite-ai/boxlite 在 2026/10/09 – 2026/11/07 这 30 天内被下载 30,013 次,日均约 1,000 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@boxlite-ai/boxlite 还在维护吗?

最新版本 0.12.0 发布于 2026/11/06(1 天前);至今共发布 43 个版本,其中过去 12 个月发布了 43 个;首次发布于 2026/01/11。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。