Moru JavaScript/TypeScript SDK
npm install @moru-ai/core
TypeScript types included.
Quick Start
import Sandbox from '@moru-ai/core'
const sbx = await Sandbox.create() try { await sbx.files.write("/app/script.py", "print('Hello from Moru!')") const result = await sbx.commands.run("python3 /app/script.py") console.log(result.stdout) } finally { await sbx.kill() }
Quick Reference
Task Code
Create sandbox await Sandbox.create() or await Sandbox.create("template")
Run command await sbx.commands.run("cmd")
Read file await sbx.files.read("/path")
Write file await sbx.files.write("/path", "content")
Background process await sbx.commands.run("cmd", { background: true })
Set timeout Sandbox.create({ timeoutMs: 600000 }) or sbx.setTimeout(600000)
Use volume Sandbox.create({ volumeId, volumeMountPath: "/workspace" })
Sandbox Lifecycle
Create
import Sandbox from '@moru-ai/core'
// Default template const sbx = await Sandbox.create()
// Specific template const sbx = await Sandbox.create("python")
// With options const sbx = await Sandbox.create("python", { timeoutMs: 600000, // milliseconds (default: 300000) metadata: { project: "myapp" }, envs: { API_KEY: "secret" }, volumeId: "vol_xxx", volumeMountPath: "/workspace", allowInternetAccess: true, })
Connect to Existing
const sbx = await Sandbox.connect("sbx_abc123") if (await sbx.isRunning()) { const result = await sbx.commands.run("echo still alive") }
Kill
await sbx.kill() // or await Sandbox.kill("sbx_abc123")
List All
for await (const info of Sandbox.list()) {
console.log(${info.sandboxId}: ${info.state})
}
Running Commands
Basic
const result = await sbx.commands.run("echo hello") console.log(result.stdout) // "hello\n" console.log(result.stderr) // "" console.log(result.exitCode) // 0
With Options
const result = await sbx.commands.run("python3 script.py", { cwd: "/app", // Working directory user: "root", // Run as root envs: { DEBUG: "1" }, // Environment variables timeoutMs: 120000, // Command timeout (ms) onStdout: (data) => process.stdout.write(data), // Stream stdout onStderr: (data) => process.stderr.write(data), // Stream stderr })
Background Process
const handle = await sbx.commands.run("python3 server.py", { background: true })
// Get public URL
const url = sbx.getHost(8080)
console.log(Server at: ${url})
// Send input await handle.sendStdin("quit\n")
// Wait for completion const result = await handle.wait()
// Or kill it await handle.kill()
Process Management
// List running processes
for (const proc of await sbx.commands.list()) {
console.log(PID ${proc.pid}: ${proc.command})
}
// Kill by PID await sbx.commands.kill(1234)
Working with Files
Read/Write
// Write await sbx.files.write("/app/config.json", '{"key": "value"}')
// Read const content = await sbx.files.read("/app/config.json")
// Binary const bytes = await sbx.files.read("/app/image.png", { format: 'bytes' }) const blob = await sbx.files.read("/app/image.png", { format: 'blob' }) await sbx.files.write("/app/output.bin", binaryData)
// Stream large files const stream = await sbx.files.read("/app/large.bin", { format: 'stream' })
Multiple Files
await sbx.files.write([ { path: "/app/file1.txt", data: "content1" }, { path: "/app/file2.txt", data: "content2" }, ])
Directory Operations
// Check existence if (await sbx.files.exists("/app/config.json")) { const config = await sbx.files.read("/app/config.json") }
// List directory
for (const entry of await sbx.files.list("/app")) {
console.log(${entry.type}: ${entry.name} (${entry.size} bytes))
}
// Recursive list const entries = await sbx.files.list("/app", { depth: 5 })
// Get info
const info = await sbx.files.getInfo("/app/file.txt")
console.log(Size: ${info.size}, Modified: ${info.modifiedTime})
// Create directory await sbx.files.makeDir("/app/data")
// Delete await sbx.files.remove("/app/old_file.txt")
// Rename/Move await sbx.files.rename("/app/old.txt", "/app/new.txt")
Watch for Changes
const handle = await sbx.files.watchDir("/app", (event) => {
console.log(${event.type}: ${event.name})
})
await handle.stop()
Volumes (Persistent Storage)
import Sandbox, { Volume } from '@moru-ai/core'
// Create volume (idempotent) const vol = await Volume.create({ name: "my-workspace" })
// Attach to sandbox const sbx = await Sandbox.create("base", { volumeId: vol.volumeId, volumeMountPath: "/workspace" // Must be /workspace, /data, /mnt, or /volumes })
// Data in /workspace persists after kill await sbx.commands.run("echo 'persistent' > /workspace/data.txt") await sbx.kill()
// Later - data still there const sbx2 = await Sandbox.create("base", { volumeId: vol.volumeId, volumeMountPath: "/workspace" }) const result = await sbx2.commands.run("cat /workspace/data.txt") console.log(result.stdout) // "persistent"
Volume Operations (No Sandbox Needed)
const vol = await Volume.get("my-workspace")
// List files
for (const f of await vol.listFiles("/")) {
console.log(${f.type}: ${f.name})
}
// Download/Upload const content = await vol.download("/data.txt") await vol.upload("/config.json", Buffer.from('{"key": "value"}'))
// Delete file await vol.delete("/old_file.txt")
// Delete volume (WARNING: permanent) await vol.delete()
Templates
import { Template, waitForPort } from '@moru-ai/core'
// Define template const template = Template() .fromPythonImage("3.11") .aptInstall(["curl", "git"]) .pipInstall(["flask", "pandas", "requests"]) .copy("./app", "/app") .setWorkdir("/app") .setEnvs({ FLASK_ENV: "production" }) .setStartCmd("python app.py", waitForPort(5000))
// Build const info = await Template.build(template, { alias: "my-flask-app" })
// Use const sbx = await Sandbox.create("my-flask-app")
From Dockerfile
const template = Template().fromDockerfile("./Dockerfile") await Template.build(template, { alias: "my-app" })
Build Options
await Template.build(template, { alias: "my-app", cpuCount: 4, memoryMB: 2048, onBuildLogs: (entry) => console.log(entry.message), })
// Background build const info = await Template.buildInBackground(template, { alias: "my-app" }) const status = await Template.getBuildStatus(info) // building, success, failed
Error Handling
import Sandbox, { SandboxError, // Base TimeoutError, // Operation timed out NotFoundError, // Resource not found AuthenticationError, // Invalid API key NotEnoughSpaceError, // Disk full CommandExitError, // Non-zero exit (has exitCode, stdout, stderr) } from '@moru-ai/core'
try {
const sbx = await Sandbox.create()
try {
const result = await sbx.commands.run("python3 script.py", { timeoutMs: 30000 })
} finally {
await sbx.kill()
}
} catch (error) {
if (error instanceof TimeoutError) {
console.log("Command timed out")
} else if (error instanceof CommandExitError) {
console.log(Failed with exit code ${error.exitCode}: ${error.stderr})
} else if (error instanceof AuthenticationError) {
console.log("Invalid API key - check MORU_API_KEY")
}
}
TypeScript Types
import type { SandboxOpts, SandboxInfo, CommandResult, CommandStartOpts, EntryInfo, VolumeInfo, } from '@moru-ai/core'
Common Pitfalls
Always cleanup sandboxes
// ❌ WRONG const sbx = await Sandbox.create() await sbx.commands.run("echo hello") // Forgot to kill - sandbox keeps running!
// ✅ CORRECT const sbx = await Sandbox.create() try { await sbx.commands.run("echo hello") } finally { await sbx.kill() }
Don't assume packages exist
// ❌ WRONG await sbx.commands.run("python3 -c 'import pandas'") // ImportError!
// ✅ CORRECT await sbx.commands.run("pip install pandas", { timeoutMs: 120000 }) await sbx.commands.run("python3 -c 'import pandas'")
Write to volume path for persistence
// ❌ WRONG - lost on kill await sbx.files.write("/home/user/data.txt", "important")
// ✅ CORRECT - persisted await sbx.files.write("/workspace/data.txt", "important")
Handle command failures
const result = await sbx.commands.run("python3 script.py")
if (result.exitCode !== 0) {
console.log(Error: ${result.stderr})
}