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.plistThe LaunchAgent daemon is attributed to com.trycua.driver, so its grants
persist across reboots.
cua-driver status
cua-driver stopstop 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 --applycheck-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
fimcp, 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 falseIf 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.
get_window_stateis lean by default (#4743). A read returns only the compacttree_markdown, walks at most 250 nodes, and leaves out_noteandbackground_input. If you readstructuredContent.elements, passtree_format: "elements"(or"both"if you also readtree_markdown). Passverbose: truefor_noteandbackground_input, andmax_elementsfor a larger walk.full_output: truereturns the 0.34 response unchanged. A row[N]of a read whosesnapshot_idiss0000002ahas theelement_tokens0000002a:N.- macOS reads collapse closed menus
(#4892). A default read lists
only the menu bar's titles, in
tree_markdownand inelements. Run a menu item withinvoke_menuand its path, or find it withquery;full_output: truestill walks every menu. The walk diagnosticsnodes_visited,nodes_pending,walk_elapsed_msandscreenshot_refreshed_snapshotneedverbose: true. - macOS
type_textsends\n,\rand\tas 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 withset_valuewhere 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:
- Pass
element_tokeninstead ofelement_indexorsnapshot_id. Read it fromstructuredContent.elements[].element_tokenin the latestget_window_stateresult (from 0.35, passtree_format: "elements"to get that array) and send it with the window'spid. The token names its window, sowindow_idis optional; a differentwindow_idis refused withconflicting_element_target.get_window_statestill returnselement_indexandsnapshot_idfor display and correlation. - 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 includeselement_indexandsnapshot_idon action tools, andfrom_zoomon the Windowstype_text,press_key, andhotkeytools, which Windows read without advertising. Pass window coordinates or anelement_tokenthere. - Read a window before you use window-relative pixels. Pixel actions and
zoomagainst a window target returnscreenshot_context_missinguntil the same session has read that window with a screenshot; Driver no longer assumes native pixels.from_zoomcoordinates returnzoom_context_missingonce a newer read replaces the zoomed snapshot. Coordinates for a desktop target are unaffected. - Re-read after a stale token. Each
get_window_statecall replaces the previous snapshot of that window and lists the ids it replaced ininvalidated_snapshot_ids. An older token, or a token from another Driver runtime, is refused withstale_element_token, which lists the process's current snapshots. Earlier releases reported a token from another runtime asgeneration_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 --applychannel 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 nightlyOn Windows, the installer adds its directory to your User Path; to skip that:
& ([scriptblock]::Create((irm https://cua.ai/driver/install.ps1))) -NoPathUpdateTo 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 offkeeps 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.leanonly tilts it a little toward where it is going;offkeeps it upright.cursor.motion.effects.glow,.rippleand.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:
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.studioChoose your cursor#
Six motion styles are built in. signature_arc is the default.
| Style | Feel |
|---|---|
signature_arc | One confident arc with a small follow-through. |
spring_settle | An arc that lands with one soft bounce. |
magnetic | Slows near the target, then is pulled in. |
comet_swoop | A wide arc with a short trail. |
adaptive | Careful for small targets, a swoop for long moves. |
classic | The 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.styleThe 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:
set_agent_cursor_motionon the sessioncursor_motioninstart_session- the saved default (
cursor.motion.*) - 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).
The agent clicks cells in a numbered grid, then Cua Driver renders the recorded actions.
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.5The 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 statusenable 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.
| Command | Effect |
|---|---|
cua-driver history list 50 | Newest 50 events. Add --json for scripts. |
cua-driver history show 42 | One event by sequence number. |
cua-driver history pause / resume / disable | Stop or restart capture; existing history is kept. |
cua-driver history delete --yes | Destroy 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 --yesHistory 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_queryHaving 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.
| Error | Fix |
|---|---|
history_preview_not_admitted | Restart a development daemon with cua-driver serve --experimental-history. |
history_key_locked, history_key_unavailable | Unlock the login keychain or start a Secret Service. |
history_quota_reached | Delete history; the action that hit the limit still ran. |
history_storage_corrupt | cua-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-idCUA_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.