Files
wordcraft/docs/control-protocol.md
T
780fd7a5af Control server closes on non-JSON input; hyperlinks follow only http/https/mailto (#34) (#72)
The control server answered a line that wasn't JSON with an error and kept
reading, so an HTTP request to the port (for example a web page's fetch to
127.0.0.1) could carry a command such as file.saveAs in its body. It now
replies once and closes the connection on the first bad line; HTTP request
lines never parse, so nothing after them runs.

Ctrl/Cmd+click on a hyperlink passed any URL to the system. It now follows
only http, https and mailto links, and shows a status message otherwise, so
a document can't launch file: paths or custom-scheme handlers.

Tests cover JSON requests, closing on bad JSON, an HTTP POST carrying a
command, and the link allow-list. The control protocol docs note the
closing behaviour.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:16:28 -04:00

36 lines
2.1 KiB
Markdown

# Control protocol
Start the app with a control port: `wordcraft --control 7981` (or `WORDCRAFT_CONTROL_PORT=7981`).
It listens on `127.0.0.1` only. Send one JSON object per line; each gets one reply line:
```json
{"id": 1, "method": "engine.execute", "params": {"command": "text.insert", "params": {"text": "Hello"}}}
{"id": 1, "ok": true, "result": {"anchor": {...}, "focus": {...}}}
```
Errors come back as `{"id": …, "ok": false, "error": "message"}`. A failed command leaves the
document unchanged. A line that isn't valid JSON gets one error reply, then the server closes the
connection (so an HTTP request sent to the port can't run a command); reconnect to continue.
## Methods
| Method | Params | Result |
|---|---|---|
| `engine.execute` | `command`, `params` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog`, `ui.backstage`, `ui.language` (`{"value": "auto"|"en"|"zh-hans"|"zh-hant"|"ja"}`)…) |
| `engine.commands` | — | every command: id, label, location, shortcut, params, enabled |
| `document.inspect` | `text?` | blocks (text, style, runs, lists, tables), parts, sections, selection, pages |
| `ui.inspect` | — | UI state, view, dialog, window size, page rects on screen, caret, perf |
| `ui.click` | `x`, `y`, `button?`, `count?`, `shift?`, `cmd?`, `alt?` | real pointer input at window coordinates |
| `ui.clickText` | `page` (0-based), `x`, `y` (points from the page's top-left), `count?` | click inside a page |
| `ui.move` / `ui.drag` | `x`,`y` / `x`,`y`,`toX`,`toY`,`steps?` | pointer move / drag |
| `ui.key` | `key` (`B`, `Enter`, `Left`…), `shift?`, `alt?`, `cmd?` | key press through egui (shortcuts apply) |
| `ui.text` | `text` | typed text through egui |
| `ui.screenshot` | `path?` | PNG of the window |
| `ui.render` | `path`, `page?` (1-based), `scale?` | PNG of a page, rendered by the engine |
| `ui.parity` | — | feature-catalog parity |
| `ui.resize` / `ui.focus` / `app.quit` | | window control |
Any other method name is treated as a command id: `{"method": "format.bold"}` works.
Positions are `{"story": "body" | {"part": n}, "path": [block, row, cell, block, …], "off": byteOffset}`.