| 1 | --- |
| 2 | name: computer-use |
| 3 | description: Desktop control that picks the right interface per step — app scripting (AppleScript/JXA), accessibility-first observation and actions, pixel fallback, screenshots, zoom, screen recording, and switching between registered computers. Qualified on macOS; Windows, Linux and HarmonyOS backends are experimental. |
| 4 | --- |
| 5 | |
| 6 | # Codewhale Computer Use |
| 7 | |
| 8 | ## Computers first |
| 9 | |
| 10 | The plugin controls **computers**, not "the screen". `computer {action:"list"}` shows the |
| 11 | registry; one computer is always **active**, and every tool acts on the active |
| 12 | computer unless given `computer`. |
| 13 | |
| 14 | A computer is an execution environment, not necessarily the user's desktop. |
| 15 | The registry holds two kinds: |
| 16 | |
| 17 | - **Spawned computers are ours.** `computer {action:"spawn", id:"<id>", |
| 18 | transport:"docker"}` provisions a disposable Linux desktop container, |
| 19 | registers it, and makes it active. Every tool works on it unchanged; the |
| 20 | user's own machine is never touched. It is destroyed by `computer |
| 21 | {action:"remove"}` or when the session ends. **Prefer a spawned computer |
| 22 | for any work that does not need the user's own session** — it is the |
| 23 | isolated workspace, not a workaround for sharing theirs politely. |
| 24 | - **Registered computers are someone's.** `local` is the machine the plugin |
| 25 | runs on — the user's desktop, with their logged-in apps and their pointer. |
| 26 | `ssh` computers run the bundled remote agent (pushed automatically at |
| 27 | registration). `hdc` computers are HarmonyOS devices driven over hdc. |
| 28 | Reach for `local` only when the task genuinely needs that session — their |
| 29 | Mail, their signed-in browser, their files on screen. A spawned desktop |
| 30 | cannot replace that, and pretending otherwise is the failure mode this |
| 31 | distinction exists to prevent. |
| 32 | |
| 33 | Other rules: |
| 34 | |
| 35 | - Pass `computer: "<id>"` on any tool to act on (and stickily switch to) that |
| 36 | computer. `computer_switch` changes the active computer without acting. |
| 37 | - Every receipt names the computer it happened on. Read it before continuing — |
| 38 | never assume the action landed on the machine you meant. |
| 39 | - Spawned containers are task-owned: never register one as a normal computer, |
| 40 | and never treat its filesystem or state as durable — it dies with the task. |
| 41 | |
| 42 | ## Human controls |
| 43 | |
| 44 | When the local helper is installed, it owns the input route even when the |
| 45 | host also includes a native binary. A disconnected helper is an error, never |
| 46 | permission to bypass it with direct input. `control_paused` and |
| 47 | `control_stopped` mean the person paused or stopped Computer Use. Stop acting |
| 48 | and wait for them; do not change environment variables, restart the helper, |
| 49 | create another session or use another tool to defeat their choice. After Stop, |
| 50 | the old session remains invalid even when the person allows new sessions. |
| 51 | The helper's own setup, permission and safety controls belong to the person. |
| 52 | Do not operate them or approve the host's pending authorization yourself. |
| 53 | |
| 54 | ## Consent on the user's computer |
| 55 | |
| 56 | The app, not the tool, is the unit of trust on `local`. The first call that |
| 57 | targets an application — `open_application`, an `app_ref`, an element or |
| 58 | `state_id`, or an action on the bound app — refuses `consent_required` |
| 59 | until the user has decided. Ask them, then record the answer: |
| 60 | |
| 61 | - `consent {action:"allow"|"deny", app:"Safari"}` — `app` accepts a name, a |
| 62 | bundle id, or `pid:`/a bare number for a pid; `name`, `bundle_id` and |
| 63 | `pid` fields work too. Decisions cover this session; `remember:true` |
| 64 | persists them for the computer until revoked. |
| 65 | - `consent {action:"status"}` — the ledger: every recorded app decision and |
| 66 | the foreground decision, each marked session or persisted. |
| 67 | `consent {action:"revoke", app:"…"}` clears a decision so the next call |
| 68 | asks again. |
| 69 | - A deny is a wall, not a hint: every spelling of the same app fails |
| 70 | `app_denied` — the ledger folds name, bundle id and pid together, and a |
| 71 | denied app cannot be opened, driven, or killed through this surface. |
| 72 | Only the user can change it; never work around it. |
| 73 | |
| 74 | Foreground is a second, separate consent. `open_application |
| 75 | {activate:true}` — the shared-desktop escalation, on any platform — |
| 76 | additionally needs `consent {action:"allow", scope:"foreground"}`; a |
| 77 | refusal reads `foreground_consent_required`, a recorded denial |
| 78 | `foreground_denied`. Background control (`activate:false`, the default |
| 79 | everywhere) needs only the app consent. |
| 80 | |
| 81 | Spawned computers are exempt — a task-owned desktop holds nothing of the |
| 82 | user's. Remote computers are covered by their transport's trust, not this |
| 83 | ledger. `app_script` keeps its own OS-level consent: Automation prompts |
| 84 | belong to macOS, not to this ledger. |
| 85 | |
| 86 | Only in explicitly authorized foreground mode, where a shared surface is taken |
| 87 | — a front lease for window-record |
| 88 | input, a real-pointer gesture, foreground keys, an activation — the helper |
| 89 | first waits for a gap in the person's hardware input rather than cutting |
| 90 | between their keystrokes. The wait is bounded, never infinite, and every |
| 91 | successful receipt that waited reports `yield_ms`. If no quiet window |
| 92 | arrives, `user_busy` means no input was sent: wait for the person to finish |
| 93 | or use an already-authorized isolated computer; do not disable the yield |
| 94 | or loop on retries. It is turn-taking, not a lock: |
| 95 | `user_input_during_lease:true` still means the outcome is contested — |
| 96 | say so. |
| 97 | |
| 98 | ## Choose the interface |
| 99 | |
| 100 | Clicking is only one way to use a computer. Before each step, pick the |
| 101 | interface that finishes it verifiably with the fewest moving parts — and |
| 102 | switch freely between steps: |
| 103 | |
| 104 | 1. **The host's own tools** — shell, files, HTTP, git, other MCP apps. |
| 105 | A step with no reason to be on screen does not belong to this plugin: |
| 106 | never drive a terminal window to run a command the host can run itself. |
| 107 | 2. **`app_script`** — AppleScript/JXA into apps that ship a scripting |
| 108 | dictionary (most native macOS apps). Deterministic, returns values, |
| 109 | needs no Accessibility grant, never touches the pointer. |
| 110 | 3. **`browser`** — CDP for web work: exact selectors, no pixels. |
| 111 | 4. **Accessibility actions** — the GUI loop below. The route for apps |
| 112 | with no better interface: background-safe, element-precise, verified. |
| 113 | 5. **Coordinates and pixels** — last resort, when nothing else can |
| 114 | express the target. |
| 115 | |
| 116 | A step that *can* be clicked still costs more than the same step |
| 117 | scripted, and a pixel click's `action_sent` proves less than a script's |
| 118 | return value or a `get_value` read-back. Prefer the interface whose |
| 119 | receipt can prove the step happened. Switching mid-task is normal — |
| 120 | script Mail for the message, process it through the host, type the |
| 121 | answer into a GUI-only editor; `get_app_state` still verifies what a |
| 122 | script changed. |
| 123 | |
| 124 | ## The GUI loop |
| 125 | |
| 126 | Once the GUI is the right interface: observe once, act once, then verify. |
| 127 | |
| 128 | 1. If readiness is unknown, call `request_access` once. It names missing |
| 129 | permissions and missing tools per platform, and never pops dialogs. Its |
| 130 | `via` field says who holds the permissions: `"app"` means the Codewhale |
| 131 | Computer Use desktop app is doing the work (grants belong to it); |
| 132 | `"direct"` means the hosting app or terminal is. Follow the actual |
| 133 | `appHint`: bundled Codewhale builds already carry their native helper. |
| 134 | `app.stale:true` means the running helper reports an older version than the |
| 135 | plugin — tell the user to restart the Codewhale Computer Use app before |
| 136 | debugging any behavior. |
| 137 | 2. `list_apps` shows user-facing apps only; pass `all:true` to include |
| 138 | menu-bar helpers and background processes. If the user names an app that is |
| 139 | absent, call `open_application` once with the original user-provided name, |
| 140 | copied character-for-character — including case, spaces, punctuation, and |
| 141 | suffixes such as `app` or `.exe`. Do not translate, localize, normalize, |
| 142 | shorten, or retry with guesses. |
| 143 | 3. `get_app_state` defaults to a text-first summary (macOS AX / Windows |
| 144 | UIA / Linux AT-SPI / HarmonyOS uitest) with controls, values, actions, |
| 145 | layout, element indices and a `state_id`. Start here without a screenshot, |
| 146 | whether or not the model supports vision. Pass `query`, `role`, `limit` |
| 147 | and `offset` instead of dumping the whole tree — truncated dumps hide the |
| 148 | title and search field. `detail:"compact"` is smaller (same indices, |
| 149 | shorter labels). `detail:"full"` adds nested menus and tree paths. |
| 150 | `find_elements` searches a cached `state_id` or observes now. Missing |
| 151 | labels or values mean unknown content, not something to guess. `get_value` |
| 152 | reads one field live. On macOS, browsers and Electron/webview apps expose |
| 153 | page content as `AXWebArea` descendants; the first observation may arrive |
| 154 | while the page is still populating — re-observe if the tree looks |
| 155 | suspiciously shallow or a control you can see is absent. |
| 156 | 4. If the tree contains the target, act on the element: `focus` then `type` |
| 157 | or `key` for composers (or pass the element `target` straight to |
| 158 | `type`/`key` — it focuses first, in the same call), `set_value` for |
| 159 | ordinary fields, `perform_action` (AXPress/Invoke/click…) for advertised |
| 160 | actions, element click. Newlines in `type` are Return/Enter; |
| 161 | `press_enter:true` sends after the text. Never expect `\\n` to send a |
| 162 | chat message. `run_actions` batches up to 8 steps |
| 163 | (click → type → key return → get_value). |
| 164 | macOS provides background element actions; Linux AT-SPI support depends |
| 165 | on the control. Windows currently refuses scoped semantic mutations. |
| 166 | Windows and Linux are development backends: do not assume their raw |
| 167 | input is background-safe or that native Pause/Stop controls are available. |
| 168 | 5. When accessibility cannot read visible text, macOS supports |
| 169 | `get_app_state({app_ref, include_ocr:true})`. Pass `ocr_region:[x,y,w,h]` |
| 170 | in screen points to recognize one rect instead of the whole window. This |
| 171 | captures locally, without a vision model. Check `ocr.status`; recognized |
| 172 | blocks include confidence, pixel bounds and ready-to-use coordinate |
| 173 | targets. OCR text is not a control role or an advertised action. Verify |
| 174 | uncertain text and observe again after changes. Other platforms return an |
| 175 | explicit unavailable status while keeping their accessibility state usable. |
| 176 | A text-only model must not infer unlabeled icons, charts or other graphical |
| 177 | meaning from OCR or a screenshot file path. |
| 178 | With vision, when accessibility cannot express the target: `screenshot` |
| 179 | (optionally `zoom` for small targets) and act with a coordinate target. |
| 180 | Default coordinates are pixels **in the latest returned raster**. Pass |
| 181 | `space:"screen"` to send absolute screen points from the AX tree and skip |
| 182 | conversion. After a new screenshot, old raster pixels are stale. |
| 183 | If the host reports an omitted or oversized image, capture a smaller app |
| 184 | window/region or zoom, then use that returned raster. Do not guess from a |
| 185 | file path or reuse coordinates from an image the model never received. |
| 186 | 5b. When the UI needs time — a page loading, a dialog appearing or |
| 187 | dismissing, a spinner finishing — call `wait_for` instead of looping |
| 188 | `get_app_state` + `wait` by hand: it polls the accessibility tree until |
| 189 | a `query`/`role` match appears (`state:"present"`) or disappears |
| 190 | (`state:"absent"`), then returns the matched elements bound to a fresh |
| 191 | `state_id` you can target immediately. A `timed_out:true` receipt means |
| 192 | the condition never held — observe and reconsider rather than repeating |
| 193 | the same wait. |
| 194 | 6. Verify with a fresh observation or a task oracle before claiming success. |
| 195 | `action_sent: true` means it may already have happened — never replay. |
| 196 | On macOS `type` also reports `verified`: `false` (with |
| 197 | `verification_required: "screenshot"`) means the focused control's value |
| 198 | did not reflect the text, so confirm with a screenshot before relying on |
| 199 | the input. |
| 200 | |
| 201 | ## Choosing targets |
| 202 | |
| 203 | - Element: `{"type":"element","index":4}` — prefer this. A bare index binds |
| 204 | that computer's latest observation; add `state_id` only to pin a specific |
| 205 | earlier snapshot (e.g. one returned by `wait_for` after newer observes). |
| 206 | Elements are revalidated against the live tree before every action: if the |
| 207 | element moved, the click lands on its fresh center and the receipt carries |
| 208 | `target_reacquired: true`; if it no longer resolves (or changed role) the |
| 209 | call fails `element_stale` — call `get_app_state` again. A `state_id` only |
| 210 | works on the computer that issued it (`state_wrong_computer`). |
| 211 | - Coordinate: `{"type":"coordinate","x":496,"y":331}` — pixels from the latest |
| 212 | raster only; submit `x`/`y` unchanged, never transform them yourself. |
| 213 | `{"type":"coordinate","x":100,"y":200,"space":"screen"}` is an absolute |
| 214 | screen point (what AX `position` uses). `zoom` returns a bindable raster of |
| 215 | its own: after zooming, raster coordinates are pixels in the zoomed image. |
| 216 | Points outside the bound raster fail `target_outside_raster` instead of |
| 217 | landing somewhere unintended. |
| 218 | - Never translate pixels into an element target; never invent `state_id`s. |
| 219 | |
| 220 | ## Raw input reality (read before clicking) |
| 221 | |
| 222 | - macOS: call `open_application` with `activate:false` to bind input to the |
| 223 | intended process, even when the app is already running; pass `pid` when two |
| 224 | processes share a bundle id. Then the two halves behave differently: |
| 225 | - **Keyboard and element actions are quiet.** `type`, `key`, `focus`, |
| 226 | `set_value`, `get_value`, `select_text` and `perform_action` reach the |
| 227 | bound process without moving the pointer or changing the foreground. |
| 228 | Prefer them. Text entry uses writable accessibility selection when |
| 229 | available; verify the resulting value. `get_app_state`, `list_windows` |
| 230 | and `screenshot` default to the selected app. |
| 231 | - **Background mode does not borrow keyboard focus.** Accessibility |
| 232 | click, focus, selection and scroll actions remain available. Raw pointer |
| 233 | fallbacks, modified/window-targeted keys, web value replacement and typing |
| 234 | paths that require a key-window lease refuse `background_focus_required` |
| 235 | before delivery. Use an accessibility menu/control, browser control or an |
| 236 | authorized separate computer. Do not escalate to foreground or retry the |
| 237 | same action merely because the user stopped typing briefly. |
| 238 | - Shared-desktop gestures and foreground keyboard delivery require explicit |
| 239 | user authorization for exclusive desktop use, followed by |
| 240 | `open_application(activate:true)` — which itself needs the foreground |
| 241 | consent (`consent {action:"allow", scope:"foreground"}`; see Consent on |
| 242 | the user's computer). Do not select it merely to work around a |
| 243 | background refusal. Receipts identify `input_scope: "shared-desktop"`; |
| 244 | pointer gestures use the physical cursor, even if it is restored afterward. |
| 245 | Keys and raw pointer gestures stop when another app takes focus. Never |
| 246 | keep reactivating after the user takes control; return to `activate:false` |
| 247 | when the shared-desktop step ends. |
| 248 | - Menus appear in `get_app_state`. Use the advertised action (often |
| 249 | `AXPress` to open a menu, then `AXPick` on its item), then observe again. |
| 250 | `invoke_menu {path:["File","New"]}` does that traversal in one call, |
| 251 | through accessibility alone — no key events, no focus lease. App-level |
| 252 | commands (New, Save, Quit) are reliable without a key window; |
| 253 | window-targeted items (Close) can validate against a key window the |
| 254 | background app does not have and legitimately no-op — close windows |
| 255 | through their close-button element instead. Exact titles only; a present |
| 256 | but disabled item is refused (`menu_item_disabled`) rather than pressed. |
| 257 | - A pointer gesture is refused when another application's window covers the |
| 258 | point; it names the owner. Observe again and use the selected control's |
| 259 | accessibility action, or wait for authorized exclusive desktop use. Do not |
| 260 | move or close the reported window. |
| 261 | - An accessibility press refuses to cross a modal sheet |
| 262 | (`window_blocked_by_modal_sheet`): deal with the sheet first. |
| 263 | - Virtualized lists vend collapsed placeholder rows (zero-size frames). |
| 264 | Acting on one fails `degenerate_frame` — scroll the real row into view |
| 265 | and re-observe rather than retrying the same index. |
| 266 | - `set_value` coerces numbers for `AXIncrementor`/`AXSlider`/`AXStepper` |
| 267 | and verifies the readback. Web-area direct AXValue writes are unreliable; |
| 268 | background mode refuses the focus/select-all replacement. Use browser |
| 269 | control. The replacement is available only during explicitly authorized |
| 270 | foreground control. |
| 271 | Use app-scoped screenshots (`app_ref`) to avoid capturing unrelated windows. |
| 272 | The nonactivating preview panel is on by default while an app is bound — |
| 273 | it shows the captured app window and a drawn cursor at each action's target |
| 274 | so the user can watch; the real pointer never moves. `preview(enabled:false)` |
| 275 | mutes it for the session. The preview is a local app view, not an isolated |
| 276 | desktop; watching it does not authorize shared-desktop control. Process-directed actions still |
| 277 | change the target app: do not work in an app the user is actively editing. |
| 278 | Close only disposable documents created by your task; never quit a user app. |
| 279 | - Windows/Linux: `open_application` still defaults to `activate:false` — |
| 280 | Windows launches the app minimized and Linux hands focus back to the |
| 281 | previous window — but raw input there is foreground by nature; |
| 282 | UIA/AT-SPI element actions are the precise path. |
| 283 | - HarmonyOS: `uitest` synthesizes touches; there is no hover or cursor. |
| 284 | |
| 285 | ## Keyboard |
| 286 | |
| 287 | - macOS uses `cmd` (`cmd+c`), Linux/Windows use `ctrl` (`ctrl+c`). |
| 288 | - `key` is the key-press tool: `return`, `enter`, `backspace`, `tab`, |
| 289 | `escape`, chords and repeats. `key {duration}` holds a key for a duration. |
| 290 | - `type` sends unicode. Newlines and `press_enter` become Return; they do |
| 291 | not insert a literal line break or U+FFFC. |
| 292 | - Prefer `set_value` on ordinary fields; prefer `focus` then `type`/`key` |
| 293 | on chat composers. |
| 294 | |
| 295 | ## Recording |
| 296 | |
| 297 | `recording {action:"start"}` → work → `recording {action:"stop", id}` returns the finalized file path. |
| 298 | macOS uses ScreenCaptureKit inside the signed helper — no system recorder UI |
| 299 | and no desktop dimming overlay (a receipt warning about Screen Recording |
| 300 | permission means the user must grant it once). Linux and Windows recording is |
| 301 | unavailable pending session-owned cleanup; use screenshots. HarmonyOS uses |
| 302 | snapshot-series (no native CLI recorder — |
| 303 | the receipt says so). `recording_status` / `recording_list` report bytes and |
| 304 | paths. Screenshots land in the same directory. |
| 305 | |
| 306 | ## Scripting apps (macOS) |
| 307 | |
| 308 | `app_script {script, language?, timeout?}` runs AppleScript (default) or |
| 309 | JXA (`language:"javascript"`) through osascript on the local computer. |
| 310 | `result` is the script's stdout; a non-zero exit fails `script_error` |
| 311 | with stderr, and `script_timeout` means the script — or a consent dialog |
| 312 | — was still open. |
| 313 | |
| 314 | - A first script targeting an app may show the person an Automation |
| 315 | consent dialog; that is their choice, not your error. A declined or |
| 316 | missing consent fails `automation_denied` (-1743): name the pane |
| 317 | (System Settings → Privacy & Security → Automation) and stop — never |
| 318 | retry it away. |
| 319 | - Read the dictionary before writing: `sdef /Applications/Mail.app` |
| 320 | through the host's shell, or Script Editor's Library window. A guessed |
| 321 | property earns `script_error` (-1728/-2740) — check the dictionary, |
| 322 | don't retry with another guess. |
| 323 | - `tell application "X"` launches X if needed; no `open_application` |
| 324 | required, and the script runs while X stays in the background. |
| 325 | - `do shell script "…"` inside a script works, but prefer the host's own |
| 326 | shell for shell work — keep `app_script` for app control and the parts |
| 327 | only a dictionary exposes. |
| 328 | - ssh, docker and hdc computers refuse it (`unsupported_on_transport`): |
| 329 | remote channels stay computer-use only, never a shell — a spawned |
| 330 | desktop is no exception. Windows and Linux backends fail |
| 331 | `unsupported_on_backend` for now. |
| 332 | |
| 333 | ## Browser (CDP) |
| 334 | |
| 335 | `browser` drives a Chromium-family browser over the DevTools protocol in a |
| 336 | self-owned profile — the user's own browser is never attached to, typed into, |
| 337 | or closed. `start` opens (or reuses) the instance and binds this session's |
| 338 | own tab; then `navigate`, `click` (CSS selector or viewport point), `type` |
| 339 | (optional focus selector, `enter`), `screenshot`, `status`, `stop`. Elements |
| 340 | are addressed exactly, no pixels: prefer this over screen clicking for web |
| 341 | work. Page screenshots are a different space from screen captures |
| 342 | (`space: "page-viewport"`) — coordinate clicks take that space, never screen |
| 343 | points. Verify effects by observing: `status` reports the tab's live url and |
| 344 | title, and a fresh `screenshot` shows the rendered truth. One tab per |
| 345 | session; the last session out closes the shared browser. Node 22+ is needed |
| 346 | for the WebSocket transport; older runtimes refuse with `unsupported_runtime`. |
| 347 | |
| 348 | ## Recording and scope |
| 349 | |
| 350 | `trajectory` records every tool call this session makes into a local JSONL |
| 351 | (off until started; arguments are stored verbatim, so treat the file as |
| 352 | sensitive). `replay` re-runs a recorded file through the same pipeline — |
| 353 | grants, permissions and the kill switch still apply — and stops at the first |
| 354 | refusal; `dry_run` lists the plan first. A host may narrow the whole session |
| 355 | with `CODEWHALE_CU_GRANT` (read-only, or a tool list): tools outside it are |
| 356 | never advertised and calls fail `not_granted`. Work inside that scope; do not |
| 357 | look for a workaround. `set_window_frame` moves or resizes one window and |
| 358 | reports the app's own readback — when an app constrains or refuses part of |
| 359 | the frame the receipt says so (`verified:false`, `ax_errors`, or |
| 360 | `frame_refused`), and that is the app's answer, not a failure to retry blindly. |
| 361 | |
| 362 | ## Safety |
| 363 | |
| 364 | - `stop_computer_control` is the kill switch; after it, actions fail closed |
| 365 | for the session. Do not continue after it or after a denied permission. |
| 366 | - `list_sessions` shows the live sessions and the user's control mode. When |
| 367 | another model or agent is mid-task on the same machine, coordinate through |
| 368 | the person instead of fighting for the same window; `kill_app` quits an app |
| 369 | (never the helper itself) and verifies the termination in its receipt. |
| 370 | - Never retry a refused action unchanged. Re-observe, choose a fresh target. |
| 371 | - If a permission is explicitly denied, tell the user which permission in |
| 372 | which Settings pane, and end the turn. Do not promise later retries. |
| 373 | |
| 374 | ## Recipes |
| 375 | |
| 376 | - **Screenshot** — optionally a computer id, display index, or `[x,y,w,h]` |
| 377 | region; call `screenshot`; report path, size, computer/display. Black or |
| 378 | empty capture means Screen Recording permission is missing (macOS) for the |
| 379 | app (`via: "app"`) or the host terminal (`via: "direct"`): say which and |
| 380 | stop. |
| 381 | - **Record** — `recording {action:"start"}` (parse computer id, fps, display, duration |
| 382 | or "record for 30s" → `durationSec` on macOS), then report id, path, mode. |
| 383 | To stop, find the running id via `recording {action:"list"}` and call `recording {action:"stop", id}`. |
| 384 | - **Switch computers** — `computer {action:"list"}`; if asked to add: ssh `user@host` |
| 385 | (agent is pushed automatically) or `hdc [target]` for a HarmonyOS device; |
| 386 | otherwise show the registry and remind that any tool accepts `computer`. |
| 387 | - **Status** — `computer {action:"list"}`, then `request_access` per computer; call out |
| 388 | anything that will fail closed with the exact install hint from the receipt. |
| 389 | |
| 390 | ## References |
| 391 | |
| 392 | The advertised tools are merged for context economy — `click`, `pointer`, |
| 393 | `clipboard`, `recording`, `computer`, and `key {duration}` for holds. The |
| 394 | per-action wire names (`left_click`, `read_clipboard`, `recording_start`, |
| 395 | `computer_list`, `hold_key`, …) remain callable as aliases. |
| 396 | |
| 397 | - `references/quick-reference.md` — every tool on one page, plus the common |
| 398 | recipes (type into a field, close a window without borrowing focus, |
| 399 | switch apps mid-task). |
| 400 | - `references/refusal-codes.md` — the fail-closed codes, what each means, |
| 401 | and the move that fixes it. |
| 402 |