Lolly
啟動應用程式

Rebuild a deck from the terminal

An old deck needs to become your design system's own, and you want a script or an agent to do the work. The CLI has the same deck reader, slide composer and checks as the app, so the result matches what a person would get there.

There are two routes:

old.pptx, a deck someone else made

Renovate in placeKeep the deck's layout; snap colours, fonts and the logo to the design system.

  1. Planlolly rebrand planplan.json
  2. Reviewedit the planrebrand inspect
  3. Compilelolly rebrand compileold.lolly, old.rebranded.pptx

Rebuild in DesignNew slides from the design system's slide master.

  1. Readlolly readold.inventory.json
  2. Brieflolly system contextbrief.json
  3. Composelolly composedeck.json, edits.json
  4. Packagelolly packagereview.lolly
  5. Checklolly checkfindings
  6. Exportlolly runreview.pptx

Both routes start from the old deck and end in a file the app opens.

signed by Lollycli-decks-routes.htmlHTMLpixels, not shapes8 KBAI editedgenerated by Claude Opus 5.5自己驗證取得已簽署檔案

The deck verbs are also MCP tools over the same code (The same verbs over MCP), and each one prints its own flags, inputs and exit codes with --help.

Renovate in place with lolly rebrand

lolly rebrand reads a deck someone else made (.pptx, .pdf or Photoshop .psd) and works in three stages, so an agent or a script can look at the first pass before anything is written:

lolly rebrand plan old.pptx --plan-out=plan.json
# writes plan.json (every object's proposal and review state) and plan.report.json (the counts)

lolly rebrand compile old.pptx --plan=plan.json --export=pptx
# writes old.lolly (a Design document), old.report.json and old.rebranded.pptx

lolly rebrand inspect plan.json --source=old.pptx
# a summary, the review queue and per-slide states; --slide=<n> lists one slide's objects

The plan is plain JSON (schemas/rebrand-plan-v1.schema.json). Between the two calls, an editor, a script or an agent can change a row: drop a slide, keep a photo, map a colour to another design-system token. compile takes exactly the plan it is handed. The plan records the source deck's hash, so compiling it against a different deck is refused.

The app's Rebrand view does the same work with a review screen, and two flags give you its bulk actions:

Each deck ends ready, needs-review or failed. compile exits 5 when every deck compiled but at least one needs review, 4 on a refused plan or an existing output and 2 on a usage error (Exit codes).

Folders, presets and longer runs
  • lolly rebrand plan|compile <dir> works through every deck a folder holds; --recursive adds its subfolders, mirrored in the output. A PDF beside a .pptx of the same name is left out.
  • --jobs=<n> runs up to eight decks at once in worker threads. --keep-going records a failed deck and moves on.
  • --resume keeps a run record beside the outputs (.lolly-rebrand-run.json), so a second call redoes only the decks that still need work.
  • --preset=<id|file.json> applies a renovation preset: a design system's or a person's default decisions. It resolves from the content profile first and then from rebrand-presets.json in the state directory; lolly rebrand presets lists what resolves.
  • --dry-run runs every stage and writes nothing. --ocr reads slides that are pictures of slides, using the text recognition model already on this machine.

Every stage reads the content profile's design system (LOLLY_PROFILE) and touches no network. The full stage reference, the object classes and the plan-editing rules for an agent are in the agent skill's Rebrand reference, and Rebrand a deck explains the ideas the CLI and the app share.

Rebuild in Design: the seven commands

These commands take an old deck to a checked, exported PowerPoint file. The sections below explain each step.

lolly read old.pptx --json --media=./media > old.inventory.json
lolly system context --json > brief.json
lolly compose --suggest --source=old.pptx --output=spec.json
lolly compose spec.json --source=old.pptx --output=deck.json --edits-out=edits.json
lolly package deck.json --source=old.pptx --label="Quarterly review" --output=review.lolly
lolly check review.lolly --source=old.pptx --edits=edits.json
lolly run review.lolly --export=pptx --output=review.pptx

read and check need no catalog, so both run on a standalone CLI.

Read the old deck

lolly read <deck> reads a .pptx, a PDF or a Photoshop .psd (or - for standard input) with the same reader rebrand uses. For each slide it gives:

With --json the envelope's result is the inventory (schemas/content-inventory-v1.schema.json); without it, an outline goes to standard output.

--media=<dir> writes each distinct picture once, named by its SHA-256 (<sha256>.<ext>). A file already there with the same bytes is reused; one with other bytes is refused with exit 4 and nothing is written, unless you pass --force. A drawing is written as its SVG, with its raster stand-in as fallbackRef. --thumbnails also draws each slide there as a PNG and records it as the slide's thumbnail.

A slide that is one picture of a slide reads as a picture, with a slide-flattened warning. --ocr reads the text of such pages with the text recognition model on this machine (lolly models fetch ocr downloads it; read never does). Each frame read that way carries a recognition record: the model, its release and languages and its confidence. A scan's own text layer is listed as hidden text with the model pdf-text-layer.

A hidden object, such as a Photoshop layer switched off, is left out of the text and pictures and listed under objects with hidden. Macros, scripts, controls and embedded files are never run or opened; a warning says when the file carries any.

Extract blocks for an agent

lolly read <file> --extract writes the file as blocks in reading order instead: on each page the text frames, then tables, charts, pictures and speaker notes. Each block records its page, its source object and where its words came from: authored, text-layer (a scan's invisible text) or recognized (with the model and its confidence).

The app's Read and extract view (#/extract) runs the same extract on the device.

Which design system a command uses

The Design verbs on this page (system context, compose, measure, package, check and run design) look for a design system in the same three places, in this order:

  1. --file=<tokens.json>;
  2. the active terminal system (lolly system use);
  3. the content profile's own system (LOLLY_PROFILE).

lolly rebrand is the exception: it always reads the content profile's own system.

lolly system context is the brief to read before composing, and its origin says which of the three answered. Beside the tokens, source evidence and rules, it gives the approved colour pairings, type per role, logos per surface, icons and their themes, photography and illustration families, the slide master's archetypes and the brand's house rules, as records a program can check. A section the design system does not declare is marked unavailable or derived in coverage, never left out.

lolly system skills lists the content profile's brand skills, the brand's own guidance per discipline, plus the baseline generated from the design brief. lolly system skill <name> prints one skill. With --into=<dir> it writes the skill as an Agent Skills folder instead; --mode=linked|bundled, --origin=<url> and --include-licensed decide how catalog links travel. lolly system skill coverage <name> sets the skill against the design system, and lolly system skill check <folder> reports whether an exported copy is the current revision. The CLI reads the brand pack on your own disk, so internal skills are listed too.

The slide master, logos, icons and media come from the content profile's catalog. So they are in the brief only when the design system is the profile's own: a --file or terminal system from another brand gets the neutral master and no catalog assets, and catalog in the result is null. lolly system check applies the same house rules with the same catalog choice. Its designSystem key has the same shape as in lolly check: { "origin": "file" | "terminal" | "profile", "profile"?, "tokensAsset"? }.

How a --file system reaches a render

lolly run design --file=<tokens.json>, lolly run <file.lolly> --file=<tokens.json> and lolly check --file=<tokens.json> hand the web shell's page that design system. A document lowered with lolly package --file then resolves its linked colours in one system everywhere: the export, the render checks and the brand checks. With no --file, an active terminal system is handed over the same way; with neither, the page uses the content profile's own.

The page holds the system in memory for that one job. Nothing is installed in its store, and a .lolly never carries a design system. So when any layer links a colour, lolly package --file (or package under an active terminal system) adds a design-system.not-carried note to its report, and its suggested next commands keep the --file. The app opens a .lolly in its own active design system, so someone who should see the file as authored adds the tokens file as a design system in the app and switches to it first.

The desktop app renders in its own design system, so a Design render with one of these goes to Chromium first and falls back to the app, with a note, only when Chromium is not there. --file belongs to Design: another tool refuses the flag, and so does --share, because a link cannot carry a design system.

Compose slides from the slide master

Placing every layer by hand is the long way round. lolly compose lays out each slide from an archetype of the design system's slide master, the way New slide from layout does in the app, and writes the Design document that package, check and run design --document take.

lolly compose --list
lolly compose --suggest --source=old.pptx --output=spec.json
lolly compose spec.json --source=old.pptx --output=deck.json --edits-out=edits.json

--list prints the master's archetypes, each with its slot keys, its ground and its dark twin. A slot key is a role such as title, or body#2 for the second body slot; label? is optional, and visual:image and data:table name the slot's kind. With --json, each slot also has its box in px at the master's own size.

--suggest --source=<deck> (or --inventory=<inventory.json>) writes a first spec: an archetype for each slide with the reason it was chosen, the slots filled with from references to the deck's text, the pictures as placeholder keys (photo: and the first 12 hex digits of the picture's SHA-256), and each slide's source and notes. Treat it as a draft to edit slide by slide.

compose <spec.json|-> composes a spec (schemas/design-compose-v1.schema.json): a list of slides, each with an archetype and its slots. A slot takes Design text markup, null to leave it out or an object with from (an inventory object id), text or image, plus any layer field to set over the master's. lolly compose --help prints every key, and the agent skill's Design reference explains each.

Every key a spec can carry

A slide can also carry cells for the repeated cells of a columns or steps archetype, source (the 1-based source slide whose text from reads and whose notes are carried), ground, notes, furniture (omit, footer, logo), emphasis and case, plus under and over rows written with the authoring keys, painted before and after the archetype's layers.

At the top, size, theme, themes, footer, pageNumbers, transition, gap and $styles apply to the whole deck. furniture, emphasis and case there are defaults a slide's own merge over: the omit lists add up, and a slide's footer and logo win.

A slot object's from takes para for one paragraph (or a list), join to run all its lines into one with the text given (such as ": "), or both. A slot can also set case, emphasis and any layer field: x and y relative to the slide, w, h, fg, fontSize, weight.

emphasis is accent (a source's bold in a title, subtitle, quote or number slot set in the brand's accent colour, the default where a weight house rule forbids bold), bold or keep (bold with the source's own colours). case: "sentence" lowers Title Case words other than the first. It lowers proper nouns too, so it is never the default, and each change is an edit with its result.

Every slide keeps its binding to the master: the archetype, each slot's role and the master's furniture. That binding is why lolly check judges a composed slide by the master's house rules, and why the .pptx export has slide layouts with title, subtitle and body placeholders a person can edit. Every empty slot is dropped and reported. Page numbers count the slides in presentation order, and the footer is the spec's text.

The master is the first of these: --master=<masters.json>, the slide master of the design system in use or the engine's neutral master. The report says which it used. A --file system from another brand has no catalog master or logos here, so it composes on the neutral master with no logo, and a note says so.

Dark slides, themes and logos on photographs

--theme=dark (or a slide's ground) takes each archetype's dark twin, and a flow-cards-N-C or flow-columns-N-C layout takes a twin made from the master's dark content slide. A light archetype without a twin is drawn from the master under the design system's Dark theme, and the report says so. An archetype that is dark by design keeps its ground in a light deck, with a note when a slide asked for light.

With themes listing more than one theme ("themes": ["light", "dark"], or --themes=light,dark), the document follows the theme it is drawn in. Logo furniture is written as <id>?theme=auto. Every ground, bar and ink the master takes from a token is stored as the colour of theme plus a link to that token, and token references in under and over rows are lowered to links the same way. On a dark deck, a light archetype whose linked ground turns dark, with ink that still reads, is used in place of its dark twin. A colour the master states as a hex, and the accent ink given to a source's bold, stay literal.

With logo left at auto, a slide whose under picture covers 90% of it or more takes the design system's on-photo mark. When the picture's bytes are at hand (from --source, or --asset=KEY=PATH for a placeholder key, repeatable), compose measures the picture under the logo. Where no mark the brand allows on photography reaches 3:1 there, it leaves the logo off with a compose.logo.photo-contrast note. It draws the under rows as the canvas does (a box's opacity, a token fill, a linear grad with its $tint); a slide it cannot draw that way, such as a picture with a photo look, gets a compose.logo.photo-unmeasured note. A text slot whose ink falls short of 3:1 (large text) or 4.5:1 against the picture under it gets a compose.text.photo-contrast note and keeps its ink.

Every composed text slot is measured with the faces the canvas loads. The report lists each one that clips, and --fit=shrink steps it down in whole px to the smallest size the master sets for its role. --size=WIDTHxHEIGHT sets the slide size (1920x1080 by default).

--edits-out=<edits.json> writes the source wording the composed slides leave out or change, as { "edits": [...] }, which lolly check --edits reads. Each entry carries a generic reason; write the real one before you hand the file to a person.

Compose a whole deck, one slide per source slide. The fidelity check pairs artboards and source slides by their text, so a spec that recreates only some slides reports the others as missing.

compose exits 0 when it composed (a slot that still clips is reported, not refused), 2 on a usage error (an unknown flag, a size or theme that is not one, a master or token file that will not read), 4 when it refuses the spec (with the JSON pointer of the key) or an output exists without --force, and 1 when the deck cannot be read. With --json the envelope's result holds the document (or, with --output, its path), the report, the edits, the pictures it named and the next commands to run.

Write and fit the text

You can also write a Design document by hand, or edit the one compose wrote. Its rows may carry authoring keys, which start with $:

Before anything else reads the document, these are lowered to ordinary layers in canvas coordinates, with every text field written out; they are never stored. Text styles take their sizes and colours from the design system the command uses. A key that cannot be lowered is refused with exit 4 and the JSON pointer of the key, and lolly check reports it as design.authoring.invalid. The keys, the style rules and the layouts are in the agent skill's Design reference; the schema is schemas/design-authoring-v1.schema.json.

Every verb here accepts an authoring document: run design --document=<file|->, compile design --inputs=<file>, validate design --inputs=<file> --document, check, measure --text-layers and package.

A placeholder picture such as photo:title gets its bytes only when lolly package --asset writes the .lolly. Before that, run design --document draws the slide without it and warns Asset not in catalog: photo:title, --asset is refused there and check lists it as a brand.asset.review finding, because it is not one of the design system's asset ids. To preview or check a document with its pictures, package it first and run or check the .lolly.

Measure text before it is drawn

lolly measure --text="Where we are" --width=900 --style=body
lolly measure deck.json --text-layers --layer=s1-title

lolly measure --text=<text|-> --width=<px> says where a plain text layer's lines break and how tall it is. It shapes the text with HarfBuzz in the faces the canvas loads and breaks it the way the browser does. For each line it gives the width, the space left and a nearEdge flag for a line within a few px of breaking differently. It also gives the height, the scrollHeight the canvas reports and, with --height=<px>, whether the box clips the text. A clipped box is an answer, not a failure: the exit code is 0.

The layer's fields are --font, --weight, --size, --line-height, --pad, --tracking, --italic and --valign; a field left out takes the renderer's default (48 px, weight 700, line height 1.12, padding 8, middle). --style=<id> measures the box as an authored layer with that style, starting from padding 0, top and left. A style's size scales with the artboard, so give --artboard-width=<px> for one that is not 1920 px wide; the output says which width it used. --italic measures the italic face the canvas draws, and --text=- reads the text from standard input.

lolly measure <design.json|.lolly> --text-layers measures every plain text layer with its own fields, including a document's own $styles; --layer=<id> (repeatable) picks layers. With --json the result follows schemas/text-measure-v1.schema.json. When lolly check cannot draw the page, it uses the same measure to report text that would clip, as a design.text.overflow warning.

Package and export

lolly package deck.json --source=old.pptx --label="Quarterly review" --output=review.lolly
lolly run review.lolly --export=pptx --output=review.pptx

lolly package <design.json|-> --output=<file.lolly> writes a .lolly the app reopens as the same document, with its pictures and its name. It finds each picture's bytes in one of these places:

The last two also resolve a key ending in the first hex digits of a picture's SHA-256, such as the photo:4fd58e623334 keys compose --suggest writes, when exactly one picture matches. Each picture is hashed and its type read from its bytes; a file that is not a picture is refused. Every other picture must be a catalog id of the active profile. A picture with no bytes or an unknown catalog id is refused with exit 4 unless you pass --allow-missing-media, and a path layer that will not decode is always refused.

--label=<name> is the name the app shows and exports under, kept as typed; a character a file name cannot hold is replaced only when a file is saved. --theme and --file choose the design system the authoring styles resolve in. An existing output is refused unless you pass --force. The file is read back after it is written, and the report lists the layers, the pictures by hash and the catalog references checked (schemas/design-package-v1.schema.json; --json needs --output). The zip times are the UTC time of SOURCE_DATE_EPOCH when it is set, so one document written by the same Lolly version gives the same bytes in any time zone.

lolly run <file.lolly> --export=<format> exports a saved Design session with the app's own exporter, so the PPTX, PDF or image is the one the app writes. Formats are pptx, pdf, png (the default), svg, jpg and webp.

This needs the browser tier, as every Design export does: a built web shell (pnpm run build:web), or LOLLY_WEB_BASE set to the address of one you run (pnpm run dev:web) under the same LOLLY_PROFILE as the CLI (Render with the CLI). A reusable tool .lolly keeps its --trust-tool path; --trust-tool on a saved session is refused, because a session carries no code to trust.

One document, every theme

A Design document follows the theme it is drawn in when its colours are token references ({color.semantic.text}), its logos and icons are written as <id>?theme=auto and its photographs carry a brand look (user/media/<sha256>?treatment=<look id>).

--themes=<a,b|all> exports once per theme, to <stem>-<theme>.<ext> beside --output:

lolly run deck.lolly --themes=light,dark --export=pptx --output=deck.pptx
# writes deck-light.pptx and deck-dark.pptx

The names are the themes the design system declares, and all takes every look in order. A design system with colourways keeps them out of all, so each look is exported once, in the colourway the session was saved in or else the canonical one. Name a colourway, as in --themes=Beach, to export in that colourway. The same flag works on lolly run <tool>, including design --document.

--themes needs --output, refuses a name the design system does not declare (and lists the ones it does), and cannot be combined with --_themes, --share or --production-repairs. Standard input is read once and given to every theme. For one file in one theme under the exact name you give, use --theme=<name> (or --_themes=<json>, the URL parameter _themes): --theme=light --output=light.pptx writes light.pptx, where --themes=light would write light-light.pptx. A photo look is baked into the picture's pixels; on a CLI without @napi-rs/canvas the plain picture is used and a warning says so.

How a Design document reaches the browser

lolly run design --document, a saved session and the render family of lolly check all reach the web shell as a saved session through its #/open route. The file is served once, from a random path on the same local server, to one GET of up to 64 MB, so a document with full-resolution pictures never has to fit in an address.

The desktop renderer keeps the address. Under LOLLY_RENDERER=auto (the default) or desktop, a running or installed Lolly app renders a Design document from its address when that address is under the app's 1 MiB request limit, and Chromium takes the session when no app is there. An address over 1 MiB goes to Chromium as a session, with a note when an app was there to take the job. On every path, --durable checks that the served web shell has the TrustMark encoder model before a browser starts.

Check the recreation

lolly check review.lolly --source=old.inventory.json --json > check.json

lolly check <file> runs every check Lolly has on a Design document (.json), a .lolly or an export (.pdf, .pptx, .png, .jpg, .webp, .svg) and returns one findings list. Each finding has a stable dotted code, a severity (error, warn or info), the layer and artboard where there is one, the message the app shows and a fix where one is safe to apply. With --json the result follows schemas/check-report-v1.schema.json.

The checks come in five families:

FamilyWhat it checks
structureLayer ids, artboards and layers outside their artboard. A picture drawn at more than twice its pixel size is design.image.low-resolution, read from the picture's own bytes; an SVG is never judged.
renderWhat the Design editor checks on the painted page: clipped text, contrast and font coverage, with the pictures painted. Text over a photograph is measured against the pixels under its lines: failing over nearly all of them is design.text.contrast-low, and a partly light, partly dark picture is design.text.contrast-review. Needs the browser tier; without one this family is unavailable, with the reason, and the others still run.
brandColours, fonts and asset ids against the design system, and its house rules: headline weight, alignment, approved colour pairings, accent edges on rounded shapes, dashed lines, logo per surface. A palette colour with an alpha channel counts as that colour; any other translucent colour is brand.color.unknown. Uploaded pictures are not reviewed. On a .pptx it reads the colours the slides paint in the --theme given: an off-palette colour is brand.color.review, and a slide ground that is not the theme's surface, primary or secondary colour is brand.ground.theme. Other exports have no brand family.
verifyThe Verify layout clues (a coloured edge on a rounded card, an eyebrow above a heading, decorative numbering), from the layers or, for an export, from the file. Text recognition is off unless you pass --ocr, so two machines give the same answer.
fidelityWith --source=<deck or inventory.json>: every source string is in the result or listed as an edit, every speaker note is carried over and the slide count and order match. A change of case or spelling is listed for review. A string repeated on several slides must appear on each of their artboards; one found only on another artboard is reported as moved. --edits=<edits.json> (a JSON array of { "source", "result", "reason" }, or the file compose --edits-out writes) records wording changed on purpose: those findings stay as notes marked excepted, listed under fidelity.excepted, never as passed.

A check never reads as clean on what it did not read. An export page with no text layer, read without --ocr, is a verify.text.unread warning, and pages past the cap are a verify.pages-capped warning.

check exits 0 when nothing needs attention (notes are allowed), 5 when there are warnings to review, 4 on an error finding, 3 with --browser=require when a document has no browser tier to render it, 2 on a usage error and 1 when a family itself failed or no page of an export could be decoded. An export has no render family, so --browser=require does not apply to one, and one command line can check documents and their PDF exports together.

The other check flags
  • --strict turns weak Verify clues into errors and refuses on any warning.
  • --browser=off skips the render family.
  • --theme=<name> is the token theme the document is checked in; a theme the design system does not declare is a usage error.
  • --themes=<a,b|all> checks in several themes in one report. The render, brand and Verify families run once per theme and each finding carries theme. Structure and fidelity run once. A link that does not resolve in a theme is brand.token-link.unresolved. --theme and --themes together are refused.
  • --page-cap=<n> limits how many artboards or pages Verify reads (1 to 100).
  • On a standalone CLI with no design system, the brand family is unavailable.

CLI batch and automation shows check as a CI gate.

The same verbs over MCP

The MCP server offers each verb as a tool, over the same code, so an agent connected there gets the same answers:

CLIMCP tool
lolly rebrandlolly_rebrand
lolly readlolly_read
lolly composelolly_compose (mode is compose, list or suggest)
lolly measure --textlolly_measure_text
lolly packagelolly_package
lolly checklolly_check