CuaBot provides an isolated Ubuntu desktop for manually testing aven without interacting with the host terminal, database, cache, or installed executable. It runs an ARM64 Linux container under OrbStack and streams application windows to macOS through Xpra.
Use this workflow for TUI runtime verification, screenshots, destructive update fixtures, and Linux-specific behavior.
The local stack has four layers:
- OrbStack provides the Docker daemon.
trycua/cuabot:latestprovides an Ubuntu desktop container.- Xpra streams individual sandbox windows to macOS.
- The CuaBot CLI sends keystrokes, clicks, shell commands, and screenshot requests through the CuaBot server.
The sandbox session name determines its server state and container name. A
session named aven-update uses the container
cuabot-xpra-aven-update.
CuaBot recreates its named container when a server starts. Installing packages interactively inside a running container therefore lasts only until that container is recreated.
The host uses the shared cua-sandbox:latest image to persist Kitty, terminal
fonts, and other desktop dependencies. The global cua-sandbox command tags that
image as CuaBot's expected trycua/cuabot:latest before every launch. CuaBot can
then recreate the container without losing the provisioned tools.
State has three lifetimes:
- Xpra, OrbStack, Playwright browsers, and npm caches persist on the host.
- Kitty, fonts, and apt packages persist in
cua-sandbox:latest. - Test binaries, databases, caches, and fixtures live in the named container and are disposable.
Rebuild the shared image only when ~/.config/cua-sandbox/Dockerfile changes or
the image is removed. Recreate test data after a CuaBot container replacement.
The shared image is global, but every agent gets a distinct named session. A session owns its server process, port files, Docker container, Xpra window, test data, and input stream. Two agents must not send input to the same session at the same time.
Choose a descriptive unique name such as aven-auto-update-a1 or
aven-header-review-b2. The wrapper requires a session name and refuses to
start a second server when that name is active.
Discover existing sessions before starting work:
scripts/cua-sandbox listThe listing reports the session name, whether its server PID is active, its host
port, and container state. An active session belongs to its creator. Treat it as
unavailable unless the creator or user explicitly hands it off. Starting a fresh
session is cheap because all sessions share cua-sandbox:latest and the host
Playwright cache.
For an explicit handoff, the receiving agent uses the existing name with
status, cua, and container. It does not call start again:
scripts/cua-sandbox status aven-auto-update-a1
scripts/cua-sandbox cua aven-auto-update-a1 --screenshot /tmp/handoff.jpg
scripts/cua-sandbox container aven-auto-update-a1Stop a session when its owner finishes. This removes ambiguity for later agents and releases its container resources.
Install Xpra:
brew install --cask xpra
xattr -cr /Applications/Xpra.appStart OrbStack through Cua Driver and wait for Docker:
cua-driver launch_app '{"bundle_id":"dev.kdrag0n.MacVirt","name":"OrbStack"}'
until docker info >/dev/null 2>&1; do sleep 2; doneProvision the global host CLI, matching Playwright browser, CuaBot settings, and shared image:
cua-sandbox setupThe setup command stores host-side CLI packages under
~/.local/state/cua-sandbox. It merges the required CuaBot settings without
replacing existing preferences.
The command gives Xpra a shared configuration from
~/.config/cua-sandbox/xpra. Host tray and global window menus are disabled
because the
sandbox is controlled through its streamed application windows. This also
avoids GTK's macOS menu integration in Xpra 6.5.1, which can abort while
constructing native submenus on recent macOS releases.
Avoid invoking CuaBot repeatedly through npx. Each invocation performs package
resolution and is noticeably slower. bunx cuabot can produce an incomplete
Sharp installation on Darwin ARM64 and fail before reaching the server.
Run the server in a long-lived terminal or background process:
just cua-sandbox-start aven-auto-update-a1The first run downloads the desktop image. Verify readiness from another shell:
scripts/cua-sandbox status aven-auto-update-a1
scripts/cua-sandbox cua aven-auto-update-a1 \
--screenshot /tmp/cuabot-ready.jpgUseful container checks:
container="$(scripts/cua-sandbox container aven-auto-update-a1)"
docker ps --filter "name=$container"
docker exec "$container" uname -mThe sandbox is ARM64 Linux. Build in a matching Rust container rather than copying the macOS binary:
rm -rf /tmp/aven-linux-build
mkdir /tmp/aven-linux-build
git archive HEAD | tar -x -C /tmp/aven-linux-build
docker run --rm \
-v /tmp/aven-linux-build:/src \
-w /src \
rust:1.96-bookworm \
cargo buildgit archive HEAD builds committed code. Copy modified source files into
/tmp/aven-linux-build before building when verifying uncommitted changes.
Copy the result into a user-owned location. docker cp creates a file whose
ownership may not be writable by the sandbox user, so copy it once more inside
the container:
container="$(scripts/cua-sandbox container aven-auto-update-a1)"
docker cp /tmp/aven-linux-build/target/debug/aven \
"$container:/tmp/aven-built"
docker exec -u user "$container" sh -lc '
mkdir -p /home/user/aven-run
cp /tmp/aven-built /home/user/aven-run/aven
chmod 755 /home/user/aven-run/aven
'Populate a session from the branch's curated demo dataset after deploying the branch's aven binary and before launching the TUI:
just cua-sandbox-db aven-auto-update-a1The bootstrap command asks the deployed branch binary to materialize the same
curated dataset as aven demo. The branch binary must successfully open the
result before it is atomically installed as /home/user/aven-run/aven.db.
Each session receives its own disposable database.
An existing sandbox database is preserved by default. Replace it explicitly when a fresh snapshot is required:
scripts/cua-sandbox db-bootstrap aven-auto-update-a1 --forceUse another source path when needed. Host database sources use SQLite's online
backup operation, run PRAGMA quick_check, and omit migration records that do
not exist in the branch from the disposable snapshot. The source database
remains unchanged.
scripts/cua-sandbox db-bootstrap aven-auto-update-a1 \
"$HOME/.local/state/aven/db.sqlite" --forceAVEN_SANDBOX_DB changes the default source. Database replacement is refused
while an aven tui process is running in the session.
The derived image includes Kitty, JetBrains Mono, Powerline symbols, and the JetBrains Mono Nerd Font. The base image's xterm remains available for diagnostics, but it does not render aven's colors and glyphs reliably.
Launch aven with isolated database and cache paths:
container="$(scripts/cua-sandbox container aven-auto-update-a1)"
docker exec -d -u user -e DISPLAY=:100 "$container" \
kitty \
--title 'Aven Verification' \
--override 'font_family=JetBrainsMono Nerd Font Mono' \
--override font_size=11 \
--override background=#0b0f14 \
--override foreground=#d8dee9 \
--override cursor=#88c0d0 \
--override selection_background=#434c5e \
--override remember_window_size=no \
--override initial_window_width=1200 \
--override initial_window_height=650 \
sh -lc '
cd /home/user/aven-run
XDG_CACHE_HOME=/home/user/aven-run/cache \
AVEN_DB=/home/user/aven-run/aven.db \
AVEN_NO_UPDATE_CHECK=1 \
./aven tui
'AVEN_NO_UPDATE_CHECK=1 suppresses only the startup check. Explicit :update
checks still run.
Inspect screenshots directly with the Read tool. Use the model's image understanding to evaluate layout, spacing, colors, focus styling, clipping, alignment, visual hierarchy, and overall appearance. Do not use OCR tools such as Tesseract as a substitute for direct image inspection. OCR is suitable only as a supplemental check when exact text extraction is useful.
CuaBot key names follow Playwright naming. Use Enter and Escape, not uppercase
ENTER or ESC.
scripts/cua-sandbox cua aven-auto-update-a1 --type ':update'
scripts/cua-sandbox cua aven-auto-update-a1 --key Enter
scripts/cua-sandbox cua aven-auto-update-a1 \
--screenshot /tmp/01-checking.jpg
# Confirmation dialogs use y and n.
scripts/cua-sandbox cua aven-auto-update-a1 --type 'y'
scripts/cua-sandbox cua aven-auto-update-a1 \
--screenshot /tmp/02-progress.jpg
# Cancellation during a cancellable phase.
scripts/cua-sandbox cua aven-auto-update-a1 --key Escape
scripts/cua-sandbox cua aven-auto-update-a1 \
--screenshot /tmp/03-cancelled.jpgAllow enough time for each asynchronous state before sending the next key. A key sent while the checking overlay is active does not carry into the later confirmation dialog.
Keep update tests disposable:
- Place the test executable under
/home/user/aven-run/aven. - Use
XDG_CACHE_HOME=/home/user/aven-run/cache. - Seed release metadata at
/home/user/aven-run/cache/aven/update.json. - Serve archives from inside the container on
127.0.0.1. - Use version
99.0.0so it is newer than development builds. - Use the platform artifact name
aven-linux-arm64.tar.gz. - Package exactly one regular root file named
aven. - Generate the checksum with the archive filename:
sha256sum aven-linux-arm64.tar.gz > aven-linux-arm64.sha256.
A throttled local server keeps the download overlay visible long enough to
capture and cancel. The release and checksum URLs in the seeded cache can point
to http://127.0.0.1:<port>/.... Fetched GitHub release metadata has stricter
URL validation, but cached fixture metadata is suitable for isolated UI tests.
Set an unreachable HTTPS proxy when the explicit live check should fail into the cached fallback while localhost remains reachable:
HTTPS_PROXY=http://10.255.255.1:9
NO_PROXY=127.0.0.1Capture the application state before changing the fixture:
scripts/cua-sandbox cua aven-auto-update-a1 \
--screenshot /tmp/aven-failure.jpg
container="$(scripts/cua-sandbox container aven-auto-update-a1)"
docker exec "$container" sh -lc '
grep GET /tmp/aven-http.log || true
/home/user/aven-run/aven --version
'The sandbox exposed a Linux-specific updater failure that macOS did not:
executing an open NamedTempFile returned Text file busy (os error 26).
Runtime verification across both operating systems is valuable for installer
changes.
Stop the named CuaBot server when the desktop is idle:
just cua-sandbox-stop aven-auto-update-a1The shared image remains available, so the next container has Kitty and fonts without another apt or font installation. Remove disposable container state when needed:
container="$(scripts/cua-sandbox container aven-auto-update-a1)"
docker rm -f "$container" 2>/dev/null || trueRemove cua-sandbox:latest only when a full image rebuild is desired. OrbStack,
Xpra, Playwright browsers, and npm caches can remain installed for later
verification sessions.