pctr 0.4.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {pctr-0.4.0/src/pctr.egg-info → pctr-0.5.0}/PKG-INFO +305 -269
  2. {pctr-0.4.0 → pctr-0.5.0}/README.md +39 -13
  3. pctr-0.5.0/pyproject.toml +68 -0
  4. {pctr-0.4.0 → pctr-0.5.0}/setup.cfg +4 -4
  5. pctr-0.5.0/src/pctr/__init__.py +1 -0
  6. pctr-0.5.0/src/pctr/backends/__init__.py +47 -0
  7. pctr-0.5.0/src/pctr/backends/atspi.py +323 -0
  8. pctr-0.5.0/src/pctr/backends/base.py +146 -0
  9. pctr-0.5.0/src/pctr/backends/input.py +269 -0
  10. pctr-0.5.0/src/pctr/backends/ocr.py +166 -0
  11. pctr-0.5.0/src/pctr/backends/windows.py +439 -0
  12. pctr-0.5.0/src/pctr/backends/x11.py +528 -0
  13. {pctr-0.4.0 → pctr-0.5.0}/src/pctr/cli.py +219 -539
  14. {pctr-0.4.0 → pctr-0.5.0}/src/pctr/data/SKILL.md +6 -5
  15. {pctr-0.4.0 → pctr-0.5.0}/src/pctr/mcp_server.py +3 -2
  16. {pctr-0.4.0 → pctr-0.5.0/src/pctr.egg-info}/PKG-INFO +305 -269
  17. {pctr-0.4.0 → pctr-0.5.0}/src/pctr.egg-info/SOURCES.txt +11 -1
  18. pctr-0.5.0/src/pctr.egg-info/requires.txt +38 -0
  19. pctr-0.5.0/tests/test_backends.py +83 -0
  20. pctr-0.5.0/tests/test_cli_parser.py +36 -0
  21. pctr-0.5.0/tests/test_dispatch.py +73 -0
  22. pctr-0.4.0/pyproject.toml +0 -55
  23. pctr-0.4.0/src/pctr/__init__.py +0 -1
  24. pctr-0.4.0/src/pctr.egg-info/requires.txt +0 -22
  25. {pctr-0.4.0 → pctr-0.5.0}/LICENSE +0 -0
  26. {pctr-0.4.0 → pctr-0.5.0}/src/pctr/__main__.py +0 -0
  27. {pctr-0.4.0 → pctr-0.5.0}/src/pctr/data/AGENTS.md +0 -0
  28. {pctr-0.4.0 → pctr-0.5.0}/src/pctr/py.typed +0 -0
  29. {pctr-0.4.0 → pctr-0.5.0}/src/pctr.egg-info/dependency_links.txt +0 -0
  30. {pctr-0.4.0 → pctr-0.5.0}/src/pctr.egg-info/entry_points.txt +0 -0
  31. {pctr-0.4.0 → pctr-0.5.0}/src/pctr.egg-info/top_level.txt +0 -0
@@ -1,269 +1,305 @@
1
- Metadata-Version: 2.4
2
- Name: pctr
3
- Version: 0.4.0
4
- Summary: PCTR - Personal Computer Tactile Response. Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates.
5
- Author: NTUserDev
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/NTUserDev/pctr
8
- Project-URL: Repository, https://github.com/NTUserDev/pctr
9
- Project-URL: Issues, https://github.com/NTUserDev/pctr/issues
10
- Keywords: automation,windows,uia,pywinauto,desktop,agent,computer-use,rpa
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Environment :: Win32 (MS Windows)
13
- Classifier: Intended Audience :: Developers
14
- Classifier: Operating System :: Microsoft :: Windows
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Topic :: Software Development :: Testing
21
- Classifier: Topic :: System :: Networking
22
- Requires-Python: >=3.10
23
- Description-Content-Type: text/markdown
24
- License-File: LICENSE
25
- Requires-Dist: pywinauto>=0.6.8; platform_system == "Windows"
26
- Requires-Dist: uiautomation>=2.0; platform_system == "Windows"
27
- Requires-Dist: pyautogui>=0.9.54; platform_system == "Windows"
28
- Requires-Dist: pydirectinput>=1.0.4; platform_system == "Windows"
29
- Requires-Dist: pillow>=9.0; platform_system == "Windows"
30
- Provides-Extra: vision
31
- Requires-Dist: ultralytics>=8.2; extra == "vision"
32
- Provides-Extra: mcp
33
- Requires-Dist: mcp>=2; extra == "mcp"
34
- Provides-Extra: pad
35
- Requires-Dist: vgamepad>=0.1.0; extra == "pad"
36
- Provides-Extra: serial
37
- Requires-Dist: pyserial>=3.5; extra == "serial"
38
- Provides-Extra: all
39
- Requires-Dist: pctr[mcp,pad,serial,vision]; extra == "all"
40
- Dynamic: license-file
41
-
42
- # pctr
43
-
44
- **PCTR — Personal Computer Tactile Response**
45
-
46
- Element-based Windows UI Automation CLI for AI agents.
47
-
48
- Instead of screenshotting and guessing pixel coordinates, `pctr` finds controls
49
- by their **name / control type / automation id** and acts on them. Window moves,
50
- DPI changes, and layout shifts don't break your script.
51
-
52
- Built on `pywinauto` (UI Automation) with `pyautogui` / `pydirectinput` for raw
53
- mouse and keyboard input. On top of UIA it also offers **OCR**
54
- (`Windows.Media.Ocr`) and **YOLO-World** open-vocabulary detection for text and
55
- objects that aren't exposed as controls.
56
-
57
- ![pctr driving by element name: enumerating windows, mapping virtual desktops, dumping the control tree, filling a field, then reading it back with OCR](docs/demo.gif)
58
-
59
- *Driven entirely by pctr itself - list windows, map virtual desktops, dump a
60
- control tree, `set` a field, read it back with OCR, screenshot it, and YOLO-World
61
- detect. Recorded with OBS, which pctr also piloted.*
62
-
63
- ## Install
64
-
65
- ```bash
66
- pip install pctr # core (UIA + OCR)
67
- pip install "pctr[vision]" # + ultralytics, for `pctr look`
68
- pip install "pctr[mcp]" # + MCP SDK, for the MCP server
69
- pip install "pctr[pad]" # + vgamepad, for `pctr pad` (needs ViGEmBus)
70
- pip install "pctr[serial]" # + pyserial, for `pctr hid` / the serial-hid backend
71
- pip install "pctr[all]" # everything
72
- ```
73
-
74
- Windows only.
75
-
76
- ## MCP server
77
-
78
- `pctr` ships a Model Context Protocol server so MCP clients (Claude Desktop,
79
- Cursor, opencode, ...) can drive the Windows desktop directly:
80
-
81
- ```bash
82
- pip install "pctr[mcp]"
83
- ```
84
-
85
- Add it to your client config (stdio):
86
-
87
- ```json
88
- { "mcpServers": { "pctr": { "command": "pctr-mcp" } } }
89
- ```
90
-
91
- (or `"command": "python", "args": ["-m", "pctr.mcp_server"]`)
92
-
93
- Tools exposed: `pctr_windows`, `pctr_tree`, `pctr_find`, `pctr_click`,
94
- `pctr_set`, `pctr_type`, `pctr_keys`, `pctr_hotkey`, `pctr_focus`, `pctr_wait`,
95
- `pctr_shot`, `pctr_ocr`, `pctr_ocrfind`, `pctr_ocrclick`, `pctr_look`,
96
- `pctr_desktop`.
97
-
98
- Or run/manage it from the CLI:
99
-
100
- ```bash
101
- pctr mcp status # SDK + server state
102
- pctr mcp start --transport streamable-http --port 8765 # background HTTP server
103
- pctr mcp restart | pctr mcp stop
104
- pctr mcp help
105
- ```
106
-
107
- `stdio` runs in the foreground (what a client spawns); `sse` / `streamable-http`
108
- run in the background, reachable at `http://127.0.0.1:8765/mcp` (or `…/sse`).
109
-
110
- ## Commands
111
-
112
- | Command | What it does |
113
- |---------|--------------|
114
- | `pctr windows [--filter RE] [--process PID]` | List top-level windows (`pid=… \| title`). |
115
- | `pctr tree --title RE [--depth N] [--limit N]` | Dump the UIA control tree of a window. |
116
- | `pctr find --title RE [--name RE] [--limit N] [--nth N]` | List matching elements. |
117
- | `pctr click --title RE --name RE [--method auto\|invoke\|mouse] [--dbl]` | Click an element. |
118
- | `pctr set --title RE --name RE --text S` | Set a field via the UIA ValuePattern (exact, instant). |
119
- | `pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1]` | Send keystrokes (see notes). |
120
- | `pctr keys --keys "{ENTER}"` | Send a global key combo (pywinauto syntax). |
121
- | `pctr hotkey --keys ctrl+s` | Send a modifier combo (prefer this over `keys "^s"`). |
122
- | `pctr focus --title RE` | Bring a window to the foreground. |
123
- | `pctr wait --title RE --name RE [--timeout S]` | Wait until an element exists. |
124
- | `pctr shot [--title RE] --out PATH` | Screenshot the screen or a window. |
125
- | `pctr size` | Print primary screen size as `WxH`. |
126
- | `pctr desktop <action>` | Virtual desktops: `list` / `windows` / `where` / `on-current` / `next` / `prev` / `new` / `close` / `move-window`. |
127
- | `pctr move --x N --y N` / `down` / `up` / `hold --ms N` | Raw mouse control (`--direct` for games). |
128
- | `pctr drag --start x,y --end x,y [--duration S]` | Drag between two points. |
129
- | `pctr keydown / keyup --key a` / `keyhold --key w --ms 1500` | Hold keyboard keys (`--direct` for games). |
130
- | `pctr ocr [--title RE] [--lang TAG]` | OCR the screen/window; list words with boxes. |
131
- | `pctr ocrfind --text RE [--lang TAG]` | OCR then list matches with click centers (single words). |
132
- | `pctr ocrclick --text RE [--nth N] [--lang TAG]` | OCR then click a word. |
133
- | `pctr look --for "a dog" [--title RE] [--conf X]` | YOLO-World detect objects by text prompt. |
134
- | `pctr lookclick --for "a dog" [--nth N]` | Detect then click the top match. |
135
- | `pctr pad <button\|stick\|trigger\|status>` | Virtual Xbox 360 gamepad via ViGEmBus. |
136
- | `pctr hid <action> --port COMx` | Serial-HID bridge to a real USB-HID microcontroller. |
137
- | `pctr mcp <start\|stop\|restart\|status\|help>` | Manage the MCP server (stdio or background http/sse). |
138
- | `pctr skill [--install]` / `pctr setup` | Print/install the agent skill + an AGENTS.md section. |
139
-
140
- Global flags: `--json` (machine-readable output) and `--file FILE` (run a batch
141
- of commands from a file).
142
-
143
- Common filters: `--title` (regex), `--name` (regex), `--control-type`
144
- (`Button`, `Edit`, `Pane`, `MenuItem`, …), `--auto-id`, `--nth`, `--process`.
145
-
146
- `click`, `set`, `wait` accept **any** selector - `--name` is not required.
147
- `tree`/`find` require a window selector (`--title` or `--process`).
148
-
149
- ## The loop
150
-
151
- ```bash
152
- pctr tree --title "Notepad" --limit 60 # see what's clickable
153
- pctr click --title "Notepad" --name "^File$"
154
- pctr set --title "Notepad" --name "Text Editor" --text "hello"
155
- ```
156
-
157
- ## JSON output
158
-
159
- Add `--json` (global, or per-command) for machine-readable output from
160
- `windows`, `tree`, `find`, `ocr`, `ocrfind`, `look`, and `desktop`:
161
-
162
- ```bash
163
- pctr windows --json
164
- # [{"hwnd":1234,"pid":5678,"title":"Untitled - Notepad"}, ...]
165
-
166
- pctr desktop list --json
167
- # [{"index":0,"guid":"ae37…","current":true,"windows":11}, ...]
168
-
169
- pctr look --for "a car" --json
170
- # [{"label":"a car","conf":0.86,"x":512,"y":300,"box":[400,220,620,380]}, ...]
171
- ```
172
-
173
- Progress and warnings go to stderr, so stdout stays valid JSON.
174
-
175
- ## Batch files
176
-
177
- ```bash
178
- pctr --file script.txt # one command per line; # comments and blanks skipped
179
- pctr --json --file script.txt # --json applies to every line
180
- ```
181
-
182
- Stops on the first failing line and reports its line number.
183
-
184
- ## Virtual desktops
185
-
186
- ```bash
187
- pctr desktop list # desktops (#index, guid, [current], window count)
188
- pctr desktop windows --desktop 1 # windows on desktop #1
189
- pctr desktop where --title "App" # which desktop a window is on
190
- pctr desktop on-current --title "App" # True / False
191
- pctr desktop next | prev | new | close # switch (moves the active desktop)
192
- pctr desktop move-window --title RE --to N
193
- ```
194
-
195
- - Switching uses the standard Win+Ctrl hotkeys and moves the **active** desktop
196
- (you included). Add `--direct` if a game has focus.
197
- - **Cross-desktop control:** UIA actions (`click --method invoke`, `set`) reach
198
- windows on another desktop **without switching** (accessibility layer, not
199
- screen input). Raw mouse/keys only affect the active desktop.
200
- - **`move-window` is blocked on Windows 11** (third-party `MoveWindowToDesktop`
201
- returns `Access denied`); it works on Windows 10.
202
-
203
- ## Raw input backends
204
-
205
- The OS flags `SendInput` events as injected, so anything built on user32 is
206
- detectable in principle. `pctr` can route raw mouse/keyboard through a different
207
- backend with `--backend`:
208
-
209
- | Backend | How | Detectable? |
210
- |---|---|---|
211
- | `pyautogui` (default / `sendinput`) | user32 `SendInput`, virtual keys | yes (`injected` flag) |
212
- | `pydirectinput` (or `--direct`) | `SendInput` with **scancodes** — better for DirectInput games | yes |
213
- | `serial-hid` | a **microcontroller presenting real USB HID** over serial | no — it *is* real hardware |
214
-
215
- ```bash
216
- pctr move --backend pydirectinput --x 800 --y 400
217
- pctr keyhold --key w --ms 400 --backend pydirectinput
218
- pctr type --text "hi" --backend serial-hid --port COM5 # needs a board
219
- ```
220
-
221
- Applies to `move` / `down` / `up` / `hold` / `keydown` / `keyup` / `keyhold` /
222
- `hotkey` / `drag` / `type` / `desktop`.
223
-
224
- ### Gamepad
225
-
226
- ```bash
227
- pip install "pctr[pad]" # + the ViGEmBus driver
228
- pctr pad button --name A --ms 150
229
- pctr pad button --name DPAD_UP --ms 100
230
- pctr pad stick --x 1.0 --y -0.5 --ms 400
231
- pctr pad trigger --right 1.0 --ms 300
232
- ```
233
-
234
- ### Serial-HID bridge
235
-
236
- Firmware protocol, newline-terminated ASCII:
237
- `MOVE x y` · `CLICK b` · `DOWN b` · `UP b` · `KEY k DOWN|UP` · `WRITE text` · `HOTKEY a+b`
238
-
239
- ```bash
240
- pip install "pctr[serial]"
241
- pctr hid ports # list COM ports
242
- pctr hid move --x 500 --y 300 --port COM5
243
- pctr hid type --text "hello" --port COM5
244
- ```
245
-
246
- ## Exit codes
247
-
248
- - `0` success, `1` no match / window not found, `2` usage/validation error.
249
- - Errors print one `error: …` line - no tracebacks.
250
-
251
- ## Notes
252
-
253
- - Prefer `set` (ValuePattern) over `type` for text fields - it can't drop or
254
- reorder characters. `type`'s element path uses pywinauto `type_keys`, which
255
- interprets `{ } + ^ % ~ ( )` as key syntax, so it is **not** literal.
256
- - Global `type` / `keys` go to the OS-focused window - `pctr focus` first.
257
- - Prefer `pctr hotkey --keys ctrl+s` over `keys "^s"`; the `^` modifier is
258
- fragile through shells.
259
- - `ocrfind`/`ocrclick` match one **word** at a time (Windows OCR tokenizes words).
260
- - `look` reads its model from `--model` or the `PCTR_YOLO_MODEL` env var; progress
261
- bars go to stderr so stdout stays parseable.
262
- - Qt apps expose a rich tree including embedded webviews. Electron apps expose
263
- only the window frame (use `focus` + global keys, or drive them over CDP).
264
- - Games ignore synthetic input - pass `--direct` to mouse/key commands and focus
265
- the game first.
266
-
267
- ## License
268
-
269
- MIT
1
+ Metadata-Version: 2.4
2
+ Name: pctr
3
+ Version: 0.5.0
4
+ Summary: PCTR - Personal Computer Tactile Response. Element-based UI Automation CLI for AI agents. Windows (UIA) and Linux X11 (AT-SPI2).
5
+ Author: UnixUserDev
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/UnixUserDev/pctr
8
+ Project-URL: Repository, https://github.com/UnixUserDev/pctr
9
+ Project-URL: Issues, https://github.com/UnixUserDev/pctr/issues
10
+ Keywords: automation,linux,windows,uia,atspi,x11,desktop,agent,computer-use,rpa
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: X11 Applications
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Classifier: Topic :: System :: Networking
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: mss>=9.0
27
+ Requires-Dist: pillow>=9.0
28
+ Provides-Extra: windows
29
+ Requires-Dist: pywinauto>=0.6.8; platform_system == "Windows" and extra == "windows"
30
+ Requires-Dist: uiautomation>=2.0; platform_system == "Windows" and extra == "windows"
31
+ Requires-Dist: pyautogui>=0.9.54; platform_system == "Windows" and extra == "windows"
32
+ Requires-Dist: pydirectinput>=1.0.4; platform_system == "Windows" and extra == "windows"
33
+ Provides-Extra: linux
34
+ Requires-Dist: python-xlib>=0.33; platform_system == "Linux" and extra == "linux"
35
+ Requires-Dist: dbus-next>=0.2.3; platform_system == "Linux" and extra == "linux"
36
+ Requires-Dist: rapidocr-onnxruntime>=1.3; platform_system == "Linux" and extra == "linux"
37
+ Provides-Extra: ocr
38
+ Requires-Dist: rapidocr-onnxruntime>=1.3; extra == "ocr"
39
+ Requires-Dist: pytesseract>=0.3.10; extra == "ocr"
40
+ Provides-Extra: vision
41
+ Requires-Dist: ultralytics>=8.2; extra == "vision"
42
+ Provides-Extra: mcp
43
+ Requires-Dist: mcp>=2; extra == "mcp"
44
+ Provides-Extra: pad
45
+ Requires-Dist: vgamepad>=0.1.0; platform_system == "Windows" and extra == "pad"
46
+ Provides-Extra: serial
47
+ Requires-Dist: pyserial>=3.5; extra == "serial"
48
+ Provides-Extra: all
49
+ Requires-Dist: pctr[linux,mcp,ocr,pad,serial,vision,windows]; extra == "all"
50
+ Dynamic: license-file
51
+
52
+ # pctr
53
+
54
+ **PCTR — Personal Computer Tactile Response**
55
+
56
+ Element-based UI automation CLI for AI agents. **Windows (UIA)** and **Linux X11 (AT-SPI2)**.
57
+
58
+ Instead of screenshotting and guessing pixel coordinates, `pctr` finds controls
59
+ by their **name / control type / automation id** and acts on them. Window moves,
60
+ DPI changes, and layout shifts don't break your script.
61
+
62
+ On Windows it is built on `pywinauto` (UI Automation) with `pyautogui` /
63
+ `pydirectinput` for raw mouse and keyboard. On Linux it uses AT-SPI2 (over the
64
+ accessibility D-Bus, no `pyatspi` needed), XTest (`python-xlib`) for input,
65
+ `mss`/compositor capture for screenshots, and `rapidocr`/tesseract for OCR.
66
+ On top of that it offers **OCR** and **YOLO-World** open-vocabulary detection for
67
+ text and objects that aren't exposed as controls.
68
+
69
+ ![pctr driving by element name: enumerating windows, mapping virtual desktops, dumping the control tree, filling a field, then reading it back with OCR](docs/demo.gif)
70
+
71
+ *Driven entirely by pctr itself - list windows, map virtual desktops, dump a
72
+ control tree, `set` a field, read it back with OCR, screenshot it, and YOLO-World
73
+ detect. Recorded with OBS, which pctr also piloted.*
74
+
75
+ ## Install
76
+
77
+ ### Windows
78
+
79
+ ```bash
80
+ pip install "pctr[windows]" # UIA + input
81
+ pip install "pctr[vision]" # + ultralytics, for `pctr look`
82
+ pip install "pctr[mcp]" # + MCP SDK, for the MCP server
83
+ pip install "pctr[pad]" # + vgamepad, for `pctr pad` (needs ViGEmBus)
84
+ pip install "pctr[serial]" # + pyserial, for `pctr hid` / the serial-hid backend
85
+ pip install "pctr[all]" # everything
86
+ ```
87
+
88
+ ### Linux (X11 / XWayland)
89
+
90
+ ```bash
91
+ pip install "pctr[linux]" # python-xlib + dbus-next + rapidocr
92
+ ```
93
+
94
+ Requires the accessibility bus to be running (`at-spi2-core`; on most desktops
95
+ it already is). Element targeting uses AT-SPI2, so **apps must expose
96
+ accessibility** (GTK/Qt/Electron do; many X11 toolkits and games do not).
97
+ Screenshots use `mss`; under Wayland it falls back to `spectacle` (KDE) or
98
+ `grim`. Global synthetic input goes through XTest, which works for X11/XWayland
99
+ apps — native Wayland clients are not reachable (protocol design). Use
100
+ `--backend auto|windows|x11|wayland` to pick explicitly.
101
+
102
+ ### Both
103
+
104
+ ```bash
105
+ pip install "pctr[ocr]" # rapidocr + pytesseract
106
+ ```
107
+
108
+ ## MCP server
109
+
110
+ `pctr` ships a Model Context Protocol server so MCP clients (Claude Desktop,
111
+ Cursor, opencode, ...) can drive the desktop directly:
112
+
113
+ ```bash
114
+ pip install "pctr[mcp]"
115
+ ```
116
+
117
+ Add it to your client config (stdio):
118
+
119
+ ```json
120
+ { "mcpServers": { "pctr": { "command": "pctr-mcp" } } }
121
+ ```
122
+
123
+ (or `"command": "python", "args": ["-m", "pctr.mcp_server"]`)
124
+
125
+ Tools exposed: `pctr_windows`, `pctr_tree`, `pctr_find`, `pctr_click`,
126
+ `pctr_set`, `pctr_type`, `pctr_keys`, `pctr_hotkey`, `pctr_focus`, `pctr_wait`,
127
+ `pctr_shot`, `pctr_ocr`, `pctr_ocrfind`, `pctr_ocrclick`, `pctr_look`,
128
+ `pctr_desktop`.
129
+
130
+ Or run/manage it from the CLI:
131
+
132
+ ```bash
133
+ pctr mcp status # SDK + server state
134
+ pctr mcp start --transport streamable-http --port 8765 # background HTTP server
135
+ pctr mcp restart | pctr mcp stop
136
+ pctr mcp help
137
+ ```
138
+
139
+ `stdio` runs in the foreground (what a client spawns); `sse` / `streamable-http`
140
+ run in the background, reachable at `http://127.0.0.1:8765/mcp` (or `…/sse`).
141
+
142
+ ## Commands
143
+
144
+ | Command | What it does |
145
+ |---------|--------------|
146
+ | `pctr windows [--filter RE] [--process PID]` | List top-level windows (`pid=… \| title`). |
147
+ | `pctr tree --title RE [--depth N] [--limit N]` | Dump the UIA control tree of a window. |
148
+ | `pctr find --title RE [--name RE] [--limit N] [--nth N]` | List matching elements. |
149
+ | `pctr click --title RE --name RE [--method auto\|invoke\|mouse] [--dbl]` | Click an element. |
150
+ | `pctr set --title RE --name RE --text S` | Set a field via the UIA ValuePattern (exact, instant). |
151
+ | `pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1]` | Send keystrokes (see notes). |
152
+ | `pctr keys --keys "{ENTER}"` | Send a global key combo (pywinauto syntax). |
153
+ | `pctr hotkey --keys ctrl+s` | Send a modifier combo (prefer this over `keys "^s"`). |
154
+ | `pctr focus --title RE` | Bring a window to the foreground. |
155
+ | `pctr wait --title RE --name RE [--timeout S]` | Wait until an element exists. |
156
+ | `pctr shot [--title RE] --out PATH` | Screenshot the screen or a window. |
157
+ | `pctr size` | Print primary screen size as `WxH`. |
158
+ | `pctr desktop <action>` | Virtual desktops: `list` / `windows` / `where` / `on-current` / `next` / `prev` / `new` / `close` / `move-window`. |
159
+ | `pctr move --x N --y N` / `down` / `up` / `hold --ms N` | Raw mouse control (`--direct` for games). |
160
+ | `pctr drag --start x,y --end x,y [--duration S]` | Drag between two points. |
161
+ | `pctr keydown / keyup --key a` / `keyhold --key w --ms 1500` | Hold keyboard keys (`--direct` for games). |
162
+ | `pctr ocr [--title RE] [--lang TAG]` | OCR the screen/window; list words with boxes. |
163
+ | `pctr ocrfind --text RE [--lang TAG]` | OCR then list matches with click centers (single words). |
164
+ | `pctr ocrclick --text RE [--nth N] [--lang TAG]` | OCR then click a word. |
165
+ | `pctr look --for "a dog" [--title RE] [--conf X]` | YOLO-World detect objects by text prompt. |
166
+ | `pctr lookclick --for "a dog" [--nth N]` | Detect then click the top match. |
167
+ | `pctr pad <button\|stick\|trigger\|status>` | Virtual Xbox 360 gamepad via ViGEmBus. |
168
+ | `pctr hid <action> --port COMx` | Serial-HID bridge to a real USB-HID microcontroller. |
169
+ | `pctr mcp <start\|stop\|restart\|status\|help>` | Manage the MCP server (stdio or background http/sse). |
170
+ | `pctr skill [--install]` / `pctr setup` | Print/install the agent skill + an AGENTS.md section. |
171
+
172
+ Global flags: `--json` (machine-readable output) and `--file FILE` (run a batch
173
+ of commands from a file).
174
+
175
+ Common filters: `--title` (regex), `--name` (regex), `--control-type`
176
+ (`Button`, `Edit`, `Pane`, `MenuItem`, …), `--auto-id`, `--nth`, `--process`.
177
+
178
+ `click`, `set`, `wait` accept **any** selector - `--name` is not required.
179
+ `tree`/`find` require a window selector (`--title` or `--process`).
180
+
181
+ ## The loop
182
+
183
+ ```bash
184
+ pctr tree --title "Notepad" --limit 60 # see what's clickable
185
+ pctr click --title "Notepad" --name "^File$"
186
+ pctr set --title "Notepad" --name "Text Editor" --text "hello"
187
+ ```
188
+
189
+ ## JSON output
190
+
191
+ Add `--json` (global, or per-command) for machine-readable output from
192
+ `windows`, `tree`, `find`, `ocr`, `ocrfind`, `look`, and `desktop`:
193
+
194
+ ```bash
195
+ pctr windows --json
196
+ # [{"hwnd":1234,"pid":5678,"title":"Untitled - Notepad"}, ...]
197
+
198
+ pctr desktop list --json
199
+ # [{"index":0,"guid":"ae37…","current":true,"windows":11}, ...]
200
+
201
+ pctr look --for "a car" --json
202
+ # [{"label":"a car","conf":0.86,"x":512,"y":300,"box":[400,220,620,380]}, ...]
203
+ ```
204
+
205
+ Progress and warnings go to stderr, so stdout stays valid JSON.
206
+
207
+ ## Batch files
208
+
209
+ ```bash
210
+ pctr --file script.txt # one command per line; # comments and blanks skipped
211
+ pctr --json --file script.txt # --json applies to every line
212
+ ```
213
+
214
+ Stops on the first failing line and reports its line number.
215
+
216
+ ## Virtual desktops
217
+
218
+ ```bash
219
+ pctr desktop list # desktops (#index, guid, [current], window count)
220
+ pctr desktop windows --desktop 1 # windows on desktop #1
221
+ pctr desktop where --title "App" # which desktop a window is on
222
+ pctr desktop on-current --title "App" # True / False
223
+ pctr desktop next | prev | new | close # switch (moves the active desktop)
224
+ pctr desktop move-window --title RE --to N
225
+ ```
226
+
227
+ - Switching uses the standard Win+Ctrl hotkeys and moves the **active** desktop
228
+ (you included). Add `--direct` if a game has focus.
229
+ - **Cross-desktop control:** UIA actions (`click --method invoke`, `set`) reach
230
+ windows on another desktop **without switching** (accessibility layer, not
231
+ screen input). Raw mouse/keys only affect the active desktop.
232
+ - **`move-window` is blocked on Windows 11** (third-party `MoveWindowToDesktop`
233
+ returns `Access denied`); it works on Windows 10.
234
+
235
+ ## Raw input backends
236
+
237
+ The OS flags `SendInput` events as injected, so anything built on user32 is
238
+ detectable in principle. `pctr` can route raw mouse/keyboard through a different
239
+ backend with `--backend`:
240
+
241
+ | Backend | How | Detectable? |
242
+ |---|---|---|
243
+ | `pyautogui` (default / `sendinput`) | user32 `SendInput`, virtual keys | yes (`injected` flag) |
244
+ | `pydirectinput` (or `--direct`) | `SendInput` with **scancodes** — better for DirectInput games | yes |
245
+ | `serial-hid` | a **microcontroller presenting real USB HID** over serial | no — it *is* real hardware |
246
+
247
+ ```bash
248
+ pctr move --backend pydirectinput --x 800 --y 400
249
+ pctr keyhold --key w --ms 400 --backend pydirectinput
250
+ pctr type --text "hi" --backend serial-hid --port COM5 # needs a board
251
+ ```
252
+
253
+ Applies to `move` / `down` / `up` / `hold` / `keydown` / `keyup` / `keyhold` /
254
+ `hotkey` / `drag` / `type` / `desktop`.
255
+
256
+ ### Gamepad
257
+
258
+ ```bash
259
+ pip install "pctr[pad]" # + the ViGEmBus driver
260
+ pctr pad button --name A --ms 150
261
+ pctr pad button --name DPAD_UP --ms 100
262
+ pctr pad stick --x 1.0 --y -0.5 --ms 400
263
+ pctr pad trigger --right 1.0 --ms 300
264
+ ```
265
+
266
+ ### Serial-HID bridge
267
+
268
+ Firmware protocol, newline-terminated ASCII:
269
+ `MOVE x y` · `CLICK b` · `DOWN b` · `UP b` · `KEY k DOWN|UP` · `WRITE text` · `HOTKEY a+b`
270
+
271
+ ```bash
272
+ pip install "pctr[serial]"
273
+ pctr hid ports # list COM ports
274
+ pctr hid move --x 500 --y 300 --port COM5
275
+ pctr hid type --text "hello" --port COM5
276
+ ```
277
+
278
+ ## Exit codes
279
+
280
+ - `0` success, `1` no match / window not found, `2` usage/validation error.
281
+ - Errors print one `error: …` line - no tracebacks.
282
+
283
+ ## Notes
284
+
285
+ - Prefer `set` (ValuePattern) over `type` for text fields - it can't drop or
286
+ reorder characters. `type`'s element path uses pywinauto `type_keys`, which
287
+ interprets `{ } + ^ % ~ ( )` as key syntax, so it is **not** literal.
288
+ - Global `type` / `keys` go to the OS-focused window - `pctr focus` first.
289
+ - Prefer `pctr hotkey --keys ctrl+s` over `keys "^s"`; the `^` modifier is
290
+ fragile through shells.
291
+ - `ocrfind`/`ocrclick` match one **word** at a time (Windows OCR tokenizes words).
292
+ - `look` reads its model from `--model` or the `PCTR_YOLO_MODEL` env var; progress
293
+ bars go to stderr so stdout stays parseable.
294
+ - Qt apps expose a rich tree including embedded webviews. Electron apps expose
295
+ only the window frame (use `focus` + global keys, or drive them over CDP).
296
+ - Games ignore synthetic input - pass `--direct` to mouse/key commands and focus
297
+ the game first.
298
+
299
+ ## License
300
+
301
+ MIT
302
+
303
+ ---
304
+
305
+ AI-generated with [OpenCode](https://opencode.ai) — primary model: DeepSeek V4.1 Flash.