skills/1wos/sdkhackthon/moru-python

moru-python

Originally frommoru-ai/skills
SKILL.md

Moru Python SDK

pip install moru

Quick Start

from moru import Sandbox

with Sandbox.create() as sbx:
    sbx.files.write("/app/script.py", "print('Hello from Moru!')")
    result = sbx.commands.run("python3 /app/script.py")
    print(result.stdout)
# Sandbox auto-killed

Quick Reference

Task Code
Create sandbox Sandbox.create() or Sandbox.create("template")
Run command sbx.commands.run("cmd")
Read file sbx.files.read("/path")
Write file sbx.files.write("/path", "content")
Background process sbx.commands.run("cmd", background=True)
Set timeout Sandbox.create(timeout=600) or sbx.set_timeout(600)
Use volume Sandbox.create(volume_id=vol.volume_id, volume_mount_path="/workspace")

Sandbox Lifecycle

Create

from moru import Sandbox

# Default template
sbx = Sandbox.create()

# Specific template
sbx = Sandbox.create("python")

# With options
sbx = Sandbox.create(
    template="python",
    timeout=600,                    # seconds (default: 300)
    metadata={"project": "myapp"},
    envs={"API_KEY": "secret"},
    volume_id="vol_xxx",
    volume_mount_path="/workspace",
    allow_internet_access=True,
)

Context Manager (Recommended)

with Sandbox.create() as sbx:
    result = sbx.commands.run("echo hello")
# Auto-killed on exit

Connect to Existing

sbx = Sandbox.connect("sbx_abc123")
if sbx.is_running():
    result = sbx.commands.run("echo still alive")

Kill

sbx.kill()
# or
Sandbox.kill("sbx_abc123")

List All

for info in Sandbox.list():
    print(f"{info.sandbox_id}: {info.state}")

Running Commands

Basic

result = sbx.commands.run("echo hello")
print(result.stdout)      # "hello\n"
print(result.stderr)      # ""
print(result.exit_code)   # 0

With Options

result = sbx.commands.run(
    "python3 script.py",
    cwd="/app",                         # Working directory
    user="root",                        # Run as root
    envs={"DEBUG": "1"},               # Environment variables
    timeout=120,                        # Command timeout (seconds)
    on_stdout=lambda d: print(d, end=""),  # Stream stdout
    on_stderr=lambda d: print(d, end=""),  # Stream stderr
)

Background Process

handle = sbx.commands.run("python3 server.py", background=True)

# Get public URL
url = sbx.get_host(8080)
print(f"Server at: {url}")

# Send input
handle.send_stdin("quit\n")

# Wait for completion
result = handle.wait()

# Or kill it
handle.kill()

Process Management

# List running processes
for proc in sbx.commands.list():
    print(f"PID {proc.pid}: {proc.command}")

# Kill by PID
sbx.commands.kill(1234)

Working with Files

Read/Write

# Write
sbx.files.write("/app/config.json", '{"key": "value"}')

# Read
content = sbx.files.read("/app/config.json")

# Binary
data = sbx.files.read("/app/image.png", format="bytes")
sbx.files.write("/app/output.bin", binary_data)

# Stream large files
for chunk in sbx.files.read("/app/large.bin", format="stream"):
    process(chunk)

Multiple Files

sbx.files.write_files([
    {"path": "/app/file1.txt", "data": "content1"},
    {"path": "/app/file2.txt", "data": "content2"},
])

Directory Operations

# Check existence
if sbx.files.exists("/app/config.json"):
    config = sbx.files.read("/app/config.json")

# List directory
for entry in sbx.files.list("/app"):
    print(f"{entry.type}: {entry.name} ({entry.size} bytes)")

# Recursive list
entries = sbx.files.list("/app", depth=5)

# Get info
info = sbx.files.get_info("/app/file.txt")
print(f"Size: {info.size}, Modified: {info.modified_time}")

# Create directory
sbx.files.make_dir("/app/data")

# Delete
sbx.files.remove("/app/old_file.txt")

# Rename/Move
sbx.files.rename("/app/old.txt", "/app/new.txt")

Watch for Changes

handle = sbx.files.watch_dir("/app")
for event in handle.events():
    print(f"{event.type}: {event.name}")
handle.stop()

Volumes (Persistent Storage)

from moru import Sandbox, Volume

# Create volume (idempotent)
vol = Volume.create(name="my-workspace")

# Attach to sandbox
sbx = Sandbox.create(
    volume_id=vol.volume_id,
    volume_mount_path="/workspace"  # Must be /workspace, /data, /mnt, or /volumes
)

# Data in /workspace persists after kill
sbx.commands.run("echo 'persistent' > /workspace/data.txt")
sbx.kill()

# Later - data still there
sbx2 = Sandbox.create(volume_id=vol.volume_id, volume_mount_path="/workspace")
result = sbx2.commands.run("cat /workspace/data.txt")
print(result.stdout)  # "persistent"

Volume Operations (No Sandbox Needed)

vol = Volume.get("my-workspace")

# List files
for f in vol.list_files("/"):
    print(f"{f.type}: {f.name}")

# Download/Upload
content = vol.download("/data.txt")
vol.upload("/config.json", b'{"key": "value"}')

# Delete
vol.delete("/old_file.txt")

# Delete volume (WARNING: permanent)
vol.delete()

Templates

from moru import Template
from moru.template import wait_for_port

# Define template
template = (
    Template()
    .from_python_image("3.11")
    .apt_install(["curl", "git"])
    .pip_install(["flask", "pandas", "requests"])
    .copy("./app", "/app")
    .set_workdir("/app")
    .set_envs({"FLASK_ENV": "production"})
    .set_start_cmd("python app.py", wait_for_port(5000))
)

# Build
info = Template.build(template, alias="my-flask-app")

# Use
sbx = Sandbox.create("my-flask-app")

From Dockerfile

template = Template().from_dockerfile("./Dockerfile")
Template.build(template, alias="my-app")

Build Options

Template.build(
    template,
    alias="my-app",
    cpu_count=4,
    memory_mb=2048,
    on_build_logs=lambda entry: print(entry.message),
)

# Background build
info = Template.build_in_background(template, alias="my-app")
status = Template.get_build_status(info)  # building, success, failed

Async Support

import asyncio
from moru import AsyncSandbox

async def main():
    async with await AsyncSandbox.create() as sbx:
        result = await sbx.commands.run("echo hello")
        print(result.stdout)

        await sbx.files.write("/tmp/test.txt", "content")
        content = await sbx.files.read("/tmp/test.txt")

asyncio.run(main())

Error Handling

from moru import Sandbox
from moru.exceptions import (
    SandboxException,           # Base
    TimeoutException,           # Operation timed out
    NotFoundException,          # Resource not found
    AuthenticationException,    # Invalid API key
    NotEnoughSpaceException,    # Disk full
    CommandExitException,       # Non-zero exit (has exit_code, stdout, stderr)
)

try:
    with Sandbox.create() as sbx:
        result = sbx.commands.run("python3 script.py", timeout=30)
except TimeoutException:
    print("Command timed out")
except CommandExitException as e:
    print(f"Failed with exit code {e.exit_code}: {e.stderr}")
except AuthenticationException:
    print("Invalid API key - check MORU_API_KEY")

Common Pitfalls

Always cleanup sandboxes

# ❌ WRONG
sbx = Sandbox.create()
sbx.commands.run("echo hello")
# Forgot to kill - sandbox keeps running!

# ✅ CORRECT
with Sandbox.create() as sbx:
    sbx.commands.run("echo hello")

Don't assume packages exist

# ❌ WRONG
sbx.commands.run("python3 -c 'import pandas'")  # ImportError!

# ✅ CORRECT
sbx.commands.run("pip install pandas", timeout=120)
sbx.commands.run("python3 -c 'import pandas'")

Write to volume path for persistence

# ❌ WRONG - lost on kill
sbx.files.write("/home/user/data.txt", "important")

# ✅ CORRECT - persisted
sbx.files.write("/workspace/data.txt", "important")

Handle command failures

result = sbx.commands.run("python3 script.py")
if result.exit_code != 0:
    print(f"Error: {result.stderr}")
Weekly Installs
3
First Seen
13 days ago
Installed on
cursor3
opencode2
gemini-cli2
antigravity2
claude-code2
github-copilot2