GitHub - trycua/cua: Open-source infrastructure for Computer-Use Agents. Sandboxes, SDKs, and benchmarks to train and evaluate AI agents that can control full d
GitHub - trycua/cua: Open-source infrastructure for Computer-Use Agents. Sandboxes, SDKs, and benchmarks to train and evaluate AI agents that can control full desktops (macOS, Linux, Windows). · GitHub
Navigation Menu
Toggle navigation
[](https://github.com/)
Appearance settings
* Platform
* AI CODE CREATION
- GitHub Copilot Write better code with AI
- GitHub Spark Build and deploy intelligent apps
- GitHub Models Manage and compare prompts
- MCP Registry New Integrate external tools
* DEVELOPER WORKFLOWS
- Actions Automate any workflow
- Codespaces Instant dev environments
- Code Review Manage code changes
* APPLICATION SECURITY
- GitHub Advanced Security Find and fix vulnerabilities
- Code security Secure your code as you build
- Secret protection Stop leaks before they start
* EXPLORE
- Blog
* Solutions
* BY COMPANY SIZE
- Startups
* BY USE CASE
- DevOps
- CI/CD
* BY INDUSTRY
* Resources
* EXPLORE BY TOPIC
- AI
- DevOps
- Security
* EXPLORE BY TYPE
* SUPPORT & SERVICES
- Partners
* Open Source
* COMMUNITY
- GitHub Sponsors Fund open source developers
* PROGRAMS
* REPOSITORIES
- Topics
- Trending
* Enterprise
* ENTERPRISE SOLUTIONS
- Enterprise platform AI-powered developer platform
* AVAILABLE ADD-ONS
- GitHub Advanced Security Enterprise-grade security features
- Copilot for Business Enterprise-grade AI features
- Premium Support Enterprise-grade 24/7 support
- Pricing
Search or jump to...
Search code, repositories, users, issues, pull requests...
Search
Clear
Provide feedback
We read every piece of feedback, and take your input very seriously.
- [x] Include my email address so I can be contacted
Cancel Submit feedback
Saved searches
Use saved searches to filter your results more quickly
Name
Query
To see all available qualifiers, see our documentation.
Cancel Create saved search
Appearance settings
Resetting focus
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
- Sponsor
- NotificationsYou must be signed in to change notification settings
- Fork 932
- Code
- Actions
- Projects
- Insights
Additional navigation options
- Code
- Issues
- Actions
- Projects
- Insights
[](https://github.com/trycua/cua)
trycua/cua
main
[](https://github.com/trycua/cua/branches)[](https://github.com/trycua/cua/tags)
Go to file
Code
Open more actions menu
Folders and files
| Name | Name | Last commit message | Last commit date | | --- | --- | --- | --- |
| ## Latest commit !Image 2: f-trycua!Image 3: claude f-trycua and claude docs(cua-driver): add integrations page and GitHub Copilot CLI MCP co… Open commit details success Apr 27, 2026 2304df1·Apr 27, 2026 ## History 3,199 Commits Open commit details [](https://github.com/trycua/cua/commits/main/)3,199 Commits |
| [.github](https://github.com/trycua/cua/tree/main/.github ".github") | [.github](https://github.com/trycua/cua/tree/main/.github ".github") | [ci: wire cua-driver into release-bump-version workflow (](https://github.com/trycua/cua/commit/212e1e895f3fcdb93b1392e6ad94b698a398a6be "ci: wire cua-driver into release-bump-version workflow (#1364) Adds cua-driver as a selectable service in the CD: Bump Version workflow, matching how lume is wired. Running the workflow with `service=cua-driver` now: 1. Bumps the version string in `libs/cua-driver/Sources/CuaDriverCore/CuaDriverCore.swift` (the single authoritative source read by TelemetryClient et al.) via the new `libs/cua-driver/.bumpversion.cfg`. 2. Creates and pushes a `cua-driver-v{new_version}` tag. 3. The existing `cd-swift-cua-driver.yml` workflow picks up the tag and runs the codesign + notarize + GitHub-release flow. Current version is 0.0.1; first bump should be `minor` to reach 0.1.0 to match the public v0.1 release. Co-authored-by: Claude <noreply@anthropic.com>")#1364[)](https://github.com/trycua/cua/commit/212e1e895f3fcdb93b1392e6ad94b698a398a6be "ci: wire cua-driver into release-bump-version workflow (#1364) Adds cua-driver as a selectable service in the CD: Bump Version workflow, matching how lume is wired. Running the workflow with `service=cua-driver` now: 1. Bumps the version string in `libs/cua-driver/Sources/CuaDriverCore/CuaDriverCore.swift` (the single authoritative source read by TelemetryClient et al.) via the new `libs/cua-driver/.bumpversion.cfg`. 2. Creates and pushes a `cua-driver-v{new_version}` tag. 3. The existing `cd-swift-cua-driver.yml` workflow picks up the tag and runs the codesign + notarize + GitHub-release flow. Current version is 0.0.1; first bump should be `minor` to reach 0.1.0 to match the public v0.1 release. Co-authored-by: Claude <noreply@anthropic.com>") | Apr 23, 2026 |
| [.vscode](https://github.com/trycua/cua/tree/main/.vscode ".vscode") | [.vscode](https://github.com/trycua/cua/tree/main/.vscode ".vscode") | [Merge pull request](https://github.com/trycua/cua/commit/38355bc1ea2aae88260348a527d8c924be7ef42c "Merge pull request #520 from skools-here/windows-compatibility-for-development-setup Make VS Code Python interpreter path cross-platform")#520[from skools-here/windows-compatibility-for-de…](https://github.com/trycua/cua/commit/38355bc1ea2aae88260348a527d8c924be7ef42c "Merge pull request #520 from skools-here/windows-compatibility-for-development-setup Make VS Code Python interpreter path cross-platform") | Oct 30, 2025 |
| [blog](https://github.com/trycua/cua/tree/main/blog "blog") | [blog](https://github.com/trycua/cua/tree/main/blog "blog") | [docs: fix several typos across blog and documentation (](https://github.com/trycua/cua/commit/2ca5c0e3098820f66d511f73dae43ddbb79d17a7 "docs: fix several typos across blog and documentation (#1406) * docs: fix typo in growth hacking blog * docs: fix typo in macOS window internals blog * docs: fix typo in lume changelog * docs: fix typo in winarena development tips")#1406[)](https://github.com/trycua/cua/commit/2ca5c0e3098820f66d511f73dae43ddbb79d17a7 "docs: fix several typos across blog and documentation (#1406) * docs: fix typo in growth hacking blog * docs: fix typo in macOS window internals blog * docs: fix typo in lume changelog * docs: fix typo in winarena development tips") | Apr 27, 2026 |
| [changelog](https://github.com/trycua/cua/tree/main/changelog "changelog") | [changelog](https://github.com/trycua/cua/tree/main/changelog "changelog") | [docs: add weekly changelog for March 10-14, 2026 (](https://github.com/trycua/cua/commit/5464de7870cfde842612e6ff1bf65f9bbc0298d9 "docs: add weekly changelog for March 10-14, 2026 (#1176) * docs: add weekly changelog for March 10-14, 2026 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: simplify changelog to highlights-only format Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: move changelog to per-week files under changelog/ Replaces monolithic CHANGELOG.md with individual weekly entries in changelog/ directory, matching the format cloud expects for syncing to cua.ai/changelog. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: remove redundant CHANGELOG.md Changelog lives in changelog/ directory now. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>")#1176[)](https://github.com/trycua/cua/commit/5464de7870cfde842612e6ff1bf65f9bbc0298d9 "docs: add weekly changelog for March 10-14, 2026 (#1176) * docs: add weekly changelog for March 10-14, 2026 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: simplify changelog to highlights-only format Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: move changelog to per-week files under changelog/ Replaces monolithic CHANGELOG.md with individual weekly entries in changelog/ directory, matching the format cloud expects for syncing to cua.ai/changelog. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: remove redundant CHANGELOG.md Changelog lives in changelog/ directory now. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>") | Mar 17, 2026 |
| [demo](https://github.com/trycua/cua/tree/main/demo "demo") | [demo](https://github.com/trycua/cua/tree/main/demo "demo") | [SDK integration tests, auto-generated docs, and snapshot support (](https://github.com/trycua/cua/commit/d7a2452fdf314c0b5faed84069d4d261cd26748f "SDK integration tests, auto-generated docs, and snapshot support (#1242) * feat: add RuntimeSupport compat check + re-export runtime/interface types from meta pkg - New `cua_sandbox/runtime/compat.py` with `check_local_support(image) -> RuntimeSupport` that detects whether the image's runtime is installed/auto-installable and whether hardware acceleration (HVF, KVM, Hyper-V) is available for the host/guest OS+arch combo - `Image.local_support()` method delegates to check_local_support (lazy import, no circulars) - `skip_if_unsupported(image)` pytest helper replaces all hardcoded skipif/env-var gates - `cua_sandbox/__init__` exports RuntimeSupport, check_local_support, skip_if_unsupported - `cua` meta package: eager compat re-export, lazy __getattr__ for all runtime classes (DockerRuntime, QEMURuntime, LumeRuntime, AndroidEmulatorRuntime, HyperVRuntime, RuntimeInfo) and interface types (Shell, CommandResult, Mouse, Keyboard, Screen, Clipboard, Tunnel, TunnelInfo, Mobile, Terminal, Window) — no import-time overhead - `cua/pyproject.toml`: add uv.sources pointing to local editable siblings - Updated test_android_multitouch.py to use skip_if_unsupported instead of manual checks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(compat): correct hw_accel for x86_64 guests — HVF on Intel Mac, TCG on Apple Silicon QEMU supports HVF (-accel hvf) for x86_64 guests on Intel Macs. On Apple Silicon, x86_64 guests must use TCG software emulation (no cross-arch HVF). - Replace _has_hvf() with _has_hvf_for_arm64_guest / _has_hvf_for_x86_guest - Add _x86_guest_hw_accel() covering KVM (Linux), HVF (Intel Mac), Hyper-V (Windows) - Linux VM and Windows VM sections now use _x86_guest_hw_accel() - Android: Intel Mac gets HVF for x86_64 Android system images - Apple Silicon correctly reported as software-only for Windows/Linux VMs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: integration tests for all interfaces & image builder methods - tests/test_interfaces.py — 117 tests covering every sb.* interface method (shell, clipboard, screen, mouse, keyboard, tunnel, terminal, window, mobile) across Linux container, Linux VM, macOS VM, Windows VM, Android VM - tests/test_image_builder.py — 37 tests covering every Image builder method (apt/brew/choco/winget/apk/pwa/pip/uv install, run, env, copy, expose, from_registry) across all guest OS types - tests/pytest.ini — add pythonpath so `from cua import ...` resolves - scripts/gen_interface_docs.py — auto-generate interfaces.mdx from pydocstrings - docs/.../interfaces.mdx — regenerated from source (replaces AI-generated copy) All tests use local=True + skip_if_unsupported(image) — no hardcoded skips. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(compat): probe common docker install paths for stripped SSH PATH SSH sessions on macOS often have a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin) that omits /usr/local/bin where OrbStack/Docker Desktop install the docker CLI. _has_docker() now falls back through common install locations so Linux tests don't incorrectly skip on machines where Docker is installed but not in PATH. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(compat): delegate all runtime detection to canonical runtime modules Instead of reimplementing binary/path detection in compat.py (which diverged from the actual runtime logic), each _has_*() probe now calls into the runtime module that owns that detection: _has_docker() → docker._has_docker() (moved multi-path logic here) _has_lume() → lume._has_lume() _has_qemu() → qemu_installer.qemu_bin() (now finds Homebrew/MacPorts/cached) _has_android_sdk()→ android_emulator._sdk_path() _has_java() → android_emulator._java_env() _has_hyperv() → hyperv._has_hyperv() Also promotes docker._has_docker() to use the same multi-path probe that compat.py introduced, so the runtime itself finds Docker in stripped SSH PATH environments (e.g. OrbStack on macOS via SSH). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): resolve docker binary path to handle stripped SSH PATH All subprocess calls in DockerRuntime now use _docker_bin() which probes common install locations (/usr/local/bin, /opt/homebrew/bin, etc.) so the runtime works in SSH sessions where PATH is stripped (e.g. cua.local). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): apply image layers via computer-server after container starts DockerRuntime.start() was missing layer execution — apt_install, pip_install, run, env, etc. layers were stored in image._layers but never applied for Docker containers (unlike VMs where create_session_disk() handles this). Now uses LayerExecutor to apply layers via the running computer-server API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(executor): use correct /cmd API format and return_code field name The computer-server /cmd endpoint expects params as {\"params\": {\"command\": ...}} not top-level \"command_args\". Also fix result parsing to check \"return_code\" (server field name) before \"returncode\", and use \"success\" flag for failure detection when return_code is absent (e.g. on handler exceptions). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(executor): use sudo for apt-get in Linux containers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(executor,docker): comprehensive layer support for Linux containers - executor: fix run/env layers to use sudo for system access - executor: fix uv_install for Linux (uv pip install --system) - executor: fix pip_install for Linux (--break-system-packages) - executor: implement copy layer via computer-server write_bytes - executor: implement expose layer as no-op (handled at docker run) - executor: add os_type param to select correct commands per platform - docker: handle image._env and image._files after container starts - docker: map image._ports as additional -p flags at docker run time Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): pass image env vars as -e flags to docker run Environment variables from image.env() are now passed as -e KEY=VAL at docker run time, ensuring they're available in subprocess shells. Previously they were written to /etc/environment post-start which doesn't work since subprocesses don't source that file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests,docker,executor): fix ordering, env-in-run, expose port, from_registry - docker: apply _files before _layers so copy → run(chmod) ordering works - docker: write env vars to /etc/profile.d/cua-env.sh for sudo access - docker: fix expose port range (port to port+1000 not 8000-9000) - executor: source /etc/profile.d/cua-env.sh in run layer bash invocation - tests: fix from_registry tests to use cua image (needs computer-server) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(executor,docker,tests): fix copy root paths, env profile writing, expose test - executor: write copy files to /tmp first then sudo mv for root-owned paths - docker: write env profile via sudo tee (direct write_bytes can't write to /etc/profile.d) - tests: simplify expose test — just verify container starts, not socket internals Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lume): use fresh httpx client for pull/start to avoid empty body Lume's Swift HTTP server closes the TCP connection after sending a 4xx response (e.g. VM-not-found on GET /lume/vms/{name}). When httpx reuses that connection for the subsequent POST /lume/pull/start, the body arrives empty at the server, causing 'Invalid request body' 400 errors. Fix: open a fresh AsyncClient for the pull POST instead of reusing the client that made the initial VM-status GET. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Apply image layers on macOS VM after startup via LayerExecutor LumeRuntime.start() was returning without applying _layers, _env, or _files — unlike DockerRuntime which runs LayerExecutor after is_ready(). Add _apply_image_layers() helper and call it in both start() return paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix ephemeral lume VM cleanup and macOS sudo in run layers - sandbox.destroy(): call runtime.delete() instead of stop() for ephemeral sandboxes, so lume VMs are permanently removed (not just stopped) - executor._exec_run: don't use sudo on macOS/Android VMs — macOS VMs require a password for sudo; only Linux containers have passwordless sudo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Use 'echo lume | sudo -S' for macOS VM run layers and env profile macOS lume VMs have the default password 'lume'. Pipe it to sudo -S so run layers and env var setup can write to system paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Retry pull/start on 'Invalid request body' 400 from Lume Lume's custom HTTP server uses minimumIncompleteLength=1, so on a fresh TCP connection the request body can arrive after the first receive() returns, causing a spurious 400. Add _pull_start_with_retry() that retries up to 3 times with a 1s pause between attempts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix macOS env var propagation: use ~/.zshenv + launchctl setenv + restart Writing to /etc/profile.d/cua-env.sh is only sourced by login shells on macOS. Instead write to ~/.zshenv (sourced by all zsh invocations) and set via launchctl setenv, then restart computer-server so it inherits the new environment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix macOS env vars: inject into launchd plist + reload service computer-server's launchd plist has an explicit EnvironmentVariables dict, so launchctl setenv is ignored. Use PlistBuddy to add/set vars directly in the plist, then unload/load the service to pick them up. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Reload computer-server via lume ssh (host-side) for macOS env vars Running launchctl unload from inside computer-server kills it before the load step runs. Use lume ssh from the host instead — it runs outside the VM so the unload+load sequence completes successfully. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Use bootout/bootstrap for macOS launchd service reload launchctl unload/load doesn't work correctly for Aqua session agents from non-GUI SSH sessions. Use bootout/bootstrap which properly handles the GUI session context (gui/<uid>). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix sudo usage in _exec_copy and _exec_env for macOS macOS VMs need 'echo lume | sudo -S' for passwordless sudo. Apply this to mkdir/mv in copy layers and to /etc/environment writes in env layers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(runtime): pull-once + clone-many for Lume macOS VMs Instead of pulling (decompressing ~30GB) for every ephemeral VM, pull once into a stopped golden base VM and then APFS-clone it for each use. Clone is instant via clonefile(2); subsequent test runs skip the ~155s decompression step entirely. Also adds CheckpointInfo dataclass and checkpoint/fork/ensure_base primitives to the Runtime base class, forming the foundation for a user-facing snapshot/fork API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lume): replace custom HTTP server with NIOHTTP1 The custom NWConnection server read with minimumIncompleteLength=1, causing 'Invalid request body' whenever TCP delivered the POST body in a second segment (common after several connections). Replace with ServerBootstrap + NIOHTTP1 pipeline, which handles Content-Length / chunked reassembly correctly before dispatching to route handlers. No more spurious 400s. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lume): fix Swift 6 data race in NIO handler; bump swift-sdk to 0.12+ - Bridge ChannelHandlerContext back via EventLoopPromise so it never crosses actor boundaries (fixes 'sending ctx risks data races') - Remove @MainActor from Server (handlers create LumeController locally) - Bump swift-sdk from 0.10.0 to 0.12.0 which fixes the data race in NetworkTransport that broke clean builds under Swift 6.3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): persist image env vars and source in adb shell transport - In _apply_layers, write image._env to /data/local/tmp/.cua_env on the device so env vars survive across independent adb shell invocations - Prefix every adb transport shell command with `. /data/local/tmp/.cua_env 2>/dev/null` - Update test_apk_install to use F-Droid APK URL instead of skipping Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): use adb push for env file, add pwa_install test - Fix env var quoting by writing to a local tempfile and pushing via `adb push` instead of shell string escaping - test_pwa_install now uses android-example-gym-pwa-app manifest URL instead of requiring ANDROID_TEST_PWA_URL env var Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): source env in grpc transport, fix node PATH for pwa_install - Source .cua_env in GRPCEmulatorTransport shell commands (this is the actual transport used by ephemeral Android sandboxes, not ADBTransport) - Augment PATH with /opt/homebrew/bin so node/npm are found when the process is launched without a login shell - test_pwa_install downloads keystore from android-example-gym-pwa-app repo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): use || true to handle missing .cua_env in mksh Android's mksh exits on source of nonexistent file even with 2>/dev/null. Using || true ensures subsequent commands always run. Also fix second shutil.which(\"node\") call to use augmented_path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): guard .cua_env source with [ -f ] check In Android's mksh, sourcing a nonexistent file causes the shell to exit immediately — || true cannot recover because the shell process is gone. Use [ -f file ] && . file to safely skip sourcing when the file doesn't exist. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): pass augmented env to _bw_init.js and bubblewrap subprocess calls Without env=env, subprocess.run inherits the process env which lacks /opt/homebrew/bin. Node's execSync('npm root -g') then fails since npm is not on /bin/sh's PATH. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): sanitize PWA package name to valid Java identifier Hyphens and other non-alphanumeric chars in hostnames (e.g. modal.run subdomain cuaai--todo-gym-web) produce invalid Java package names. Replace invalid chars with underscores and prefix digit-leading parts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(interfaces): clipboard.get() extract content, drag use path format - clipboard.get() was returning raw dict; now extracts 'content' key - mouse.drag() now sends path=[[x1,y1],[x2,y2]] matching server's VNCAutomationHandler.drag(path) signature - local transport drag handler supports both old and new path format Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * make CUA_BASE_URL configurable in cua-sandbox * remove name= from ephemeral test * pass snapshot cloud tests * docs: add snapshots guide + fix MDX escaping in interface docs generator - Add docs/content/docs/cua/guide/sandbox/snapshots.mdx covering the snapshot API, use cases, and performance characteristics - Fix gen_interface_docs.py: single-brace import, escape {}<> outside code spans with HTML entities instead of backslashes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(android): Windows SDK detection, shell compat, and env var quoting - Add Windows paths to _sdk_path() and check for emulator.exe - Remove hard unsupported bail on Windows; detect existing SDK + WHPX - Use sh -c and source /data/local/tmp/.cua_env for Android run layers - Shell-quote env var values with shlex.quote and validate key names Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(android): enable SDK auto-install on Windows - Add Windows commandlinetools download URL to _ensure_sdk() - Use .exe/.bat extensions for binary detection on Windows - Remove Windows-only bail in compat.py — auto-install works everywhere Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(android): handle .exe/.bat extensions in binary lookup on Windows - _find_bin() now checks .exe and .bat extensions on Windows - grpc_emulator _find_adb() checks for adb.exe on Windows Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix .env and .pwa_install on android images on windows + linux hosts * fix: address security, correctness, and concurrency issues from PR review Security: - executor.py, docker.py, lume.py: use shlex.quote() for env var values and validate names with re.fullmatch() to prevent shell injection in profile scripts - executor.py: quote dst/dst_dir with _sh_quote() in privileged copy commands Correctness: - image.py: propagate _snapshot_source through _add_layer() and _with() so chained image mutations don't silently drop the snapshot descriptor - sandbox.py: use src_image.os_type/distro/version/kind for snapshot Image instead of image_desc[\"kind\"] which is snapshot kind, not OS type - lume.py: resume existing stopped VMs instead of re-cloning (name collision); require base VM to be stopped before cloning; stop→clone→restart in checkpoint() to match cloud behaviour; scope list/delete_checkpoint to cua-base-* / cua-ckpt-* prefix; fix cloud.py .cua.sh polling loop to only run in local-dev mode (not prod reverse-proxy) - compat.py: return supported=java_ok for Android (Java is hard prerequisite) - cua/__init__.py: replace None fallbacks with stubs that raise ImportError - docker.py: tear down container on post-start provisioning failure - test_snapshots.py: capture t_create after install completes, not after boot Concurrency: - Server.swift: protect serverChannel and eventLoopGroup with NSLock to eliminate data race between start() writer and stop() reader Tests: - test_android_multitouch.py: resolve local_android_sb lazily in autouse fixture so cloud tests don't trigger emulator boot or compat skip - test_interfaces.py: fix _start_http_server docstring and formatting Style: run isort + black across affected files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>")#1242[)](https://github.com/trycua/cua/commit/d7a2452fdf314c0b5faed84069d4d261cd26748f "SDK integration tests, auto-generated docs, and snapshot support (#1242) * feat: add RuntimeSupport compat check + re-export runtime/interface types from meta pkg - New `cua_sandbox/runtime/compat.py` with `check_local_support(image) -> RuntimeSupport` that detects whether the image's runtime is installed/auto-installable and whether hardware acceleration (HVF, KVM, Hyper-V) is available for the host/guest OS+arch combo - `Image.local_support()` method delegates to check_local_support (lazy import, no circulars) - `skip_if_unsupported(image)` pytest helper replaces all hardcoded skipif/env-var gates - `cua_sandbox/__init__` exports RuntimeSupport, check_local_support, skip_if_unsupported - `cua` meta package: eager compat re-export, lazy __getattr__ for all runtime classes (DockerRuntime, QEMURuntime, LumeRuntime, AndroidEmulatorRuntime, HyperVRuntime, RuntimeInfo) and interface types (Shell, CommandResult, Mouse, Keyboard, Screen, Clipboard, Tunnel, TunnelInfo, Mobile, Terminal, Window) — no import-time overhead - `cua/pyproject.toml`: add uv.sources pointing to local editable siblings - Updated test_android_multitouch.py to use skip_if_unsupported instead of manual checks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(compat): correct hw_accel for x86_64 guests — HVF on Intel Mac, TCG on Apple Silicon QEMU supports HVF (-accel hvf) for x86_64 guests on Intel Macs. On Apple Silicon, x86_64 guests must use TCG software emulation (no cross-arch HVF). - Replace _has_hvf() with _has_hvf_for_arm64_guest / _has_hvf_for_x86_guest - Add _x86_guest_hw_accel() covering KVM (Linux), HVF (Intel Mac), Hyper-V (Windows) - Linux VM and Windows VM sections now use _x86_guest_hw_accel() - Android: Intel Mac gets HVF for x86_64 Android system images - Apple Silicon correctly reported as software-only for Windows/Linux VMs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: integration tests for all interfaces & image builder methods - tests/test_interfaces.py — 117 tests covering every sb.* interface method (shell, clipboard, screen, mouse, keyboard, tunnel, terminal, window, mobile) across Linux container, Linux VM, macOS VM, Windows VM, Android VM - tests/test_image_builder.py — 37 tests covering every Image builder method (apt/brew/choco/winget/apk/pwa/pip/uv install, run, env, copy, expose, from_registry) across all guest OS types - tests/pytest.ini — add pythonpath so `from cua import ...` resolves - scripts/gen_interface_docs.py — auto-generate interfaces.mdx from pydocstrings - docs/.../interfaces.mdx — regenerated from source (replaces AI-generated copy) All tests use local=True + skip_if_unsupported(image) — no hardcoded skips. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(compat): probe common docker install paths for stripped SSH PATH SSH sessions on macOS often have a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin) that omits /usr/local/bin where OrbStack/Docker Desktop install the docker CLI. _has_docker() now falls back through common install locations so Linux tests don't incorrectly skip on machines where Docker is installed but not in PATH. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(compat): delegate all runtime detection to canonical runtime modules Instead of reimplementing binary/path detection in compat.py (which diverged from the actual runtime logic), each _has_*() probe now calls into the runtime module that owns that detection: _has_docker() → docker._has_docker() (moved multi-path logic here) _has_lume() → lume._has_lume() _has_qemu() → qemu_installer.qemu_bin() (now finds Homebrew/MacPorts/cached) _has_android_sdk()→ android_emulator._sdk_path() _has_java() → android_emulator._java_env() _has_hyperv() → hyperv._has_hyperv() Also promotes docker._has_docker() to use the same multi-path probe that compat.py introduced, so the runtime itself finds Docker in stripped SSH PATH environments (e.g. OrbStack on macOS via SSH). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): resolve docker binary path to handle stripped SSH PATH All subprocess calls in DockerRuntime now use _docker_bin() which probes common install locations (/usr/local/bin, /opt/homebrew/bin, etc.) so the runtime works in SSH sessions where PATH is stripped (e.g. cua.local). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): apply image layers via computer-server after container starts DockerRuntime.start() was missing layer execution — apt_install, pip_install, run, env, etc. layers were stored in image._layers but never applied for Docker containers (unlike VMs where create_session_disk() handles this). Now uses LayerExecutor to apply layers via the running computer-server API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(executor): use correct /cmd API format and return_code field name The computer-server /cmd endpoint expects params as {\"params\": {\"command\": ...}} not top-level \"command_args\". Also fix result parsing to check \"return_code\" (server field name) before \"returncode\", and use \"success\" flag for failure detection when return_code is absent (e.g. on handler exceptions). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(executor): use sudo for apt-get in Linux containers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(executor,docker): comprehensive layer support for Linux containers - executor: fix run/env layers to use sudo for system access - executor: fix uv_install for Linux (uv pip install --system) - executor: fix pip_install for Linux (--break-system-packages) - executor: implement copy layer via computer-server write_bytes - executor: implement expose layer as no-op (handled at docker run) - executor: add os_type param to select correct commands per platform - docker: handle image._env and image._files after container starts - docker: map image._ports as additional -p flags at docker run time Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): pass image env vars as -e flags to docker run Environment variables from image.env() are now passed as -e KEY=VAL at docker run time, ensuring they're available in subprocess shells. Previously they were written to /etc/environment post-start which doesn't work since subprocesses don't source that file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests,docker,executor): fix ordering, env-in-run, expose port, from_registry - docker: apply _files before _layers so copy → run(chmod) ordering works - docker: write env vars to /etc/profile.d/cua-env.sh for sudo access - docker: fix expose port range (port to port+1000 not 8000-9000) - executor: source /etc/profile.d/cua-env.sh in run layer bash invocation - tests: fix from_registry tests to use cua image (needs computer-server) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(executor,docker,tests): fix copy root paths, env profile writing, expose test - executor: write copy files to /tmp first then sudo mv for root-owned paths - docker: write env profile via sudo tee (direct write_bytes can't write to /etc/profile.d) - tests: simplify expose test — just verify container starts, not socket internals Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lume): use fresh httpx client for pull/start to avoid empty body Lume's Swift HTTP server closes the TCP connection after sending a 4xx response (e.g. VM-not-found on GET /lume/vms/{name}). When httpx reuses that connection for the subsequent POST /lume/pull/start, the body arrives empty at the server, causing 'Invalid request body' 400 errors. Fix: open a fresh AsyncClient for the pull POST instead of reusing the client that made the initial VM-status GET. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Apply image layers on macOS VM after startup via LayerExecutor LumeRuntime.start() was returning without applying _layers, _env, or _files — unlike DockerRuntime which runs LayerExecutor after is_ready(). Add _apply_image_layers() helper and call it in both start() return paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix ephemeral lume VM cleanup and macOS sudo in run layers - sandbox.destroy(): call runtime.delete() instead of stop() for ephemeral sandboxes, so lume VMs are permanently removed (not just stopped) - executor._exec_run: don't use sudo on macOS/Android VMs — macOS VMs require a password for sudo; only Linux containers have passwordless sudo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Use 'echo lume | sudo -S' for macOS VM run layers and env profile macOS lume VMs have the default password 'lume'. Pipe it to sudo -S so run layers and env var setup can write to system paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Retry pull/start on 'Invalid request body' 400 from Lume Lume's custom HTTP server uses minimumIncompleteLength=1, so on a fresh TCP connection the request body can arrive after the first receive() returns, causing a spurious 400. Add _pull_start_with_retry() that retries up to 3 times with a 1s pause between attempts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix macOS env var propagation: use ~/.zshenv + launchctl setenv + restart Writing to /etc/profile.d/cua-env.sh is only sourced by login shells on macOS. Instead write to ~/.zshenv (sourced by all zsh invocations) and set via launchctl setenv, then restart computer-server so it inherits the new environment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix macOS env vars: inject into launchd plist + reload service computer-server's launchd plist has an explicit EnvironmentVariables dict, so launchctl setenv is ignored. Use PlistBuddy to add/set vars directly in the plist, then unload/load the service to pick them up. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Reload computer-server via lume ssh (host-side) for macOS env vars Running launchctl unload from inside computer-server kills it before the load step runs. Use lume ssh from the host instead — it runs outside the VM so the unload+load sequence completes successfully. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Use bootout/bootstrap for macOS launchd service reload launchctl unload/load doesn't work correctly for Aqua session agents from non-GUI SSH sessions. Use bootout/bootstrap which properly handles the GUI session context (gui/<uid>). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix sudo usage in _exec_copy and _exec_env for macOS macOS VMs need 'echo lume | sudo -S' for passwordless sudo. Apply this to mkdir/mv in copy layers and to /etc/environment writes in env layers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(runtime): pull-once + clone-many for Lume macOS VMs Instead of pulling (decompressing ~30GB) for every ephemeral VM, pull once into a stopped golden base VM and then APFS-clone it for each use. Clone is instant via clonefile(2); subsequent test runs skip the ~155s decompression step entirely. Also adds CheckpointInfo dataclass and checkpoint/fork/ensure_base primitives to the Runtime base class, forming the foundation for a user-facing snapshot/fork API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lume): replace custom HTTP server with NIOHTTP1 The custom NWConnection server read with minimumIncompleteLength=1, causing 'Invalid request body' whenever TCP delivered the POST body in a second segment (common after several connections). Replace with ServerBootstrap + NIOHTTP1 pipeline, which handles Content-Length / chunked reassembly correctly before dispatching to route handlers. No more spurious 400s. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lume): fix Swift 6 data race in NIO handler; bump swift-sdk to 0.12+ - Bridge ChannelHandlerContext back via EventLoopPromise so it never crosses actor boundaries (fixes 'sending ctx risks data races') - Remove @MainActor from Server (handlers create LumeController locally) - Bump swift-sdk from 0.10.0 to 0.12.0 which fixes the data race in NetworkTransport that broke clean builds under Swift 6.3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): persist image env vars and source in adb shell transport - In _apply_layers, write image._env to /data/local/tmp/.cua_env on the device so env vars survive across independent adb shell invocations - Prefix every adb transport shell command with `. /data/local/tmp/.cua_env 2>/dev/null` - Update test_apk_install to use F-Droid APK URL instead of skipping Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): use adb push for env file, add pwa_install test - Fix env var quoting by writing to a local tempfile and pushing via `adb push` instead of shell string escaping - test_pwa_install now uses android-example-gym-pwa-app manifest URL instead of requiring ANDROID_TEST_PWA_URL env var Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): source env in grpc transport, fix node PATH for pwa_install - Source .cua_env in GRPCEmulatorTransport shell commands (this is the actual transport used by ephemeral Android sandboxes, not ADBTransport) - Augment PATH with /opt/homebrew/bin so node/npm are found when the process is launched without a login shell - test_pwa_install downloads keystore from android-example-gym-pwa-app repo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): use || true to handle missing .cua_env in mksh Android's mksh exits on source of nonexistent file even with 2>/dev/null. Using || true ensures subsequent commands always run. Also fix second shutil.which(\"node\") call to use augmented_path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): guard .cua_env source with [ -f ] check In Android's mksh, sourcing a nonexistent file causes the shell to exit immediately — || true cannot recover because the shell process is gone. Use [ -f file ] && . file to safely skip sourcing when the file doesn't exist. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): pass augmented env to _bw_init.js and bubblewrap subprocess calls Without env=env, subprocess.run inherits the process env which lacks /opt/homebrew/bin. Node's execSync('npm root -g') then fails since npm is not on /bin/sh's PATH. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(android): sanitize PWA package name to valid Java identifier Hyphens and other non-alphanumeric chars in hostnames (e.g. modal.run subdomain cuaai--todo-gym-web) produce invalid Java package names. Replace invalid chars with underscores and prefix digit-leading parts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(interfaces): clipboard.get() extract content, drag use path format - clipboard.get() was returning raw dict; now extracts 'content' key - mouse.drag() now sends path=[[x1,y1],[x2,y2]] matching server's VNCAutomationHandler.drag(path) signature - local transport drag handler supports both old and new path format Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * make CUA_BASE_URL configurable in cua-sandbox * remove name= from ephemeral test * pass snapshot cloud tests * docs: add snapshots guide + fix MDX escaping in interface docs generator - Add docs/content/docs/cua/guide/sandbox/snapshots.mdx covering the snapshot API, use cases, and performance characteristics - Fix gen_interface_docs.py: single-brace import, escape {}<> outside code spans with HTML entities instead of backslashes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(android): Windows SDK detection, shell compat, and env var quoting - Add Windows paths to _sdk_path() and check for emulator.exe - Remove hard unsupported bail on Windows; detect existing SDK + WHPX - Use sh -c and source /data/local/tmp/.cua_env for Android run layers - Shell-quote env var values with shlex.quote and validate key names Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(android): enable SDK auto-install on Windows - Add Windows commandlinetools download URL to _ensure_sdk() - Use .exe/.bat extensions for binary detection on Windows - Remove Windows-only bail in compat.py — auto-install works everywhere Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(android): handle .exe/.bat extensions in binary lookup on Windows - _find_bin() now checks .exe and .bat extensions on Windows - grpc_emulator _find_adb() checks for adb.exe on Windows Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix .env and .pwa_install on android images on windows + linux hosts * fix: address security, correctness, and concurrency issues from PR review Security: - executor.py, docker.py, lume.py: use shlex.quote() for env var values and validate names with re.fullmatch() to prevent shell injection in profile scripts - executor.py: quote dst/dst_dir with _sh_quote() in privileged copy commands Correctness: - image.py: propagate _snapshot_source through _add_layer() and _with() so chained image mutations don't silently drop the snapshot descriptor - sandbox.py: use src_image.os_type/distro/version/kind for snapshot Image instead of image_desc[\"kind\"] which is snapshot kind, not OS type - lume.py: resume existing stopped VMs instead of re-cloning (name collision); require base VM to be stopped before cloning; stop→clone→restart in checkpoint() to match cloud behaviour; scope list/delete_checkpoint to cua-base-* / cua-ckpt-* prefix; fix cloud.py .cua.sh polling loop to only run in local-dev mode (not prod reverse-proxy) - compat.py: return supported=java_ok for Android (Java is hard prerequisite) - cua/__init__.py: replace None fallbacks with stubs that raise ImportError - docker.py: tear down container on post-start provisioning failure - test_snapshots.py: capture t_create after install completes, not after boot Concurrency: - Server.swift: protect serverChannel and eventLoopGroup with NSLock to eliminate data race between start() writer and stop() reader Tests: - test_android_multitouch.py: resolve local_android_sb lazily in autouse fixture so cloud tests don't trigger emulator boot or compat skip - test_interfaces.py: fix _start_http_server docstring and formatting Style: run isort + black across affected files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>") | Mar 30, 2026 |
| [docs](https://github.com/trycua/cua/tree/main/docs "docs") | [docs](https://github.com/trycua/cua/tree/main/docs "docs") | [docs(cua-driver): add integrations page and GitHub Copilot CLI MCP co…](https://github.com/trycua/cua/commit/2304df1fc5e7ec20d70d064085ed8d148c88ece6 "docs(cua-driver): add integrations page and GitHub Copilot CLI MCP config (#1407) - New integrations.mdx covering Claude Code, GitHub Copilot CLI, Codex, Cursor, Gemini CLI, OpenCode, Hermes, OpenClaw, and generic clients - install.sh: add GitHub Copilot CLI mcp-config.json snippet to post-install instructions - meta.json: add integrations to getting-started nav Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>") | Apr 27, 2026 |
| [examples](https://github.com/trycua/cua/tree/main/examples "examples") | [examples](https://github.com/trycua/cua/tree/main/examples "examples") | [Rename agent/core packages to cua_agent/cua_core to avoid namespace c…](https://github.com/trycua/cua/commit/f13f728808d1a4a257c5118b1f02195d80f10c32 "Rename agent/core packages to cua_agent/cua_core to avoid namespace collisions (#1319) * Rename agent/core packages to cua_agent/cua_core to avoid namespace collisions - Rename libs/python/agent/agent/ to libs/python/agent/cua_agent/ - Rename libs/python/core/core/ to libs/python/core/cua_core/ - Update all imports from 'agent' to 'cua_agent' - Update all imports from 'core' to 'cua_core' - Pin cua-core dependency to >=0.3.0,<0.4.0 across all packages - Pin cua-agent dependency to >=0.8.0 across all packages - Update documentation and blog posts with new import paths Fixes CUA-445 https://claude.ai/code/session_013snU7pHE5ZNs6nEzjHmLXR * Complete agent/core → cua_agent/cua_core namespace migration Fix 24 files that still imported from the old 'agent' and 'core' namespaces after the initial rename commit, including two runtime-critical missed imports in computer_server/main.py (core.http at line 282 and agent.computers at line 985), blog examples, tests, cua-cli, cua-sandbox, mcp-server, computer library, and cua-bench agent files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Pin cua-sandbox to >=0.1.11 in cua metapackage Ensures the metapackage requires cua-sandbox with the cua_core namespace migration (0.1.11+). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>") | Apr 15, 2026 |
| [img](https://github.com/trycua/cua/tree/main/img "img") | [img](https://github.com/trycua/cua/tree/main/img "img") | [cua-driver v0.1 — initial public release (](https://github.com/trycua/cua/commit/9bb952c61b42c710196bc48853b95d30bbd3a4c5 "cua-driver v0.1 — initial public release (#1359) *