Driving Litho from agents and scripts
Every user-visible action in Litho is a command
(crates/engine/src/cmd), and every command is reachable
three ways — pick whichever fits the agent:
| Surface | Use it when | Entry point |
|---|---|---|
| CLI | One-shot jobs, shell pipelines, CI: build or edit a document and export it | litho-cli run, script, app,
describe, commands |
| MCP | Claude and other MCP clients; images of pages and the window come back as content | litho-cli mcp (mcp.md) |
| Control channel | Your own client driving the running app (JSON lines over TCP) | litho --control 7979 (control-protocol.md) |
Discover commands
litho-cli commands footnote # every command whose id/label/menu mentions "footnote"
litho-cli describe xref.insert # label, menu path, shortcut and parameters of one command
Parameters are documented on each command (params), e.g.
{anchor? | paragraph?: text to find | story? + para?, format?}.
Scripts: several commands, later ones using earlier results
A script is one command per line
(command.id {json params}; # comments), JSON
lines, or a JSON array of {command, params}, in UTF-8 with
or without a byte order mark. A parameter string "$N.path"
becomes that value of step N's result: N is the 0-based step index
(comments and blank lines are not steps, $last is the
previous step) and the path goes through result fields and array indices
("$1.story", "$3.ids.0");
"${N.path}" interpolates inside longer strings. Any other
string starting with $ stays as written
("$1-$2", "$5 off"), and $$
writes a literal $ anywhere ("$$1" passes
$1, a GREP group). In text parameters \n
starts a new paragraph and \u2028 is a forced line break
(\u000b, Word's line break, is read as one too). Most
commands ignore parameters they don't know instead of rejecting them, so
check names with describe.
litho-cli script --help prints this reference.
cat > page.dcs <<'DCS'
frame.create {"rect": [72, 72, 540, 300], "content": "text", "text": "Results of the survey"}
text.select {"story": "$0.story", "anchor": 0, "focus": 0}
footnote.insert {"text": "Survey run in 2026."}
frame.create {"rect": [72, 320, 540, 500], "content": "text", "text": "See ."}
text.select {"story": "$3.story", "anchor": 4, "focus": 4}
xref.insert {"paragraph": "Results", "format": "Paragraph Text & Page Number"}
DCS
litho-cli script page.dcs --save page.litho --export page.pdf --export page.png
litho-cli script page.dcs --connect 7979 # the same steps, live in the running app
The result is JSON: {"completed": n, "results": [...]},
plus failedIndex / failedCommand /
error when a step fails (the exit status is non-zero;
--keep-going records errors and continues).
run --cmd ID=JSON accepts the same references, and MCP's
batch tool takes commands or
script text with them.
run works through its arguments in order.
--page, --scale and --pdf-options
apply to the --export / --all-pages that
follow them, so one run can write several sizes
(run --sample --scale 0.5 --export small.png --scale 2 --export large.png);
one with no export after it is an error.
--export writes the file type its extension names:
.png or .jpg (a page), .pdf,
.litho, .idml, .epub,
.html, and .rtf or .txt (the
story being edited or of the selected frame). A name with no extension
is a PNG of the page; another extension is an error, and nothing
runs.
Color themes
color.extractTheme {id?} previews up to five RGB colours
from a placed raster image (or the selected image).
swatch.addColorTheme {colors, name?} adds that palette as a
colour group in one undo step. Extraction samples the whole source image
on a bounded grid, weights partial transparency, and ignores fully
transparent pixels. The image must contain at most 16 million pixels.
PDF/SVG graphics are unsupported. This is local median-cut extraction,
not Adobe's palette algorithm. In the UI, click an image with the Color
Theme Tool (Shift+I), or choose Color Theme… in the Swatches panel menu
with one image selected. Cancel keeps the document unchanged.
PDF/X-4 validation
litho-cli validate-pdf FILE.pdf prints JSON with
valid and issues, and exits non-zero when a
built-in check fails. Checks cover PDF version, output intent and
profile, Info and XMP identification, image interpolation, encryption,
trim boxes, Type 3 fonts, device RGB under a CMYK output intent, and
unembedded fonts drawing visible text (including placed PDF forms).
Render mode 3 (invisible OCR text) is exempt from the font-embedding
check. PDF/X-4 export reports these issues as warnings; it does not
embed or outline placed fonts. These checks are not a complete external
PDF/X certification.
Data merge
data.source.select {path, sheet?} links a
CSV/TSV/.xlsx file to the document
(data.source.update re-reads it,
data.source.remove drops it); data.fields
lists its fields.
data.placeholder.add {field, role?, story?, at?, end?, item?}
marks text or a frame as a field (role: text, image, qr,
hyperlink); data.options sets records, tiling and image
fitting; data.preview {record} /
data.preview.stop show one record without saving it.
data.merge now creates a new document
and leaves the template unchanged (the old spread
parameter, which appended pages to the template, is an error). It uses
the linked source, or inline csv / rows /
path / bytes:
echo 'data.merge {"path": "people.csv", "records": "range", "range": "1-20"}' | litho-cli script - --in template.litho --save merged.litho
The running app
litho --sample --control 7979 &
litho-cli app type.changeCase '{"case": "title"}' # any command
litho-cli app --method ui.screenshot '{"path": "/tmp/w.png"}' # any control-channel method
ui.menu.invoke {command} without params acts like
choosing the menu item: a command labelled "…" opens its dialog, which
ui.dialog.set / ui.dialog.confirm fill in and
close like a user would.
Parent pages
In the Pages panel, right-click a parent row and choose
Delete Parent. The equivalent command is
layout.parents.delete {id}, using the parent spread's
numeric ID. It deletes that parent's shared artwork and clears its
assignments from document pages and other parents. Local override copies
remain as ordinary editable objects; document pages and their own
content remain. Undo restores the parent and its relationships in one
step. Deleting the last parent returns the canvas to document pages.
Parent navigation is a separate operation.