Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

pi/agent/skillz/win32-drive/SKILL.md

Raw
Rendered preview

name: win32-drive os: win32 description: "Use for inspecting, driving, or debugging Windows desktop GUIs, including cursor and pointer issues." compatibility: "Windows PowerShell 5.1 (powershell.exe), 64-bit recommended"

Win32 Drive

Resolve scripts/win32-drive.ps1 against this skill's directory and pass that absolute path; never rely on the current directory.

powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File <skill-dir>/scripts/win32-drive.ps1 <cmd> [opts]

All commands emit one JSON object with ok, cmd, and elapsedMs; failures add code and hint and exit nonzero. help prints the full command and option catalog, so read it instead of guessing flags.

Work in this order

  1. Observe — list, shot, uia, pixel, focus, idle, clipboard. Free, invisible, never disturbs the human.
  2. Act in the background — uia-invoke, uia-set, window, clipboard -Set, theme. These change the app through accessibility or messages: no activation, no pointer, no keystrokes. The human keeps typing in another window.
  3. Take over the machine — click, type, key, or any -Focus auto. Last resort only.

Before anything in step 3: ask the human first and say what you will click or type. The human is usually working on this machine; a takeover steals the pointer and the foreground window. Never chain takeovers silently; ask once per task, not once per click.

If step 2 fails with no-pattern, that app has no usable accessibility action — report that to the human and ask for permission to use the pointer instead of just escalating.

Be fast

Process start costs about 1.4s and dwarfs every action, so batch whole interactions into one invocation:

# steps.json
[{"cmd":"uia-invoke","TitleContains":"Notepad","Name":"File"},
 {"cmd":"wait","ms":250},
 {"cmd":"uia-set","TitleContains":"Notepad","ClassName":"*EDIT*","Value":"hello"},
 {"cmd":"shot","TitleContains":"Notepad","Out":"C:/temp/after.png"}]
... -File <skill-dir>/scripts/win32-drive.ps1 batch -File steps.json
  • Steps use the same option names as the command line; flags are true ("Pointer": true).
  • {"cmd":"wait","ms":300} pauses between steps; use it instead of separate invocations.
  • Execution stops at the first failing step (failedAt, per-step code) unless -ContinueOnError.
  • -Json also works from Bash or Nushell, but PowerShell mangles embedded quotes: prefer -File or stdin.

Do not re-query what a result already contains:

  • list returns count, foreground, idleMs, and per window z, focused, minimized; it accepts -Process/-TitleContains/-Hwnd filters.
  • click/type/key return target, foreground, returnedTo.
  • window returns the updated window rect.
  • uia-invoke/uia-set return the matched element and which pattern fired.
  • Errors return context: candidates for no-window, ambiguous-window, no-element, ambiguous-element; foreground, lockTimeoutMs, idleMs for focus-denied.

The native layer is compiled once and cached in %LOCALAPPDATA%\win32-drive; help.nativeLoad reports cache, compiled, or loaded. A compiled result means the cache was cold, which also briefly spawns the C# compiler; steady state is cache.

Acting without stealing focus

Read the tree with uia, then address the element by -Name, -AutomationId, -ClassName, or -ControlType; string filters accept wildcards, -Index disambiguates, ambiguity reports candidates.

uia-invoke tries Invoke, Toggle, SelectionItem, ExpandCollapse, MSAA default action, then BM_CLICK. uia-set tries ValuePattern, then posts characters to the control and verifies the resulting text. Both report which path fired and leave the foreground window alone. They cannot hover, drag, or reach anything outside the accessibility tree; GPU-rendered apps often expose nothing.

Takeover contract

Pointer input requires the explicit -Pointer flag. click always needs it; type and key need it only for -Focus auto, whose fallback is a pointer click on the target caption.

Every takeover: wait for an input gap, then show a red border, a ring on the target point, and a banner naming the action and target window, counting down (-CountdownMs, default 1200). Any keyboard or mouse input during the countdown cancels the action with code takeover-aborted; that is the human vetoing you, so stop and ask again. -NoCountdown skips the countdown and belongs to tests, not to a desktop in use.

Focus modes: require (default, activation APIs only), auto (adds the caption click, needs -Pointer), skip (no focusing). Focusing happens after the guard, because Windows denies activation while the human is typing. Minimized targets fail; restore them first.

After acting, focus returns to the window the human was using (-Return restore, default; returnedTo reports the result). Pass -Return keep when the next step in the same batch needs the target focused.

-IdleMs n (default 800) and -TimeoutMs n (default 10000) tune the idle gate. overlay -Text "..." previews the banner without acting.

Coordinates

Screen pixels everywhere, except click -X/-Y with a window selector, which are relative to the window origin reported by list and shot. Without -X/-Y, a window click targets the center.

uia returns screen coordinates: convert with click -X (uia.x - shot.x) -Y (uia.y - shot.y), or click without a selector. shot -Hwnd returns exactly the list frame bounds, so image pixel (0,0) is screen pixel (x,y) in both printwindow and screen-crop modes. After shot, read the returned PNG path with the harness read tool.

Limits

No UAC secure desktop or lock screen; elevated apps need an elevated agent. GPU/fullscreen windows fall back to screen-crop, which captures whatever is on top. A hung foreground window (foreground.hung) cannot be displaced at all. Theme changes broadcast WM_SETTINGCHANGE to every window and have wedged apps here: set a theme once, never in a loop, and -Refresh full additionally perturbs per-user parameters. Never use AttachThreadInput for activation: it couples input queues and has hung the host terminal here.

Tests

mise run win32-drive-test (from the dotfiles root) drives a fixture window and asserts real effects. It takes over mouse, keyboard, and clipboard for about a minute; never run it while the user is working. Theme writes need -IncludeTheme on a direct invocation.

---
name: win32-drive
os: win32
description: "Use for inspecting, driving, or debugging Windows desktop GUIs, including cursor and pointer issues."
compatibility: "Windows PowerShell 5.1 (powershell.exe), 64-bit recommended"
---

# Win32 Drive

Resolve `scripts/win32-drive.ps1` against this skill's directory and pass that absolute path; never rely on the current directory.

```powershell
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File <skill-dir>/scripts/win32-drive.ps1 <cmd> [opts]
```

All commands emit one JSON object with `ok`, `cmd`, and `elapsedMs`; failures add `code` and `hint` and exit nonzero. `help` prints the full command and option catalog, so read it instead of guessing flags.

## Work in this order

1. **Observe** — `list`, `shot`, `uia`, `pixel`, `focus`, `idle`, `clipboard`. Free, invisible, never disturbs the human.
2. **Act in the background** — `uia-invoke`, `uia-set`, `window`, `clipboard -Set`, `theme`. These change the app through accessibility or messages: no activation, no pointer, no keystrokes. The human keeps typing in another window.
3. **Take over the machine** — `click`, `type`, `key`, or any `-Focus auto`. Last resort only.

Before anything in step 3: **ask the human first** and say what you will click or type. The human is usually working on this machine; a takeover steals the pointer and the foreground window. Never chain takeovers silently; ask once per task, not once per click.

If step 2 fails with `no-pattern`, that app has no usable accessibility action — report that to the human and ask for permission to use the pointer instead of just escalating.

## Be fast

Process start costs about 1.4s and dwarfs every action, so **batch** whole interactions into one invocation:

```powershell
# steps.json
[{"cmd":"uia-invoke","TitleContains":"Notepad","Name":"File"},
 {"cmd":"wait","ms":250},
 {"cmd":"uia-set","TitleContains":"Notepad","ClassName":"*EDIT*","Value":"hello"},
 {"cmd":"shot","TitleContains":"Notepad","Out":"C:/temp/after.png"}]
```

```powershell
... -File <skill-dir>/scripts/win32-drive.ps1 batch -File steps.json
```

- Steps use the same option names as the command line; flags are `true` (`"Pointer": true`).
- `{"cmd":"wait","ms":300}` pauses between steps; use it instead of separate invocations.
- Execution stops at the first failing step (`failedAt`, per-step `code`) unless `-ContinueOnError`.
- `-Json` also works from Bash or Nushell, but PowerShell mangles embedded quotes: prefer `-File` or stdin.

Do not re-query what a result already contains:

- `list` returns `count`, `foreground`, `idleMs`, and per window `z`, `focused`, `minimized`; it accepts `-Process`/`-TitleContains`/`-Hwnd` filters.
- `click`/`type`/`key` return `target`, `foreground`, `returnedTo`.
- `window` returns the updated window rect.
- `uia-invoke`/`uia-set` return the matched `element` and which `pattern` fired.
- Errors return context: `candidates` for `no-window`, `ambiguous-window`, `no-element`, `ambiguous-element`; `foreground`, `lockTimeoutMs`, `idleMs` for `focus-denied`.

The native layer is compiled once and cached in `%LOCALAPPDATA%\win32-drive`; `help.nativeLoad` reports `cache`, `compiled`, or `loaded`. A `compiled` result means the cache was cold, which also briefly spawns the C# compiler; steady state is `cache`.

## Acting without stealing focus

Read the tree with `uia`, then address the element by `-Name`, `-AutomationId`, `-ClassName`, or `-ControlType`; string filters accept wildcards, `-Index` disambiguates, ambiguity reports candidates.

`uia-invoke` tries `Invoke`, `Toggle`, `SelectionItem`, `ExpandCollapse`, MSAA default action, then `BM_CLICK`. `uia-set` tries `ValuePattern`, then posts characters to the control and verifies the resulting text. Both report which path fired and leave the foreground window alone. They cannot hover, drag, or reach anything outside the accessibility tree; GPU-rendered apps often expose nothing.

## Takeover contract

Pointer input requires the explicit `-Pointer` flag. `click` always needs it; `type` and `key` need it only for `-Focus auto`, whose fallback is a pointer click on the target caption.

Every takeover: wait for an input gap, then show a red border, a ring on the target point, and a banner naming the action and target window, counting down (`-CountdownMs`, default 1200). **Any keyboard or mouse input during the countdown cancels the action** with code `takeover-aborted`; that is the human vetoing you, so stop and ask again. `-NoCountdown` skips the countdown and belongs to tests, not to a desktop in use.

Focus modes: `require` (default, activation APIs only), `auto` (adds the caption click, needs `-Pointer`), `skip` (no focusing). Focusing happens after the guard, because Windows denies activation while the human is typing. Minimized targets fail; restore them first.

After acting, focus returns to the window the human was using (`-Return restore`, default; `returnedTo` reports the result). Pass `-Return keep` when the next step in the same batch needs the target focused.

`-IdleMs n` (default 800) and `-TimeoutMs n` (default 10000) tune the idle gate. `overlay -Text "..."` previews the banner without acting.

## Coordinates

Screen pixels everywhere, except `click -X/-Y` with a window selector, which are relative to the window origin reported by `list` and `shot`. Without `-X/-Y`, a window click targets the center.

`uia` returns screen coordinates: convert with `click -X (uia.x - shot.x) -Y (uia.y - shot.y)`, or click without a selector. `shot -Hwnd` returns exactly the `list` frame bounds, so image pixel `(0,0)` is screen pixel `(x,y)` in both `printwindow` and `screen-crop` modes. After `shot`, read the returned PNG path with the harness `read` tool.

## Limits

No UAC secure desktop or lock screen; elevated apps need an elevated agent. GPU/fullscreen windows fall back to `screen-crop`, which captures whatever is on top. A hung foreground window (`foreground.hung`) cannot be displaced at all. Theme changes broadcast `WM_SETTINGCHANGE` to every window and have wedged apps here: set a theme once, never in a loop, and `-Refresh full` additionally perturbs per-user parameters. Never use `AttachThreadInput` for activation: it couples input queues and has hung the host terminal here.

## Tests

`mise run win32-drive-test` (from the dotfiles root) drives a fixture window and asserts real effects. It takes over mouse, keyboard, and clipboard for about a minute; never run it while the user is working. Theme writes need `-IncludeTheme` on a direct invocation.