> ## Documentation Index
> Fetch the complete documentation index at: https://docs.boxlite.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Human tools

> Inspect or take over a sandbox's graphical interface from your browser — no VNC client to install.

Watch an agent work in real time, to step in when automation stalls, or to treat a browser or desktop as a disposable remote workbench. The GUI reaches you over a local port from whichever box provides it.

## Pages in this section

| Page                                            | What it covers                                                    |
| ----------------------------------------------- | ----------------------------------------------------------------- |
| [Share a desktop](/human-tools/desktop-access)  | Share the in-sandbox XFCE desktop with a person through a browser |
| [Browser DevTools](/human-tools/browser-access) | Attach your own DevTools to the browser running inside a sandbox  |

***

## Quick Example

The code below: (1) starts a sandbox that ships with a desktop (`ComputerBox`, image `lscr.io/linuxserver/webtop:ubuntu-xfce`); (2) waits until the desktop is ready; (3) **prints a local URL you can open directly in a browser**, then keeps running until you press Enter. During that window you can operate the sandbox like a remote desktop in your browser.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
# pip install boxlite
import asyncio
import boxlite

async def main():
    # ComputerBox ships with an XFCE desktop plus a noVNC web client.
    # gui_http_port / gui_https_port map "host local ports" -> in-sandbox GUI ports.
    try:
        async with boxlite.ComputerBox(
            cpu=2,
            memory=2048,
            gui_http_port=3000,   # host http port; open it in a browser
            gui_https_port=3001,  # host https port (self-signed cert; browser warns)
        ) as desktop:
            # Wait until the desktop environment is fully up (default max 60s)
            await desktop.wait_until_ready(timeout=60)

            print("Desktop ready. Open either URL below in a browser to take over:")
            print("  HTTP :  http://localhost:3000")
            print("  HTTPS:  https://localhost:3001  (self-signed cert; click 'Advanced' -> 'Proceed')")

            # Keep the sandbox alive to give the human time to operate.
            # On async with exit the sandbox is destroyed and all changes are lost (ephemeral).
            input("When done, come back here and press Enter to close the sandbox...")
    except RuntimeError as exc:
        # Image pull failure / no hardware virtualization, etc. all raise standard RuntimeError
        print(f"Sandbox failed to start: {exc}")

if __name__ == "__main__":
    asyncio.run(main())
```

After it runs, open `http://localhost:3000` in a browser to view and operate the sandbox desktop. When you close the script (press Enter), the sandbox and every change inside it are destroyed.

> Node users: `ComputerBox` is also available in the Node SDK (`import { ComputerBox } from "@boxlite-ai/boxlite"`), with camelCase method names (`waitUntilReady` / `screenshot`, etc.). The GUI port exposure logic is identical.

***

## Three paths for a human to reach the GUI

Choose by what you need to view or operate. Each path corresponds to one GUI-bearing, purpose-built box:

| Goal                                                                      | Box used      | How a human connects                                | Default image                                |
| ------------------------------------------------------------------------- | ------------- | --------------------------------------------------- | -------------------------------------------- |
| View / operate a full Linux desktop (mouse, keyboard, apps)               | `ComputerBox` | Open the noVNC web desktop in a browser             | `lscr.io/linuxserver/webtop:ubuntu-xfce`     |
| Watch Claude Code / an AI CLI run in the sandbox in real time             | `SkillBox`    | Open the noVNC web desktop in a browser             | `ghcr.io/boxlite-ai/boxlite-skillbox:0.1.0`  |
| Manually take over the in-sandbox browser (tune selectors, inspect pages) | `BrowserBox`  | Connect browser developer tools to the CDP endpoint | `mcr.microsoft.com/playwright:v1.58.0-jammy` |

> All three inherit from `SimpleBox`, so you can also run commands with `await box.exec(...)` and transfer files with `copy_in` / `copy_out`. The GUI is the extra "human view" they expose.

### Path A: full desktop (ComputerBox)

The most direct "remote desktop" approach. See the Quick Example above. Key points:

* The GUI ports are **fixed and controllable** on the host side (constructor arguments `gui_http_port` / `gui_https_port`, defaults `3000` / `3001`).
* HTTPS uses a self-signed certificate, so the browser shows a security warning; click "Advanced -> Proceed" to continue.
* Default resolution is 1024x768 (controlled by the `DISPLAY_SIZEW` / `DISPLAY_SIZEH` environment variables, which are injected for you).

### Path B: watch an AI agent run (SkillBox)

`SkillBox` runs an AI CLI such as Claude Code inside the sandbox and **ships with a noVNC desktop**, so you can watch in real time while a task completes. Its GUI ports are **randomly assigned by default** (constructor arguments default to `0`); read the assigned values from the instance attributes `gui_http_port` / `gui_https_port`:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
# pip install boxlite
# Requires a Claude OAuth token: env var CLAUDE_CODE_OAUTH_TOKEN or the oauth_token constructor argument
import asyncio
import boxlite

async def main():
    try:
        async with boxlite.SkillBox(
            skills=["anthropics/skills"],
            # oauth_token="<YOUR_CLAUDE_CODE_OAUTH_TOKEN>",  # the env var also works
        ) as box:
            await box.wait_until_ready(timeout=60)

            # Ports are randomly assigned; read the real value from the instance attribute
            print("Open the URL below in a browser to watch Claude work in real time:")
            print(f"  HTTP :  http://localhost:{box.gui_http_port}")
            print(f"  HTTPS:  https://localhost:{box.gui_https_port}")

            # Assign a task to Claude; call() returns its answer (the human can watch its actions live in the browser)
            answer = await box.call("List your currently available skills")
            print(answer)
    except ValueError as exc:
        # When the OAuth token is missing, __aenter__ raises ValueError
        print(f"SkillBox configuration missing: {exc}")
    except RuntimeError as exc:
        print(f"Sandbox failed to start: {exc}")

if __name__ == "__main__":
    asyncio.run(main())
```

### Path C: manually take over the in-sandbox browser (BrowserBox)

`BrowserBox` runs a Playwright browser inside the sandbox. What it exposes is a **debugging endpoint (CDP / Playwright Server endpoint)**, not a web desktop. You can:

* Get the CDP address with `await box.endpoint()` and paste it into Chrome's `chrome://inspect` to inspect pages and tune selectors — full walkthrough on [Browser DevTools](/human-tools/browser-access#take-over-the-browser-from-your-own-devtools).
* Get the Playwright Server address with `await box.playwright_endpoint()` and connect to it from a local script to observe while it runs.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
# pip install boxlite
import asyncio
import boxlite

async def main():
    try:
        # Browser type and other config are passed via BrowserBoxOptions (not constructor keywords)
        async with boxlite.BrowserBox(
            boxlite.BrowserBoxOptions(browser="chromium")
        ) as browser:
            # CDP direct-connect endpoint: paste into Chrome DevTools remote debugging to view pages and tune selectors.
            # Note: endpoint() and playwright_endpoint() are mutually exclusive; an instance can use only one.
            # Calling the other after one has been used raises RuntimeError.
            cdp_endpoint = await browser.endpoint(timeout=60)
            print(f"CDP endpoint (paste into Chrome DevTools remote debugging): {cdp_endpoint}")

            input("When done debugging, press Enter to close the sandbox...")
    except ValueError as exc:
        # browser="webkit" does not support endpoint() (CDP mode); use playwright_endpoint() instead
        print(f"This browser does not support CDP direct connect: {exc}")
    except RuntimeError as exc:
        print(f"Sandbox failed to start: {exc}")

if __name__ == "__main__":
    asyncio.run(main())
```

> For Playwright Server mode (which supports all browser types and is connected to with `connect()` from a local script), use `await browser.playwright_endpoint(timeout=60)` instead, and **do not** also call `endpoint()` on the same instance. Both modes occupy in-sandbox port 3000, so they are mutually exclusive.

***

## Parameters and Returns

### ComputerBox (human-access related)

| Parameter        | Required | Type  | Default | Description                               |
| ---------------- | -------- | ----- | ------- | ----------------------------------------- |
| `cpu`            | Optional | `int` | `2`     | Number of CPU cores                       |
| `memory`         | Optional | `int` | `2048`  | Memory (MiB)                              |
| `gui_http_port`  | Optional | `int` | `3000`  | Host HTTP port -> in-sandbox desktop GUI  |
| `gui_https_port` | Optional | `int` | `3001`  | Host HTTPS port (self-signed certificate) |

| Method                               | Returns                                                       | Description                                                                     |
| ------------------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `await wait_until_ready(timeout=60)` | `None`                                                        | Blocks until the desktop is ready; raises `TimeoutError` on timeout             |
| `await screenshot()`                 | `dict` (`data` is base64; plus `width` / `height` / `format`) | Captures the current desktop screenshot (a human can also view it in a browser) |

### SkillBox (human-access related)

| Parameter / attribute | Type                | Default                         | Description                                                                                            |
| --------------------- | ------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `skills`              | `list[str] \| None` | `None`                          | List of skills to install on first call                                                                |
| `oauth_token`         | `str \| None`       | reads `CLAUDE_CODE_OAUTH_TOKEN` | Claude OAuth token; when missing, `__aenter__` raises `ValueError`                                     |
| `gui_http_port`       | `int`               | `0` (random)                    | Pass `0` to the constructor for random assignment; **read the real value from the instance attribute** |
| `gui_https_port`      | `int`               | `0` (random)                    | Same as above                                                                                          |
| `await call(prompt)`  | `str`               | —                               | Assign a task to the AI and get its answer (the human can watch live in the browser)                   |

### BrowserBox (human-access related)

> `BrowserBox` configuration is not passed as constructor keyword arguments but wrapped in `BrowserBoxOptions`: `BrowserBox(BrowserBoxOptions(browser="firefox"))`. Writing `BrowserBox(browser=...)` directly raises `TypeError`.

`BrowserBoxOptions` fields (all optional; a dataclass):

| Field      | Type                                            | Default                     | Description                    |
| ---------- | ----------------------------------------------- | --------------------------- | ------------------------------ |
| `browser`  | `str` (`"chromium"` / `"firefox"` / `"webkit"`) | `"chromium"`                | Browser type                   |
| `memory`   | `int`                                           | `2048`                      | Memory (MiB)                   |
| `cpu`      | `int`                                           | `2`                         | Number of CPU cores            |
| `port`     | `int \| None`                                   | `None` (resolves to `3000`) | Host -> Playwright Server port |
| `cdp_port` | `int \| None`                                   | `None` (resolves to `9222`) | Host -> CDP port               |

| Method                                  | Returns | Description                                                   |
| --------------------------------------- | ------- | ------------------------------------------------------------- |
| `await playwright_endpoint(timeout=60)` | `str`   | Playwright Server endpoint (all browsers)                     |
| `await endpoint(timeout=60)`            | `str`   | CDP / BiDi direct-connect endpoint (**webkit not supported**) |

> `playwright_endpoint()` and `endpoint()` are **mutually exclusive** modes; a single `BrowserBox` instance uses only one of them.

***

## Troubleshooting

### The browser warns "Your connection is not private" when opening the HTTPS desktop

The HTTPS desktop on `ComputerBox` / `SkillBox` uses a **self-signed certificate**; this is expected. Click "Advanced -> Proceed to localhost", or switch to the HTTP port (`gui_http_port`, default `3000`).

### `http://localhost:3000` will not open / connection refused

* Confirm the script is still running. Once the `async with` block exits, the sandbox is destroyed and the port closes with it. The Quick Example suspends the process with `input(...)` for exactly this reason.
* Confirm `await wait_until_ready()` has returned; the desktop service takes seconds to tens of seconds to start.
* The port is taken by another local program: change `gui_http_port`, for example `ComputerBox(gui_http_port=8080)`.
* `SkillBox` ports are randomly assigned, so do not hardcode `3000`; read the real value from `box.gui_http_port`.

### `SkillBox` raises `ValueError` on startup

The Claude OAuth token is missing. Set the environment variable `CLAUDE_CODE_OAUTH_TOKEN`, or pass `oauth_token="<YOUR_CLAUDE_CODE_OAUTH_TOKEN>"` to the constructor (replace with your real token).

### `BrowserBox` errors when calling `endpoint()` under webkit

CDP direct-connect mode does not support webkit. For webkit, use `playwright_endpoint()` (Playwright Server mode, which supports all browsers) instead.

### The sandbox will not start (no hardware virtualization)

This is an **environment constraint**:

* Hardware virtualization is required. Linux needs KVM (`/dev/kvm` readable/writable, user in the `kvm` group); macOS arm64 uses Apple's Hypervisor.framework (**no `/dev/kvm` needed**); Windows uses WSL2 + KVM.
* The first image pull depends on the network; on failure it raises a standard `RuntimeError` (you can `try/except` and retry), **not** a `BoxliteError` subclass.
* macOS Intel: not supported.

### The third volume element is mistakenly written as a string

If you mount a host directory for the desktop as `volumes=[("/host", "/box", "ro")]`, you get:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
```

In the SDK the third volume element is a **bool `read_only`** (`True` = read-only / `False` = read-write), not the string `"ro"` / `"rw"`. The correct form is `volumes=[("/host/path", "/box/path", True)]`, or a 2-tuple `("/host/path", "/box/path")` (read-write by default).
(Note: only the CLI's `-v` syntax uses the `ro` / `rw` strings; this differs from the SDK, so do not conflate them.)

***

## Related pages

* [Agent Tools](/agent-tools/index): let an AI agent (not a human) operate inside the sandbox.
* [Manage Sandbox](/manage-sandbox/index): lifecycle, runtime methods, the context-manager model.
