Metadata-Version: 2.4
Name: instavm
Version: 0.21.0
Summary: Official Python SDK and CLI for InstaVM APIs
Author-email: InstaVM <hello@instavm.io>
License: MIT
Project-URL: Documentation, https://instavm.io/docs/sdks/python/overview
Project-URL: Changelog, https://instavm.io/docs/sdks/python/changelog
Project-URL: Support, https://instavm.io/support
Project-URL: Source, https://github.com/BandarLabs/sandbox_client
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests
Requires-Dist: PyYAML<7,>=6
Requires-Dist: websockets<16,>=12.0
Provides-Extra: agents
Requires-Dist: openai-agents<0.15,>=0.14.1; extra == "agents"
Provides-Extra: pty
Dynamic: license-file

# InstaVM Python SDK + CLI

Official Python SDK and installed CLI for [InstaVM](https://instavm.io). Use it to manage VMs, snapshots, shares, volumes, desktops, account settings, and code execution from Python or your shell.

## Installation

```bash
pip install instavm
```

**Requirements:** Python 3.10+

## Table of Contents

- [CLI](#cli)
  - [Auth & Config](#auth--config)
  - [Common Commands](#common-commands)
  - [Cookbooks](#cookbooks)
  - [Deploy](#deploy)
  - [Command Reference](#command-reference)
- [Library Quick Start](#library-quick-start)
- [Code Execution](#code-execution)
  - [Sessions & Configuration](#sessions--configuration)
  - [File Operations](#file-operations)
  - [Async Execution](#async-execution)
- [Sessions & Sandboxes](#sessions--sandboxes)
- [VMs & Snapshots](#vms--snapshots)
- [Volumes](#volumes)
  - [Volume CRUD & Files](#volume-crud--files)
  - [VM Volume Attachments](#vm-volume-attachments)
- [Networking](#networking)
  - [Egress, Shares, SSH](#egress-shares-ssh)
- [Browser Automation](#browser-automation)
  - [Basic Browser Flow](#basic-browser-flow)
  - [Interactions: Click, Type, Fill, Scroll](#interactions-click-type-fill-scroll)
  - [Content Extraction](#content-extraction)
- [Computer Use](#computer-use)
- [PTY (Interactive Terminals)](#pty-interactive-terminals)
- [OpenAI Agents SDK](#openai-agents-sdk-sandbox-provider)
- [Platform APIs](#platform-apis)
- [Error Handling](#error-handling)
- [Development & Testing](#development--testing)
- [Changelog](#changelog)

---

## CLI

`pip install instavm` installs the `instavm` command in the active Python environment.

```bash
instavm --help
python -m instavm.cli --help
```

The CLI stores defaults in `~/.instavm/config.json`, checks `INSTAVM_API_KEY` when no key is stored, and also respects `INSTAVM_BASE_URL` and `INSTAVM_SSH_HOST`. `instavm login` is the easiest way to populate the config — see [Auth & Config](#auth--config) below.

### Auth & Config

The fastest way to authenticate is the browser-based login (recommended for laptops/workstations):

```bash
instavm login
```

This opens your browser, asks you to confirm the pairing on the dashboard,
and writes a labelled API key (`CLI – <hostname>`) back to your local config.
The key never touches the URL bar — it's exchanged server-to-server over a
local loopback callback using PKCE. To switch accounts, run
`instavm auth logout` then `instavm login` again.

For headless environments (CI, servers without a browser) paste an existing key:

```bash
instavm auth set-key                          # prompts for the key (stdin / hidden)
printf '%s' "$INSTAVM_API_KEY" | instavm auth set-key
instavm auth status
```

#### Pointing the CLI at a non-production environment

The CLI honors `INSTAVM_BASE_URL` (and the `--base-url` flag). To run the same
flow against staging — useful for verifying a release before it reaches
production:

```bash
export INSTAVM_BASE_URL=https://api.staging.instavm.io
instavm login          # browser will open the staging dashboard
```

The login flow always opens the dashboard URL returned by the API for the
configured base URL, so a single `INSTAVM_BASE_URL` switches both the API and
the browser side of the handshake.

If your dashboard's API key gets revoked from the web UI, the next CLI call
will print a hint to re-run `instavm login`.

### Common Commands

```bash
instavm whoami
instavm ls
instavm ls -a
instavm ls --watch
instavm create --type computer-use --memory 4096
instavm connect vm_123
instavm pty vm_123                           # interactive PTY (like docker exec -it)
instavm pty vm_123 /bin/zsh                  # custom shell program
instavm deploy
instavm deploy --plan
instavm egress get --vm vm_123
instavm exec --cmd "print('hello from CLI')" --language python
instavm exec ./script.py
instavm browser read https://example.com
instavm browser screenshot https://example.com --out page.png
instavm browser session create
instavm browser navigate https://example.com --session $SID
instavm browser click "button#submit" --session $SID
instavm browser type "input[name=q]" "hello world" --session $SID
instavm browser fill "input[name=email]" "user@example.com" --session $SID
instavm browser scroll --session $SID --y 500
instavm browser extract --session $SID --selector "a"
instavm browser session close $SID
instavm snapshot ls
instavm snapshot get <snapshot_id> --watch
instavm volume ls
instavm volume files upload <volume_id> ./README.md --path docs/README.md
instavm share create vm_123 3000 --public
instavm share set-private <share_id>
instavm ssh-key list
instavm desktop viewer <session_id>
instavm doc
instavm billing
```

`instavm ls` shows active VMs only. Use `-a` or `--all` to include terminated VM records. On ANSI terminals the human-readable list uses colored status badges, and both `instavm ls` and `instavm snapshot get` support `--watch` for periodic refreshes.

### Cookbooks

`instavm cookbook` pulls curated starter apps from the public [`instavm/cookbooks`](https://github.com/instavm/cookbooks) catalog, creates a VM, starts the service, creates the share, and returns the public URL.

```bash
instavm cookbook list
instavm cookbook info neon-city-webgl
instavm cookbook deploy neon-city-webgl
instavm cookbook deploy hello-fastapi
```

The CLI syncs the cookbook repo into `~/.instavm/cookbooks/`, checks for `git`, `ssh`, `scp`, and `tar`, prompts for any required secrets, and auto-registers a local public SSH key if your account does not already have one.

### Deploy

`instavm deploy` tries to deploy the app in the current directory without asking you to create an `instavm.yaml` first. It detects a simple Node.js or Python web app, creates a VM, uploads the project, starts the service, and gives you a share URL.

```bash
instavm deploy
instavm deploy --plan
instavm deploy ./path/to/app
```

`--plan` shows the detected runtime, install command, start command, port, and secrets without creating a VM.

`instavm deploy` is experimental right now. The zero-config path is working best for straightforward Node.js and Python apps. Some runtimes and projects still need follow-up fixes or backend support.

### Command Reference

- `auth`: `set-key`, `status`, `logout`
- `whoami`: show account details and SSH keys
- `ls`/`list`: show active VMs by default; use `-a` or `--all` for all VM records
- `cookbook`: `list`, `info`, `deploy` for curated starter apps from `instavm/cookbooks`
- `deploy`: experimental zero-config deploy for the current app directory
- `egress`: `get`, `set` for session and VM network egress policy
- `exec`: run inline code or a local file, plus `result` for async task lookup
- `pty`: open an interactive PTY inside a VM (like `docker exec -it`)
- `browser`: `read`, `screenshot`, `navigate`, `click`, `type`, `fill`, `scroll`, `wait`, `extract`, `session`
- `create`/`new`, `rm`/`delete`, `clone`, `connect`: core VM workflows
- `snapshot`: `ls`, `create`, `build`, `get`, `rm`
- `desktop`: `status`, `start`, `stop`, `viewer`
- `volume`: `ls`, `get`, `create`, `update`, `rm`, `checkpoint`, `files`
- `share`: `create`, `set-public`, `set-private`, `revoke`
- `ssh-key`: `list`, `add`, `remove`
- `doc`/`docs`, `billing`: docs and billing links

All leaf commands support `--json`. Share visibility updates use `share_id`, which matches the public API.

## Library Quick Start

```python
import os
from instavm import InstaVM

client = InstaVM(
    api_key=os.environ.get("INSTAVM_API_KEY"),
    auto_start_session=False,
)

me = client.get_current_user()
vms = client.vms.list()

print(me["email"])
print(len(vms))
```

---

## Code Execution

### Sessions & Configuration

```python
from instavm import InstaVM

client = InstaVM(
    api_key="your_api_key",
    cpu_count=2,
    memory_mb=1024,
    env={"APP_ENV": "dev"},
    metadata={"team": "platform"},
)

result = client.execute("print('session id:', 'ok')")
print(result)
print(client.session_id)
```

### File Operations

```python
client = InstaVM(api_key="your_api_key")

client.upload_file("local_script.py", "/app/local_script.py")
client.execute("python /app/local_script.py", language="bash")
client.download_file("output.json", local_path="./output.json")
```

### Async Execution

```python
client = InstaVM(api_key="your_api_key")

task = client.execute_async("sleep 5 && echo 'done'", language="bash")
result = client.get_task_result(task["task_id"], poll_interval=2, timeout=60)
print(result)
```

---

## Sessions & Sandboxes

```python
client = InstaVM(api_key="your_api_key")

# Get the publicly-reachable app URL (optionally for a specific port)
app_url = client.get_session_app_url(port=8080)
print(app_url.get("app_url"))

# List sandbox records with optional metadata filter and limit
sandboxes = client.list_sandboxes(metadata={"env": "production"}, limit=50)
print(len(sandboxes))
```

---

## VMs & Snapshots

```python
client = InstaVM(api_key="your_api_key")

# Create a basic VM
vm = client.vms.create(wait=True, metadata={"purpose": "dev"})

# Create a VM with pre-attached volumes
vm_with_vols = client.vms.create(
    wait=True,
    volumes=[{"volume_id": "vol_abc", "mount_path": "/data", "mode": "rw"}],
)

# List VMs
vms = client.vms.list()                 # GET /v1/vms  (running)
all_records = client.vms.list_all_records()  # GET /v1/vms/ (all records)

# Snapshot a running VM
snap_from_vm = client.vms.snapshot(vm_id=vm["vm_id"], wait=True, name="dev-base")

# Build a snapshot from an OCI image
snap_from_oci = client.snapshots.create(
    oci_image="docker.io/library/python:3.11-slim",
    name="python-3-11-dev",
    vcpu_count=2,
    memory_mb=1024,
    snapshot_type="user",
    build_args={
        "git_clone_url": "https://github.com/example/repo.git",
        "git_clone_branch": "main",
        "envs": {"PIP_INDEX_URL": "https://pypi.org/simple"},
    },
)

user_snaps = client.snapshots.list(snapshot_type="user")
```

---

## Volumes

### Volume CRUD & Files

```python
client = InstaVM(api_key="your_api_key")

# Create
volume = client.volumes.create(name="project-data", quota_bytes=10 * 1024 * 1024 * 1024)
volume_id = volume["id"]

# Read / Update
client.volumes.list(refresh_usage=True)
client.volumes.get(volume_id, refresh_usage=True)
client.volumes.update(volume_id, name="project-data-v2", quota_bytes=20 * 1024 * 1024 * 1024)

# File operations
client.volumes.upload_file(volume_id, file_path="./README.md", path="docs/README.md", overwrite=True)
files = client.volumes.list_files(volume_id, prefix="docs/", recursive=True, limit=1000)
download = client.volumes.download_file(volume_id, path="docs/README.md")
client.volumes.delete_file(volume_id, path="docs/README.md")

# Checkpoints
checkpoint = client.volumes.create_checkpoint(volume_id, name="pre-release")
client.volumes.list_checkpoints(volume_id)
client.volumes.delete_checkpoint(volume_id, checkpoint["id"])

# Cleanup
client.volumes.delete(volume_id)
```

### VM Volume Attachments

```python
vm = client.vms.create(wait=True)
vm_id = vm["vm_id"]

client.vms.mount_volume(vm_id, volume_id, mount_path="/data", mode="rw", wait=True)
client.vms.list_volumes(vm_id)
client.vms.unmount_volume(vm_id, volume_id, mount_path="/data", wait=True)
```

---

## Networking

### Egress, Shares, SSH

```python
client = InstaVM(api_key="your_api_key")

# Egress policy
policy = client.set_session_egress(
    allow_package_managers=True,
    allow_http=False,
    allow_https=True,
    allowed_domains=["pypi.org", "files.pythonhosted.org"],
)

# Public/private share links
share = client.shares.create(port=3000, is_public=False)
client.shares.update(share_id=share["share_id"], is_public=True)

# SSH key registration
key = client.add_ssh_key("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host")
```

---

## Browser Automation

### Basic Browser Flow

```python
client = InstaVM(api_key="your_api_key")

session = client.browser.create_session(viewport_width=1366, viewport_height=768)
session.navigate("https://example.com")
links = session.extract_elements("a", ["text", "href"])
shot_b64 = session.screenshot(full_page=True)
session.close()
```

### Interactions: Click, Type, Fill, Scroll

All interaction methods use **CSS selectors** to target elements (not pixel coordinates):

```python
client = InstaVM(api_key="your_api_key")

with client.browser.create_session() as session:
    session.navigate("https://www.google.com")

    # Type into an input field (keystroke-by-keystroke with configurable delay)
    session.type("textarea[name=q]", "InstaVM cloud VMs", delay=50)

    # Fill a form field (clears existing value first, sets value instantly)
    session.fill("input[name=email]", "user@example.com")

    # Click an element (supports force-click for obscured elements)
    session.click("button[type=submit]", force=False)

    # Scroll the page or a specific element
    session.scroll(y=500)
    session.scroll(selector="#results")

    # Wait for a condition before continuing
    session.wait_for("visible", selector="#results", timeout=5000)

    # Extract DOM elements with specific attributes
    elements = session.extract_elements("a.result-link", ["href", "text"])
    for el in elements:
        print(el["href"], el["text"])

    # Take a screenshot
    session.screenshot(full_page=True)
```

### Content Extraction

LLM-friendly extraction with optional interactive-element and anchor discovery:

```python
client = InstaVM(api_key="your_api_key")

content = client.browser.extract_content(
    url="https://example.com/docs",
    include_interactive=True,
    include_anchors=True,
    max_anchors=30,
)

print(content["readable_content"].get("title"))
for anchor in (content.get("content_anchors") or [])[:5]:
    print(anchor.get("text"), anchor.get("selector"))
```

---

## Computer Use

Control a full desktop environment inside a VM session:

```python
client = InstaVM(api_key="your_api_key")

session_id = client.session_id

# Viewer URL and state
viewer = client.computer_use.viewer_url(session_id)
state = client.computer_use.get(session_id, "/state")

# Proxy methods (GET, POST, HEAD)
head_resp = client.computer_use.head(session_id, "/state")

# VNC websockify URL for remote desktop streaming
vnc = client.computer_use.vnc_websockify(session_id)
```

---

## PTY (Interactive Terminals)

Create and manage interactive pseudo-terminal sessions inside VMs. Used by the OpenAI Agents SDK's `Shell` capability for `write_stdin` support.

```bash
pip install instavm[pty]  # adds websockets dependency
```

```python
import asyncio
from instavm import InstaVM

client = InstaVM(api_key="your_api_key")
session_id = client.session_id

# Create a PTY session
pty_info = client.pty.create(session_id, cols=120, rows=40)
pty_id = pty_info["session_id"]

# List / get / resize / kill PTY sessions
sessions = client.pty.list(session_id)
info = client.pty.get(session_id, pty_id)
client.pty.resize(session_id, pty_id, cols=200, rows=50)
client.pty.kill(session_id, pty_id)

# WebSocket URL for interactive I/O (use with websockets library)
ws_url = client.pty.ws_url(session_id, pty_id)
```

### WebSocket Protocol

Connect to `ws_url` for bidirectional PTY I/O:

- **Client → Server:** Binary frames = stdin, Text frames = JSON control (`{"type": "resize", "cols": N, "rows": N}`)
- **Server → Client:** Binary frames = stdout/stderr, Text frame = exit notification (`{"type": "exit", "exit_code": N}`)

### OpenAI Agents SDK Integration

With PTY support enabled, the `Shell` capability's `write_stdin` tool works automatically:

```python
from agents.sandbox import SandboxAgent

agent = SandboxAgent(
    name="Developer",
    model="gpt-5.4",
    instructions="Run interactive commands and inspect output.",
)
# Shell tool's write_stdin is available when supports_pty() returns True
```

---

## OpenAI Agents SDK: Sandbox Provider

To use InstaVM as a sandbox backend for the [OpenAI Agents SDK](https://github.com/openai/openai-agents-python), install the `agents` extra:

```bash
pip install instavm[agents]
```

This pulls in `openai-agents>=0.14.1,<0.15` and registers InstaVM as a sandbox provider automatically. Each agent runs in its own cloud VM with filesystem, shell, networking, and snapshot support.

```python
import asyncio, os
from agents import Runner, RunConfig
from agents.sandbox import SandboxAgent, SandboxRunConfig
from instavm.integrations.openai_agents import (
    InstaVMSandboxClient,
    InstaVMSandboxClientOptions,
)

agent = SandboxAgent(
    name="Analyst",
    model="gpt-5.4",
    instructions="Analyze workspace files and answer concisely.",
)

async def main():
    client = InstaVMSandboxClient(api_key=os.environ["INSTAVM_API_KEY"])
    result = await Runner.run(
        agent,
        "What OS is this sandbox running?",
        run_config=RunConfig(
            sandbox=SandboxRunConfig(
                client=client,
                options=InstaVMSandboxClientOptions(memory_mb=1024),
            ),
        ),
    )
    print(result.final_output)

asyncio.run(main())
```

**Features:** streaming, resume & snapshots, persistent volumes, egress control, cloud bucket mounts, exposed ports, and more.

📖 **[Full documentation](docs/openai-agents.md)** · 📂 **[Examples](examples/sandbox/)**

---

## Platform APIs

API keys, audit logs, and webhooks:

```python
client = InstaVM(api_key="your_api_key")

# API Keys
api_key = client.api_keys.create(description="ci key")

# Audit log
audit_page = client.audit.events(limit=25, status="success")

# Webhooks
endpoint = client.webhooks.create_endpoint(
    url="https://example.com/instavm/webhook",
    event_patterns=["vm.*", "snapshot.*"],
)

deliveries = client.webhooks.list_deliveries(limit=10)
```

---

## Error Handling

All SDK errors extend a typed hierarchy for precise `except` handling:

```python
from instavm import (
    InstaVM,
    AuthenticationError,
    ExecutionError,
    NetworkError,
    RateLimitError,
    SessionError,
)

client = InstaVM(api_key="your_api_key")

try:
    client.execute("raise Exception('boom')")
except AuthenticationError:
    print("Invalid API key")
except RateLimitError:
    print("Rate limited")
except SessionError as exc:
    print(f"Session issue: {exc}")
except ExecutionError as exc:
    print(f"Execution failed: {exc}")
except NetworkError as exc:
    print(f"Network issue: {exc}")
```

---

## Development & Testing

```bash
pip install -e .                              # Install for development
python3 -m pytest tests/test_api_client.py -v # Unit tests
```

---

## Further Reading

- [Python SDK Overview](https://instavm.io/docs/sdks/python/overview)
- [VM Management](https://instavm.io/docs/sdks/python/vm-management)
- [Snapshots](https://instavm.io/docs/sdks/python/snapshots)
- [Egress and Networking](https://instavm.io/docs/sdks/python/egress-and-networking)
- [Platform APIs](https://instavm.io/docs/sdks/python/platform-apis)
- [Browser Automation](https://instavm.io/docs/sdks/python/browser-automation)
- [Error Handling](https://instavm.io/docs/sdks/python/error-handling)

---

## Changelog

Current package version: **0.19.1**

### 0.19.1

- **Fix: `browser navigate` now shows page title.** The CLI was reading `title` from the wrong level of the API response, always showing `-`. Now correctly extracts title from the nested `data` field.
- **Fix: `egress set` now shows the new policy state.** Previously displayed the old state (all `allowed`) because the backend only returns `{"status": "ok"}`. The CLI now fetches the updated state after applying.

### 0.19.0

- **Full browser interaction CLI.** `instavm browser` now exposes `navigate`, `click`, `type`, `fill`, `scroll`, `wait`, and `extract` subcommands — all backed by Playwright via CSS selectors. Previously these were SDK-only; now they're available from the terminal.
- **Browser session management CLI.** `instavm browser session create|close|ls` for managing persistent browser sessions across multiple interactions.
- **PTY CLI.** `instavm pty <vm_id>` opens an interactive terminal inside a VM, like `docker exec -it`. Supports custom shell programs and automatic window resize.
- **ForbiddenError handling.** The SDK now catches 403 responses as `ForbiddenError` instead of retrying indefinitely, with proper error messages for tier limit violations.

### 0.18.0

- **OpenAI Agents SDK sandbox provider.** `pip install instavm[agents]` adds InstaVM as a backend for Sandbox Agents with auto-registration, no SDK patches needed.
- **PTY (interactive terminal) support.** `pip install instavm[pty]` enables `PtyManager` for creating, resizing, and killing interactive terminal sessions over WebSocket. The OpenAI Agents SDK's `Shell` capability (`write_stdin`) works automatically when PTY is available.
- `InstaVMSandboxClient` / `InstaVMSandboxSession` implementing the full sandbox session contract (exec, read, write, persist, hydrate, resume, exposed ports).
- `InstaVMSandboxClientOptions` with VM sizing, snapshots, egress control, exposed ports, environment variables, and metadata.
- `InstaVMCloudBucketMountStrategy` for S3/R2/GCS/Azure Blob mounts via rclone+FUSE.
- Persistent JuiceFS volume helpers (mount, unmount, list).
- Workspace snapshots with mount-safe tar archiving (unmount, tar, remount).
- Exit-code auto-detection: uses native API `exit_code` when available, falls back to sentinel wrapping on older backends.
- Egress policy management (allow/block HTTP/HTTPS, package managers, domain/CIDR allowlists).
- 125 unit tests, 21 e2e integration tests, full `Runner.run` agent smoke test verified.
- CI matrix: Python 3.10–3.13, Ubuntu/macOS/Windows, plus early-warning job tracking `openai-agents @ git+main`.

### 0.17.0

- Added CLI parity for `egress`, `exec`, and terminal-first `browser` workflows
- Added colored VM status badges, `--watch` refreshes for `ls` and `snapshot get`, and snapshot-build spinner coverage

### 0.16.1

- Fixed deploy and cookbook uploads through the SSH gateway by forcing legacy SCP mode

### 0.16.0

- Expanded CLI docs for `instavm cookbook`
- Added experimental `instavm deploy` for zero-config app deploys from the current directory

### 0.15.1

- `ls` now matches the SSH gateway: active VMs by default, `-a` or `--all` for all VM records
- `whoami` now uses the live `/v1/users/me` endpoint

### 0.15.0

- Installed `instavm` CLI for `pip install instavm`, including `python -m instavm.cli`
- Stored CLI auth/config in `~/.instavm/config.json` with `INSTAVM_API_KEY` fallback
- Added [`get_current_user()`](#library-quick-start) and [`get_session_status(session_id=None)`](#computer-use) helpers for account and desktop workflows

### 0.13.0

- [`get_session_app_url(session_id?, port?)`](#sessions--sandboxes) — session app URL with optional port
- [`list_sandboxes(metadata?, limit?)`](#sessions--sandboxes) — list sandbox records with metadata filtering
- [`computer_use.head(session_id, path)`](#computer-use) — HEAD proxy method for computer-use sessions
- [`computer_use.vnc_websockify(session_id)`](#computer-use) — VNC websockify URL for remote desktop streaming
- VM creation now accepts [`volumes`](#vms--snapshots) for pre-attached volume mounts

### 0.12.0

- Manager-based APIs across VMs, volumes, snapshots, shares, custom domains, computer use, API keys, audit, and webhooks
- Snapshot build args support for env vars and Git clone inputs
- Distinct VM list helpers for `/v1/vms` and `/v1/vms/`

For detailed history, see repository tags and PR history.
