---
name: headless-deskman
description: "The Headless Deskman — turn a headless Adom container into a browser-accessible desktop. Use when a container has no display and you need to SEE a GUI app (KiCad, Fusion, a browser, any X11 program) running inside it, or when X11 forwarding over ssh -Y fails. Runs Xvfb+fluxbox+x11vnc+noVNC inside the container and maps a *.adom.cloud port so any browser (or a Hydrogen webview tab) shows the container's GUI. Trigger words: headless desktop, container desktop, headless deskman, deskman, headview (old name), view GUI from container, noVNC, run a GUI app in the container, remote desktop for container, browser desktop, X server in container, see kicad/gui in browser."
---

# The Headless Deskman

`headless-deskman` gives a **headless** Adom container a real desktop you can open
in a browser. It starts a virtual X display in the container, scrapes it with VNC,
bridges that to WebSocket with noVNC, and maps a `*.adom.cloud` port so
**Cloudflare serves the page — the container hosts it itself**, no SSH tunnel and
no local VNC viewer. Open the URL anywhere, or drop it into a Hydrogen webview tab.

> Renamed in 0.2.0: this was `headview`. The command is now `headless-deskman`; the
> old binary is removed on install, and a desktop started under the old name is
> picked up automatically (state migrates from `~/.config/headview`).

Why not `ssh -Y`? X11 forwarding does **not** work through the `adom.cloud` SSH
gateway (the gateway relays the connection, so the X11 channel never reaches the
container). The Headless Deskman sidesteps that entirely by serving over
HTTP/WebSocket, which the gateway/Cloudflare pass fine.

## Stack

`Xvfb (:99)` → `fluxbox` → `x11vnc (localhost:5900)` → `noVNC/websockify (:6080)` → port-mapping → `https://<prefix>-*.adom.cloud/vnc.html`

Rendering is **software only** (Xvfb has no GPU) — great for 2D GUIs, not for
OpenGL/3D views.

## Commands

### `headless-deskman up`
Install deps if missing, start the desktop, map the port, print the URL + password. Idempotent — safe to re-run.

| Flag | Default | Description |
|------|---------|-------------|
| `--display :N` | `:99` | X display number |
| `--geometry WxH` | `1600x1000` | screen size |
| `--vnc-port N` | `5900` | internal VNC port (loopback only) |
| `--web-port N` | `6080` | internal noVNC port that gets mapped |
| `--prefix NAME` | `desktop` | subdomain prefix → `NAME-<rand>.adom.cloud` |
| `--app "CMD"` | — | launch CMD on the desktop after starting |
| `--no-password` | — | passwordless (only behind other access control) |

```bash
headless-deskman up                              # bare desktop
headless-deskman up --app "kicad"                # desktop with KiCad already open
headless-deskman up --geometry 1920x1080
```

Set `DESKMAN_PASSWORD` to pin the VNC password instead of generating one.

### `headless-deskman open`
Open the running desktop as a Hydrogen webview tab named "Container Desktop".
Must be run from a **Hydrogen workspace terminal** (needs `VSCODE_PROXY_URI`).
From anywhere else, use `headless-deskman url` and open it in a browser.

### `headless-deskman status` / `url` / `password`
Inspect state: what's running, the URL, and the VNC password.

### `headless-deskman down`
Stop Xvfb/fluxbox/x11vnc/noVNC and **remove the port mapping**. Add `--keep-mapping` to leave the mapping in place.

### `headless-deskman install`
Install/refresh this skill from the wiki into `~/.claude/skills/headless-deskman/`.

## Launching apps onto the desktop
Anything you run with `DISPLAY=:99` appears in the browser window:
```bash
DISPLAY=:99 pcbnew &
DISPLAY=:99 xterm &
```

## Notes
- Run `headless-deskman` **inside the target container** — it auto-detects the
  container slug via `adom-cli carbon containers current` and maps its own port.
- The VNC server binds `localhost` only; the public face is the noVNC HTTPS URL,
  protected by the generated VNC password (shown by `up`/`password`).
- WebSocket idle: Cloudflare can drop very idle WS connections; noVNC reconnects,
  and any desktop activity keeps it warm.
