Operate Cua Driver

Keep the Cua Driver daemon running, update it, choose a release channel, personalize the agent cursor, record what an agent did, and control telemetry.

How to run Cua Driver day to day: keep the daemon up, update it, change how the agent cursor looks and moves, record what an agent did, and decide what telemetry it sends.

Keep it running#

MCP clients on macOS and all one-shot cua-driver call commands need a running daemon. Apps that use CuaDriver.create() do not.

Save a LaunchAgent at ~/Library/LaunchAgents/com.trycua.cua-driver.plist (from a repo checkout, bash libs/cua-driver/scripts/install-local.sh --autostart writes it for you):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.trycua.cua-driver</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Applications/CuaDriver.app/Contents/MacOS/cua-driver</string>
    <string>serve</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/com.trycua.cua-driver.plist

The LaunchAgent daemon is attributed to com.trycua.driver, so its grants persist across reboots.

cua-driver status
cua-driver stop

stop ends the daemon; the autostart entry brings it back at the next logon.

Pin a permission mode#

Mode flags are read once at start, so put them in the autostart entry.

Add each flag as its own <string> after serve, with an absolute path (launchd does not expand ~), then launchctl unload and load the plist:

  <string>serve</string>
  <string>--permission-mode</string>
  <string>bounded</string>
  <string>--capability-manifest</string>
  <string>/Users/you/cua-session.yaml</string>
  <string>--approve-capability-manifest</string>

A bad configuration fails startup instead of falling back to standard. A user policy file works the same way: set CUA_DRIVER_POLICY_FILE in the plist's EnvironmentVariables, the unit, or the User environment.

Allow a brief foreground step (macOS)#

Some actions can't finish in the background. Today that means a drag that starts a macOS drag-and-drop session in a native window, such as reordering a list, because macOS follows the real pointer for those. By default the driver refuses them with background_unavailable.

To let the driver finish them, set CUA_DRIVER_ALLOW_BRIEF_FOREGROUND=1 in the daemon's environment (the plist's EnvironmentVariables). Then only that one step runs in the foreground: the window comes forward and the pointer moves for the drag, and afterwards the previous frontmost app and the pointer go back. The result carries a foreground_step object that says whether both were restored, and the daemon log records the step. A call that passes delivery_mode: "background" explicitly is never escalated. Leave the variable unset on a Mac someone is using.

Update#

cua-driver check-update
cua-driver update --apply

check-update only checks (--json for scripts, --no-cache to skip the 1-hour cache); update --apply runs the canonical installer. Then restart the daemon with your usual start command (cua-driver permissions grant, cua-driver autostart kick, or cua-driver serve), keeping any mode flags. The same check is the check_for_update MCP tool.

The check reads the version the installer itself would install, so it agrees with curl -fsSL https://cua.ai/driver/install.sh | bash, and it does not spend GitHub's REST API quota for stable releases. When GitHub's API does answer with a rate limit (nightly lookups and fallbacks), the message says so and shows when the limit resets; set GITHUB_TOKEN or GH_TOKEN to use your own quota.

if cua-driver check-update --json | jq -e '.update_available' > /dev/null; then
  cua-driver update --apply
fi

mcp, serve, and doctor print a banner when a newer release exists. Turn the check off for one run with CUA_DRIVER_RS_UPDATE_CHECK=false, or always:

cua-driver config set update_check_enabled false

If pacman owns the executable, update with sudo pacman -Syu; Driver's own update and channel commands refuse with pacman guidance (builds with #3636).

Before updating to 0.35#

0.35 changes three things that scripts and wrappers may depend on. The typed Rust, Python and TypeScript SDKs keep the full get_window_state response, so they are only affected by item 3.

  1. get_window_state is lean by default (#4743). A read returns only the compact tree_markdown, walks at most 250 nodes, and leaves out _note and background_input. If you read structuredContent.elements, pass tree_format: "elements" (or "both" if you also read tree_markdown). Pass verbose: true for _note and background_input, and max_elements for a larger walk. full_output: true returns the 0.34 response unchanged. A row [N] of a read whose snapshot_id is s0000002a has the element_token s0000002a:N.
  2. macOS reads collapse closed menus (#4892). A default read lists only the menu bar's titles, in tree_markdown and in elements. Run a menu item with invoke_menu and its path, or find it with query; full_output: true still walks every menu. The walk diagnostics nodes_visited, nodes_pending, walk_elapsed_ms and screenshot_refreshed_snapshot need verbose: true.
  3. macOS type_text sends \n, \r and \t as Return and Tab (#4821) when it types keys (Chromium and Electron fields, delivery_mode: "foreground", pixel targets). Text with a line break now submits a form or an address bar, and a tab moves focus. Remove them from text that should not submit, or write the value with set_value where the field supports it.

Scripts that read raw list_apps JSON on macOS should also treat bundle_id, launch_path, kind and last_used as optional: since #4916 they are omitted when unknown instead of null, and the always-empty windows: [] is gone. The typed SDKs already treat them as optional. For an app's windows, call list_windows({app: "Name"}).

tools/list also grows by one: run_script (sandboxed JavaScript that drives the driver's own tools in one call) is on by default. Client allowlists and tool-count checks need the new name. Turn it off with CUA_DRIVER_DISABLE_RUN_SCRIPT=1 or "disable_run_script": true in ~/.cua-driver/config.json; the old opt-in CUA_DRIVER_EXPERIMENTAL_SCRIPT=1 is no longer needed and is ignored.

Before updating to token-only element targeting#

Releases that include #3873 address accessibility elements only by element_token. Update scripts, agents, and wrappers that act on elements before you update:

  1. Pass element_token instead of element_index or snapshot_id. Read it from structuredContent.elements[].element_token in the latest get_window_state result (from 0.35, pass tree_format: "elements" to get that array) and send it with the window's pid. The token names its window, so window_id is optional; a different window_id is refused with conflicting_element_target. get_window_state still returns element_index and snapshot_id for display and correlation.
  2. Remove arguments that a tool does not advertise. Tools with closed input schemas now refuse unknown argument names with invalid_arguments (<tool>: unknown argument <name>) instead of ignoring them. This includes element_index and snapshot_id on action tools, and from_zoom on the Windows type_text, press_key, and hotkey tools, which Windows read without advertising. Pass window coordinates or an element_token there.
  3. Read a window before you use window-relative pixels. Pixel actions and zoom against a window target return screenshot_context_missing until the same session has read that window with a screenshot; Driver no longer assumes native pixels. from_zoom coordinates return zoom_context_missing once a newer read replaces the zoomed snapshot. Coordinates for a desktop target are unaffected.
  4. Re-read after a stale token. Each get_window_state call replaces the previous snapshot of that window and lists the ids it replaced in invalidated_snapshot_ids. An older token, or a token from another Driver runtime, is refused with stale_element_token, which lists the process's current snapshots. Earlier releases reported a token from another runtime as generation_mismatch.

Each one-shot cua-driver call without a session label runs in its own disposable session, and that session's snapshots are retired when the call returns. Pass the same session label to the read and to the actions that use its tokens or pixels.

Channels#

Stable is the default. Nightlies are immutable builds of main.

cua-driver channel status
cua-driver channel set nightly
cua-driver update --apply

channel set only saves the preference; update --apply installs. Fresh installs can choose the channel directly:

curl -fsSL https://cua.ai/driver/install.sh | bash -s -- --channel nightly

On Windows, the installer adds its directory to your User Path; to skip that:

& ([scriptblock]::Create((irm https://cua.ai/driver/install.ps1))) -NoPathUpdate

To pin one exact nightly for a single install, set CUA_DRIVER_RS_VERSION to its full nightly-cua-driver-rs-v… tag from the GitHub release before running the installer. The pin does not change the saved channel and never falls back to another release.

Personalize the cursor#

The agent's cursor is an overlay, not the real pointer: a plain session-colored arrow (cua.default, the only built-in theme). Its color tells agents apart. It is a visual aid, not an authorization signal.

By default the arrow floats gently, with a soft halo in its own color behind it. While it moves, its tip points along the path; it is upright again whenever it arrives. It shows no marks next to it, and no ripple or bounce. Each extra is a setting you can change for every cursor at once:

  • cursor.float: the gentle float, a slow sway and bob with a slight tilt. It is on by default and settles to zero before every click, drag, scroll and typing, so they land exactly; cua-driver config set cursor.float off keeps the cursor still.
  • cursor.halo: the soft glow behind the arrow. On by default.
  • cursor.marks: a small mark for what the agent is doing (observing, clicking, typing, pressing keys, scrolling and more).
  • cursor.rotate: by default (heading) the tip points along the path while the cursor moves, and it is upright again for every click. lean only tilts it a little toward where it is going; off keeps it upright.
  • cursor.motion.effects.glow, .ripple and .squish: a glow while it moves, a ring on each click, and a small press on each click.

For example, cua-driver config set cursor.marks on.

The cursor shows its session name when you hover over it, or while two or more agent cursors are on screen, and never while it moves. The cursor.badges setting changes this: minimal (the default), verbose (the session name on reveal, delivery and target chips on every action, and a short chip for key combos such as ⌘C), or off. For example, cua-driver config set cursor.badges verbose restores the earlier badge:

Verbose badge combinations for background and foreground delivery across accessibility, pixel, browser, and desktop targets

cua-driver start_session '{"session":"research"}'
cua-driver set_agent_cursor_theme \
  '{"session":"research","theme_id":"cua.default","reduced_motion":"auto"}'
cua-driver get_agent_cursor_state '{"session":"research"}'

set_agent_cursor_enabled hides or shows it; set_agent_cursor_motion tunes movement: style picks the trajectory (signature_arc by default, or spring_settle, magnetic, comet_swoop, adaptive, classic), timing picks native, fitts, or fixed, and effects turns the trail, glow, magnet, ripple, and squish on or off. The shell shortcut is cua-driver cursor motion --session research --style comet_swoop. Labels longer than 28 characters are shortened.

A custom theme is a dotLottie archive with a cua/theme.json manifest ("schema": "cua.cursor-theme/2"), 128×128 30 fps animations for all twelve actions (idle, observe, click, drag, scroll, text, key, navigate, app, transfer, record, system), a still frame each, and an author and license. Compile and install it locally; agents can select an installed ID but never install one:

cua-driver cursor-theme validate theme.lottie
cua-driver cursor-theme build theme.lottie --output theme.cua-theme
cua-driver cursor-theme preview theme.cua-theme --output preview
cua-driver cursor-theme install theme.cua-theme
cua-driver cursor-theme list
cua-driver cursor-theme uninstall com.example.cursor.studio

Choose your cursor#

Six motion styles are built in. signature_arc is the default.

StyleFeel
signature_arcOne confident arc with a small follow-through.
spring_settleAn arc that lands with one soft bounce.
magneticSlows near the target, then is pulled in.
comet_swoopA wide arc with a short trail.
adaptiveCareful for small targets, a swoop for long moves.
classicThe original glide.

Pick one for every new session by saving it as the default:

cua-driver config set cursor.motion.style magnetic
cua-driver config set cursor.motion.timing fitts
cua-driver config set cursor.motion.effects.trail true
cua-driver config get cursor.motion.style

The default is saved in ~/.cua-driver/config.json and applies to sessions started afterwards. Use null as the value to clear a key, or cua-driver config set cursor.motion null to clear them all. A bad value is rejected with the list of allowed styles.

Or choose per run, before the cursor first appears, in start_session:

cua-driver start_session '{"session":"research","cursor_motion":{"style":"spring_settle","timing":"fitts"}}'

cursor_motion takes the same fields as set_agent_cursor_motion, and start_session echoes it back. When several apply, the most specific wins:

  1. set_agent_cursor_motion on the session
  2. cursor_motion in start_session
  3. the saved default (cursor.motion.*)
  4. the built-in signature_arc

Reduced motion always wins: when the theme or host asks for it, every move is a short straight glide with no effects, whatever you chose.

Cua Driver has no settings window, so there is no cursor picker in the app. Use cua-driver config set or start_session.

Record a trajectory#

A recording saves every action with its arguments and before-and-after screenshots, plus an optional display video. Rendering needs ffmpeg on every platform; capture also needs it on Windows and Linux (macOS 15+ uses ScreenCaptureKit).

Turn an agent trajectory into a zoom-on-click demo

The agent clicks cells in a numbered grid, then Cua Driver renders the recorded actions.

View the original post on X

Have your agent call start_recording before the task:

{
  "output_dir": "~/cua-trajectories/calendar-demo",
  "record_video": true
}

Actions such as click, type_text, press_key, and scroll are added automatically, one turn-NNNNN folder each. Call stop_recording with {} and wait for it: it finalizes recording.mp4. Then render:

cua-driver recording render \
  ~/cua-trajectories/calendar-demo \
  ~/cua-trajectories/calendar-demo.mp4 \
  --scale 2.5

The renderer zooms around each recorded action; --scale sets the strength and --no-zoom gives a plain render. All options: cua-driver recording and the recording tools.

Computer History#

Computer History keeps an encrypted local record of actions performed through Cua Driver, so an agent can pick up where a previous run stopped. It is an opt-in preview in nightly builds (see Channels) on macOS, Windows, and Linux, and records nothing outside Cua-mediated actions.

cua-driver channel set nightly
cua-driver update --apply
cua-driver history enable
cua-driver history status

enable creates a key in the macOS Keychain, Windows Credential Manager, or Linux Secret Service (which must be unlocked; there is no plaintext fallback) and starts capture. Defaults: 7-day retention, 100 MiB quota, no network I/O.

CommandEffect
cua-driver history list 50Newest 50 events. Add --json for scripts.
cua-driver history show 42One event by sequence number.
cua-driver history pause / resume / disableStop or restart capture; existing history is kept.
cua-driver history delete --yesDestroy the key and the encrypted store.
cua-driver history list 50
cua-driver history show 42
cua-driver history pause
cua-driver history resume
cua-driver history delete --yes

History stores timestamps, opaque IDs, capability names, app identity, and fixed outcome categories. It never stores screenshots, typed text, keystrokes, clipboard, tool arguments or results, accessibility trees, file paths, window titles, or URLs. Deleting destroys the key; it cannot erase backups or snapshots of the ciphertext.

Let an agent read history#

A runtime that admits the preview exposes two read-only tools: history_status (capability history.status) and history_query (history.query, at most 200 events, filters limit, session_id, since_sequence, until_sequence). Every call passes the normal permission stack; a bounded manifest must name both:

version: 3
expires_after: 1h
idle_timeout: 10m
resources:
  computer_history:
    operations:
      - status
      - query
allow:
  tools:
    - history_status
    - history_query

Having the tools does not make a model use them. For "continue" or "what did I do" requests, give the agent a trusted instruction (or run the status-then-query preflight in your host) to check history first, treat events as metadata, and verify current state before acting. A query that returns events appends an encrypted access record. Agents cannot enable, pause, delete, or export history.

ErrorFix
history_preview_not_admittedRestart a development daemon with cua-driver serve --experimental-history.
history_key_locked, history_key_unavailableUnlock the login keychain or start a Secret Service.
history_quota_reachedDelete history; the action that hit the limit still ran.
history_storage_corruptcua-driver history delete --yes, then enable again.

The event schema (CloudEvents 1.0, urn:cua-driver:schema:history-event:v0) and storage profile are specified in the agent integration RFC.

To return to stable: cua-driver history disable, cua-driver channel set stable, cua-driver update --apply. History is kept unless you delete it.

Telemetry#

Cua Driver sends content-free, pseudonymous usage events to PostHog's EU endpoint, on by default. The installer shows a notice before the first event.

cua-driver telemetry status --json
cua-driver telemetry disable
cua-driver telemetry inspect cua_driver_mcp_tool_completed --json
cua-driver telemetry reset-id

CUA_DRIVER_RS_TELEMETRY_ENABLED=false overrides the saved setting for one process. The switches every Cua program shares also turn it off: DO_NOT_TRACK=1, CUA_TELEMETRY=0 and cua telemetry off (see Telemetry and privacy). inspect builds an event locally without sending it; reset-id creates a new installation ID (uninstall --purge removes local state).

Events carry the product version, OS family and major version, architecture, transport, built-in tool names (any other tool is other), and bucketed durations and outcomes. GeoIP lookup is disabled on every event. Never collected: prompts, tool arguments or results, typed text, screenshots, accessibility trees, window titles, app names, file paths, URLs, or error messages. The update check is separate and not controlled by this setting. See the privacy policy.