# Control protocol `solvecraft --control PORT` (or `SOLVECRAFT_CONTROL_PORT=PORT`) listens on `127.0.0.1:PORT` for JSON lines. Each request is one line `{"id": …, "method": "…", "params": {…}}`; each reply is one line `{"id": …, "ok": true, "result": …}` or `{"id": …, "ok": false, "error": "…"}`. Requests are handled on the UI thread between frames; pointer and keyboard input is injected as real egui events, one pointer event per frame. ## Engine | Method | Params | Result | |---|---|---| | `engine.execute` | `command`: command id, `params`: JSON object | the command's result | | `engine.script` | `commands`: `[{command, params}, …]` | results, stops at the first error | | `engine.commands` | — | every command: id, label, tab, panel, icon, shortcut, params doc, enabled | | `document.inspect` | `measure?`: bool | parameters, timeline (errors, timings), bodies, sketches, selection | Commands never open dialogs when run this way. `solvecraft-cli commands` prints the same registry. ## UI | Method | Params | Notes | |---|---|---| | `ui.inspect` | — | UI state, what is hidden (`visibility`: bodies, origin and construction items, sketches; set it with the `browser.visibility` command), viewport rect, camera, active tool/dialog, hover, renderer, timings, live preview (`active`, `busy`, `error`, `ms`, bodies it stands in for) | | `ui.set` | any `UiState` field (`tab`, `perspective`, `showGrid`, `dark`, …), and `hiddenBodies`, `hiddenOrigin`, `hiddenSketches`, `shownSketches` (whole lists; `browser.visibility` changes single items) | `dark: false` switches to the light theme | | `ui.view` | `view`: `front`, `back`, `top`, `bottom`, `left`, `right`, `iso`, `home`, `fit`; `animate` (bool, default false: snap) | | | `ui.start` | `command` | like a toolbar click: starts the sketch tool or dialog for the command | | `ui.click` | `x`, `y` (screen points), `button?`: left/right/middle, `shift?`, `ctrl?`, `double?` | real pointer input | | `ui.move` | `x`, `y` | | | `ui.drag` | `x0`, `y0`, `x1`, `y1`, `button?`, `shift?`, `ctrl?`, `steps?`, `hold?`, `hold_end?` | press, move, release; a left drag on the model is a box selection (left to right: window, right to left: crossing); `hold` keeps the button down that many frames first (a right press-and-hold opens the marking menu, releasing over a direction picks it), `hold_end` that many frames at the end (a sketch drag shows its snap) | | `ui.menu` | `x?`, `y?`, `target?`, `close?` | with a point: open a context menu there as a right-click would (the viewport's marking menu, or a browser menu with `target: {"type": "body", "name"} \| {"type": "sketch", "id"} \| {"type": "component", "id"}`); without: the open menu (`radial` and `items`: id, label, shortcut, enabled) | | `ui.menuPick` | `item`: id or label | runs an item of the open menu, as clicking it does | | `ui.rename` | `text?`, `commit?` (default true) | finishes the rename box a Rename item opened | | `ui.selection` | | the selection, the open dialog's inputs, the hovered item, and the active sketch's drawn dimensions (`dimensions`: id and text centre, `dimension_selected`, `dimension_editing`) | | `ui.worldToScreen` | `points`: `[[x, y, z]…]` world points | their screen points | | `ui.triad` | | screen points of the 3D sketch move triad's handles (`x`, `y`, `z`, `plane`) | | `ui.sketchToScreen` | `points`: `[[x, y]…]` in active-sketch coordinates | their screen points (null when behind the camera) | | `ui.dialogInput` | `index` | what clicking an input's Select chip does: picks go to that input | | `ui.at` | `world: [x,y,z]` \| `sketch: [x,y]` \| `plane: "XY"` \| `axis: "Z"` \| `dimension: "d1"` (its value text) \| `handle: "arrow"` (a manipulator handle, see `scenario::publish_handle`) | the screen point (a scenario step's target; see `crates/ui-egui/src/scenario.rs`) | | `ui.handles` | `prefix?` | the handles drawn last frame, `[{name, x, y}]`: toolbar buttons and drop-down items `toolbar:`, panel labels `panel::`, browser rows `row: