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

# Desktop Streaming

> How Box opens a browser-based desktop stream for GUI work.

Every box includes a browser-accessible Linux desktop. Use it when you need to inspect a running app visually, use Chrome, debug Electron apps, or control GUI tools that are awkward over SSH.

## Open the desktop

From your local machine, open an authenticated desktop URL. Integrations can request the same URL through the SDK or API:

<CodeGroup>
  ```bash CLI theme={null}
  box desktop bx_f7k2q9hd
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/desktop?theme=light" \
    -H "Authorization: Bearer $BOX_API_KEY"
  ```

  ```ts TypeScript theme={null}
  const desktop = await box.desktop({ boxId: "bx_f7k2q9hd", theme: "light" });
  console.log(desktop.desktopUrl);
  ```

  ```python Python theme={null}
  desktop = box.desktop("bx_f7k2q9hd", theme="light")
  print(desktop.desktop_url)
  ```
</CodeGroup>

You can also open the desktop from the Box dashboard when the box is running.

<Note>
  Desktop streaming is available only after the box's machine is up. If the box is provisioning, archived, or stopped, start or resume it first.
</Note>

## Streaming modes

The default desktop streams over **Moonlight** (60fps WebRTC) for the smoothest, lowest-latency picture. WebRTC relies on UDP and peer connectivity, so on restrictive, corporate, or low-bandwidth networks it can be choppy or fail to connect.

For those cases, use **VNC**, which tunnels over plain HTTPS and is far more tolerant of poor connections (at a lower frame rate):

<CodeGroup>
  ```bash CLI theme={null}
  box desktop bx_f7k2q9hd --vnc
  box desktop bx_f7k2q9hd --vnc --public
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/desktop?vnc=1" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"publicAccess":true}'
  ```

  ```ts TypeScript theme={null}
  const vnc = await box.desktop({
    boxId: "bx_f7k2q9hd",
    vnc: 1,
    requestBody: { publicAccess: true },
  });
  console.log(vnc.desktopUrl);
  ```

  ```python Python theme={null}
  vnc = box.desktop("bx_f7k2q9hd", vnc=1, request_body={"publicAccess": True})
  print(vnc.desktop_url)
  ```
</CodeGroup>

You don't have to decide up front: the browser viewer shows a **"Switch to VNC"** option while the default stream is connecting or playing, and again if it errors. The dashboard also exposes both **Desktop** and **Desktop via VNC** directly.

<Note>
  The VNC viewer opens as its own top-level page (not embedded), which is required for its connection to authenticate. The first `--vnc` on a box takes a few seconds to prepare; later opens are instant.
</Note>

## Desktop Automation

Boxes also include Lux for desktop and browser automation. Lux is available inside the Box as both:

* a `lux` CLI for driving the visible desktop from shell commands
* a Lux skill that helps agents use the CLI for GUI work

Use Lux when a workflow needs real browser or desktop interaction: logging in through Chrome, checking a visual UI state, testing an Electron app, or producing a demo video.

<CodeGroup>
  ```bash CLI theme={null}
  box ssh bx_f7k2q9hd
  lux start "Open Chrome and test the login flow"
  lux run
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"lux start \"Open Chrome and test the login flow\" && lux run"}'
  ```

  ```ts TypeScript theme={null}
  await box.command({
    boxId: "bx_f7k2q9hd",
    commandRequest: { command: 'lux start "Open Chrome and test the login flow" && lux run' },
  });
  ```

  ```python Python theme={null}
  box.command("bx_f7k2q9hd", CommandRequest(
      command='lux start "Open Chrome and test the login flow" && lux run',
  ))
  ```
</CodeGroup>

`lux run` drives the active Lux session to completion and records a demo video under:

```text theme={null}
/home/user/lux-demos/
```

To get a recording out of the box, download it, or serve the folder and share the token-gated link:

<CodeGroup>
  ```bash CLI theme={null}
  box scp bx_f7k2q9hd:/home/user/lux-demos/<video>.mp4 ./demo.mp4

  # or share a link straight from the box:
  box ssh bx_f7k2q9hd "cd ~/lux-demos && (python3 -m http.server 8090 --bind 0.0.0.0 &) && host 8090 --private"
  ```

  ```bash curl theme={null}
  curl -L -o demo.mp4 "$BOX_API_BASE/boxes/bx_f7k2q9hd/artifacts?path=lux-demos/<video>.mp4" \
    -H "Authorization: Bearer $BOX_API_KEY"
  ```

  ```ts TypeScript theme={null}
  const video = await box.artifact({ boxId: "bx_f7k2q9hd", path: "lux-demos/<video>.mp4" });
  ```

  ```python Python theme={null}
  video = box.artifact("bx_f7k2q9hd", "lux-demos/<video>.mp4")
  ```
</CodeGroup>

The common commands are:

| Command                 | Use                                                           |
| ----------------------- | ------------------------------------------------------------- |
| `lux start "<task>"`    | Start a desktop automation session.                           |
| `lux run`               | Run the session until complete. Records a demo automatically. |
| `lux run --max-steps N` | Raise the step budget for longer flows.                       |
| `lux status`            | Show Lux usage, limits, and active recorder state.            |

Lux controls the Box's single shared desktop, so run only one Lux session at a time. Wait for the active `lux run` to finish before starting another session.

### Lux quotas

Lux has its own quota, separate from your plan's machine time: **20 sessions per day per account**, shared across every box you own, including no-env boxes you hand to your users. The quota is the same on every plan and resets daily.

Each session also has a step budget. Lux picks it from the task (20 steps for quick actions, 100 for thorough flows); raise it with `lux run --max-steps N`. `lux status` shows today's usage.

You can combine Lux with normal desktop tools. For example, launch Chrome or focus a window with shell commands, then let Lux interact with the UI:

<CodeGroup>
  ```bash CLI theme={null}
  DISPLAY=:0 google-chrome-stable "https://example.com"
  wmctrl -a "Chrome"
  lux start "Complete the checkout flow in the open browser"
  lux run
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"DISPLAY=:0 google-chrome-stable \"https://example.com\" && wmctrl -a Chrome && lux start \"Complete the checkout flow in the open browser\" && lux run"}'
  ```

  ```ts TypeScript theme={null}
  await box.command({
    boxId: "bx_f7k2q9hd",
    commandRequest: {
      command: 'DISPLAY=:0 google-chrome-stable "https://example.com" && wmctrl -a Chrome && lux start "Complete the checkout flow in the open browser" && lux run',
    },
  });
  ```

  ```python Python theme={null}
  box.command("bx_f7k2q9hd", CommandRequest(
      command='DISPLAY=:0 google-chrome-stable "https://example.com" && wmctrl -a Chrome && lux start "Complete the checkout flow in the open browser" && lux run',
  ))
  ```
</CodeGroup>

## What happens when you open it

The CLI or dashboard asks Box for a fresh authenticated desktop URL for your box. If the desktop stream is not ready yet, Box prepares it and then opens the browser viewer.

The returned URL opens a browser page. For the default Moonlight stream it looks like this:

```text theme={null}
https://<box-desktop-host>/stream.html?hostId=<host>&appId=<app>&token=<token>&theme=light
```

For a VNC stream (`--vnc`) it points at a noVNC page instead:

```text theme={null}
https://<box-vnc-host>/vnc.html?autoconnect=true&password=<pw>&_token=<token>
```

If you request `--public` or send `publicAccess: true` to `POST /boxes/{boxId}/desktop?vnc=1`, the noVNC URL does not include `_token`. The exact host, IDs, password, and token fields are generated for the running box. Treat desktop URLs as sensitive because they can let the browser attach to that desktop session.

## Security model

Desktop URLs are generated through the authenticated Box API. The URL contains a desktop access token, so do not paste the full URL into shared chats or logs. If you need to give someone else access to a box, use the intended Box account and access controls rather than sharing a desktop URL.

Stopping or archiving a box makes the desktop unavailable until the box is resumed.

Do not rely on desktop processes surviving resume or fork. After a resume or fork, reopen Chrome, restart your app or dev server, and start a new Lux session if you need automation.

## Related

* [SSH Access](/box/ssh-access)
* [Long-Running Tasks](/box/long-running-tasks)
* [Hosting](/box/hosting)
* [Machine Capabilities](/box/machines)
