Metadata-Version: 2.4
Name: dynamiq-sandboxes
Version: 0.2.0
Summary: Python SDK for Dynamiq Sandboxes - Secure code execution, browser automation, and virtual desktops
Author-email: Dynamiq <support@getdynamiq.ai>
Maintainer-email: Dynamiq <support@getdynamiq.ai>
License-Expression: Apache-2.0
Project-URL: Homepage, https://sandboxes.getdynamiq.ai
Project-URL: Documentation, https://sandboxes.getdynamiq.ai
Project-URL: Repository, https://github.com/dynamiq-ai/dynamiq
Project-URL: Bug Tracker, https://github.com/dynamiq-ai/dynamiq/issues
Keywords: sandbox,code-execution,browser-automation,virtual-desktop,ai-agents,dynamiq,e2b-alternative,daytona-alternative,code-interpreter,selenium-alternative
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
Classifier: Topic :: System :: Emulators
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx[http2]>=0.24.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: typing-extensions>=4.0.0; python_version < "3.10"
Provides-Extra: streaming
Requires-Dist: websocket-client>=1.6.0; extra == "streaming"
Requires-Dist: websockets>=11.0; extra == "streaming"
Provides-Extra: all
Requires-Dist: websocket-client>=1.6.0; extra == "all"
Requires-Dist: websockets>=11.0; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: coverage>=7.0; extra == "dev"
Requires-Dist: black>=24.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Dynamic: license-file

# Dynamiq Sandboxes

[![PyPI version](https://badge.fury.io/py/dynamiq-sandboxes.svg)](https://pypi.org/project/dynamiq-sandboxes/)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

Python SDK for [Dynamiq Sandboxes](https://sandboxes.getdynamiq.ai) — secure, isolated environments for code execution, browser automation, and virtual desktops.

Built for AI agents and automation pipelines. Part of the [Dynamiq](https://github.com/dynamiq-ai/dynamiq) ecosystem.

## Installation

```bash
pip install dynamiq-sandboxes
```

## Quick Start

```bash
export DYNAMIQ_API_KEY="your-api-key"
```

```python
from dynamiq_sandboxes import Sandbox

sandbox = Sandbox.create(template="python")

result = sandbox.execute_command("echo 'Hello from the sandbox!'")
print(result["stdout"])  # Hello from the sandbox!

sandbox.close()
```

## Code Sandboxes

Isolated execution environments with Python, Node.js, and shell support.

```python
from dynamiq_sandboxes import Sandbox

sandbox = Sandbox.create(template="python", timeout=3600)

# Execute shell commands (auto-wrapped in /bin/bash, like E2B)
result = sandbox.execute_command("echo hello && ls /")
print(result["stdout"])
print(result["exit_code"])  # 0

# Run Python code directly
output = sandbox.run_code("print(2 + 2)", language="python")
print(output["stdout"])  # 4

# Explicit args mode (no shell wrapping)
result = sandbox.execute_command("python3", args=["-c", "print('direct')"])

# File operations
sandbox.filesystem.write("/app/config.json", '{"key": "value"}')
content = sandbox.filesystem.read("/app/config.json")
print(content)  # {"key": "value"}

files = sandbox.filesystem.list("/app")
for f in files:
    print(f"{f.name}  {f.size}B  ({f.type})")

# Directory operations
sandbox.filesystem.mkdir("/app/data")
sandbox.filesystem.copy("/app/config.json", "/app/data/config.json")
sandbox.filesystem.move("/app/data/config.json", "/app/data/settings.json")
sandbox.filesystem.remove("/app/data", recursive=True)

# Upload / download files
sandbox.filesystem.upload("local_file.py", "/app/script.py")
sandbox.filesystem.download("/app/output.csv", "local_output.csv")

# Check file existence and metadata
if sandbox.filesystem.exists("/app/script.py"):
    info = sandbox.filesystem.stat("/app/script.py")
    print(f"{info.name}: {info.size} bytes")

# Resource metrics
metrics = sandbox.metrics()
print(f"CPU: {metrics['cpu']['usage_percent']}%")

# Extend session timeout
sandbox.extend_timeout(3600)  # Add 1 hour

sandbox.close()
```

## Browser Automation

Headless Chromium with CDP access, screenshots, scraping, and live streaming.

```python
from dynamiq_sandboxes import Browser

browser = Browser.create(
    stealth=True,            # Anti-detection mode
    viewport_width=1920,
    viewport_height=1080,
)

# Navigate to pages
browser.navigate("https://example.com")

# Take screenshots
result = browser.screenshot(full_page=True)
# result["image"] is a data URI: data:image/png;base64,...
# result["width"], result["height"]

# Scrape page content
scrape = browser.scrape(format="text")
print(scrape["content"])

# Execute JavaScript
title = browser.execute_script("() => document.title")
url = browser.execute_script("() => window.location.href")

# Navigation history
browser.go_back()
browser.go_forward()
browser.reload()

# Browser context (cookies, localStorage)
ctx = browser.get_context()
print(ctx)

# Console logs and network requests
logs = browser.get_logs()
requests = browser.get_network_requests()
har = browser.export_har()

# Live streaming info (for embedding in UI)
live = browser.get_live_view()
print(live["stream_url"])     # WebSocket stream URL
print(live["livekit_url"])    # LiveKit WebRTC URL
print(live["livekit_token"])  # JWT token for viewer

browser.close()
```

### CDP Access (Playwright / Puppeteer)

Every browser session exposes a **Chrome DevTools Protocol** WebSocket that works with any CDP-compatible tool:

```python
# Get the CDP URL from the sandbox
browser = Browser.create(stealth=True)
cdp_url = browser.data["cdp_websocket_url"]
# wss://api.sandboxes.getdynamiq.ai/v1/browser/sessions/{id}/cdp
```

**Playwright (Python):**

```python
from playwright.async_api import async_playwright

async with async_playwright() as p:
    b = await p.chromium.connect_over_cdp(
        cdp_url,
        headers={"X-API-Key": "your-api-key"},
    )
    page = b.contexts[0].pages[0]

    await page.goto("https://example.com")
    print(await page.title())
    await page.screenshot(path="screenshot.png")
```

**Puppeteer (Node.js):**

```javascript
const browser = await puppeteer.connect({
  browserWSEndpoint: cdpUrl,
  headers: { "X-API-Key": "your-api-key" },
});
const page = (await browser.pages())[0];
await page.goto("https://example.com");
await page.screenshot({ path: "screenshot.png" });
```

## Virtual Desktops

Full Ubuntu desktop with XFCE, mouse/keyboard control, and VNC streaming.

```python
from dynamiq_sandboxes import Desktop

desktop = Desktop.create(template="ubuntu-desktop")

# Take screenshots
result = desktop.screenshot()
# result["image"] is a data URI, result["width"], result["height"]

# Launch applications
desktop.launch(application="xfce4-terminal")
desktop.launch(application="firefox")

# Mouse control
desktop.mouse_click(x=500, y=300)
desktop.mouse_click(x=500, y=300, button="right")     # Right click
desktop.mouse_click(x=500, y=300, double_click=True)   # Double click
desktop.mouse_move(x=100, y=200)
desktop.mouse_scroll(x=500, y=300, delta_x=0, delta_y=-3)
desktop.mouse_drag(start_x=100, start_y=100, end_x=300, end_y=300)

# Keyboard input
desktop.keyboard_type(text="Hello, World!")
desktop.keyboard_press(keys=["Return"])
desktop.keyboard_press(keys=["ctrl", "c"])     # Hotkey combo
desktop.keyboard_press(keys=["ctrl", "l"])     # Clear terminal

# Open URLs
desktop.open(path="https://example.com")

# Get cursor position
cursor = desktop.cursor()
print(f"Cursor at ({cursor['x']}, {cursor['y']})")

# VNC streaming (for embedding in your UI)
stream = desktop.stream_start()
info = desktop.stream_info()
print(f"noVNC URL: {info.get('novnc_url')}")
print(f"WebSocket: {info.get('stream_url')}")

desktop.close()
```

## REST API

All SDK methods map to REST API calls:

```bash
# Create a sandbox
curl -X POST https://api.sandboxes.getdynamiq.ai/v1/sandboxes \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template_id": "python", "timeout": 3600}'

# Execute a command
curl -X POST https://api.sandboxes.getdynamiq.ai/v1/sandboxes/{id}/commands \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "echo hello"}'

# Create a browser session
curl -X POST https://api.sandboxes.getdynamiq.ai/v1/browser/sessions \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"stealth": true}'

# Create a desktop
curl -X POST https://api.sandboxes.getdynamiq.ai/v1/sandboxes \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template_id": "ubuntu-desktop", "sandbox_type": "desktop"}'
```

## Configuration

| Environment Variable | Description | Default |
|---------------------|-------------|---------|
| `DYNAMIQ_API_KEY` | API key for authentication | Required |
| `DYNAMIQ_API_URL` | API base URL | `https://api.sandboxes.getdynamiq.ai/v1` |

```python
# Or pass directly
sandbox = Sandbox.create(
    template="python",
    api_key="your-api-key",
    base_url="https://api.sandboxes.getdynamiq.ai/v1",
)
```

## API Reference

### Sandbox

| Method | Description |
|--------|-------------|
| `Sandbox.create(template, timeout, vcpu, memory_mb, ...)` | Create a new sandbox |
| `Sandbox.get(sandbox_id)` | Connect to existing sandbox |
| `sandbox.execute_command(command, args, working_dir, timeout)` | Execute a shell command |
| `sandbox.run_code(code, language, timeout)` | Run code in a language interpreter |
| `sandbox.filesystem.write(path, content)` | Write a file |
| `sandbox.filesystem.read(path)` | Read a file |
| `sandbox.filesystem.list(path)` | List directory contents |
| `sandbox.filesystem.mkdir(path)` | Create directory |
| `sandbox.filesystem.remove(path, recursive)` | Delete file or directory |
| `sandbox.filesystem.exists(path)` | Check if path exists |
| `sandbox.filesystem.stat(path)` | Get file metadata |
| `sandbox.filesystem.copy(src, dst)` | Copy file/directory |
| `sandbox.filesystem.move(src, dst)` | Move/rename file |
| `sandbox.filesystem.upload(local, remote)` | Upload local file |
| `sandbox.filesystem.download(remote, local)` | Download file |
| `sandbox.metrics()` | Get CPU/memory/disk metrics |
| `sandbox.extend_timeout(seconds)` | Extend session lifetime |
| `sandbox.refresh()` | Refresh sandbox state |
| `sandbox.close()` | Terminate sandbox |

### Browser

| Method | Description |
|--------|-------------|
| `Browser.create(stealth, viewport_width, viewport_height, ...)` | Create browser session |
| `Browser.get(session_id)` | Connect to existing session |
| `browser.navigate(url, wait_until, timeout)` | Navigate to URL |
| `browser.screenshot(format, full_page, quality)` | Capture screenshot |
| `browser.scrape(format, wait_for, timeout)` | Extract page content |
| `browser.execute_script(script)` | Execute JavaScript |
| `browser.go_back()` | Navigate back |
| `browser.go_forward()` | Navigate forward |
| `browser.reload()` | Reload page |
| `browser.get_current_url()` | Get current URL |
| `browser.get_context()` | Get cookies/storage |
| `browser.get_logs()` | Get console logs |
| `browser.get_network_requests()` | Get captured requests |
| `browser.export_har()` | Export HAR archive |
| `browser.get_live_view()` | Get streaming info |
| `browser.send_input(type, x, y, text, ...)` | Send input events |
| `browser.close()` | Close session |

### Desktop

| Method | Description |
|--------|-------------|
| `Desktop.create(template, timeout, ...)` | Create virtual desktop |
| `Desktop.get(desktop_id)` | Connect to existing desktop |
| `desktop.screenshot(format, quality)` | Capture screenshot |
| `desktop.launch(application, args)` | Launch application |
| `desktop.mouse_click(x, y, button, double_click)` | Mouse click |
| `desktop.mouse_move(x, y)` | Move cursor |
| `desktop.mouse_scroll(x, y, delta_x, delta_y)` | Scroll |
| `desktop.mouse_drag(start_x, start_y, end_x, end_y)` | Drag |
| `desktop.keyboard_type(text)` | Type text |
| `desktop.keyboard_press(keys, duration_ms)` | Press keys |
| `desktop.cursor()` | Get cursor position |
| `desktop.open(path)` | Open file/URL |
| `desktop.stream_start()` | Start VNC stream |
| `desktop.stream_stop()` | Stop VNC stream |
| `desktop.stream_info()` | Get stream status |
| `desktop.list_windows()` | List windows |
| `desktop.close()` | Close desktop |

## Error Handling

```python
from dynamiq_sandboxes import (
    Sandbox, APIError, AuthenticationError,
    NotFoundError, RateLimitError, TimeoutError,
)

try:
    sandbox = Sandbox.create(template="python")
    result = sandbox.execute_command("echo hello")
except AuthenticationError:
    print("Invalid API key")
except RateLimitError:
    print("Rate limited, retry later")
except TimeoutError:
    print("Request timed out")
except APIError as e:
    print(f"API error: {e}")
```

## Use with Dynamiq Agents

Dynamiq Sandboxes integrates with the [Dynamiq](https://github.com/dynamiq-ai/dynamiq) orchestration framework for building AI agents:

```python
from dynamiq.nodes.tools.e2b_sandbox import E2BInterpreterTool
from dynamiq.connections import E2B as E2BConnection

# Use Dynamiq Sandboxes as a tool for AI agents
tool = E2BInterpreterTool(
    connection=E2BConnection(api_key="your-dynamiq-sandbox-key")
)
```

## Requirements

- Python 3.9+
- An active [Dynamiq](https://sandboxes.getdynamiq.ai) account

## Links

- **Dashboard**: [sandboxes.getdynamiq.ai](https://sandboxes.getdynamiq.ai)
- **Dynamiq Framework**: [github.com/dynamiq-ai/dynamiq](https://github.com/dynamiq-ai/dynamiq)
- **Issues**: [GitHub Issues](https://github.com/dynamiq-ai/dynamiq/issues)

## License

Apache 2.0
