docs(automation): document the native entry points and their limits

Refs #1349. The automation README describes `--mcp` and never mentions
`--serve`, even though every native build accepts it and it is the only channel
that works without a display. It also leaves out the two startup dialogs, the
`document_id` routing, and what `waiting_input` actually means.

`docs/automation/native.md` covers, all measured on a Linux headless machine
with `OpenCADStudio 2026.37+165`:

- choosing between `--mcp` and `--serve`, and what each needs (display,
  no-display, `--export` for one-shot conversion);
- the writable `HOME`/`XDG_CONFIG_HOME` requirement, without which
  `ocs_sessions` answers `No user configuration directory`;
- the startup dialogs: `AssocPrompt` then `DonationPrompt`, `new` answering
  `ok` with no document until `close_modal` clears them, and that the donation
  prompt appears once per application version per profile;
- targeting a document: reads and writes follow the active tab, so
  `document_id` has to be passed explicitly after `new`;
- feeding a whole line: `waiting_input` means more input is required (not that
  nothing happened), `cancel` closes a command, and the `unconsumed` /
  `blocked_by` fields added in #1392;
- the interactive `start`/`input` shapes, including that a typed value goes
  through `kind: "text"` while a keyword uses `kind: "token"` with its payload
  in a field named `text`, and that the `TEXT` content step belongs to the
  in-place editor (`text_input` / `text_commit`);
- what only native builds have: `measure`, `ocs_capture` (inline base64, or a
  PNG path), and `set_properties` with `collection` and colours as
  `{"Index": n}` / `"ByLayer"`;
- the remaining gap: `MTEXT`, `HATCH` and `POLYGON` report `completed` with
  `added: 0` on the `--serve` path (#1390).

The README gains one line pointing to the page, placed after the tool list
rather than at the end, so it does not overlap #1394.

Docs only; no code changes.
This commit is contained in:
buyun14 committed 2026-09-20 19:21:49 +08:00
1 parent 70d7e9a31f
commit 747245a75f
2 files changed
+111

No files matched your search

+4
View File
@@ -17,6 +17,10 @@ The server provides four tools:
- `ocs_execute` performs one operation, an atomic record update, or a sequential batch against the real editor.
- `ocs_capture` returns a bounded PNG of the drawing viewport or complete window.
Native builds also accept a second, headless entry point (`OpenCADStudio --serve`, one
JSON request per line) for scripts and CI; the two native channels, their startup dialogs
and their limits are described in [native.md](native.md).
The normal flow is to call `ocs_sessions`, pass its returned `session_id` as `ocs_session_id` to the other tools, read the chosen session with `ocs_read`, then pass the returned document state into `ocs_execute`. For example, an undo request has this shape:
```json
+107
View File
@@ -0,0 +1,107 @@
# Driving the native builds
Every native build exposes the same control core through two entry points: the MCP
server described in the [README](README.md) (`OpenCADStudio --mcp`), and a headless
JSON-line channel meant for scripts and CI (`OpenCADStudio --serve`). The operations,
request envelope and replies are the same; this page covers what is specific to the
native side. The browser entry is described in [web.md](web.md).
## Choosing a channel
| | `--mcp` | `--serve` |
|---|---|---|
| Transport | MCP over stdio | one JSON request per line on stdin, one reply per line on stdout |
| Editor | starts the desktop editor and exposes the live document | no window; works on its own document |
| Needs a display | yes — on a headless machine `xvfb-run` works, on software rendering the viewport may show the GPU warning dialog | no |
| `measure`, `ocs_capture`, `set_properties`, interactive steps | available | `measure` and `capture` are not; the remaining operations are |
| Good for | an MCP client driving a real editor | batch jobs, CI, agents without a display |
`OpenCADStudio --export IN OUT` converts a drawing in one shot, without any session. It
accepts `.dwg` and `.dxf` targets only.
## Requirements
The process needs a writable `HOME` (or `XDG_CONFIG_HOME`) — without one, `ocs_sessions`
answers `No user configuration directory` and nothing else works.
## Startup dialogs
A fresh profile opens two dialogs before any drawing exists, and both of them block
document creation:
| Step | `ocs_sessions` reports |
|---|---|
| right after launch | `modal: "AssocPrompt"` |
| after `close_modal` | `modal: "DonationPrompt"` |
| after a second `close_modal` | `modal: null` |
While a dialog is open, `{"op":"new"}` answers `ok` but no tab is created, so there is
no `document_id` to work with. Close the dialogs first:
```python
while (info := sessions())["modal"]:
execute({"op": "action", "name": "close_modal", "document_id": info["document_id"]})
```
The donation prompt appears once per application version per profile, so an automated
profile meets it once per version rather than on every run. See
[web.md](web.md) for the same pattern on the browser side.
## Targeting a document
Reads and mutations target the active tab. `new` creates a document but does not move
the active tab, so pass the `document_id` from the reply explicitly — without it, reads
answer empty and it looks like nothing was created. `activate` switches the active tab.
A client connection to `--mcp` starts a new editor instance, so session state does not
carry over between connections.
## Feeding a whole command line
`run` takes one complete command line, including the answers to every prompt, and then
terminates the command as if Enter were pressed. A prompt cannot be continued on a
following line — each line is parsed as a fresh command line, so a command left waiting
is replaced by the next one (send `{"op":"cancel"}` if you want to drop it explicitly).
`waiting_input` means input is still required — not that nothing happened. `SPLINE` and
`ARC`, for example, keep accepting further input even after the entity exists, and
`zoom_extents` reports it while the view has already changed. Send `{"op":"cancel"}` to
close the command when the line is done with it.
Two reply fields describe what happened to the line:
| Field | Meaning |
|---|---|
| `unconsumed` | Tokens that no prompt ever asked for, in order. `CIRCLE 0,0 5 9` reports `["9"]`, because the radius step ends the command; a fully consumed line reports `[]`. |
| `blocked_by` | What *this line* left open: `"command"`, `"text_editor"`, `"mtext_editor"`, `"modal:<Kind>"`, or `null`. Set whenever `status` is `waiting_input`. |
`TEXT 0,0 5 0 hello world` therefore creates one `Text` entity that holds
`hello world`, reports `completed` with `added: 1`, and leaves nothing unconsumed. The
content step runs in the in-place editor, which the feeder commits and then closes so
that the command does not stay armed for a second line.
## Interactive steps
For commands whose answers cannot be written on one line, use `start` and then `input`.
`start` replies with `state.command`, whose `accepts`, `options`, `prompt` and
`input_example` say what the current step wants:
| Step | Request | Note |
|---|---|---|
| a point | `{"op":"input","kind":"point","point":[x,y,0],"space":"wcs"}` | the point is an array; separate `x`/`y` fields are rejected |
| a typed value (height, rotation) | `{"op":"input","kind":"text","text":"5"}` | digits go through `text`, not `token`; `{"op":"input","kind":"enter"}` accepts the default |
| a keyword (`J`, `ST`, `C`) | `{"op":"input","kind":"token","text":"C"}` | the payload field is named `text` |
| the text of `TEXT`/`MTEXT` | `{"op":"action","name":"text_input","value":"hello"}` then `{"op":"action","name":"text_commit"}` | the command has already ended at that point and the editor owns the input |
## Native-only operations
| Operation | Device |
|---|---|
| `measure` | `ocs_read` with `op: "measure"` and `parameters.handles` returns kernel-computed length, area, bounds and mass properties. |
| `ocs_capture` | Returns the viewport or the whole window. Either an inline base64 image, or a PNG written under the temporary directory with the reply carrying its `path`. |
| `set_properties` | Mutates record properties. It requires `collection`, and colours are the serialised enum — `{"Index": 1}` or `"ByLayer"`; a colour name such as `"Red"` is rejected with `invalid_value`. |
## Known gaps
On the `--serve` path, `MTEXT`, `HATCH` and `POLYGON` report `completed` with
`added: 0`: they need interactive surfaces (a pattern choice, a boundary selection) that
a single command line has no tokens to fill. `TEXT` no longer belongs to that list.