msandbox-panel-sdk
v0.3.1
Published
TypeScript/JavaScript SDK for the Microsandbox Panel API: sandboxes, commands and files, in the style of the e2b SDK
Downloads
651
Maintainers
Readme
msandbox-panel-sdk
TypeScript/JavaScript SDK for the Microsandbox Panel API: create microVM sandboxes, run commands and work with their files. The API mirrors the e2b SDK, so e2b code ports with few changes.
- No runtime dependencies: uses the built-in
fetch. Works in Node.js 18+, Bun, Deno and browsers. - ESM and CommonJS builds with TypeScript types.
Install
npm install msandbox-panel-sdk # once published to a registry
npm install /path/to/msandbox/sdk # straight from this repo (run `npm run build` there first)
npm install ./msandbox-panel-sdk-0.1.0.tgz # from a tarball made with `npm pack` in sdk/Quick start
Create an API key in the panel (API keys page), then:
export MSB_PANEL_URL=http://localhost:8080
export MSB_PANEL_API_KEY=msbp_...import { Sandbox } from 'msandbox-panel-sdk'
// Boots the default template (e2b-base: Python 3.11, Node.js 20, git, ...).
const sbx = await Sandbox.create({ ttlSeconds: 3600 })
const result = await sbx.commands.run('python -c "print(6*7)"')
console.log(result.stdout) // "42\n"
await sbx.files.write('/app/main.py', 'print("hello")')
console.log(await sbx.commands.run('python /app/main.py'))
await sbx.delete()apiKey and baseUrl can also be passed explicitly to every static method:
const sbx = await Sandbox.create({ apiKey: 'msbp_...', baseUrl: 'https://panel.example.com' })Sandboxes
await Sandbox.create() // default template
await Sandbox.create('e2b-base', { cpus: 2, memoryMib: 2048 })
await Sandbox.create({ image: 'python:3.12', envs: { MODE: 'prod' }, ports: [{ host: 18000, guest: 8000 }] })
await Sandbox.create({ name: 'worker', ttlSeconds: 600 }) // name is generated when omitted
const sbx = await Sandbox.connect(id) // existing sandbox
await Sandbox.list() // SandboxInfo[]
await Sandbox.templates() // Template[]
sbx.id, sbx.name, sbx.info // info: details as of the last call
await sbx.getInfo() // fresh details: status, metrics, config, ...
await sbx.isRunning()
await sbx.stop() // { force: true } kills the VM
await sbx.start()
await sbx.restart()
await sbx.setTtl(3600) // restarts the auto-stop countdown; null removes it
await sbx.logs({ tail: 100 })
await sbx.metrics()
await sbx.delete()Running code (runCode)
Like e2b's code interpreter: code runs in a stateful kernel, so variables and imports persist between calls.
Use the code-interpreter template for pandas/matplotlib and TypeScript.
const sbx = await Sandbox.create('code-interpreter')
await sbx.runCode('x = 41')
const execution = await sbx.runCode('x + 1')
execution.text // "42": the last expression's value (its repr, like Jupyter)
execution.results // Result[]: main result plus display()/chart outputs
execution.logs.stdout // string[]
execution.error // { name, value, traceback } if the code raised (not thrown)
// Plain data (dict/list/number/bool) also comes as native JSON
const stats = await sbx.runCode('{"words": 2, "upper": "TEST"}')
stats.results[0].json // { words: 2, upper: 'TEST' }
// Rich results
const df = await sbx.runCode('import pandas as pd\npd.DataFrame({"a": [1, 2]})')
df.results[0].html // "<table ..."
const chart = await sbx.runCode('import matplotlib.pyplot as plt\nplt.plot([1, 4, 9])\nplt.show()')
chart.results[0].png // base64 PNG
// JavaScript / TypeScript: top-level await, imports, auto-awaited promises
await sbx.runCode('import os from "node:os"\nawait Promise.resolve(os.platform())', { language: 'js' })
await sbx.runCode('const n: number = 42; n', { language: 'ts' })
// Streaming callbacks
await sbx.runCode('for i in range(3): print(i)', {
onStdout: (msg) => console.log(msg.line),
onResult: (result) => console.log(result.formats()),
onError: (error) => console.error(error.traceback),
timeoutMs: 30_000,
envs: { MODE: 'test' },
})
// Separate contexts with their own state
const ctx = await sbx.createCodeContext({ language: 'python', cwd: '/tmp' })
await sbx.runCode('y = 1', { context: ctx })
await sbx.listCodeContexts()
await sbx.restartCodeContext(ctx) // clears its state
await sbx.removeCodeContext(ctx)Commands
Commands run with bash -l -c (sh -l -c in images without bash), in the user's home directory by default.
// Wait for the result. A non-zero exit, timeout or signal throws CommandExitError.
const r = await sbx.commands.run('ls -la', { cwd: '/tmp', envs: { A: '1' }, user: 'node', timeoutMs: 30_000 })
r.exitCode, r.stdout, r.stderr
// Live output.
await sbx.commands.run('npm install', {
onStdout: (data) => process.stdout.write(data),
onStderr: (data) => process.stderr.write(data),
})
// Background process.
const server = await sbx.commands.run('python -m http.server 8000', { background: true, timeoutMs: 0 })
server.pid
await sbx.commands.list() // running commands
await server.kill()
// stdin
const proc = await sbx.commands.run('cat | tr a-z A-Z', { background: true, stdin: true })
await proc.sendStdin('hello\n')
await proc.closeStdin()
const { stdout } = await proc.wait() // "HELLO\n"
// Attach to a command started elsewhere; past output is replayed.
const handle = await sbx.commands.connect(pid, { onStdout: console.log })
await handle.wait()import { CommandExitError } from 'msandbox-panel-sdk'
try {
await sbx.commands.run('exit 3')
} catch (err) {
if (err instanceof CommandExitError) console.log(err.exitCode, err.stderr, err.error)
}Files
Relative paths resolve to the user's home directory. Writes create parent directories and overwrite files.
await sbx.files.write('notes.txt', 'text') // string, Uint8Array, ArrayBuffer, Blob, ReadableStream
await sbx.files.write('/data/big.bin', fs.createReadStream('big.bin')) // Node streams are streamed, not buffered
await sbx.files.write([{ path: 'a.txt', data: 'a' }, { path: 'b.txt', data: 'b' }])
await sbx.files.read('notes.txt') // string
await sbx.files.read('image.png', { format: 'bytes' }) // Uint8Array; also 'blob' and 'stream'
await sbx.files.list('/app', { depth: 2 }) // EntryInfo[]
await sbx.files.getInfo('notes.txt') // size, permissions, owner, modifiedTime, ...
await sbx.files.exists('notes.txt')
await sbx.files.makeDir('/app/data') // false if it already existed
await sbx.files.rename('notes.txt', '/app/notes.txt')
await sbx.files.remove('/app') // directories are removed recursivelyAll file methods accept { user } to resolve paths and set ownership for another user.
Errors
| Class | When |
|---|---|
| AuthenticationError | 401/403: missing or invalid API key |
| InvalidArgumentError | 400: invalid name, path, unknown template, ... |
| NotFoundError | 404: sandbox, file or process doesn't exist |
| ConflictError | 409: e.g. the sandbox is not running |
| ApiError | any other error response; base of the above, has status and body |
| CommandExitError | a command failed; has exitCode, stdout, stderr, error |
All of them extend MsandboxError.
Differences from e2b
Sandbox.create()takesname,cpus,memoryMib,ports,ttlSeconds; the sandbox is not deleted when the TTL expires, only stopped. Usesbx.delete()to remove it (e2b'skill()).setTtl(seconds)replaces e2b'ssetTimeout(ms).runCodesupports Python, JavaScript and TypeScript (no R, Java or bash kernels); chart data extraction (result.chart) is not implemented, charts come as PNG.- No PTY, file watching or file metadata (
X-Metadata-*) yet. - Running commands are tracked by the panel backend in memory; after a backend restart
commands.list()no longer shows background commands started before it.
Development
npm install
npm run build # dist/: ESM, CJS and .d.ts
npm run typecheck
MSB_PANEL_URL=http://localhost:8080 MSB_PANEL_API_KEY=msbp_... npm test # integration tests, create real sandboxes
npm pack # msandbox-panel-sdk-<version>.tgzTo publish: npm login, then npm publish.
License
MIT, see LICENSE.
