mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 22:22:25 +00:00
MCP: list UI_COMMANDS in command_list and close catalogue gaps (#6460)
Headless command_list exposes the desktop UI catalogue with ui: true, docs clarify effect vs command ids, and prompts cover spelling, variables and path tools.
This commit is contained in:
@@ -129,10 +129,17 @@ impl Headless {
|
||||
fn exec(&mut self, id: &str, params: &Value) -> Result<Value, String> {
|
||||
// A key the command's description doesn't name is an error, not a default (#1442).
|
||||
match HOST_COMMANDS.iter().find(|c| c.0 == id) {
|
||||
Some((_, _, doc)) => vectorcraft_engine::check_keys(id, doc, params),
|
||||
None => self.session.check_params(id, params),
|
||||
Some((_, _, doc)) => vectorcraft_engine::check_keys(id, doc, params).map_err(|e| e.to_string())?,
|
||||
None if vectorcraft_engine::find_command(id).is_some() => {
|
||||
self.session.check_params(id, params).map_err(|e| e.to_string())?;
|
||||
}
|
||||
None if crate::ui_catalog::UI_COMMANDS.iter().any(|(cid, ..)| *cid == id) => {
|
||||
return Err(format!(
|
||||
"`{id}` is a desktop UI command (`ui: true`); start `vectorcraft --control 7979` and use `vectorcraft-cli mcp --connect`"
|
||||
));
|
||||
}
|
||||
None => self.session.check_params(id, params).map_err(|e| e.to_string())?,
|
||||
}
|
||||
.map_err(|e| e.to_string())?;
|
||||
match id {
|
||||
"file.open" => self.open(params),
|
||||
"file.save" => self.save(params),
|
||||
@@ -146,9 +153,31 @@ impl Headless {
|
||||
|
||||
fn commands(&self) -> Value {
|
||||
let mut v: Vec<Value> = self.session.commands().into_iter().map(|c| serde_json::to_value(c).unwrap_or_default()).collect();
|
||||
let mut seen: std::collections::HashSet<String> =
|
||||
v.iter().filter_map(|c| c.get("id").and_then(Value::as_str).map(str::to_owned)).collect();
|
||||
for (id, label, params) in HOST_COMMANDS {
|
||||
if !seen.insert((*id).to_owned()) {
|
||||
continue;
|
||||
}
|
||||
v.push(json!({"id": id, "label": label, "menu": [], "shortcut": null, "params": params, "enabled": true, "host": true}));
|
||||
}
|
||||
// Desktop menu commands (`UI_COMMANDS`): listed for discovery with `ui: true`. Running one
|
||||
// that only the desktop app implements still needs `--connect` (see `exec`).
|
||||
for (id, label, shortcut, params) in crate::ui_catalog::UI_COMMANDS {
|
||||
if !seen.insert((*id).to_owned()) {
|
||||
continue;
|
||||
}
|
||||
let sc = if shortcut.is_empty() { Value::Null } else { json!(shortcut) };
|
||||
v.push(json!({
|
||||
"id": id,
|
||||
"label": label,
|
||||
"menu": [],
|
||||
"shortcut": sc,
|
||||
"params": params,
|
||||
"enabled": true,
|
||||
"ui": true,
|
||||
}));
|
||||
}
|
||||
Value::Array(v)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ mod prompts;
|
||||
mod resources;
|
||||
mod server;
|
||||
mod tools;
|
||||
mod ui_catalog;
|
||||
|
||||
pub use backend::{Backend, Remote};
|
||||
pub use headless::Headless;
|
||||
|
||||
@@ -365,6 +365,116 @@ Method:
|
||||
Live effects are kept on export (geometry baked; shadows, glows and blur become SVG filters),
|
||||
and text stays text unless you pass outlineText.",
|
||||
},
|
||||
PromptDef {
|
||||
name: "check-spelling",
|
||||
title: "Check spelling",
|
||||
description: "Run text.spelling.check on the selection or document, then apply suggestions or dictionary changes.",
|
||||
arguments: &[
|
||||
PromptArg {
|
||||
name: "scope",
|
||||
description: "What to check",
|
||||
required: false,
|
||||
default: "the selection, or every story when nothing is selected",
|
||||
values: Values::Fixed(&[
|
||||
"the selection, or every story when nothing is selected",
|
||||
"the active story only",
|
||||
"every story in the document",
|
||||
]),
|
||||
},
|
||||
PromptArg {
|
||||
name: "action",
|
||||
description: "What to do with each unknown word",
|
||||
required: false,
|
||||
default: "list suggestions and ask before changing",
|
||||
values: Values::Fixed(&[
|
||||
"list suggestions and ask before changing",
|
||||
"apply the first suggestion for each word",
|
||||
"add unknown words to the custom dictionary",
|
||||
"ignore all for this document",
|
||||
]),
|
||||
},
|
||||
],
|
||||
text: "\
|
||||
Check spelling in VectorCraft for {scope}, then {action}.
|
||||
|
||||
Method:
|
||||
1. inspect_document to see the selection and type objects.
|
||||
2. run_command text.spelling.check (and text.spelling.suggest for each unknown word). Offsets are
|
||||
byte ranges for text.editRange / text.spelling.changeWord.
|
||||
3. For a fix, text.spelling.changeWord; for the custom dictionary, text.spelling.addWord /
|
||||
removeWord / dictionary; for this document only, text.spelling.ignoreAll.
|
||||
4. Prefer those commands over inventing replacements. screenshot if the layout may reflow.
|
||||
|
||||
Desktop-only Auto Spell Check is edit.autoSpellCheck (ui: true); use --connect for the underlines.",
|
||||
},
|
||||
PromptDef {
|
||||
name: "variables-bind",
|
||||
title: "Bind variables",
|
||||
description: "Define text, visibility or image variables, bind them to art, and load or save a variable library.",
|
||||
arguments: &[
|
||||
PromptArg {
|
||||
name: "kind",
|
||||
description: "Which variable kinds to set up",
|
||||
required: false,
|
||||
default: "text",
|
||||
values: Values::Fixed(&["text", "visibility", "image", "graph", "mixed"]),
|
||||
},
|
||||
PromptArg {
|
||||
name: "source",
|
||||
description: "Where the data comes from",
|
||||
required: false,
|
||||
default: "define variables on the selection by hand",
|
||||
values: Values::Fixed(&[
|
||||
"define variables on the selection by hand",
|
||||
"import a CSV or TSV data set",
|
||||
"load a variable library XML",
|
||||
]),
|
||||
},
|
||||
],
|
||||
text: "\
|
||||
Set up {kind} variables in VectorCraft from {source}.
|
||||
|
||||
Method:
|
||||
1. inspect_document and variable.list (or the Variables panel commands) to see what exists.
|
||||
2. variable.define / variable.bind for text, visibility, image or graph bindings; one text
|
||||
variable per type object.
|
||||
3. For CSV/TSV, dataset.import; for a library file, variable.loadLibrary / variable.saveLibrary.
|
||||
4. Switch data sets with dataset.apply and verify with inspect_document / screenshot.
|
||||
5. Prefer run_command over inventing panel clicks. Undo covers each binding step.",
|
||||
},
|
||||
PromptDef {
|
||||
name: "path-tools",
|
||||
title: "Path Tools add-on",
|
||||
description: "Use addon.pathtools.* commands (and addon.path.* live effects) to edit path geometry.",
|
||||
arguments: &[
|
||||
PromptArg {
|
||||
name: "task",
|
||||
description: "What to do to the selection",
|
||||
required: true,
|
||||
default: "",
|
||||
values: Values::Fixed(&[
|
||||
"extend open ends",
|
||||
"close paths",
|
||||
"add anchors along curves",
|
||||
"simplify anchors",
|
||||
"split at anchors",
|
||||
"expand dashes to geometry",
|
||||
"create a parametric shape",
|
||||
]),
|
||||
},
|
||||
],
|
||||
text: "\
|
||||
Perform this Path Tools task in VectorCraft: {task}.
|
||||
|
||||
Method:
|
||||
1. command_list with filter \"addon.pathtools\" for the command ids (extend, close, addPoints,
|
||||
simplify, crossings, shape.create, …). Those are commands.
|
||||
2. Live Path Effects keep the effect ids addon.path.* (dashes, addAnchors, extend, close,
|
||||
smooth, retract, reverse, simplify, split). Apply them with apply_effect / effect.apply;
|
||||
they are not command ids.
|
||||
3. inspect_document the selection, run the command or effect, then screenshot.
|
||||
4. Expand Appearance (effect.expandAppearance) only when the brief needs baked geometry.",
|
||||
},
|
||||
];
|
||||
|
||||
/// The message body as a client should receive it.
|
||||
|
||||
@@ -209,9 +209,80 @@ fn tool_schemas_are_valid() {
|
||||
"export",
|
||||
"undo",
|
||||
"redo",
|
||||
"command_list",
|
||||
"command_run",
|
||||
"command_batch",
|
||||
"doc_inspect",
|
||||
"render_preview",
|
||||
"ui_inspect",
|
||||
"ui_screenshot",
|
||||
"type_text",
|
||||
"answer_dialog",
|
||||
"add_text",
|
||||
"apply_effect",
|
||||
"pathfinder",
|
||||
"transform",
|
||||
"create_graph",
|
||||
"text_wrap",
|
||||
] {
|
||||
assert!(names.contains(want), "missing tool {want}");
|
||||
}
|
||||
assert_eq!(tools.len(), 33, "parity docs and #6460 expect 33 MCP tools");
|
||||
}
|
||||
|
||||
/// Headless `command_list` must expose engine commands that a stale release CLI used to omit (#6460).
|
||||
#[test]
|
||||
fn headless_command_list_includes_post_022_engine_commands() {
|
||||
let mut s = server();
|
||||
rpc(&mut s, 1, "initialize", json!({"protocolVersion": PROTOCOL_VERSION}));
|
||||
s.handle_line(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#);
|
||||
let want = [
|
||||
"actionSet.read",
|
||||
"engine.apiReference",
|
||||
"script.run",
|
||||
"script.stop",
|
||||
"text.spelling.check",
|
||||
"document.move",
|
||||
"document.compare",
|
||||
"edit.newDocumentFromState",
|
||||
];
|
||||
for (n, id) in want.iter().enumerate() {
|
||||
let r = call(&mut s, 10 + n as u64, "command_list", json!({"filter": id, "brief": true}));
|
||||
assert_eq!(r["isError"], false, "{id}: {r}");
|
||||
let list: Value = serde_json::from_str(&text_of(&r)).unwrap();
|
||||
assert!(
|
||||
list.as_array().unwrap().iter().any(|c| c["id"] == *id),
|
||||
"missing {id} in {list}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Headless `command_list` lists desktop UI commands with `ui: true` (#6460).
|
||||
#[test]
|
||||
fn headless_command_list_includes_ui_commands() {
|
||||
let mut s = server();
|
||||
rpc(&mut s, 1, "initialize", json!({"protocolVersion": PROTOCOL_VERSION}));
|
||||
s.handle_line(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#);
|
||||
let want = [
|
||||
"addon.arrange.zoomSelection",
|
||||
"file.newDialog",
|
||||
"view.fitArtboard",
|
||||
"view.rotateReset",
|
||||
"app.quit",
|
||||
"window.arrange.tile",
|
||||
"align.horizontalLeft",
|
||||
"ui.action.play",
|
||||
];
|
||||
for (n, id) in want.iter().enumerate() {
|
||||
let r = call(&mut s, 20 + n as u64, "command_list", json!({"filter": id, "brief": true}));
|
||||
assert_eq!(r["isError"], false, "{id}: {r}");
|
||||
let list: Value = serde_json::from_str(&text_of(&r)).unwrap();
|
||||
let entry = list.as_array().unwrap().iter().find(|c| c["id"] == *id).unwrap_or_else(|| panic!("missing {id} in {list}"));
|
||||
assert_eq!(entry["ui"], true, "{entry}");
|
||||
}
|
||||
let r = call(&mut s, 40, "command_run", json!({"id": "view.fitArtboard", "params": {}}));
|
||||
assert_eq!(r["isError"], true, "{r}");
|
||||
assert!(text_of(&r).contains("--connect"), "{r}");
|
||||
}
|
||||
|
||||
// ---------- headless end-to-end ----------
|
||||
|
||||
@@ -98,7 +98,7 @@ pub fn tool_definitions() -> Vec<Value> {
|
||||
tool(
|
||||
"command_list",
|
||||
"List commands",
|
||||
"Discover command ids, labels, parameter descriptions and enabled state. A command with a parameter schema also lists it as schema (JSON Schema 2020-12) and as inputSchema (the same schema with an object root, the form of a tool's inputSchema). Returns an array. The whole catalogue is large: narrow it with filter (or query), menu, and brief. list_commands lists the same commands; it takes enabledOnly instead of enabled_only and returns {count, commands}.",
|
||||
"Discover command ids, labels, parameter descriptions and enabled state. Lists engine and plug-in commands, and desktop UI menu commands (ui: true; running a UI-only id headless needs --connect). A command with a parameter schema also lists it as schema (JSON Schema 2020-12) and as inputSchema (the same schema with an object root, the form of a tool's inputSchema). Returns an array. The whole catalogue is large: narrow it with filter (or query), menu, and brief. list_commands lists the same commands; it takes enabledOnly instead of enabled_only and returns {count, commands}. Effect ids (effect.list) are not command ids.",
|
||||
obj(
|
||||
json!({"filter": string("Case-insensitive id, label or menu substring"), "query": string("Same as filter"), "menu": string(MENU_FILTER), "enabled_only": {"type":"boolean"}, "brief": {"type":"boolean", "description": BRIEF}}),
|
||||
&[],
|
||||
@@ -156,7 +156,7 @@ pub fn tool_definitions() -> Vec<Value> {
|
||||
tool(
|
||||
"list_commands",
|
||||
"List commands",
|
||||
"List VectorCraft commands (id, label, menu path, shortcut, parameter description, enabled state, and schema and inputSchema for a command with a parameter schema). Every editing action is a command; run any of them with run_command. Coordinates are points in document space, y down, origin at the first artboard's top-left. command_list lists the same commands as a bare array and takes enabled_only.",
|
||||
"List VectorCraft commands (id, label, menu path, shortcut, parameter description, enabled state, and schema and inputSchema for a command with a parameter schema). Includes engine commands and desktop UI menu commands (ui: true; a UI-only id run headless needs --connect). Every editing action is a command; run any of them with run_command (or invoke_menu for a menu path). Coordinates are points in document space, y down, origin at the first artboard's top-left. command_list lists the same commands as a bare array and takes enabled_only. Effect ids (effect.list) are not command ids.",
|
||||
obj(
|
||||
json!({"filter": string("Case-insensitive substring matched against id, label and menu path (e.g. \"align\", \"Object\")"), "query": string("Same as filter"), "menu": string(MENU_FILTER), "enabledOnly": {"type": "boolean", "description": "Only commands that can run right now"}, "brief": {"type": "boolean", "description": BRIEF}}),
|
||||
&[],
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -695,6 +695,7 @@ macOS cursor delivery (#2292) is narrowed to a native host adapter using the exi
|
||||
- **Estimate:** 4–8 h.
|
||||
- **Done (2026-10-16):** the MCP server hears the client during a long call: `notifications/cancelled` stops a queued or running export (PDF pages, flattening, baking, drawing, artboards) without writing anything or changing the document, `ping` and the list methods answer at once, any other request that would wait behind a call running for two seconds or more is refused as busy (`-32003`, naming the call), and `_meta.progressToken` gets page progress (#2165, #2171, #2172). Reading the document during an export is not served (refused as busy); estimate unchanged.
|
||||
- **Exposed in MCP:** `add_text {vertical: true}` uses the existing vertical point, area and path type commands. Horizontal writing remains the default; the broader automation gap and estimate are unchanged.
|
||||
- **Done (#6460):** headless and remote `command_list` list desktop `UI_COMMANDS` (`ui: true`); UI-only runs still need `--connect`; effect ids are not command ids; 33 tools; eight prompts (spelling, variables, path-tools added); `vectorcraft://commands/reference` in the rebuilt CLI; prefer a checkout-built CLI when a release binary lags. Estimate unchanged.
|
||||
|
||||
## G15 Performance budgets and a GPU renderer
|
||||
|
||||
@@ -852,6 +853,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-10 | minor | G6: MCP catalogue (#6460): headless lists `UI_COMMANDS` (`ui: true`), eight prompts, effect vs command ids, 33 tools, commands/reference; estimate unchanged. |
|
||||
| 2026-11-11 | minor | G5: built-in Status Effects graphic style library (Poison Tint, Freeze Edge, Burn Aura, Stun Pulse, Stealth Fade, Shield Buff); Missing trimmed; estimate 7–17 → 6–16 h; MCP docs list UI Frames and Map Markers under Symbol libraries. |
|
||||
| 2026-11-11 | minor | G5: built-in Inventory Icons symbol library (Sword, Shield, Potion, Key, Gem; no default 9-slice); Missing trimmed; estimate 8–18 → 7–17 h. |
|
||||
| 2026-11-10 | minor | G4 type: Edit › Edit Custom Dictionary… adds, deletes and changes the custom dictionary's words, #4936; the Check Spelling dialog remains; scores and estimates unchanged. |
|
||||
|
||||
+33
-14
@@ -14,6 +14,17 @@ It has two backends:
|
||||
With no flag, the server tries `127.0.0.1:7979` and falls back to headless. Logs go to stderr; stdout carries
|
||||
only protocol messages.
|
||||
|
||||
### Command catalogue (headless vs remote)
|
||||
|
||||
`command_list` / `list_commands` / `vectorcraft://commands` return the live catalogue for the backend in use:
|
||||
|
||||
- **Headless:** engine and plug-in commands, plus the desktop menu catalogue (`UI_COMMANDS`) marked `"ui": true` so agents can discover ids such as `view.fitArtboard`, `file.newDialog` and `window.*`. Running a UI-only command that the headless host does not implement returns an error that points at `--connect`.
|
||||
- **Remote:** the same lists, with each UI entry's `enabled` taken from the live app. Run UI commands with `command_run` / `run_command` or `invoke_menu`.
|
||||
|
||||
Effect ids from `effect.list` / `apply_effect` / `vectorcraft://effect/{id}` (for example `warp.arc`, `blur.gaussian`, `threeD.revolve`, `adjust.hueSaturation`, `addon.path.dashes`, `addon.color.halftone`) are not command ids. Apply one with `effect.apply` or the `apply_effect` tool; do not pass an effect id to `command_run`. Path Effects keep the `addon.path.*` effect ids; the Path Tools add-on's *commands* are `addon.pathtools.*`.
|
||||
|
||||
A release `vectorcraft-cli` on `PATH` can lag the checkout. Prefer `cargo install --path apps/vectorcraft-cli --locked` and put `$CARGO_HOME/bin` first in the MCP client's environment, or set the client's `command` to that binary's absolute path (#6460).
|
||||
|
||||
## Install and register
|
||||
|
||||
From the repository root, with Rust 1.95+ and the platform's source-build prerequisites installed:
|
||||
@@ -195,7 +206,7 @@ dropped; the call reports uncertainty like any reply that never arrived whole.
|
||||
|
||||
### Prompts
|
||||
|
||||
`prompts/list` offers five workflows; `prompts/get` returns one templated user message with its
|
||||
`prompts/list` offers eight workflows; `prompts/get` returns one templated user message with its
|
||||
arguments filled in. A missing required argument is `-32602`.
|
||||
|
||||
| Prompt | Arguments (required first) |
|
||||
@@ -205,6 +216,9 @@ arguments filled in. A missing required argument is `-32602`.
|
||||
| `recolor` | `palette`, `method` |
|
||||
| `trace-and-style` | `path`, `preset`, `effect` |
|
||||
| `export-set` | `formats`, `directory`, `scale` |
|
||||
| `check-spelling` | `scope`, `action` |
|
||||
| `variables-bind` | `kind`, `source` |
|
||||
| `path-tools` | `task` |
|
||||
|
||||
```sh
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"prompts/get","params":{"name":"poster","arguments":{"brief":"a jazz festival"}}}' \
|
||||
@@ -374,11 +388,9 @@ The older names below remain listed because existing workflows use them.
|
||||
|
||||
| Tool | Arguments and result |
|
||||
|---|---|
|
||||
| `command_list` | Optional `filter` (or `query`), `menu` (a menu path substring, levels joined with ` > `), `enabled_only`, `brief` (no `params` text, `schema` or `inputSchema`); returns the command array. A command with a parameter schema lists `schema` and `inputSchema` (see [Parameter schemas](#parameter-schemas)). `list_commands` lists the same commands; it takes `enabledOnly` and returns `{count, commands}` |
|
||||
| `command_list` | Optional `filter` (or `query`), `menu` (a menu path substring, levels joined with ` > `), `enabled_only`, `brief` (no `params` text, `schema` or `inputSchema`); returns the command array for the backend in use (see [Command catalogue](#command-catalogue-headless-vs-remote)). A command with a parameter schema lists `schema` and `inputSchema` (see [Parameter schemas](#parameter-schemas)). `list_commands` lists the same commands; it takes `enabledOnly` and returns `{count, commands}` |
|
||||
| `command_run` | `id`, optional `params`; runs the command through the existing backend. An unknown key in `params` is an error that names it and the nearest key the command takes (`invalid parameters for `file.new`: unknown parameter `widht`; did you mean `width`?`), so a misspelt key no longer runs the command with its default (see [Parameter keys](#parameter-keys)). `run_command` runs a command the same way and takes its id as `command` |
|
||||
| `command_batch` | `steps: [{id, params?}]` (at most 256), optional `stop_on_error` (default true), `atomic` and `label`; returns `completed`, `failed`, `results: [{ok, result\|error}]` and `created` (the ids the `draw_path` / `draw_shape` steps made, in order). A step's `id` is a command id, or `draw_path` / `draw_shape` with that tool's arguments as `params` (checked as `tools/call` checks them; the step's result is the tool's, so `"$N.id"` is the new object). Each edit has its own undo step, or with `atomic: true` the batch's edits are one undo step named `label` (else after its first edit; `edit.undoGroup.begin`/`end` underneath), and the edits made in a document that a step opens or creates (`document.open`, `file.new`) are one undo step there, named the same, also when no document was open before the batch. In a document that was open before the batch and that a step switches to (`document.activate`), each edit stays its own undo step; failures set `isError`. A param string `"$N.path"` or `"$last.path"` takes a value from an earlier step's result (see `run` below). Kept step results total at most 8 MiB; past that the batch stops with the reason. This is the MCP tool, not the engine command `command.batch` (that one takes `commands: [{command, params}]` and is what the Actions panel plays); use `command_run` with `command.batch` when replaying a recorded action |
|
||||
| `command_list` | Optional `filter` (or `query`), `menu` (a menu path substring, levels joined with ` > `), `enabled_only`, `brief` (no `params` text, `schema` or `inputSchema`); returns the command array |
|
||||
| `command_run` | `id`, optional `params`; runs the command through the existing backend. An unknown key in `params` is an error that names it and the nearest key the command takes (`invalid parameters for `file.new`: unknown parameter `widht`; did you mean `width`?`), so a misspelt key no longer runs the command with its default (see [Parameter keys](#parameter-keys)) |
|
||||
| `doc_inspect` | Optional `depth`, `childLimit`, `ids`, `kind`, `limit`, `offset`; same summary as `inspect_document` |
|
||||
| `render_preview` | Optional `max_side` (1–4096, default 1024), `artboard` (0-based, default 0) and `traceViews`; inline PNG of that artboard without editing it, drawn as `export` draws a PNG with `background: "white"` at the scale that fits `max_side` (at most 1). With `traceViews: true`, Image Trace objects draw in the view set with `imageTrace.setView` (outlines, the source image…), as in the app's window; by default they draw their tracing result. Artboards too large to render within the allocation bound return a tool error |
|
||||
| `ui_inspect`, `ui_screenshot` | Connected desktop state/window capture; tool errors in headless mode. `ui_inspect` is the same call as `inspect_ui`, and `ui_screenshot` is `screenshot` with `window: true`. `ui_screenshot` takes an optional `max_side` (1–4096), as `screenshot {window: true}` does: a capture whose longer side is larger is scaled down to `max_side` pixels on that side, and the reply's text gives the returned size as `width`/`height` and the capture's as `capturedWidth`/`capturedHeight`. Without `max_side` the full capture comes back |
|
||||
@@ -464,7 +476,7 @@ opens the picker through `run_command` with the remote backend.
|
||||
| `press_key` | `{key, mods?}` | Remote: a real key event. Headless: runs the command or tool bound to that shortcut, or sends the key to the busy tool (digits too: `5` while dragging with the Perspective Selection tool; `e` while dragging a Selection or Group Selection marquee; `c` and `f` while dragging out an arc; `f`, `v`, `x` and `c` while dragging out a rectangular or polar grid). |
|
||||
| `type_text` | `{text}` | Remote only. |
|
||||
| `answer_dialog` | `{set?: [{field, value?}], action?}` | Remote only. Sets fields of the app's open dialog in the order given with one `ui.dialog.set {fields, action?}` (a missing `value` is `null`). The app stores all of them or none, then answers that same dialog with `action` in the same request: `confirm` (OK) or `cancel`. Another connection cannot open a dialog between the two steps. When the app refuses the `ui.dialog.set`, for example because no dialog is open, the call is an error and the dialog is not confirmed or canceled. `action` alone is sent as `ui.dialog.confirm` or `ui.dialog.cancel`. Give `set`, `action` or both; `set` holds at most 256 fields. `inspect_ui` shows the open dialog and its fields as `ui.dialog`. Returns `{dialog, result}`: the dialog after the fields were set (or `null`) and the reply to `confirm` or `cancel` (or `null`). After `invoke_menu` `app.quit`, `file.close` or `file.closeAll` replies `{"pending": "saveChanges"}`, `{action: "confirm"}` saves, `{set: [{field: "discard", value: true}], action: "confirm"}` closes without saving and `{action: "cancel"}` cancels the close or quit. |
|
||||
| `invoke_menu` | `{command, params?}` | Invokes a menu item by command id. Includes UI commands such as `view.*` and `window.*` in remote mode. |
|
||||
| `invoke_menu` | `{command, params?}` | Invokes a menu item by command id. UI commands such as `view.*` and `window.*` are in `command_list` with `ui: true`; running them needs the desktop app (`--connect`). |
|
||||
| `open_panel` | `{panel}` | Remote only. `panel` is a panel id (`layers`, `swatches`, `colorGuide`, …, as `window.panel` takes) or its display label (`"Color Guide"`), in any case. |
|
||||
| `screenshot` | `{path?, scale?, artboard?, window?, max_side?, traceViews?}` | Returns MCP image content (`image/png`, base64) plus a text block. Renders the artboard with the pixels of a PNG `export` with `background: "white"` at the same scale; `traceViews:true` draws Image Trace objects in the view set with `imageTrace.setView`, as in the app's window; `window:true` captures the app window (remote only), as `ui_screenshot` does, and `max_side` (1–4096, window captures only) scales the returned image down as described for `ui_screenshot`. With `path` the backend (the headless session or the app) writes the PNG there too; a window capture written there keeps its full size with `max_side`. |
|
||||
| `open_file` | `{path, importer?, params?}` | Opens any readable file as a new active document: `.vectorcraft`/`.drawcraft`, `.vctemplate`, `.svg`/`.svgz`, `.pdf`/`.ai`, `.ait`, `.eps`/`.epsf`, `.ps`, `.dxf`, `.emf`, `.wmf`, PNG (`.png`/`.pns`), JPEG, GIF, WebP, TIFF, BMP (`.bmp`/`.dib`/`.rle`), Targa (`.tga`/`.vda`/`.icb`/`.vst`), PCX, Pixar (`.pxr`), JPEG 2000 (`.jpf`/`.jpx`/`.jp2`/`.j2k`/`.j2c`/`.jpc`), HEIC/HEIF (`.heic`/`.heif`), JPEG XL (`.jxl`), PSD (`.psd`/`.pdd`)/PSB (an image opens as a document of its pixel size; a Photoshop file as its merged image; a JPEG XL file as its first frame). Templates (`.vctemplate`, `.ait`) open as a new untitled document. PDF, `.ai` and SVG files saved with Preserve Editing reopen as the document they carry. The reply is `document.open`'s: its `warnings` say what didn't come in as it was (an EPS whose PostScript can't be read opens as its preview, and the warning names the PostScript error, the operator and the procedure). A file of an extension only an installed importer plug-in lists opens with that importer ([Plug-ins](#plug-ins)); `importer: "plugin.<id>"` opens the file with the importer it names, which must list the file's extension, also one a built-in format reads (in the app, as File › Open With does), and `params` gives that importer's parameters (their defaults when omitted). `run_command document.formats` lists the formats. |
|
||||
@@ -1419,8 +1431,9 @@ vectorcraft-cli run --cmd file.new --params '{"width":600,"height":200}' --cmd
|
||||
|
||||
### Parameter schemas
|
||||
|
||||
Some commands have a machine-readable parameter schema: at present `view.capsLock`, `view.saved.new`,
|
||||
`view.saved.edit`, `view.saved.list`, `view.transparencyGrid`, `view.snapToGlyph.options` and `engine.apiReference`. The engine's
|
||||
Some commands have a machine-readable parameter schema: at present a small set including `view.capsLock`, `view.saved.new`,
|
||||
`view.saved.edit`, `view.saved.list`, `view.transparencyGrid`, `view.snapToGlyph.options` and `engine.apiReference`
|
||||
(most of the catalogue still documents params in prose only; schemas grow command by command). The engine's
|
||||
`engine.commands` list gives such a command a `schema` field: an object schema in the shape of JSON Schema 2020-12
|
||||
(https://json-schema.org/draft/2020-12). Its root has `$schema` (the 2020-12 dialect), `description` when the
|
||||
command has a summary, and `deprecated: true` when the command is deprecated. The schema uses `type`, `properties`, `required` and
|
||||
@@ -4570,7 +4583,7 @@ draws, and View › Snap to Grid snaps to its subdivisions. Changing either sets
|
||||
every document they read except a native file and the native document that an SVG, PDF, `.ai` or EPS file carries
|
||||
(`restored: true`), which keep the grid they were saved with.
|
||||
|
||||
`view.rotate {angle}` (desktop app) turns the view to `angle` degrees about the center of the canvas; a positive angle
|
||||
`view.rotate {angle}` (desktop app, `ui: true`) turns the view to `angle` degrees about the center of the canvas; a positive angle
|
||||
turns the art clockwise on screen. View › Rotate View and the status bar's angle menu run it. It returns `{angle}`, the
|
||||
angle it set, normalized to (-180, 180], and `view.rotateReset` turns the view back to 0, returning `null` on success.
|
||||
|
||||
@@ -4578,7 +4591,9 @@ Select the Rotate View Tool with `select_tool` and the tool id `rotateView`. Its
|
||||
drags turn the canvas about its center; Shift snaps to 15°. Document coordinates for a rotation
|
||||
gesture use the view at the initial down event, including when the gesture spans several calls.
|
||||
The control channel rejects malformed rotation packets without changing the angle and cancels
|
||||
the current press. Start a new down event after an error.
|
||||
the current press. Start a new down event after an error. Menu commands that only the desktop view
|
||||
implements (`view.rotate`, `view.rotateReset`, `view.rotateToSelection`) are listed with `ui: true`
|
||||
and need `--connect` when the headless host does not run them.
|
||||
Reset Rotate View is the command `view.rotateReset`:
|
||||
|
||||
```json
|
||||
@@ -4908,7 +4923,10 @@ settings and the next untitled name, without opening the dialog, and returns wha
|
||||
open document in tab order the current one, as a click on its tab does, and return `{index}`. After the last document
|
||||
comes the first, and before the first document comes the last. From the Home screen they go to the first and the last
|
||||
document. With no document open they fail with "no document open". `document.activate {index}` makes a given document
|
||||
the current one.
|
||||
the current one. `document.move {from?, to}` reorders open documents the way dragging a document tab along the tab strip
|
||||
does: `from` defaults to the current document, `to` is the destination index, documents between them shift by one, the
|
||||
current document stays current, and each document keeps its own view (#5663). It is an engine command, so headless and
|
||||
remote `command_list` both list it.
|
||||
`window.arrange.tile` (Window › Arrange › Tile) shows every open document in its own pane, edge to edge in the document
|
||||
area, in a grid of as many columns as the square root of the number of documents, rounded up, and fits each document to
|
||||
its pane; with fewer than two documents open it leaves the view and `tiled` as they are. It returns `{tiled, panes}`.
|
||||
@@ -8460,10 +8478,11 @@ plain path from then on.
|
||||
### Path Effects (live)
|
||||
|
||||
Effect › Path Effects holds the add-on's live geometry effects, applied with `effect.apply {effect, params}` like any
|
||||
other effect, with no shortcuts. Each draws, measures, exports and stacks as the other geometry effects do; the object's
|
||||
own path stays as it is until Expand Appearance (`effect.expandAppearance`) writes the result. `effect.list` documents
|
||||
every parameter. Out-of-range values are clamped, and an effect whose result would pass the geometry effects' budget
|
||||
(2 million anchors) leaves the path as it is.
|
||||
other effect, with no shortcuts. The ids below (`addon.path.*`) are **effect** ids, not command ids; the Path Tools
|
||||
add-on's commands are `addon.pathtools.*` (see above). Each effect draws, measures, exports and stacks as the other
|
||||
geometry effects do; the object's own path stays as it is until Expand Appearance (`effect.expandAppearance`) writes
|
||||
the result. `effect.list` documents every parameter. Out-of-range values are clamped, and an effect whose result would
|
||||
pass the geometry effects' budget (2 million anchors) leaves the path as it is.
|
||||
|
||||
| Id | Menu item | Parameters |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -52,6 +52,7 @@ M7 type: Touch Type moves a right-to-left character along its line and up from i
|
||||
| 2026-11-07 | minor | M4/M7: `.ai`/EPS type from the text document keeps auto leading at the paragraph's share, the paragraph's hyphenation options and may show more of the last lines the file sets in an area frame of its own, when growing the frame by its lines' deepest descent plus 0.05 pt shows them, and a line a soft return ends is justified as a wrapped line is (#6137); a user's `.ai` that opened from its PDF part now opens from its layers. Limits: frames grow only for area type of one frame, horizontal, by its lines' deepest descent plus 0.05 pt; type in a font that isn't installed, and hyphenation in a language that isn't imported, still lay out differently. Scores and estimates unchanged. |
|
||||
| 2026-11-08 | minor | Character Styles panel: a double-click on a user style's row opens its Character Style Options (`ui.charStyleOptions`); the dialog keeps a fixed width and scrolls its sections in a shorter window, so OK and Cancel stay on screen. Tests `panels::text_styles::tests::double_clicking_a_character_style_row_opens_its_options` and `dialogs::char_style_options::tests::the_sections_scroll_in_a_window_shorter_than_the_general_list`. Scores and estimates unchanged. |
|
||||
| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. |
|
||||
| 2026-11-10 | minor | M13 / G6: MCP catalogue (#6460): headless lists UI commands, eight prompts, 33 tools, effect vs command ids; scores and estimates unchanged. |
|
||||
| 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. |
|
||||
| 2026-11-02 | minor | Selection QoL: saved marquee modes, screen drag direction with a 3 px dead zone, and translated mode/E feedback (#5313); VectorCraft addition, scores and estimates unchanged. |
|
||||
| 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. |
|
||||
|
||||
@@ -741,7 +741,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Open Recent Files under `--automation-read-root`: the control channel's `ui.menu.list` and `file.recentFiles` leave out the recent files outside the read roots, the other rows keep their ids, and `file.openRecentN` for one outside them answers `no such recent file`, as an empty slot does. `crates/ui-egui/src/tests/automation.rs`.
|
||||
- Scripts: `alert`, `confirm` and `prompt` open dialogs during a run, each with a Stop button that ends the run and puts every document back. In the app the run waits at the call for the person's answer; a run over MCP or the command line takes its answers in order from `script.run`'s `answers`, and the report lists each dialog with its answer. `crates/engine/src/tests/script_dialogs.rs`, `crates/ui-egui/src/tests/script_dialogs.rs`.
|
||||
- Parameter keys: the engine refuses a key that a command's params description doesn't name before the command runs, in every build and for every caller, including the app's own calls, the commands a command runs and the steps of `command.batch` and `action.batch`. An action recorded by an older version still plays from the Actions panel and the Batch dialog (`Session::replay`). `crates/engine/tests/it/param_keys.rs`.
|
||||
- MCP server with 25 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, any command), attached to the running app or headless. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings. `--automation-read-root` / `--automation-write-root` (on `vectorcraft-cli mcp` and on `vectorcraft --control`) confine the files commands read and write to chosen folders, links followed, with the flags PhotoCraft takes (#832); each flag may be given more than once, a path inside any of its folders being allowed (#3546, `file_access::tests::several_roots_of_a_kind_each_grant_their_access`, `cli::mcp_automation_roots_take_several_folders`). A long export can be cancelled (`notifications/cancelled` stops it at the next page or group and writes nothing), reports page progress to a client that sent a progress token, and does not make other requests wait: engine-free ones answer at once, the rest are refused as busy after two seconds (#2165, #2171, #2172).
|
||||
- MCP server with 33 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, undo/redo, UI inspect/screenshot aliases, any command), attached to the running app or headless. Headless and remote `command_list` include desktop `UI_COMMANDS` (`ui: true`); a UI-only id still needs `--connect` to run. Eight prompts (poster, icon-set, recolor, trace-and-style, export-set, check-spelling, variables-bind, path-tools). Effect ids are not command ids. Prefer a CLI built from the checkout when a release binary lags (#6460). `vectorcraft://commands/reference` serves the API reference. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings. `--automation-read-root` / `--automation-write-root` (on `vectorcraft-cli mcp` and on `vectorcraft --control`) confine the files commands read and write to chosen folders, links followed, with the flags PhotoCraft takes (#832); each flag may be given more than once, a path inside any of its folders being allowed (#3546, `file_access::tests::several_roots_of_a_kind_each_grant_their_access`, `cli::mcp_automation_roots_take_several_folders`). A long export can be cancelled (`notifications/cancelled` stops it at the next page or group and writes nothing), reports page progress to a client that sent a progress token, and does not make other requests wait: engine-free ones answer at once, the rest are refused as busy after two seconds (#2165, #2171, #2172).
|
||||
- MCP protocol: five prompts (`prompts/list` / `prompts/get`: poster, icon set, recolour, trace-and-style, export set), `completion/complete` for prompt arguments and template variables read from the live catalogues (formats, effect ids, command ids, trace presets, swatch and object ids), four resource templates (`vectorcraft://object/{id}`, `//command/{id}`, `//effect/{id}`, `//swatch/{name}`) so an agent reads one thing instead of the whole document, and `logging/setLevel` with `notifications/message`. `window.panel` ids are case-insensitive (display labels work too), `vectorcraft://object/{id}` reads through `document.node {summary: true}` (the same reply bytes from one lookup instead of summarizing the whole document: a leaf read on a 2,000-object document drops from ~20 ms to ~0.06 ms), large containers slice with `document.node {depth?, childLimit?}` (truncated levels report `childCount`), `document.inspect` and the `inspect_document` tool slice the same way so a depth-0 read is the layer skeleton (~9.7 KB instead of ~2 MB on a 2,000-object document), `document.find` locates nodes by name/kind/text with ancestor paths, and the CLI sweeps the protocol surface over stdio against the compiled binary. `docs/mcp.md` has a Protocol section covering the capability table and what is deliberately absent (subscriptions, progress, cancellation, sampling, elicitation, Streamable HTTP — all of which need the transport to interleave reads).
|
||||
- Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`). A `run` step, like a `command_batch` step over MCP, can use an earlier step's result (`"$1.id"`, #785); `run --commands FILE` (or `-` for standard input) reads the steps from a JSON array or JSON Lines, past the Windows command-line limit (#3147, `apps/vectorcraft-cli/tests/it/cli.rs` `run_reads_*`, `run_refuses_malformed_commands_naming_the_step`).
|
||||
- File › Scripts › Check Script… reads a script written for Illustrator (`.jsx`, `.jsxinc` or a compiled `.jsxbin`) without running any of it and reports what it needs (the document model, files, dialogs, menu commands, features only Illustrator has), what it would do outside the document, the menu commands and document-model members it uses, its directives and included files, and the problems that would stop it loading (`script.check`, which also reads `.js` files); File › Open and drag-and-drop show the report for `.jsx`, `.jsxinc` and `.jsxbin` files.
|
||||
@@ -1973,6 +1973,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-10 | minor | Automation: MCP tool count 33; headless `command_list` lists `UI_COMMANDS` (`ui: true`); eight prompts; effect-id vs command-id clarified (#6460); scores and hours unchanged. |
|
||||
| 2026-11-11 | minor | Bundled content: built-in Status Effects graphic style library; style libraries evidence updated; ~40% → ~42%; MCP Symbol libraries list UI Frames and Map Markers; more built-in graphic-style libraries remain. |
|
||||
| 2026-11-11 | minor | Bundled content: built-in Inventory Icons symbol library (Sword, Shield, Potion, Key, Gem); symbol libraries evidence updated; ~38% → ~40%; more built-in symbol libraries remain. |
|
||||
| 2026-11-10 | minor | Type advanced: the Edit Custom Dictionary dialog, #4936; evidence added; the Check Spelling dialog remains; scores and hours unchanged. |
|
||||
|
||||
Reference in New Issue
Block a user