An OpenClaw plugin that gives AI agents full control over the macOS desktop
through Apple's Accessibility API β no vision model required.
π¨π³ δΈζζζ‘£
macOS exposes every native UI element through the Accessibility API (AXUIElement). CAM traverses this tree β similar to how a browser exposes the DOM β and returns structured data: element roles, labels, screen coordinates, sizes, and available actions. The agent reads this tree to decide what to click or type, then issues atomic tool calls to perform the interaction.
This approach means:
- π« No vision model needed β the agent reads structured element data, not pixels
- π No screen recording β AX queries use only the Accessibility permission
- π Works with native apps β Finder, Safari, Terminal, App Store, and any Cocoa app with a full AX tree
- β‘ Partial support for Electron apps β elements exposed via
AXEnhancedUserInterfaceare accessible; Web-rendered content may require coordinate-based fallback
-
macOS (Apple Silicon, recent version)
-
OpenClaw β the host application that loads and runs plugins
-
Node.js Accessibility permission β on first use, macOS will prompt you:
- Open System Settings β Privacy & Security β Accessibility
- Add
nodeand enable the toggle
Without this, calls will fail with "not authorized to send Apple events"
-
Optional:
cliclickβ improves click reliability:brew install cliclick
If absent, the plugin falls back to CoreGraphics
CGEventautomatically.
# 1. Clone the repository
git clone https://github.com/Aziily/CAM.git
# 2. Install via OpenClaw CLI
openclaw plugins install ./CAMList all running macOS applications. Always call this first to get the exact app name.
Parameters: none
Running applications (12):
Frontmost: Terminal
βΆ Terminal (com.apple.Terminal) [pid:1234]
Finder (com.apple.finder) [pid:456]
Safari (com.apple.Safari) [pid:789]
Get the full AX UI element tree of an app (Playwright-style hierarchical format).
| Parameter | Type | Required | Description |
|---|---|---|---|
app |
string | β | App name from cam_list_apps |
max_depth |
number | β | Tree depth (default: 6, max: 12) |
max_elements |
number | β | Element cap (default: 150, max: 400) |
App: Finder (45 elements)
AXWindow "Downloads" @(0,25) 1280x755 {AXRaise|AXClose}
AXToolbar @(0,25) 1280x52
AXButton "Back" @(10,35) 30x30 {AXPress}
Get a flat numbered list of all interactive elements with center coordinates. This is the primary tool for UI automation.
Auto-screenshot: When β€ 5 elements are returned (e.g. app is loading or Electron content is not yet exposed), a screenshot is automatically attached to help the agent understand the current screen state.
Standard workflow:
cam_list_elements(app)β get numbered list with coordinates- Identify target by label/role
cam_click({ x, y })β click using Center coordinatescam_type({ text })β type into focused elementcam_screenshot()β verify result
| Parameter | Type | Required | Description |
|---|---|---|---|
app |
string | β | App name |
max_elements |
number | β | Cap (default: 200, max: 400) |
ID | Role | Label | Center | Size
----|---------------------|----------------|-----------|------
1 | AXButton | Back | (45,45) | 30x30
2 | AXTextField | Address Bar | (640,45) | 800x30
Click by coordinates or by label.
| Parameter | Type | Required | Description |
|---|---|---|---|
x |
number | cond. | X coordinate |
y |
number | cond. | Y coordinate |
app |
string | cond. | App name (use with label) |
label |
string | cond. | Element label to find |
role |
string | β | AX role filter (e.g. AXButton) |
button |
"left" | "right" |
β | Mouse button (default: "left") |
double_click |
boolean | β | Double-click (default: false) |
wait_ms |
number | β | Extra wait after click in ms (default: 150) |
{ "x": 640, "y": 400 }
{ "app": "Finder", "label": "Desktop" }
{ "x": 200, "y": 300, "button": "right" }
{ "x": 400, "y": 500, "double_click": true }Type text via clipboard paste. Works with all characters including CJK, paths, symbols.
| Parameter | Type | Required | Description |
|---|---|---|---|
text |
string | β | Text to type |
app |
string | β | Activate this app before typing |
{ "text": "Hello, world!" }Press a key or keyboard shortcut.
| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | β | return, escape, tab, space, delete, aβz, f1βf12, up/down/left/right |
modifiers |
string[] | β | cmd, ctrl, alt/option, shift |
wait_ms |
number | β | Extra wait after key press in ms. Default: 600 ms for cmd shortcuts (dialog open time), 80 ms otherwise. Use 1200+ when opening Save dialogs before cam_type. |
{ "key": "return" }
{ "key": "a", "modifiers": ["cmd"] }
{ "key": "z", "modifiers": ["cmd", "shift"] }Scroll at a screen position.
| Parameter | Type | Required | Description |
|---|---|---|---|
x |
number | β | X coordinate |
y |
number | β | Y coordinate |
delta_x |
number | β | Horizontal scroll (default: 0) |
delta_y |
number | β | Vertical scroll (default: -3; negative = scroll down) |
{ "x": 640, "y": 400, "delta_y": -5 }Hold the mouse button for a duration.
| Parameter | Type | Required | Description |
|---|---|---|---|
x |
number | β | X coordinate |
y |
number | β | Y coordinate |
duration_ms |
number | β | Hold duration in ms (default: 800) |
{ "x": 500, "y": 300, "duration_ms": 800 }Take a screenshot. Returns base64 PNG, auto-resized to max 1280 px wide.
| Parameter | Type | Required | Description |
|---|---|---|---|
region |
object | β | { x, y, width, height } |
{}
{ "region": { "x": 0, "y": 0, "width": 800, "height": 600 } }Bring an app to the foreground. Waits 600 ms after activation.
| Parameter | Type | Required | Description |
|---|---|---|---|
app |
string | β | Application name |
{ "app": "Terminal" }Prompt: "Use CAM tools to install QQ Music from the App Store for me. Do not use any other tools."
The agent will:
1. cam_activate_app { "app": "App Store" }
2. cam_list_elements { "app": "App Store" }
β find the search field
3. cam_click { "x": <search_x>, "y": <search_y> }
4. cam_type { "text": "QQ Music" }
5. cam_key { "key": "return" }
6. cam_screenshot {}
β confirm search results loaded
7. cam_list_elements { "app": "App Store" }
β find "QQ Music" result and "Get" button
8. cam_click { "x": <get_x>, "y": <get_y> }
β click Get / Install
9. cam_screenshot {}
β confirm installation started
| Option | Type | Default | Description |
|---|---|---|---|
screenshotOnQuery |
boolean | false | Auto-capture screenshot on every UI query |
maxElements |
number (1β500) | 200 | Global cap on elements returned per query |
- Electron / web-rendered content β elements inside Web views are not in the AX tree; use
cam_screenshot+ coordinate-based clicks as fallback - Single primary display β multi-monitor coordinate translation is not handled
- No native drag-and-drop β complex drag operations are not supported
- JXA timeout β very large app trees may time out (60 s limit); use
max_depth/max_elementsto limit scope
CAM/
βββ index.js # Plugin entry point (ES module)
βββ macos-ax.js # Standalone AX helper (reference only)
βββ ax_traverse.swift # Swift source for ax_traverse binary
βββ ax_traverse # Swift binary: recursive AX tree traversal (arm64)
βββ ax_search # Swift binary: AXUIElementsForSearchPredicate (arm64)
βββ openclaw.plugin.json # Plugin manifest
βββ package.json
βββ README.md / README.zh.md
- Vimac β inspired the hint-mode element enumeration approach using
AXUIElementsForSearchPredicate - Apple Accessibility API β the foundation that makes desktop automation possible without screen recording
MIT Β© Aziily