mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-29 21:07:24 -06:00
W1: MCP, control channel and CLI address an open document by id
- MCP: execute, batch, type_text, select_text, get_text, inspect_document, render_page and save_document take an optional doc (an open document's id); the headless server runs them through Session::with_document and the bridge passes doc to the app. New list_documents tool; new_document replies with the new document's id. Tool descriptions and the server instructions say that file.new and file.open open another document. - Control channel: engine.execute, document.inspect and ui.render take doc and work on that document while the window keeps the active one; file.close on an addressed document refuses unsaved changes unless forced; ui.* commands need the document on screen. ui.inspect reports document and documents. Unknown or malformed ids are errors. - CLI run starts with one open document; help and docs describe how file.new, file.open and view.switchWindows work in a run. - docs/mcp.md and docs/control-protocol.md describe document ids and doc.
This commit is contained in:
@@ -52,10 +52,10 @@ and set `WORDCRAFT_CONTROL_KEY` to that app's key.
|
||||
|
||||
| Method | Params | Result |
|
||||
|---|---|---|
|
||||
| `engine.execute` | `command`, `params` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog` (`{"name": dialog, "part"?: table style part}`; `part` opens New / Modify Table Style (`newTableStyle` / `modifyTableStyle`) on that part: `wholeTable`, `headerRow`, `firstColumn`, `bandedRows`, `nwCell`… as `table.modifyStyle` names them), `ui.backstage`, `ui.zotero.*` (see `docs/zotero.md`), `ui.language` (`{"value": "auto"\|"en"\|"zh-hans"\|"zh-hant"\|"ja"\|"pt-br"\|"es"\|"uk"\|"sr"\|"sr-latn"\|"et"\|"tr"\|"ru"\|"pl"\|"de"\|"it"\|"cs"\|"hu"\|"fr"}`), `ui.theme` (`{"value": "system"\|"light"\|"dark"}`; `system` follows the OS appearance, light when it reports none), `ui.interfaceSize` (`{"value": "auto"}` or `{"percent": 50–300}`, clamped; no value reads it; `auto` keeps the system display scale, a percentage is absolute, so 100 is one screen pixel per point (#822); the result's `percent` is the size drawn at and `auto` whether it follows the system; document zoom stays `view.zoom`), `ui.animations` (`{"value": "system"\|"on"\|"off"}`; no value reads it; `system` follows the system's animation setting, which the desktop app reads on Windows, macOS, Linux and the BSDs and the web app from `prefers-reduced-motion`, and means on where the system doesn't say; the result's `enabled` is what the setting comes to and `system` what the system reports, `null` when it doesn't say), `ui.fileBrowser` (desktop: `{"purpose": "document"\|"picture"\|"recipients"\|"save"}` opens WordCraft's own file browser, for when the system file dialog doesn't show up, #576), `ui.saveAsTemplate` (File › Save As › Save as Word Template: the Save dialog for a `.dotx`, starting in the user templates folder; the result's `dir` is that folder, `null` on the web, #2852), `ui.caretAnimation` (`{"value": true\|false}`; no value reads it; off, the caret jumps and blinks on and off instead of gliding and fading), `ui.startScreen` (`{"value": true\|false}`; no value reads it; on, the default, the desktop and web apps start on File › Home when launched with nothing to open, #766), `ui.alignmentGuides` (`{"value": true\|false}`; no value reads it; on, the default, moving a picture or shape snaps it to the page, the margins and other objects, with guide lines while dragging; Alt moves freely), `ui.quickAccessToolbar` (`{"value": true\|false}`; no value toggles it; false takes the Quick Access Toolbar out of the title bar, as its menu's Hide Quick Access Toolbar does, and true brings it back, #1945), `ui.focusRibbon` (`{"value": true\|false}`; no value toggles it; in Focus, true drops the ribbon down over the page, as the bar's ⋯ does, and false puts it away, as a click on the page does; outside Focus it stays put away, #1678), `ui.contextMenu` (opens the page's context menu below the caret, as Shift+F10 does)…) |
|
||||
| `engine.execute` | `command`, `params`, `doc?` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog` (`{"name": dialog, "part"?: table style part}`; `part` opens New / Modify Table Style (`newTableStyle` / `modifyTableStyle`) on that part: `wholeTable`, `headerRow`, `firstColumn`, `bandedRows`, `nwCell`… as `table.modifyStyle` names them), `ui.backstage`, `ui.zotero.*` (see `docs/zotero.md`), `ui.language` (`{"value": "auto"\|"en"\|"zh-hans"\|"zh-hant"\|"ja"\|"pt-br"\|"es"\|"uk"\|"sr"\|"sr-latn"\|"et"\|"tr"\|"ru"\|"pl"\|"de"\|"it"\|"cs"\|"hu"\|"fr"}`), `ui.theme` (`{"value": "system"\|"light"\|"dark"}`; `system` follows the OS appearance, light when it reports none), `ui.interfaceSize` (`{"value": "auto"}` or `{"percent": 50–300}`, clamped; no value reads it; `auto` keeps the system display scale, a percentage is absolute, so 100 is one screen pixel per point (#822); the result's `percent` is the size drawn at and `auto` whether it follows the system; document zoom stays `view.zoom`), `ui.animations` (`{"value": "system"\|"on"\|"off"}`; no value reads it; `system` follows the system's animation setting, which the desktop app reads on Windows, macOS, Linux and the BSDs and the web app from `prefers-reduced-motion`, and means on where the system doesn't say; the result's `enabled` is what the setting comes to and `system` what the system reports, `null` when it doesn't say), `ui.fileBrowser` (desktop: `{"purpose": "document"\|"picture"\|"recipients"\|"save"}` opens WordCraft's own file browser, for when the system file dialog doesn't show up, #576), `ui.saveAsTemplate` (File › Save As › Save as Word Template: the Save dialog for a `.dotx`, starting in the user templates folder; the result's `dir` is that folder, `null` on the web, #2852), `ui.caretAnimation` (`{"value": true\|false}`; no value reads it; off, the caret jumps and blinks on and off instead of gliding and fading), `ui.startScreen` (`{"value": true\|false}`; no value reads it; on, the default, the desktop and web apps start on File › Home when launched with nothing to open, #766), `ui.alignmentGuides` (`{"value": true\|false}`; no value reads it; on, the default, moving a picture or shape snaps it to the page, the margins and other objects, with guide lines while dragging; Alt moves freely), `ui.quickAccessToolbar` (`{"value": true\|false}`; no value toggles it; false takes the Quick Access Toolbar out of the title bar, as its menu's Hide Quick Access Toolbar does, and true brings it back, #1945), `ui.focusRibbon` (`{"value": true\|false}`; no value toggles it; in Focus, true drops the ribbon down over the page, as the bar's ⋯ does, and false puts it away, as a click on the page does; outside Focus it stays put away, #1678), `ui.contextMenu` (opens the page's context menu below the caret, as Shift+F10 does)…) |
|
||||
| `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, comment balloons (`balloons`: id + screen rect; `balloon`: the selected one), perf |
|
||||
| `document.inspect` | `text?`, `doc?` | blocks (text, style, runs, lists, tables), parts, sections, selection, pages |
|
||||
| `ui.inspect` | — | UI state, view, dialog, window size, page rects on screen, caret, comment balloons (`balloons`: id + screen rect; `balloon`: the selected one), perf; `document`: the active document's id; `documents`: the open documents (`id`, `title`, `path`, `dirty`, `active`) |
|
||||
| `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 |
|
||||
@@ -64,12 +64,36 @@ and set `WORDCRAFT_CONTROL_KEY` to that app's key.
|
||||
| `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: written to `path`, or without one returned as base64 `png` in the reply |
|
||||
| `ui.render` | `path?`, `page?` (1-based), `scale?` | PNG of a page, rendered by the engine: written to `path`, or without one returned as base64 `png` in the reply (never a fixed name in the shared temporary folder) |
|
||||
| `ui.render` | `path?`, `page?` (1-based), `scale?`, `doc?` | PNG of a page, rendered by the engine: written to `path`, or without one returned as base64 `png` in the reply (never a fixed name in the shared temporary folder) |
|
||||
| `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.
|
||||
|
||||
### Open documents
|
||||
|
||||
Several documents can be open at once. Each has an id, a whole number that stays the same while
|
||||
the document is open and is never reused by the running app. One document is the active one: the
|
||||
window shows it, and commands work on it unless told otherwise.
|
||||
|
||||
- `file.new` opens another document and makes it active. `file.open` does the same with a file;
|
||||
it replaces a blank document nobody has used yet, and a file that is already open becomes
|
||||
active again (`"alreadyOpen": true`). Its result carries the document's `id`.
|
||||
- `view.switchWindows` lists the open documents (`{"documents": [{"id", "title", "path",
|
||||
"dirty", "active"}]}`); with `{"id": n}` it makes that document active.
|
||||
- `file.closeDocument {"id"?, "force"?}` closes a document without touching the window.
|
||||
- `engine.execute`, `document.inspect` and `ui.render` take `doc`, the id of the document to work
|
||||
on, next to their other params. The command runs on that document and the window keeps showing
|
||||
the active one:
|
||||
|
||||
```json
|
||||
{"id": 2, "key": "…", "method": "engine.execute", "params": {"command": "text.insert", "params": {"text": "Hello"}, "doc": 3}}
|
||||
```
|
||||
|
||||
`doc` left out, or `null`, is the active document. An id that names no open document, or a value
|
||||
that isn't a whole number from 0 up, is an error and nothing runs. Window commands (`ui.*`) work
|
||||
on the document on screen only: with the id of another document they are an error.
|
||||
|
||||
The `CharProps` and `ParaProps` that `format.set`, `para.set` and `styles.modify` take are listed
|
||||
in [MCP server](mcp.md#character-and-paragraph-properties).
|
||||
|
||||
@@ -94,7 +118,9 @@ stay); save first (`file.save`), or pass `{"force": true}` to discard the change
|
||||
The bare form `{"method": "file.close", "force": true}` (a method name that is a command id) is
|
||||
guarded the same way, and only a boolean `true` counts as `force`. A document without unsaved
|
||||
changes closes at once (#1268). Closing closes the document, not the app: the window stays and
|
||||
shows no page, and `file.new` or `file.open` goes on from there (#1712).
|
||||
shows no page, and `file.new` or `file.open` goes on from there (#1712). With `doc` naming a
|
||||
document in the background, `file.close` closes that document under the same rule (unsaved
|
||||
changes need `{"force": true}`), and the window keeps showing the active one.
|
||||
|
||||
### Native printing
|
||||
|
||||
|
||||
Reference in New Issue
Block a user