Skip to main content

windows-harness

Control a whole Windows desktop from one persistent Python session with foreground-first input (focus holding, SendInput with pen/message fallbacks), background screenshots, UI Automation, Browser Harness, clipboard paste, PowerShell, and filesystem access. Use for native, Electron, browser, dialog, file, or cross-app tasks.

Quellinformationen

Repository
browser-use/windows-harness
Letzte Quellaktivität
26. August 2026 um 00:03
Erkannte Sprache von SKILL.md
Englisch
Sterne
74
Forks
8

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
15 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
windows-harness
description
Control a whole Windows desktop from one persistent Python session with foreground-first input (focus holding, SendInput with pen/message fallbacks), background screenshots, UI Automation, Browser Harness, clipboard paste, PowerShell, and filesystem access. Use for native, Electron, browser, dialog, file, or cross-app tasks.
# Windows Harness Use one CLI call per decision point, not per primitive: ```bash windows-harness <<'PY' app = "Notepad" win.see(app) win.type("hello", app=app) print(win.see(app)) PY ``` The CLI preloads `win`, lazy `browser`, `Path`, and `subprocess`. Prefer bounded stdin programs; reserve `windows-harness repl` for manual exploration and always exit it. ## How to invoke: exactly two forms One-off questions — use a subcommand (identical in PowerShell, cmd, and bash): ```bash windows-harness apps # app inventory, compact JSON (--all adds system windows) windows-harness see "CC Switch" # one screenshot -> JSON with its "path" windows-harness state "Notepad" # UIA tree as JSON (--screenshot adds image) ``` Anything multi-step — write a `.py` file, then run it. No quoting or encoding traps in any shell; this is always the most reliable channel: ```bash windows-harness run task.py # win, browser, Path, subprocess preloaded ``` Write generated task scripts to the harness scripts dir (printed by `windows-harness doctor` as `scripts_dir`, default `%USERPROFILE%\.windows-harness\scripts`); `run` resolves bare filenames there. Never write task scripts into the caller's working directory or invent ad-hoc folders like `.windows-harness-temp/` — they pollute the user's repo and show up as untracked files. Never run them via `python script.py` / `python -c` either: bypassing `windows-harness run` also bypasses the preloaded `win` session, so every call pays a fresh interpreter start and loses the element table behind `element_index`. Never use `<<'PY'` heredocs outside bash; never enumerate windows through hand-rolled C#/PowerShell — `win.list_apps()` already did it. Screenshots land in `%TEMP%\windows-harness-*.png` and persist: the returned `"path"` stays readable after the CLI exits, so just open it. Set `WINDOWS_HARNESS_OVERLAY=off` to skip the pointer renderer for minimum latency. ## Delivery: foreground first Every input primitive takes `delivery="foreground"` (default) or `delivery="background"`. | mode | transports | disturbance | |---|---|---| | `foreground` (default) | front the target + SendInput; pen/message fallback when hook software swallows SendInput | the target comes to the front and STAYS (`hold=True`) until `win.release()` | | `background` | synthetic-pen injection, PostMessage, UIA patterns | none — never fronts, never moves the cursor | Why foreground is the default: focus-driven UI (search boxes with suggestion popups, context menus, IME candidate windows) closes the instant its window loses the foreground, and CEF off-screen / XAML input stacks only accept message input while truly foreground and focused. Holding the foreground across a burst turns "sometimes works" into a procedure. - `win.release()` hands the foreground back to the user's previous window. Call it when a task burst is done. - For one quick invisible action use `delivery="foreground", hold=False` (cloaked ~150 ms takeover, restored after) or `delivery="background"`. - Foreground clicks use SendInput mouse events and do NOT summon the Windows touch keyboard; synthetic pen/touch injection (background clicks) can pop it up and wreck the focus chain — avoid pen taps into text fields. - `background` results carry `"mode": "pen" | "message"` plus `"verified": false` when the effect cannot be confirmed. When a framework's input stack silently drops background events the call raises `BackgroundUnavailable` with a structured `code`; redo the SAME action with default foreground delivery — do not probe more background transports. - `windows-harness doctor` reports `input_health.sendinput`: `ok`, `swallowed` (hook software such as audio enhancers or overlay recorders is eating injected events — suspects under `possible_injectors`), or `unknown`. When SendInput is swallowed, foreground actions automatically fall back to pen/message transports against the now-foreground target. ## Use the small surface Think in seven verbs: `see`, `key`, `type`, `click`, `paste`, `ax`, `script` — plus two inventory calls: `win.list_apps()` (processes with their window titles) and `win.windows(app)` (one app's windows). ```python frame = win.see("Notepad") win.key("ctrl+s", app="Notepad") win.type("Alessia Cara", app="Notepad") # type at the focused field win.type("Alessia Cara", app="Notepad", x=640, y=420) # click the field, then type win.click(640, 420, app="Notepad") win.click(640, 420, app="Notepad", clicks=2) # double click win.click(640, 420, app="Notepad", button="right") # context menu win.drag(200, 300, 500, 600, app="Notepad") # press, glide, release win.scroll(-720, app="Notepad") # wheel at window center (or x/y) win.hover(640, 420, app="Notepad") # really hover -> tooltip appears win.paste("Alessia Cara", app="Notepad") # clipboard paste (see ladder) win.release() # hand the foreground back item = win.ax.at(640, 420, app="Notepad") win.ax.perform(item["element_index"], "invoke") win.ax.click(item["element_index"]) # click the element's center — no coordinates win.ax.hover(item["element_index"]) # hover it so its tooltip fires win.script("Get-Process | Select-Object -First 3 Name") ``` `win.click()` is raw coordinate input; it never guesses a UIA action. For semantic actions use `win.ax.at()` + `win.ax.perform()`, and fill text fields with `win.ax.set_value()` (verified by read-back) when an element exposes a ValuePattern — no keystrokes at all. `win.ax.click()` / `win.ax.hover()` act on the element's own BoundingRectangle center: coordinates never leave the harness, so no coordinate-space mismatch is possible — prefer them over copying frame values into `win.click()` yourself. `win.hover(x, y)` delivers a real hover (physical cursor, or pen hover when SendInput is filtered) so tooltips and hover-only UI appear; `win.move(x, y)` only animates the virtual pointer and delivers no input. Hover is coordinate-routed — the target needs to be unoccluded, not foreground — and the cursor stays put so the tooltip survives long enough for a `win.see()`. ## Modal dialogs and overlays `win.see(app)` is **window-scoped**: it captures only that one window's client area, so an overlay sitting on top (a Save As / Open / Print dialog, a confirm prompt) is **not** in the shot even though it is on screen. To see a popup that just appeared, do not guess its title: ```python win.key("ctrl+s", app="Notepad") # an untitled doc opens Save As fg = win.foreground_window() # the dialog's title/hwnd frame = win.see(fg["hwnd"]) # capture the dialog itself # or grab the whole desktop, which sees every window at once: desktop = win.capture_screen() ``` `win.capture_screen()` captures the entire virtual desktop (all monitors) and its screenshot-space coordinates pair with `coordinate_space="screen"`. A bare `win.see()` with no app now targets the foreground window, so it also catches a modal that is currently in front. `win.see(app, bring_to_front=True)` fronts the target and keeps it fronted (restoring it if minimized) until `win.release()`; the default is a quiet background grab that never raises or activates the window, so `see` stays non-intrusive unless you opt in. ## Text input ladder 1. `win.ax.set_value()` when the element exposes a ValuePattern — verified by read-back and works fully in the background. 2. `win.type(text, x=..., y=...)` — focuses the field and types in one call. 3. `win.paste(text)` — the clipboard route. Setting the clipboard is a plain data handoff no hook software can filter; only the paste trigger needs input (SendInput Ctrl+V, or a posted WM_PASTE when SendInput is filtered — never a posted Ctrl+V, whose modifier never reaches GetKeyState). Focus the target field first (a click, or `win.type` with x/y). 4. If the paste trigger also fails: right-click the field and click the context menu's Paste item (pen injection can drive the menu). 5. When `doctor` reports `input_health.sendinput == "swallowed"`, tell the user which processes (`possible_injectors`) are eating injected input — removing the hook software is the real fix. `win.type()` reports what actually landed, not just that keystrokes were sent. XAML/WinUI text boxes (Win11 Notepad, Settings) and CEF editors drop much of a `KEYEVENTF_UNICODE` burst while claiming `verified: True`; the harness reads the field back and, on a mismatch, retries via `win.ax.set_value()` (verified) or `win.paste()`. If the result carries `"retried_via"`, the keyboard route dropped characters and the retry landed them. The result's `verified_via` says how `verified` was established: `"value_pattern"`/`"clipboard"` mean a read-back confirmed the text, `"transport"` means the target exposes no readable text state and only delivery was confirmed. A `verified: false` with a `read_back` field means the window accepted the keystrokes but the text did not appear (e.g. an app-launch/session-restore race) — click the field and retry, or switch to `win.paste()`. Replacing a field's existing text: there is no reliable select-all keystroke on hook-filtered machines (Ctrl+A is a combo and refuses; CEF ignores a posted Backspace). The dependable sequence is a triple click — `win.click(x, y, clicks=3)` selects the field's text — then `win.type` or `win.paste`, which replaces the selection. On machines with healthy SendInput, `win.key("ctrl+a")` first is fine too. Bare keys vs combos: when SendInput is swallowed, `win.key("enter")` and other modifier-less keys still work (posted to the foreground target); only combos like `ctrl+a` refuse, honestly. ## Coordinates `coordinate_space` accepts: - `'screenshot'` (default) — pixels of the latest `win.see()` image; the returned `scale_x`/`scale_y` map them to the window. - `'normalized'` — a 0..1000 grid over the client area of the window from the latest `win.see()`, independent of screenshot resolution and DPI; use directly when the model outputs normalized coordinates: ```python frame = win.see("Notepad") # anchors the window win.click(500, 48, app="Notepad", coordinate_space="normalized") ``` The window's origin is resolved live at click time, so the window may move between `see()` and `click()`; only a resize invalidates the grid — take a fresh `see()` after any resize. - `'client'` / `'screen'` — raw window-client or physical screen pixels. Every positional primitive (`click`, `double/right click`, `drag`, `scroll` with x/y, `type` with x/y, `hover`) accepts `coordinate_space`; the normalized grid works for all of them. Default to vision + normalized coordinates; only fall back to element centers from the ax tree when you need semantic identity or a layout shift makes a coordinate fragile. ax output reports frames in the pixel space of your latest `win.see()` screenshot of that window (the `frame` key, alongside `"frame_space": "screenshot"` in `state`), so they drop straight into the default coordinate space. Without a matching screenshot the raw physical screen pixels are reported under `frame_screen` instead — those only pair with `coordinate_space="screen"`, never the default. Cleanest of all is keeping numbers out of the loop: `win.ax.click(element_index)` / `win.ax.hover(element_index)`. Target apps by exe name when titles can collide — resolution prefers an exact process-name match (`"Discord.exe"`, or bare `"Discord"`), then an exact title, and only then substrings, so a window whose title merely mentions the name (a VS Code tab titled "… Discord …") never hijacks the query. ## Action proof: look only when it misfires Every coordinate primitive (`click`, `drag`, `scroll`, `hover`, `type` with x/y) stamps the intended point back onto the screenshot it was anchored to and returns the path under `result["proof"]["path"]`. The `proof` key is optional: it is present only when annotation is on (the default) and the proof was actually generated, so read it through `result.get("proof")` before using it. ```python out = win.click(640, 420, app="Notepad") proof = out.get("proof") print(proof["path"] if proof else "no proof") # annotated PNG you can open ``` It stays absent for non-coordinate primitives (`key`, `paste`, `move`, `ax.click`, `see`) and for any call given `annotate=False`, or when `WINDOWS_HARNESS_PROOF=off`. * `click` / double / right / `hover` / click-to-`type` draw a red reticle (circle-cross) at the point, labelled with the button and count. * `scroll` draws an arrow from the anchor point (the window centre whenever `x`/`y` are omitted) in the direction of the wheel delta. * `drag` draws the glide as a blue line with a reticle at the start and a square at the end. Do NOT open the proof on every action — the input already ran. Open it only to decide WHY a coordinate action mis-landed: you aimed at the wrong spot, the effect did not happen, or you need to nudge a coordinate before retrying. A normal `win.see()` on the finished step is the usual confirmation. The concrete trigger: an action reports success (or `verified: true`) but `win.verify_change(app)` says nothing changed, or a `win.see()` shows the page changed in a way you did not expect. In that case open `result["proof"]["path"]` FIRST — before retrying, nudging, or switching transports. The reticle on the anchored screenshot tells you at a glance whether the input even landed where you intended; guessing from coordinates alone just burns rounds. Forgot to print the proof path, or need an earlier one? Every proof is journaled to `proofs.jsonl` under the harness config dir (default `%USERPROFILE%\.windows-harness`) as it is written, so history survives across CLI invocations: ```python win.last_proof() # newest proof whose PNG still exists win.proofs(limit=5) # recent proofs, newest first win.proofs(app="Notepad", kind="click") # filter by process/title and kind ``` Each entry carries the same fields as `result["proof"]` plus `ts` and the target `app` (process/title); entries whose PNG was cleaned up are skipped. Pass `annotate=False` to any primitive (or set `WINDOWS_HARNESS_PROOF=off`) to turn the proof off for a coordinate you already know is good. ## Minimize round trips - Bundle deterministic, reversible steps into one program, then verify once. - Stop at a genuine decision boundary: ambiguous identity, new coordinates, an irreversible action, or unexpected state. - Do not screenshot merely to confirm a known shortcut opened a field before typing; let the final screenshot verify the whole sequence. - Poll exact UIA state inside the same Python program when possible. - Use the cheapest strong end-state check: one screenshot for visible state, one exact UIA query for semantic state. ## Choose the lowest useful mode Vision-first: if you can read a screenshot, lead with `win.see(app)` + normalized coordinates (a 0..1000 grid) instead of the UIA tree. Many apps (Electron/custom-drawn UI, game launchers, some frameless and webview windows) expose no usable UIA tree, and even when they do, screenshot + coordinates need no per-app support. Reach for `win.ax` only when you need semantic identity or state. 1. Use `win.script()` for a known exact command (Get-Process, registry reads). 2. Otherwise use `win.see(app)` and vision: locate the target in the screenshot, then act with `coordinate_space="normalized"` (0..1000) so the numbers are resolution/DPI independent. 3. Prefer a known keyboard route; use a verified coordinate for a visible, low-risk target. 4. Use targeted `win.ax` only when semantic identity or state matters. For CEF/Electron custom-drawn UI (chat apps, music players, game launchers) the tree fills lazily: `state`/`ax.query`/`ax.at` automatically warm a near-empty CEF tree with a brief (usually invisible, cloaked) foreground round, so warm it once if you truly need a semantic handle. Icon-only controls (server/avatar images) often expose only generic names — hover them (`win.ax.hover`) and read the tooltip from a `win.see()`. If a dump still comes back near-empty with a `note`, go straight to screenshot + normalized coordinate clicks instead of spending more rounds on UIA. 5. Use `delivery="background"` only for apps already proven to accept it on this machine (classic Win32 controls, UIA patterns) — it is the quiet opt-in, not the default to probe with. After a failed verified burst, switch mode or stop. Never repair uncertainty with repeated keys, clicks, deletion loops, or bulk input. ## Verify, then teach the matrix - After a `"verified": false` action, confirm the effect with `win.verify_change(app)` — a background pixel diff that costs no focus and no vision round trip. Use a full `win.see()` only when you need to know WHAT changed, not THAT it changed. Animated windows (video, games) report change on their own; verify those semantically through `win.ax` instead. - When a background action provably had no effect (verify_change negative AND the task did not advance), record it once with `win.note_drop(kind)` — e.g.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen