Install
$ agentstack add skill-chrislamdev-cua-desktop-automation-skills-cua-driver-macos-automation ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Cua Driver macOS Desktop Automation
When to use
- User wants to automate macOS desktop apps (browsers, dev tools, etc.)
- Need to click buttons, type text, or interact with web pages programmatically
- macOS native Automation (AppleScript/cliclick) is blocked by OS restrictions
- Want background (non-focus-stealing) desktop control
Version Updates
v0.6.8 (2026-06-28)
- Hermes 原生支援! 執行
cua-driver mcp-config --client hermes可直接輸出 Hermes config.yaml - 更新後要 restart daemon:
cua-driver stop && cua-driver serve - TCC grants 理論上保留,但 macOS 可能出一次性 re-grant prompt
- 如果 permission gate 卡住:
cua-driver serve --no-permissions-gate - 新增
mcp-config指令,支援 claude/codex/openclaw/cursor/opencode/hermes 等 client - Update 方法:
cua-driver update --apply
Installation
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh)"
Permissions Setup
After installation, grant two macOS permissions:
- Accessibility — System Settings > Privacy & Security > Accessibility > tick CuaDriver
- Screen Recording — System Settings > Privacy & Security > Screen Recording > tick CuaDriver
Check status:
cua-driver permissions status
Trigger permission dialogs:
cua-driver permissions grant
Core Workflow
1. List running apps
cua-driver call list_apps
2. Launch app in background (no focus steal)
cua-driver call launch_app '{"bundle_id":"com.google.Chrome","urls":["https://gemini.google.com/app"]}'
Useful flags:
creates_new_application_instance: true— force new instance for concurrent sessionsurls— array of URLs/file paths to open
3. List windows to find window_id
cua-driver call list_windows '{"pid":}'
cua-driver call list_windows '{"on_screen_only":true}'
4. Snapshot + get UI tree (element_index)
cua-driver call get_window_state '{"pid":,"window_id":}'
Use query to filter large trees:
cua-driver call get_window_state '{"pid":,"window_id":,"query":"button|input|search"}'
5. Click by element_index
cua-driver call click '{"pid":,"window_id":,"element_index":}'
6. Type text
cua-driver call type_text '{"pid":,"text":"Hello world"}'
7. Keyboard shortcuts
cua-driver call press_key '{"pid":,"key":"return"}'
cua-driver call hotkey '{"pid":,"keys":["cmd","c"]}'
Web Page Automation (Critical Path)
For interacting with web pages, elementindex + typetext often fail due to web component shadow DOMs.
Enable JavaScript Apple Events (one-time per browser)
echo '{"action":"enable_javascript_apple_events","bundle_id":"com.google.Chrome","user_has_confirmed_enabling":true}' | cua-driver call page
This restarts the browser.
Execute JavaScript directly in the page
Method A — Short JS (inline):
echo '{"pid":,"window_id":,"action":"execute_javascript","javascript":"document.title"}' | cua-driver call page
Method B — Long JS (file → stdin pipe, preferred):
# Write JS to file first, then pipe via python for proper JSON escaping
cat /tmp/script.js | python3 -c "
import sys, json
js = sys.stdin.read()
payload = json.dumps({'pid': , 'window_id': , 'action': 'execute_javascript', 'javascript': js})
with open('/tmp/payload.json', 'w') as f:
f.write(payload)
" && cat /tmp/payload.json | cua-driver call page
Method C — AppleScript fallback (for Safari when page tool times out):
osascript -e 'tell application "Safari" to do JavaScript (read posix file "/tmp/script.js") in current tab of window 1'
Other page actions
echo '{"pid":,"window_id":,"action":"get_text"}' | cua-driver call page
echo '{"pid":,"window_id":,"action":"query_dom","css_selector":"button"}' | cua-driver call page
React SPA Text Input Strategy (e.g. X.com, Medium)
For React controlled components, standard textContent assignment doesn't trigger React's state update. Use this 3-layer approach:
Layer 1 — execCommand('insertText'): Best for React Draft.js editors (X.com compose box).
const el = document.querySelector('[data-testid="tweetTextarea_0"]');
el.focus();
const sel = window.getSelection();
const range = document.createRange();
range.selectNodeContents(el);
sel.removeAllRanges();
sel.addRange(range);
document.execCommand('insertText', false, tweetText);
Layer 2 — Cua typetext (method='keyevents'): Cua's type_text tool synthesises CGEvent keystrokes. Works when JS execCommand fails but may cause character duplication on SPA inputs. Use delay_ms: 10 for stability.
# Step 1: Click the input area first (use screenshot+vision to find pixel coords)
cua-driver call click '{"pid":,"window_id":,"x":,"y":}'
# Step 2: Type text via keystroke synthesis
cua-driver call type_text '{"pid":,"text":"Your tweet content here","delay_ms":10}'
Layer 3 — ClipboardEvent paste: Dispatches a programmatic paste event that React Draft.js recognizes.
const dataTransfer = new DataTransfer();
dataTransfer.setData('text/plain', tweetText);
el.dispatchEvent(new ClipboardEvent('paste', {
bubbles: true, cancelable: true, clipboardData: dataTransfer
}));
X.com specific:
- Post button
[data-testid="tweetButtonInline"]is disabled until React state has content - Pixel coordinates for clicking Post button typically ~(610, 420) on 1600×796 Safari window
- After typing via typetext, use screenshot + visionanalyze to verify Post button state
- If Safari shows "確定要離開此網頁嗎?" dialog, click "停留在網頁" to dismiss
Common Pitfalls
- Shell quoting: When passing JSON with long JS strings, prefer stdin piping.
- elementindex is per-snapshot: Always call
get_window_statebefore using elementindex. - Web apps ignore AX type_text: Chromium/Electron web inputs often don't implement kAXSelectedText.
- After "Allow JS Apple Events": Chrome restarts with a NEW pid.
- list_windows can return no windows: Some Electron apps report 0 windows on main PID but have windows on child Renderer process PID.
- hotkey vs press_key: Use
hotkeyfor keyboard shortcuts (more reliable on Electron apps). - Screenshot capture: v0.5.3
get_window_statedoes NOT include screenshot despitecapture_mode. Use macOSscreencapture -x /tmp/screen.pngas fallback.
Verification
cua-driver status
cua-driver permissions status
cua-driver list-tools
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ChrisLamDev
- Source: ChrisLamDev/cua-desktop-automation-skills
- License: MIT
- Homepage: https://chrislamdev.github.io/cua-desktop-automation-skills/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.