Tool sequences for tasks that take several calls. Each recipe lists the calls in order, what each should return, a final check, and where it tends to go wrong. See tools for each tool’s contract.
The tool behavior described here comes from the code. How each app lays out its UI, such as button titles, roles and which process owns a panel, was not observed live and is marked (unverified). When the observed UI differs, follow what the inspection shows.
Conventions:
desktop_inspect with the window’s pid and window_id from desktop_windows. Each completed action returns a fresh observation, so use its snapshot_id and element IDs for the next action. Snapshots are single-use.{pid, process_start_identity_decimal} from desktop_apps. Window target means the target object of a desktop_windows row.refused or unknown, observe again before acting. Never repeat input blindly.cp, hdiutil. They are no substitute for the desktop tools on UI steps.If the app has File > Export as PDF…, as Preview, Pages, Safari and Notes do, use it: it opens a Save panel directly and skips steps 2 to 5.
desktop_menu with the app target and path: ["File", "Print…"]. Menu titles are exact, including the … character; read them with desktop_menus if unsure. Expect completed.desktop_wait with until: "dialog_appears" and pid. Expect matched. The print panel is a sheet or a window of the app.AXMenuButton, title PDF).desktop_click the PDF button. The fresh observation lists the open menu’s items first.desktop_element_action with action: "AXPick" on Save as PDF (or desktop_click it). A Save panel opens.desktop_dialog with the app target. Expect is_file_panel true. Use its resolved window target for the next calls.desktop_dialog_answer with path: "/Users/me/Desktop/out.pdf". Expect completed: the panel reads back the folder and name.desktop_dialog_answer with button: "Save".Verify: test -s /Users/me/Desktop/out.pdf in a shell, or desktop_wait with until: "dialog_gone" and then check the file. Printing to PDF writes the file a moment after the panel closes.
Goes wrong:
desktop_dialog refuse; pass the window target.desktop_dialog and answer deliberately.desktop_select the text.desktop_menu with path: ["Format", "Font", "Show Colors"], or ["Format", "Show Colors"] depending on the app (unverified per app).desktop_wait with until: "window_appears", the app’s pid and title: "Colors". The panel belongs to the app’s own process.AXRadioButton or AXButton). Then choose RGB Sliders from the pop-up button, by desktop_click and desktop_element_action AXPick.desktop_type the Hex Color # text field (unverified label) with text: "FF8000". Expect completed with the value read back.desktop_key with key: "enter", using the snapshot from step 5’s result.Verify: inspect the panel again and check the hex field’s value. Whether the selection took the color can only be seen in the app. Inspect it with ocr off and compare the screenshot, or read an attribute the app exposes (unverified).
Goes wrong:
desktop_menu with path: ["Format", "Font", "Show Fonts"].desktop_wait with until: "window_appears", pid and title: "Fonts".AXSearchField or AXTextField); orscope set to the family table to list every row.desktop_click the family row, then the typeface row, each from the latest observation.desktop_type the size field with text: "18", then desktop_key enter.Verify: inspect the panel and check that the family and typeface rows read as selected (isSelected) and that the size field shows the value. The text itself carries no readable font through desktop_read.
Goes wrong:
desktop_element_action AXScrollToVisible when the row is listed.The simplest verifiable route is the shell: hdiutil attach, then ditto the app into /Applications, then hdiutil detach. The desktop tools are needed for the first-launch prompt either way.
Through Finder:
desktop_open with item: {path: "/Users/me/Downloads/App.dmg"}. macOS may verify the image first.desktop_wait with until: "window_appears", bundle_id: "com.apple.finder" and title_contains set to the volume name. Allow timeout_ms up to 45000 for a large image.desktop_drag within that window, from the app icon’s point to the alias’s point. It’s a background drag in one window.desktop_wait with until: "dialog_gone" and the Finder pid, for the copy progress window (unverified that progress shows as a dialog). Or poll the file with test -d /Applications/App.app.desktop_launch with application: {path: "/Applications/App.app"}.desktop_notifications lists system alerts under system_alerts. Read it with desktop_dialog, using the owner’s app target from desktop_apps, then press Open with desktop_dialog_answer.desktop_menu on Finder with ["File", "Eject \"Volume\""] (unverified title), or hdiutil detach in a shell.Verify: desktop_wait with until: "app_running" and the app’s bundle_id.
Goes wrong:
.pkg, asks for an administrator password. Password entry is not supported, so a person must do it.unknown, check the file before dragging again.timed_out as “no prompt”.A shell cp is simpler. Use this when the copy must go through Finder.
desktop_focus the destination window target, desktop_click an empty part of its file list, then inspect it.desktop_insert with format: "files" and the paths. It pastes them with Command-V and restores the clipboard once every name is in the folder.To copy files picked in Finder instead, desktop_click the first one and the rest with modifiers: ["command"] (foreground, unknown; inspect to check the selection), then desktop_key c with command, and desktop_key v with command in the destination. Add option to move instead of copy.
Verify: the result is completed with copy: "started". Large files may still be copying; ls the destination to check sizes.
Goes wrong:
PASTE_NAME_EXISTS before anything is pasted. A manual Command-V would open a Replace / Keep Both / Stop dialog instead.unknown.A shell mv is simpler and can be checked. Finder shows a name written into a file’s name field through Accessibility without renaming the file, so desktop_set_cell, desktop_type and desktop_set_value refuse it with CELL_NOT_EDITABLE. In Finder itself the rename is foreground input (unverified):
desktop_focus the Finder window target, desktop_click the file’s row, then inspect.desktop_key enter to edit the name, then inspect.desktop_insert the new name and desktop_key enter.Verify: ls the folder. The row’s text alone doesn’t prove the rename.
Goes wrong:
desktop_open or desktop_launch is better when the target is known. Use Spotlight only for what needs its search.
desktop_system_key with key: "space" and modifiers: ["command"]. The result is unknown; nothing reads back a system shortcut.desktop_apps with query: "Spotlight", then desktop_windows with its pid, to find the search window (unverified: on recent macOS the field may belong to another process). desktop_apps’ system.focus also names where keyboard focus is now.desktop_insert the query; the field is the focused control.desktop_wait with until: "text_contains" on that window, for a result line.desktop_key with key: "enter".Verify: desktop_wait with until: "app_frontmost" and the expected bundle_id, or window_appears for a document.
Goes wrong:
desktop_system_key and use desktop_open instead.desktop_input_source with no arguments lists the enabled sources by id, name and kind, with current.desktop_input_source with select set to the wanted id, such as com.apple.keylayout.French. It reads the source back.before source again; the switch affects every app.Verify: the result is completed, and current equals the requested ID.
Goes wrong:
unknown means no readback within a second. List again rather than selecting again.desktop_key maps characters through keyboard_layout, so switch before typing with it.desktop_menus with the app target and path: ["File"]. This shows whether Share is a submenu of services or a single Share… command.desktop_menu with path: ["File", "Share", "Mail"] (the service’s exact title). It opens that service directly.desktop_menu with path: ["File", "Share…"]. A popover with service icons opens. created_windows may be empty, because the popover can be hosted by another process (unverified). Find it with desktop_screen, which lists the windows on the display front to back, then inspect that window and desktop_click the service.Verify: the service’s own window. For Mail, use desktop_wait with until: "window_appears" and bundle_id: "com.apple.mail"; for AirDrop, the AirDrop sheet.
Goes wrong:
To nudge the level, desktop_system_key with volume_up, volume_down, mute, brightness_up or brightness_down. Each press is one step and the result is unknown; mute toggles, so don’t repeat it blindly. For an exact level, use Control Center:
desktop_menubar_items, then desktop_menubar_click the item owned by com.apple.controlcenter. Pick it by owner, not title.AXSlider, titles vary); its current value shows the range, 0 to 1 or 0 to 100.desktop_set_value the slider. Expect completed with the value read back.Goes wrong:
There is no pinch or rotate gesture. Use the app’s own command: it acts on an exact target, and you can read the result back.
desktop_menus with the app target, and look for zoom and rotate items in View, and rotate items in Tools or Image. Common items are Zoom In, Zoom Out, Actual Size and Zoom to Fit in Preview, Safari, Maps and Photos, and Rotate Left and Rotate Right in Preview and Photos (unverified: titles and menus vary by app and macOS version). Each item shows its shortcut.desktop_menu with the app target and that item’s exact path. Expect completed. Repeat it to zoom further, observing between steps.desktop_key with = and command to zoom in, - and command to zoom out, and 0 and command for actual size, against that snapshot. In Preview, l with command rotates left and r with command rotates right (unverified). Pass repeat to press a key several times against one snapshot.Verify: inspect the window. The zoom level often shows in a toolbar control, in the window title or in a status bar, and a rotated image changes its bounds. A menu item that became disabled, such as Zoom In at the largest size, means there is nothing further to do.
Goes wrong:
desktop_menus rather than guessing it.desktop_inspect in pixels mode instead.desktop_open with item: {url: "https://example.com/form"} and optionally application: {bundle_id: "com.apple.Safari"}.desktop_wait with until: "window_appears", the browser’s bundle_id and title_contains set to part of the page title. Then desktop_wait with until: "element_appears", that window’s pid and window, and the first field as element: {role: "AXTextField", title: "Email"} (the field’s label as the page exposes it). Its result includes an inspection snapshot_id with the matching element IDs.truncated, inspect again with scope set to the page’s AXWebArea element.desktop_click the field. The fresh observation reports it as focused.desktop_type the field with that observation’s snapshot_id and element ID. A web field is typed into with key events, so the page sees input and change. Expect completed; the field is read back.<select>: desktop_click it, then desktop_element_action AXPick on the option, or desktop_click it.desktop_click it, or desktop_set_value true (unverified per site: some custom checkboxes ignore Accessibility values).<textarea>: click it, desktop_key a with command to select its text, then desktop_insert with route: "paste". Typed text can’t contain newlines.desktop_click the submit button.Verify: desktop_wait with until: "text_contains" and the confirmation text, or window_appears with the next page’s title. Then desktop_read the page if the outcome needs details.
Goes wrong:
desktop_type on a web field that isn’t focused is refused before any input, with a hint to click it first. Clicking is never done implicitly.unknown result means the readback didn’t match. Autocomplete or input masks can reformat the text, such as phone numbers. Inspect before retrying.escape with desktop_key before the next click if it covers the next field.truncated, inspect again with scope set to the page’s AXWebArea element.AXStaticText or AXHeading. Pick the smallest one that contains all of it.desktop_select that element with text set to the exact text, and prefix or suffix if it occurs more than once. Expect completed with selection read back.desktop_key with key: "c", modifiers: ["command"] and the snapshot from step 3’s result.desktop_clipboard_read.Verify: the clipboard text equals the requested text. If you only need the text, desktop_read the window instead; it changes neither the selection nor the clipboard.
Goes wrong:
desktop_read.unknown means the page’s selection didn’t read back as the requested range, for example because a script changed it. Inspect before retrying.desktop_set_cell writes cells that hold a field or a checkbox. Spreadsheet cells such as Numbers’ and Excel’s usually show only text and are refused, so edit them in place (unverified: which apps expose cell controls).
scope set to the table, then desktop_read with table: true and that scope. Find the row and column.desktop_click the cell’s AXCell element from that inspection, a child of the row. A single click selects the cell; typing replaces its content.desktop_insert with the value and route: "type". Expect unknown, since nothing reads the cell during editing.desktop_key with key: "enter" to commit. In Numbers and Excel, Return moves to the cell below.Verify: inspect with scope set to the table again and desktop_read it. The cell shows the value as the app formats it, such as 1,000 for 1000.
Goes wrong:
= starts a formula. The app reformats values it reads as dates or numbers.escape cancels an edit that hasn’t been committed.