Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 46 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,13 @@ client = Leap0(Leap0Config())
sandbox = client.sandboxes.create()

try:
result = client.code_interpreter.execute(
sandbox,
result = sandbox.code_interpreter.execute(
code="sum([10, 20, 30, 40]) / 4",
language="python",
)
print(result.main_text) # "25.0"
finally:
client.sandboxes.delete(sandbox)
sandbox.delete()
client.close()
```

Expand All @@ -68,11 +67,11 @@ from leap0 import Leap0Client

with Leap0Client(api_key="your-api-key") as client:
sandbox = client.sandboxes.create()
result = client.code_interpreter.execute(
sandbox, code="print('Hello from the sandbox!')", language="python"
result = sandbox.code_interpreter.execute(
code="print('Hello from the sandbox!')", language="python"
)
print(result.main_text)
client.sandboxes.delete(sandbox)
sandbox.delete()
```

## Features
Expand All @@ -82,13 +81,11 @@ with Leap0Client(api_key="your-api-key") as client:
Stateful REPL sessions for Python and TypeScript. Variables persist across calls, with Matplotlib charts auto-captured as PNG and SVG.

```python
result = client.code_interpreter.execute(
sandbox, code="x = 42", language="python"
)
result = sandbox.code_interpreter.execute(code="x = 42", language="python")

# Stream output in real-time
for event in client.code_interpreter.execute_stream(
sandbox, code="for i in range(5): print(i)", language="python"
for event in sandbox.code_interpreter.execute_stream(
code="for i in range(5): print(i)", language="python"
):
print(event.type, event.data)
```
Expand All @@ -98,30 +95,30 @@ for event in client.code_interpreter.execute_stream(
Full filesystem access inside every sandbox. List, read, write, edit, search, and more.

```python
client.filesystem.write_file_text(sandbox, path="/workspace/hello.txt", content="Hello!")
content = client.filesystem.read_file_text(sandbox, path="/workspace/hello.txt")
tree = client.filesystem.tree(sandbox, path="/workspace", max_depth=2)
matches = client.filesystem.grep(sandbox, path="/workspace", pattern="TODO")
sandbox.filesystem.write_file(path="/workspace/hello.txt", content="Hello!")
content = sandbox.filesystem.read_file(path="/workspace/hello.txt")
tree = sandbox.filesystem.tree(path="/workspace", max_depth=2)
matches = sandbox.filesystem.grep(path="/workspace", pattern="TODO")
```

### Git

Clone repos, create branches, stage files, commit, push, and pull, all inside the sandbox.

```python
client.git.clone(sandbox, url="https://github.com/user/repo.git", path="/workspace/repo")
status = client.git.status(sandbox, path="/workspace/repo")
client.git.add(sandbox, path="/workspace/repo", files=["README.md"])
client.git.commit(sandbox, path="/workspace/repo", message="Update README")
client.git.push(sandbox, path="/workspace/repo")
sandbox.git.clone(url="https://github.com/user/repo.git", path="/workspace/repo")
status = sandbox.git.status(path="/workspace/repo")
sandbox.git.add(path="/workspace/repo", files=["README.md"])
sandbox.git.commit(path="/workspace/repo", message="Update README")
sandbox.git.push(path="/workspace/repo")
```

### Process Execution

Execute one-shot shell commands inside a running sandbox.

```python
result = client.process.execute(sandbox, command=["ls", "-la", "/workspace"])
result = sandbox.process.execute(command="ls -la /workspace")
print(result.stdout)
```

Expand All @@ -130,8 +127,8 @@ print(result.stdout)
Interactive terminal sessions over WebSocket with persistent state.

```python
session = client.pty.create(sandbox, cols=120, rows=30, cwd="/home/user")
conn = client.pty.connect(sandbox, session.id)
session = sandbox.pty.create(cols=120, rows=30, cwd="/home/user")
conn = sandbox.pty.connect(session.id)
conn.send("ls -la\n")
print(conn.recv().decode())
conn.close()
Expand All @@ -142,17 +139,27 @@ conn.close()
Start language servers for Python and TypeScript to get completions, symbols, and document lifecycle events.

```python
client.lsp.start(sandbox, language="python")
client.lsp.did_open(sandbox, uri="file:///workspace/main.py", language="python")
completions = client.lsp.completions(sandbox, uri="file:///workspace/main.py", line=10, character=5)
sandbox.lsp.start(language_id="python", path_to_project="/workspace")
sandbox.lsp.did_open(
language_id="python",
path_to_project="/workspace",
uri="file:///workspace/main.py",
)
completions = sandbox.lsp.completions(
language_id="python",
path_to_project="/workspace",
uri="file:///workspace/main.py",
line=10,
character=5,
)
```

### SSH Access

Generate and manage time-bound SSH credentials for direct sandbox access.

```python
ssh = client.ssh.create_access(sandbox)
ssh = sandbox.ssh.create_access()
print(ssh.hostname, ssh.port, ssh.username)
```

Expand All @@ -161,10 +168,12 @@ print(ssh.hostname, ssh.port, ssh.username)
Control a graphical desktop inside the sandbox. Take screenshots, move the pointer, click, type, and record the screen.

```python
sandbox = client.sandboxes.create(template_name="system/desktop:v0.1.0")
client.desktop.move_pointer(sandbox, x=500, y=300)
client.desktop.click(sandbox, button=1)
screenshot = client.desktop.screenshot(sandbox, image_format="png")
from leap0 import DEFAULT_DESKTOP_TEMPLATE_NAME

sandbox = client.sandboxes.create(template_name=DEFAULT_DESKTOP_TEMPLATE_NAME)
sandbox.desktop.move_pointer(x=500, y=300)
sandbox.desktop.click(button=1)
screenshot = sandbox.desktop.screenshot(image_format="png")
```

### Snapshots
Expand Down Expand Up @@ -241,10 +250,16 @@ client = Leap0(Leap0Config(
See the [`examples/`](examples/) directory for complete usage examples:

- **[quickstart.py](examples/quickstart.py)** - Basic code execution
- **[async_quickstart.py](examples/async_quickstart.py)** - Async client quickstart
- **[async_filesystem_and_git.py](examples/async_filesystem_and_git.py)** - Async file and Git operations
- **[async_code_interpreter_stream.py](examples/async_code_interpreter_stream.py)** - Async streaming code execution output
- **[async_pty.py](examples/async_pty.py)** - Async interactive terminal session
- **[code_interpreter_stream.py](examples/code_interpreter_stream.py)** - Streaming code execution output
- **[filesystem_and_git.py](examples/filesystem_and_git.py)** - File and Git operations
- **[pty.py](examples/pty.py)** - Interactive terminal session
- **[desktop.py](examples/desktop.py)** - Desktop GUI automation
- **[snapshots.py](examples/snapshots.py)** - Save and restore sandbox state
- **[ssh.py](examples/ssh.py)** - Generate and validate SSH access

## Development

Expand Down
29 changes: 29 additions & 0 deletions examples/async_code_interpreter_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import annotations

import asyncio
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from leap0 import AsyncLeap0Client, AsyncSandbox, StreamEvent


async def main() -> None:
async with AsyncLeap0Client() as client:
sandbox: AsyncSandbox = await client.sandboxes.create()

try:
async for event in sandbox.code_interpreter.execute_stream(
code="import time\nfor i in range(3):\n print(f'async step {i}')\n time.sleep(1)",
language="python",
timeout_ms=10_000,
):
typed_event: StreamEvent = event
print(typed_event)
finally:
await sandbox.delete()


if __name__ == "__main__":
asyncio.run(main())
43 changes: 43 additions & 0 deletions examples/async_filesystem_and_git.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from __future__ import annotations

import asyncio
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from leap0 import AsyncLeap0Client, AsyncSandbox, FileInfo, GitResult, TreeResult


async def main() -> None:
async with AsyncLeap0Client() as client:
sandbox: AsyncSandbox = await client.sandboxes.create()
repo_path = "/workspace/hello-world"

try:
clone: GitResult = await sandbox.git.clone(
url="https://github.com/octocat/Hello-World.git",
path=repo_path,
branch="master",
)
print("clone exit:", clone.exit_code)

status: GitResult = await sandbox.git.status(path=repo_path)
print("git status:\n", status.output)

await sandbox.filesystem.write_file(
path=f"{repo_path}/async-demo.txt",
content="Hello from the async Leap0 Python SDK\n",
)

file_info: FileInfo = await sandbox.filesystem.stat(path=f"{repo_path}/README")
print("readme size:", file_info.size)

tree: TreeResult = await sandbox.filesystem.tree(path=repo_path, max_depth=2)
print("tree items:", [entry.name for entry in tree.items])
finally:
await sandbox.delete()


if __name__ == "__main__":
asyncio.run(main())
34 changes: 34 additions & 0 deletions examples/async_pty.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

import asyncio
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from leap0 import AsyncLeap0Client, AsyncPtyConnection, AsyncSandbox, PtySession


async def main() -> None:
async with AsyncLeap0Client() as client:
sandbox: AsyncSandbox = await client.sandboxes.create()

try:
session: PtySession = await sandbox.pty.create(
session_id="async-demo-terminal",
cols=120,
rows=30,
cwd="/home/user",
)
connection: AsyncPtyConnection = await sandbox.pty.connect(session.id)
try:
await connection.send("pwd\n")
print((await connection.recv()).decode("utf-8", errors="replace"))
finally:
await connection.close()
finally:
await sandbox.delete()


if __name__ == "__main__":
asyncio.run(main())
28 changes: 28 additions & 0 deletions examples/async_quickstart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import annotations

import asyncio
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from leap0 import AsyncLeap0Client, AsyncSandbox, CodeExecutionResult


async def main() -> None:
async with AsyncLeap0Client() as client:
sandbox: AsyncSandbox = await client.sandboxes.create()

try:
result: CodeExecutionResult = await sandbox.code_interpreter.execute(
code="print('hello from async leap0')",
language="python",
)
print("sandbox:", sandbox.id)
print("result:", result.main_text)
finally:
await sandbox.delete()


if __name__ == "__main__":
asyncio.run(main())
14 changes: 8 additions & 6 deletions examples/code_interpreter_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from leap0 import Leap0, Leap0Config
from leap0 import Leap0, Leap0Config, Sandbox, StreamEvent


def main() -> None:
client = Leap0(Leap0Config())
sandbox = client.sandboxes.create()
sandbox: Sandbox = client.sandboxes.create()

try:
for event in client.code_interpreter.execute_stream(
sandbox,
event: StreamEvent
for event in sandbox.code_interpreter.execute_stream(
code="import time\nfor i in range(3):\n print(f'step {i}')\n time.sleep(1)",
language="python",
timeout_ms=10_000,
):
print(event)
finally:
client.sandboxes.delete(sandbox)
client.close()
try:
sandbox.delete()
finally:
client.close()


if __name__ == "__main__":
Expand Down
27 changes: 18 additions & 9 deletions examples/desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,38 @@

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from leap0 import Leap0, Leap0Config, Leap0Error
from leap0 import (
DEFAULT_DESKTOP_TEMPLATE_NAME,
DesktopDisplayInfo,
Leap0,
Leap0Config,
Leap0Error,
Sandbox,
)


def main() -> None:
client = Leap0(Leap0Config())
sandbox = client.sandboxes.create(template_name="system/desktop:v0.1.0")
sandbox: Sandbox | None = None

try:
client.desktop.wait_until_ready(sandbox, timeout=60.0)
print("Desktop:", client.desktop.desktop_url(sandbox))
sandbox = client.sandboxes.create(template_name=DEFAULT_DESKTOP_TEMPLATE_NAME)
sandbox.desktop.wait_until_ready(timeout=60.0)
print("Desktop:", sandbox.desktop.desktop_url())

display = client.desktop.display_info(sandbox)
display: DesktopDisplayInfo = sandbox.desktop.display_info()
print("Display:", display)

client.desktop.move_pointer(sandbox, x=display.width // 2, y=display.height // 2)
client.desktop.click(sandbox, button=1)
sandbox.desktop.move_pointer(x=display.width // 2, y=display.height // 2)
sandbox.desktop.click(button=1)

screenshot = client.desktop.screenshot(sandbox, image_format="png")
screenshot = sandbox.desktop.screenshot(image_format="png")
Path("desktop-screenshot.png").write_bytes(screenshot)
print("Saved screenshot to desktop-screenshot.png")
finally:
try:
client.sandboxes.delete(sandbox)
if sandbox is not None:
sandbox.delete()
except Leap0Error:
pass
finally:
Expand Down
Loading