# Control protocol Shape stroke defaults are drivable with `ui.set {shapeStroke: {width, align, cap, join, miterLimit, dashes, dashOffset, opacity}}`; partial fields merge, null disables the stroke. Values use the existing `shape.edit` names and units. Colour uses `tools.setColors`. `ui.inspect` exposes `toolOptions.shape_stroke`, `stroke_width`, and `strokeEditor`. Stroke Options applies valid UI edits immediately; `strokeEditor` is the open editor state, not an uncommitted document preview. Escape/outside clicks close it without reverting edits. See [Shape stroke options](shape-strokes.md) for limits, preview and persistence behavior. The desktop app listens on `127.0.0.1:` (loopback only). Start it with a token file so the credential is not exposed in the process command line: ```sh photocraft --control 7878 --control-token-file /private/path/photocraft-control.token \ --automation-read-root /work/project --automation-write-root /work/project ``` If the file does not exist, PhotoCraft creates it with a fresh 256-bit token. On Unix the new file is mode `0600`; on Windows, protect it with an appropriate user-only ACL. An existing file is reused. If neither a token nor token file is configured, PhotoCraft generates a token for that launch and writes it to standard error. `PHOTOCRAFT_CONTROL_TOKEN` and `PHOTOCRAFT_CONTROL_TOKEN_FILE` are the environment-variable equivalents. A flag wins over its variable and an empty variable counts as unset; with `--control` (or `PHOTOCRAFT_CONTROL_PORT`), a token together with a token file, from flags or variables, is a usage error (exit status 2). Without it no token is read, so the pair is ignored. ### Without `--control`: the agents switch and Preferences › AI Integrations An app started from the Dock, the Start menu or a file manager has no command line. It starts and stops the control server while it runs. With the suite's `AI-MCP` flag, ArtCraft Launcher › Settings › AI agents › *Let AI agents use Craft apps* is the one switch (for every Craft app; PhotoCraft looks at it every 2 s); without the flag, as before, a saved Control Port. Turning it on or off, or changing the port or folders in Edit › Preferences › AI Integrations, applies at once, with no restart. | Preference | What it does | |---|---| | `integrations.connectAgents` (default off, not in the dialog) | Without the `AI-MCP` flag: serve agents on loopback without `--control`, and publish the connection (below). Set from a saved Control Port; with the flag only the launcher's switch counts | | `integrations.controlPort` (default 0) | The port for that; 0 = any free port (clients read it from the connection file) | | `integrations.readRoot` (default empty) | The agents' read folder; with `--control`, stands in for `--automation-read-root` | | `integrations.writeRoot` (default empty) | The agents' write folder; with `--control`, stands in for `--automation-write-root` | | `integrations.whenAppClosed` (default `launch`) | What `photocraft-cli mcp` does while agents may use the app but it is closed: `launch`, `headless` or `error` | | `integrations.allowAgentControl` (default on, not in the dialog) | Off refuses `--control` too | A file whose `controlPort` (1 to 65535) started the server at launch, as on `main` before the agents switch, loads with `connectAgents` on, so that keeps working without the flag. Started with `--control`, the app leaves the connection to the launch flags and the dialog says so; only `--control` (or `PHOTOCRAFT_CONTROL_PORT`) opens a port at launch. With `--control`, each launch option, or its environment variable, wins over its preference; the two folders are decided separately (any `--automation-read-root` replaces the read folder). The same folder for both reading and writing shares one capability, as it does when given by flags, so an implicit save works there too. A folder named by a flag that can't be opened stops the launch (exit status 2), as before. A folder named only in the preferences that can't be opened, or isn't an absolute path, is left out, so that direction of file access fails closed; the app still starts, prints the reason on standard error and shows it in a notice. Automation can't change the two folders (`prefs.set` refuses `integrations.readRoot`, `integrations.writeRoot` and the whole `integrations` section): an agent must not widen its own file access. While agents may connect, the app keeps its token in its settings folder, in `agent-control.token`, exactly as `--control-token-file /agent-control.token` would (created mode `0600` with a fresh token when missing, reused otherwise, also when the port changes; delete it to get a new token). If that file can't be written or read (a read-only or full settings folder), agents can't connect and the dialog shows why. Otherwise the app publishes the connection beside it, in `agent-connection.json`, and removes it when the connection stops: ```json { "version": 1, "pid": 41234, "port": 7878, "tokenFile": "/Users/me/Library/Application Support/Photocraft/agent-control.token", "app": "0.20.1 (8e9277e20, 2026-11-04)" } ``` A local client reads that file, then the token from `tokenFile`, connects to `127.0.0.1:` and authenticates as below. The file holds no secret. A `--control` launch publishes nothing: whoever started it chose the port and token. On Windows and Linux the app removes the file when it quits. On macOS (quitting ends the process inside AppKit) and after a crash the file stays behind, naming a `port` nothing listens on: a client should treat a refused connection as "not running", and the next launch replaces or removes the file. Until then, another local program could be listening on that port. A program running as the same user can read the token file anyway, so this matters only against one that can listen on a loopback port but can't read that `0600` file (another user's, or a sandboxed app). A client must check that the process `pid` names is still running before it sends the token, and treat the file as stale otherwise. That check narrows the gap but doesn't close it: the pid can be reused, and the app can quit between the check and the connection. A client that must rule this out should not send the token on the strength of this file at all; it should use challenge authentication (below), which never sends the token to the listener, with no fallback to sending the token. `version` changes only with an incompatible format. The settings folder is the one that holds `preferences.json` (see [Preferences](#preferences); `PHOTOCRAFT_CONFIG_DIR` moves it). The first request on every TCP connection must authenticate. No other method is dispatched before this succeeds. There are two ways; a refused frame gets `{"ok": false, "error": "authentication required"}` and the connection is closed. **Challenge authentication** (what `photocraft-cli mcp --bridge` uses) never sends the token, and the server proves it holds the token before the client reveals anything derived from it, so a process that took the port (the app is not running yet, or has exited) learns nothing: ```json {"id": 1, "method": "auth.challenge", "params": {"clientNonce": "<64 hex: 32 fresh random bytes>"}} {"id": 1, "ok": true, "result": {"serverNonce": "<64 hex>", "serverProof": "<64 hex>"}} {"id": 2, "method": "auth", "params": {"proof": "<64 hex>"}} {"id": 2, "ok": true, "result": {"authenticated": true}} ``` Both proofs are lowercase hex HMAC-SHA256, keyed by the token's 64 lowercase hex characters (as ASCII), over `