Command line

The popcraft CLI edits .popcraft files headlessly, renders exports, calls the REST API, publishes to the marketplace and manages the desktop app's plugins, themes, fonts, skills and agents.

Command line

popcraft is PopCraft from a terminal or a CI job. It runs the same commands the editor, the agent and plugins run, against .popcraft files on disk, and it speaks the REST API.

npm i -g @popcraft/cli      # Node 25 or newer
brew install getpopcraft/popcraft/popcraft   # or with Homebrew: one executable, no Node needed
popcraft --help

Editing files

popcraft new makes a blank document; popcraft run runs one command on a file and saves the file when the command changed it.

popcraft new poster.popcraft --name Poster
popcraft run poster.popcraft node.create --type RECTANGLE --width 320 --height 200 --fill "#ff0055"
popcraft run poster.popcraft doc.outline

popcraft commands lists every command that runs from the CLI, and popcraft commands <command> shows its flags. A command can be named by its id (node.create), its agent tool name (create_node) or its plugin method (createNode). Flags come straight from the command's parameters: parentId is --parent-id, a list takes --ids a,b,c or a repeated flag, and a nested value takes JSON, inline or from a file:

popcraft run poster.popcraft paint.setFills --ids "$ID" --gradient @gradient.json
popcraft run poster.popcraft node.update --params @changes.json

--params <json|@file|-> gives every parameter at once (- reads stdin), and flags are laid over it. A parameter whose name the CLI uses itself is spelled with a param- prefix: doc.importJson --param-json @other.popcraft.

FlagWhat it does
--page <id or name>Run on that page instead of the first
--select <id,…>Select these layers first (for commands that act on the selection)
--out <file>Save the edited document somewhere else, or say where an export goes
--dry-runRun, report, and write nothing
--batch <json|@file|->A list of { "command": …, "params": … } run in order and saved once
--keep-goingIn a batch, carry on past a failed step (and save what the others did)
--jsonMachine-readable output

Each popcraft run is its own session, so history.undo only undoes steps from the same batch. In a batch, a step can use what an earlier one returned: "$0.id" is the id step 0 created.

The .popcraft file

A .popcraft file is JSON: { "version", "name", "document", "exportedAt" }, the document holding its pages and layers. It is portable: the pictures, video and sound its layers use are written into it, and so are its fonts. document.fonts lists each font face the file carries:

{ "id": "font-3f1c…", "family": "Helvetica", "src": "media://3f1c…", "weight": 400, "style": "normal", "face": 0,
  "fileName": "Helvetica.ttc", "postscriptName": "Helvetica", "data": "data:font/collection;base64,…" }

src names the font file by the hash of its bytes, and data holds the bytes themselves (opening the file moves them into the app's media store). face is the face of a collection, from 0. Files with no fonts open as they always did.

Cloud files

With a token that has the files:write scope, cloud:<key> in place of a file runs the commands on that cloud file through the REST API: people editing it see the change merge in. push and pull move whole documents.

popcraft run cloud:abc123 node.create --type RECTANGLE --width 320 --height 200
popcraft run cloud:abc123 --batch @steps.json --if-match y1234
popcraft push poster.popcraft                      # a new cloud file; prints its key
popcraft push poster.popcraft --key abc123         # replace that file's document
popcraft pull abc123 poster.popcraft

Cloud runs take the commands that need nothing but the document; exports and vision.lookAt need a local file.

Approvals

Commands that ask for approval in the app (replacing a whole document, for example) ask in the terminal too, showing exactly what will happen. --yes approves without a prompt. With no terminal to ask in and no --yes, the answer is no and popcraft exits with code 3. Commands that are never available to automation (minting tokens, billing, moderation) are refused here as everywhere else.

Exports

Exports and vision.lookAt need the WebGPU canvas, so they run in a real editor: a headless Chrome on the PopCraft app, with your file opened in it. Chrome comes from puppeteer, installed once:

npm i -g puppeteer
popcraft run poster.popcraft export.nodes --ids "$ID" --format PNG --scale 2 --out poster.png
popcraft run poster.popcraft export.mp4 --out trailer.mp4

--app-url picks the app to render with (it defaults to the server, https://popcraft.app), and --headful shows the browser.

The file's own fonts go with it. When a text is set in a family that nothing can draw (not carried by the file and not on Google Fonts), the export still runs, in a stand-in face, and says so: a warning: line on stderr, and warnings in the --json result.

Fonts

A .popcraft file can carry font files, so every machine and every export draws its text in them:

popcraft run label.popcraft font.add --file Helvetica.ttf --family Helvetica
popcraft run label.popcraft font.list --file Helvetica.ttc          # a collection's faces
popcraft run label.popcraft font.add --file Helvetica.ttc --family Helvetica --face 0
popcraft run label.popcraft font.add --file Helvetica.ttc --family Helvetica --face "Helvetica-Bold" --weight 700
popcraft run label.popcraft font.list                                # the file's fonts, and the missing ones
popcraft run label.popcraft font.remove --family Helvetica --weight 700

--file takes .ttf, .otf, .ttc, .woff or .woff2. --family is the name texts use (by default, the name in the file); --weight and --style (normal or italic) default to the face's own. --face chooses a face of a collection by its number from 0, or by its name. Adding a face in the same family, weight and style replaces it.

Signed in, font.teamAdd --file … --family …, font.teamList and font.teamRemove --id … manage your team's fonts: uploaded once, usable in every file of the team.

A shop exports headlessly, with no browser, as a store's project (Storefronts):

popcraft storefront shop.popcraft --target next-commerce --out .     # components for a Next.js Commerce store
popcraft storefront shop.popcraft --target shopify-theme --out theme  # a Shopify theme's folders

Designs in code

A whole design can be written as one script with the design kit and built in a single command: every size, its text styles, colour roles, components, motion and music. The script stays on the design, so editing it and running the command again with --frame rebuilds it in place.

popcraft run launch.popcraft write_design --size ig-story --name "Launch reel" --script @reel.js
popcraft run launch.popcraft write_design --frame "$FRAME" --script @reel.js --look '{"at":[0,4000],"kits":true}' --out look/
popcraft run launch.popcraft run_script --code @fix.js        # any JavaScript over the document, one undo step

The build runs here, in a sandbox, and answers with its checks (contrast, text size, safe areas, overlap, loop seams). --look also renders the review into --out: every size, the chosen moments and a light and a dark brand kit (this needs puppeteer, as exports do). The kit is also a library, @popcraft/kit, with types for writing scripts in an editor. Designing with an AI agent is the whole loop. Working projects: a launch in three sizes from one script and translated copies of a design.

In the desktop app

With the desktop app running, popcraft open opens a file in it and popcraft run --ui runs commands in its active tab's file, as you. Everything the app can do works there, including the commands marked sign-in, app only and unavailable below: generating images, cloud files and versions, panels and tools.

popcraft open poster.popcraft                 # becomes a new cloud file, in its own tab
popcraft open abc123                          # a cloud file's tab (also cloud:abc123 or its /pop/ link)
popcraft run --ui vision.generateImage --prompt "a red bicycle on a beach"
popcraft run --ui export.nodes --format PNG --out frame.png
popcraft run --ui publish.site --address launch-day    # publish the open file as a website
popcraft run --ui --batch @steps.json
popcraft tabs                                 # the app's tabs; --tab <id> runs in another one

Commands that ask for approval ask in the terminal before they're sent to the app. A long command waits up to two minutes for the app; --timeout <seconds> waits longer. On a Mac, popcraft open starts the app if it isn't running. Only the downloaded app has this: the Mac App Store build can't accept connections from the command line.

The app listens on 127.0.0.1 only, and writes a fresh random token to cli-bridge.json in its data folder each time it starts. Only your user account can read that file, and requests without the token are refused, as is anything sent from a web page. popcraft://open?key=<key> links open a cloud file in the app the same way.

What runs where

popcraft commands --all lists every command with where it runs:

WhereMeaning
localRuns in the CLI, on the file
browserRenders in a headless editor (exports, vision)
sign-inWorks on PopCraft's servers as the signed-in user (cloud files, versions, sharing, libraries, stock photos): run it with --ui
app onlyNeeds the app itself: its screen (panels, tools, the camera), or building and uploading as you (publishing a website): --ui
unavailableNeeds a generative model, a plugin host or the agent loop: --ui runs it in the app
plugin onlyPart of the plugin API
blockedNever available to automation

The REST API

popcraft login                     # asks for a personal access token and keeps it in the system keychain
popcraft whoami
popcraft api                       # every operation
popcraft api files.list --all      # follows nextCursor to the end
popcraft api files.get "$KEY" --depth metadata
popcraft api files.thumbnail.get "$KEY" --out thumb.png
popcraft api get /files/$KEY/versions

Create the token from the account menu (Account → Personal access tokens) with the scopes you need. In CI, pass --token or set POPCRAFT_TOKEN instead of logging in. --url (or POPCRAFT_URL) points at another server; popcraft login --url … makes it the default.

Signing in as you

A token can read and write files, but it can't generate images, video or audio, or act on your account. popcraft login --hypery signs you in in the browser, as the app does. After that, local runs can use those commands too: the generate commands, and the cloud file, version, sharing, library and stock photo commands. The sign-in is kept in the keychain next to the token, and popcraft logout forgets both.

popcraft login --hypery                       # --no-browser prints the link instead of opening it
popcraft run poster.popcraft generate.video --prompt "waves at dusk" --duration 5

vision.generateImage also needs the canvas, so it still runs only in the app (--ui).

Publishing to the marketplace

popcraft publish [folder] sends every item under the folder: a folder with manifest.json is a plugin, widget or theme pack, pack.json a brush or shader pack, and template.json a template. It needs a token with the marketplace:publish scope.

popcraft publish examples --dry-run                    # build and check everything, send nothing
POPCRAFT_TOKEN=pop_… popcraft publish examples
popcraft publish examples --changed-since origin/main  # packs and templates only when their folder changed

A plugin version that is already published is skipped, so bump version to ship. Packs and templates are updated in place by their meta.localId. A template is sent with its own media (media/<hash>, the files it references as media://<hash>) and its pictures (pictures/).

Templates

popcraft template makes templates the way PopCraft's own are made: defineTemplate sources written with @popcraft/kit, which also ships every official source to start from. The whole guide is Making templates.

popcraft template list black friday countdown --animated     # official templates to start from
popcraft template new templates/sale.ts --from motion-promo-sale-drop
popcraft template build --out build                           # each source → build/<id>/ (template.json, media/, .popcraft)
popcraft template check                                       # build, then every check the official templates pass
popcraft template look templates/sale.ts                      # every size at rest, at four moments and under two kits
popcraft template look templates/sale.ts --at 0,1200,4000     # …or at the moments you pick, in milliseconds
popcraft template pictures                                    # the gallery's thumbnail, still and hover loop
popcraft template publish                                     # all of that, then upload (a marketplace:publish token)
popcraft template publish --no-pictures --dry-run             # without drawing pictures; show what would upload

They build with the project's own @popcraft/kit, so install it beside the sources.

popcraft pictures [folder] draws those pictures for every template.json under the folder, in a headless Chrome on the app (puppeteer, --app-url), exactly as PopCraft's own templates' are drawn: the card thumbnail (written into template.json), the detail still (pictures/still.webp) and, for a template that plays, the hover loop (pictures/anim.mp4).

popcraft pictures templates          # then look at templates/*/pictures before publishing
POPCRAFT_TOKEN=pop_… popcraft publish templates

The desktop app's folders

The desktop app loads plugins, themes, fonts, skills and subagent profiles from folders in its data directory and watches them, so whatever the CLI puts there shows up in an open window straight away.

popcraft plugins install ./my-plugin            # a folder with manifest.json, a .zip or a .json bundle
popcraft plugins install widget.zip --kind widget
popcraft themes install Night.popcraft-theme.json
popcraft fonts install https://example.com/Inter.woff2
popcraft skills install brief.md
popcraft agents list
popcraft plugins remove my-plugin
popcraft fonts path                             # the folder itself

--force replaces an item that is already there, and --as <name> installs under another name. Set POPCRAFT_DATA_DIR to use a different folder.

Running a plugin on a file

popcraft plugins run runs a plugin's main script on a .popcraft file and saves what it changed. The plugin can be a folder, a .zip, a .json bundle, or the name of one installed in the app's folders.

popcraft plugins run ./tidy-layers poster.popcraft
popcraft plugins run tidy-layers poster.popcraft --command rename --out tidied.popcraft

The first run asks you to approve the permissions the plugin's manifest declares, as the app does; your answer is kept, and you're asked again when a new version declares more (--yes approves without a prompt). The script runs in its own Node process under Node's permission model, with no access to your files, other processes or the CLI itself, and no network unless you approved network. It can use only the plugin API, with the permissions you approved, just as in the app. Running plugins needs Node 25 or later. A run ends when the plugin calls closePlugin, or when it goes quiet with no calls or timers pending. It's stopped after a minute; --timeout <seconds> allows longer. Plugins that open a panel, export with the canvas or create files only work in the app.