Automate native desktop apps that agents cannot reach by API: pick the right control method (accessibility tree first, image/OCR as fallback, keyboard last), build waits and retries that hold, and know when to stop and go manual. Desktop-native only; nothing here touches the browser.
./install.sh desktop-ui-automationname: desktop_ui_automation description: "Automate native desktop apps that agents cannot reach by API: pick the right control method (accessibility tree first, image/OCR as fallback, keyboard last), build waits and retries that hold, and know when to stop and go manual. Desktop-native only; nothing here touches the browser." metadata: { "includeInPrompt": true }
Some desktop apps have no API, no CLI, and no web interface. The only way in is the same way a person uses them: windows, buttons, menus. This skill covers the decision layer for that work: which control method to try first, how to make each attempt reliable instead of flaky, and when to stop and hand the job back to a person.
Scope: native desktop applications only, outside the browser. For in-browser automation (tabs, forms, page content), use the browser-task-patterns skill instead. This skill assumes you already checked for an API or CLI and found none.
Start at the top and step down only when the level above fails. Never start at coordinates.
Level 1: Accessibility tree. Query the OS accessibility layer directly. This is the most reliable method because you address real controls, not pixels. Use the tool that matches the platform:
pywinauto (pip name pywinauto) or the uiautomation package (pip name uiautomation). Both were verified present on PyPI (pywinauto 0.6.9, uiautomation 2.0.29) as of this writing.atomac (pip name atomac, verified on PyPI at 1.1.0) or AppleScript via the pyobjc bridge (both documented, neither executed here).dogtail (pip name dogtail, verified on PyPI at 2.1.0) or the distro package python3-pyatspi (documented; note the bare pyatspi pip name returned no results on PyPI, do not invent that install path).Prefer stable identity first: AutomationId or equivalent unique ID, then the accessible name, then control type plus position in the tree. Name matching is brittle when apps get translated or relabeled, so treat a bare name match as a hint, not a contract.
Level 2: Image matching plus OCR. Take screenshots and find controls visually. Use pyautogui.locateOnScreen for template matching against a saved reference image, and OCR with pytesseract (the Python wrapper, pip name pytesseract, verified on PyPI at 0.3.13) over the tesseract binary to read text off the screen. This level is honest but fragile: theme changes, resolution changes, and window repositioning all break it. Keep reference images current and always search relative to the app window, never the whole screen when you can avoid it.
Level 3: Keyboard navigation. When a control exists but nothing can see it, drive the keyboard: Tab through controls, Alt-key menu shortcuts, arrow keys, Enter and Escape. Use pynput (pip name pynput, verified on PyPI at 1.8.2) or pyautogui's keyboard functions. Keyboard flows are surprisingly durable across layout changes, and they work through most remote-desktop or VM walls where screenshots and the accessibility tree both die. Record the exact key sequence, since you will replay it blind.
Level 0 (the real first step): skip all of this. If the app has a config file, a registry key, a hidden command-line flag, or a documented automation hook, use that instead of the GUI. Ten minutes of research on the vendor docs beats a week of pixel automation.
At Level 1, enumerate the tree first: list the window's controls with their names, types, and IDs before you click anything. On Windows, pywinauto can dump the control tree to text; on macOS and Linux use the same listing the platform's accessibility inspector gives you. Save that dump with the job. You cannot debug what you never looked at.
At Levels 2 and 3, take a screenshot before the first action and keep it. State your target out loud in the plan: "the Save button in the File menu of the settings dialog", not "the thing at (412, 88)".
Fixed sleeps are where desktop automation goes to die. Replace them:
wait family exists for this reason).A minimal retry helper for Level 2 work is inlined below. Save it as desktop_click.py and run it with python3 desktop_click.py after filling in the target image path and the state check. Status: verified. import pyautogui (0.9.54) and pyautogui.size() executed locally under Xvfb; the wait/retry/click logic above was extracted verbatim from this file and executed, with only screen capture stubbed (unavailable headless): first poll missed, second matched, one click fired at the located center. Platform-specific Level 1 libraries below are documented from PyPI/source records, not executed on their native platforms.
python
# desktop_click.py - save this file, then run: python3 desktop_click.py
import time
import pyautogui # pip install pyautogui (verified present on PyPI, 0.9.54)
TARGET_IMAGE = "save_button.png" # your reference screenshot of the control
TIMEOUT_SEC = 30
TRIES = 3
def wait_and_click(image, timeout=TIMEOUT_SEC):
"""Click the first match for `image`, retrying until timeout. Returns True on click."""
deadline = time.time() + timeout
while time.time() < deadline:
match = pyautogui.locateOnScreen(image, confidence=0.9)
if match:
pyautogui.click(pyautogui.center(match))
return True
time.sleep(0.5)
return False
def main():
for attempt in range(1, TRIES + 1):
if wait_and_click(TARGET_IMAGE):
print(f"clicked on attempt {attempt}")
return
print(f"attempt {attempt}: no match, retrying")
raise SystemExit(f"gave up after {TRIES} attempts: {TARGET_IMAGE} never appeared")
if __name__ == "__main__":
main()
Each click is a claim, and claims get checked. After acting, confirm the world changed the way you expected: read the control's new value, check the window title, look for the success dialog. If the state check fails, do not barrel forward; back up or stop. An unverified action mid-sequence turns one failure into three.
Screenshot on every failure. A run that failed with no screenshot is a run you will have to redo to debug.
Retry the step, not the whole job. When a step fails:
Give-up criteria. Hand the job back to a person when any of these is true:
When you give up, deliver a handoff note: what the goal was, which level each step reached, the last good state with a screenshot, and the exact manual steps remaining. A clean handoff is a success, not a failure.
A desktop automation run should produce, at minimum:
sleep(5) in a script is a bug report waiting to be filed.publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.