openpencil/packages/docs/programmable/cli/app-control.md
Danila Poyarkov b52d7e2651
feat: control documents, history, settings, and tools from the CLI and MCP (#871)
* fix(app): record MCP and CLI structural edits as undo steps

The automation bridge ran non-atomic tools, render, and eval without an
undo entry, so Edit > Undo could not revert layers an MCP client or the
CLI created, deleted, or rearranged. Snapshot the page around these
edits as the AI chat does, and skip the entry when nothing changed so
read-only scripts leave the history alone.

* feat(app): activate documents, undo, redo, and change settings over automation

Add activate_document, undo, redo, get_settings, and update_settings to
the app's automation bridge. Settings cover appearance, snapping, canvas
rendering, recovery, and chat preferences, validated with Valibot and
applied through their owning stores; credentials, models, MCP
connections, storage, and tool access stay out of reach.

* feat(mcp): expose document activation, history, and settings tools

* feat(cli): manage documents, history, settings, and tools in the running app

Turn documents into a command group (list, open, new, save, close,
activate), add undo, redo, and settings get/set, and add tool
list/describe/call so every MCP tool runs from the shell, against the
running app or headlessly on a file.

* docs: document app control from the CLI and MCP

* fix: never prompt in the app from automation closes and saves

close_file opened the app's Save changes dialog, which an agent cannot
answer: the call timed out and the dialog stayed open. It now fails on
unsaved changes unless the caller passes unsaved "save" or "discard"
(CLI --save or --discard). save_file and new_document no longer open a
Save dialog for a document that was never saved, report a failed save
as an error, and leave the document untouched when the path is refused.

* docs: describe non-interactive close and save

* fix: address review findings in app automation

Keep a document's source when a save to a new path fails, report
vector-edit undo and redo no-ops as unapplied, echo only the applied
patch from update_settings so writing cannot read settings, reject
tool call --write/--output without a file, and stop settings get from
following inherited keys.

* fix(app): record render undo on the page that receives the layers

A render into a parent on another page was snapshotted against the
target page, so undo left the new layers in place. Snapshot the page
that contains the parent instead, and document that eval edits made
after switching pages stay outside the undo step.

* feat(app): limit automation undo to its own steps and expose design check settings

The undo history is shared with the person in the editor, so an agent's
undo could revert the user's last edit. Automation undo and redo now act
only on steps made through the bridge, and only while they are newest;
otherwise they fail and leave the history alone. Vector edit mode's
session history is off limits entirely. Settings automation also covers
the design check preferences that landed on master.
2026-10-04 16:02:36 +00:00

113 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Controlling the App
description: Open, save, switch, and close documents, undo and redo, change settings, and call any editor tool in the running app from the CLI.
---
# Controlling the App
While the desktop app is running, the CLI drives it over the same automation bridge as the [MCP server](/programmable/mcp-server). Every command on this page except headless `tool call` needs the running app.
## Documents
Each open tab is a document with a stable ID. List them, then pass `--document-id` (and `--page-id`) to aim a command at a background tab without switching to it:
```sh
openpencil documents list --json
openpencil tree --document-id tab-123 --page-id 0:1
```
Open, create, save, and close documents:
```sh
openpencil documents open designs/landing.fig # opens in a new tab
openpencil documents new --path designs/draft.fig # empty document, saved to the path
openpencil documents save --document-id tab-123
openpencil documents save --document-id tab-123 --path designs/copy.fig
openpencil documents close --document-id tab-123
openpencil documents close --document-id tab-123 --save # or --discard
```
Relative paths resolve against the shell's working directory. These commands never open a dialog in the app, because nobody may be there to answer it. Closing a document with unsaved changes fails unless you pass `--save` or `--discard`, and saving a document that has never been saved needs `--path`. Each command prints the document and page it acted on; with `--json` it prints `{ "result", "target" }`, so a script can read the new document's ID from `target.documentId`.
To bring a tab to the front, optionally on a specific page:
```sh
openpencil documents activate tab-123 --page-id 0:4
```
## Undo and redo
Step back through changes made through the CLI and MCP, like **Edit → Undo** and **Edit → Redo**:
```sh
openpencil undo --document-id tab-123
openpencil redo --document-id tab-123 --json
```
```json
{
"result": { "applied": true, "label": "Set opacity", "scope": "document" },
"target": { "documentId": "tab-123", "documentName": "Landing", "pageId": "0:1", "pageName": "Page 1" }
}
```
When there is nothing to undo or redo, the command says so and `result.applied` is `false`.
The editor has one history, shared by you and every automation client, so **Edit → Undo** in the app steps back through any change, newest first. Automation is narrower: `undo` and `redo` only act on a step that the CLI or MCP made, and only while it is the newest one. If the newest change was made in the editor, the command fails and leaves it alone, so an agent can't revert your work. They also fail while the document is in vector edit mode, whose history belongs to the editing session.
Each CLI or MCP command that edits the document is one undo step. An `eval` script is recorded against its target page: if it switches `figma.currentPage` and edits another page, those edits are not part of the undo step.
## Settings
Read and change editor settings by dotted key:
```sh
openpencil settings get
openpencil settings get appearance.theme
openpencil settings set appearance.theme light
openpencil settings set editing.snapping.pixelGrid false
openpencil settings set chat.maxAgentSteps 100
```
Values are read as JSON when they parse (`false`, `100`, `"auto"`) and as plain strings otherwise. Invalid keys and values are rejected without changing anything.
| Key | Values |
|-----|--------|
| `appearance.theme` | `dark`, `light`, `auto` |
| `appearance.language` | `en`, `de`, `es`, `fr`, `it`, `ja`, `pl`, `ru`, `zh-CN` |
| `appearance.animations` | `system`, `off` |
| `editing.snapping.geometry` | `true`, `false` |
| `editing.snapping.objects` | `true`, `false` |
| `editing.snapping.pixelGrid` | `true`, `false` |
| `rendering.canvasMode` | `retained`, `tiled` (applies after a reload) |
| `recovery.enabled` | `true`, `false` |
| `chat.reasoningDisplay` | `collapsed`, `while-thinking`, `expanded` |
| `chat.maxAgentSteps` | integer 1–1000 |
| `chat.changePreviewSize` | `off`, `small`, `medium`, `large` |
| `designCheck.showOnCanvas` | `true`, `false` |
| `designCheck.preset` | `recommended`, `strict`, `accessibility` |
| `designCheck.disabledRules` | JSON array of rule IDs, e.g. `["no-default-names"]`; see `openpencil lint --list-rules` |
Credentials, AI models, MCP connections, storage, and tool access are deliberately not available here: an automation client can't read secrets or grant itself access.
## Calling tools
Every editor tool the MCP server exposes is also available from the CLI. List them, inspect a tool's arguments as JSON Schema, and call it with a JSON object:
```sh
openpencil tool list
openpencil tool describe set_fill
openpencil tool call set_fill --args '{"id":"0:5","color":"#2563eb"}'
openpencil tool call set_fill --document-id tab-123 --args-file fill.json
echo '{"name":"Icons"}' | openpencil tool call create_page --args-file -
```
Pass a file to run a tool headlessly instead. Changes to a file are kept only with `--write` (back to the input) or `--output`:
```sh
openpencil tool call create_page design.fig --args '{"name":"Icons"}' --write
openpencil tool call get_page_tree design.fig --json
```
For multi-step edits, [`eval`](./scripting) runs a whole script against the same Figma-compatible API.