Files
cadcraft/docs/control-protocol.md
T
Valentin KleinandClaude Opus 5.5 54d75a1623 Control channel: cmdline.key replies like cmdline.input (fixes #360) (#361)
When Enter was refused (Enter repeats a command that isn't available),
headless failed the cmdline.key call while the app dropped the error,
and pressing Enter in the app's command line echoes it. Both now echo
the error to the history and reply with the command-line state, the
`output` lines the key added and `error` only when Enter was refused,
the shape #351 gives cmdline.input.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:09:26 -04:00

5.3 KiB

CADCraft control protocol

Start the desktop app with --control PORT (or CADCRAFT_CONTROL_PORT=PORT). It listens on 127.0.0.1:PORT for JSON lines: one request per line, one reply per line.

{"id": 1, "method": "cmdline.input", "params": {"text": "circle 0,0 5"}}
{"id": 1, "ok": true, "result": {"prompt": "Command:", "output": ["Command: CIRCLE"], ...}}

Errors come back as {"id": …, "ok": false, "error": "message"}.

The server waits up to 60 s for the app to answer a request. After that it replies with a timeout, and state tells whether sending the request again is safe:

  • {"id": …, "ok": false, "error": "timeout", "state": "not-run"}: the app had not started the request. It is cancelled and will never run, so sending it again is safe.
  • {"id": …, "ok": false, "error": "timeout", "state": "may-have-run"}: the app had already started it. It may still finish and change the drawing, and its late result is discarded. Inspect the drawing (drawing.inspect) before sending it again.
Method Params What it does
cmdline.input {text} Type a line at the command line exactly like a user. Starts commands by name/alias and answers prompts (points x,y, @dx,dy, @d<a, distances, keywords, empty = Enter). Spaces act as Enter except at text prompts.
cmdline.script {text} Run a multi-line script (like .scr files).
cmdline.key {key: "enter"|"escape"} Press Enter or Escape. Replies like cmdline.input: the state, output, and error when Enter was refused.
cmdline.state Current prompt, keywords, accepted input kinds, history tail.
engine.execute {command, params} Run any command with JSON parameters. Never opens a dialog.
engine.commands Every command: id, label, menu path, shortcut, aliases, params doc, enabled.
drawing.inspect {entities?, limit?} Drawing summary (counts, extents, layers, styles, blocks, layouts, selection, undo) and entities with handles and geometry.
ui.inspect UI state, canvas rect, view, cursor and snap, performance counters; closePrompt: the drawing a "Save changes?" prompt is asking about (null when none).
ui.menu.list / ui.menu.invoke {command} The menu tree; invoke an item like a click (interactive).
ui.pointer {x, y, space?: "world"|"screen", button?, action?: "click"|"move"} Click in the drawing area (world coordinates by default). Right button = Enter.
ui.click / ui.move {x, y, button?, shift?} Real egui pointer events in screen points (reach every widget).
ui.drag {x, y, to: [x, y], button?} Real press, move and release (drag the command line's top edge, the ViewCube ring…).
ui.key / ui.text {key, cmd?, shift?, alt?} / {text} Synthetic keyboard input.
ui.set UiState fields Show/hide palettes, toolbars, command line, ViewCube…; theme: "system"|"light"|"dark" (also engine.execute with ui.theme {theme}); ui.inspect reports the theme shown (theme: light/dark). saveFormat: "dxf"|"dwg" (also ui.saveformat {format}) is the format Save As suggests for new drawings.
ui.resize {width, height} Resize the window.
ui.screenshot {path?} PNG of the window (needs a presented frame).
ui.render {path?, width?, height?, fit?} Headless render. fit (default true) frames the whole drawing; false uses the current view. Replies {pngBase64, width, height, bytes, path?}.
app.open / app.save / app.quit {path} File operations.

The MCP server (cadcraft-cli mcp --connect 127.0.0.1:PORT) wraps this protocol; headless (cadcraft-cli mcp) it serves the same methods from an in-process session.

Command-line replies

The app and the headless session reply with the same fields.

cmdline.state, cmdline.key and cmdline.input return the command-line state:

Field Value
prompt The prompt text ("Command:" when idle).
running The id of the running command, or null.
keywords The current prompt's options.
accept The kinds of input the current prompt accepts, or null when idle.
buffer Text typed in the command line but not yet submitted (always "" headless).
history The last 20 history lines.
historyExpanded Whether the history is expanded (always false headless).

cmdline.input adds:

  • output: the history lines the input added. This includes the error message when the line was refused.
  • error: the error message, only when the line was refused (unknown command, invalid input…).

A refused line still replies "ok": true, because the text was typed. Check error.

app.open without a string path fails with "missing path" before any command runs.

Interface language

Select a language with engine.execute and the UI command ui.language:

{"id":1,"method":"engine.execute","params":{"command":"ui.language","params":{"lang":"uk"}}}

Supported values are auto, en and uk. ui.set also accepts {"interfaceLanguage":"uk"}; ui.inspect reports the saved choice in its UI state. Invalid choices leave the current language unchanged. Menus, command labels and prompt text returned by the control channel retain their English source text. See localization.md.