CLI reference
Everything the plicine command does: every command, argument, flag, exit code and environment variable.
Running plicine, plicine help, plicine --help or plicine -h prints the usage summary and exits 0. An unknown command prints plicine: Unknown command "<command>". Run plicine --help. to stderr and exits 2 (see Exit codes below).
Getting the command
The plicine command comes inside the app, so there's nothing else to download. Plicine > Install Command-Line Tool… puts it on your PATH, pointing at the program bundled inside the app, and the same menu item removes it again. The app also offers this once, the first time it runs.
- On a Mac it links
plicineinto/usr/local/bin, or/opt/homebrew/binon Apple Silicon if that's writable and/usr/local/binisn't, asking for your password if it needs to. If neither works it uses~/.local/bin, which you may have to add to your PATH yourself; the app says so. - On Windows it writes a small
plicine.cmdinto%LOCALAPPDATA%\Plicine\binand adds that folder to your user PATH. Open a new terminal to pick the change up. If your PATH is too long for Windows to extend, the app names the folder so you can add it by hand.
It never overwrites a plicine that isn't its own; it tells you where the other one is. To set it up yourself and skip the menu item:
ln -sf "/Applications/Plicine.app/Contents/MacOS/plicine-server" /usr/local/bin/plicine
plicine open [vault]
Serves a vault and prints its URL. vault defaults to the current directory.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--port <n> |
number | random free port | Port for the local server |
--no-browser |
boolean | off | Don't open the URL in a browser |
plicine open my-vault
Prints:
Serving /path/to/my-vault
http://127.0.0.1:54213/?t=3f9c...
Press Ctrl+C to stop.
Unless --no-browser is set, that URL is opened with open (macOS), cmd /c start (Windows) or xdg-open (everything else). The process runs until Ctrl+C (SIGINT) or SIGTERM, then stops the server and exits 0.
The URL's t= query parameter is a one-time sign-in token: visiting it hands the token to that browser tab, sets a session cookie and moves on to the same page without the token in the address.
plicine export-pdf <doc.md>
Renders one document to PDF in a headless browser. If the machine has none, the pinned renderer is downloaded first (see plicine chrome and Environment below).
| Flag | Type | Default | Meaning |
|---|---|---|---|
-o, --out <file> |
string | <doc-basename>.pdf in the current directory |
Output file |
--theme <id> |
string | the document's resolved theme | Theme id or hash to render with instead |
--vault <dir> |
string | nearest vault root above doc.md |
Vault to resolve the document against |
--frozen |
boolean | off | Fail instead of writing blocks.lock if resolution has drifted |
--strict |
boolean | off | Exit 1 if the render produced any warnings |
doc.md must exist and end in .md, and be inside the vault root. Without --vault, the vault root is found by walking up from the document's folder looking for a .vault/ folder.
plicine export-pdf handbook/welcome.md
Prints, to stdout:
Wrote welcome.pdf: 6 pages in 1.0s
and (cached) is appended when an identical render (same document hash, theme hash, page size, block hashes and trust decisions) is already in the PDF cache (see The app data folder). Any render warnings (unapproved blocks, unknown blocks, missing themes, a fenced code sample that happens to parse as invalid YAML, and so on) are printed to stderr as warning: <message> lines, one per warning.
With --frozen, the export first checks blocks.lock against a fresh resolution; if resolution has drifted, it prints the drift to stderr and exits 1 without rendering. With --strict, the command exits 1 if result.warnings is non-empty, even though the PDF was written.
plicine export-site <vault>
Renders a whole vault as a folder of static HTML: one page per document in the infinite layout, in the theme's own CSS, with every block's Print output already in the page. No browser is involved, so this is the one export that doesn't need Chrome. vault defaults to the current directory. There's no server, no sign-in, no editing and no script in what comes out, so the folder reads the same opened from file:// as it does from a plain web host. These docs are made this way.
| Flag | Type | Default | Meaning |
|---|---|---|---|
-o, --out <dir> |
string | none, required | Folder to write the site into. It must be outside the vault |
--theme <id> |
string | the theme each document resolves to | Theme id or hash to render every page with |
--strict |
boolean | off | Exit 1 if the export produced any warnings |
plicine export-site my-vault --out my-site
Prints, to stdout:
Wrote site: 46 pages and 3 files in 0.4s
The pages are the vault's documents, plus a page for each folder that has no INDEX.md of its own, listing what's in it. The vault counts as a folder, so a vault without an INDEX.md still gets a front page. In the sidebar of every page, the vault's name is the link to the front page, as a folder's name is the link to the folder's page, and the front page has no row of its own among the documents. The files are the vault's assets/ folder and anything else a page pointed at. Warnings (an unapproved block, an unknown block, invalid block data, a link to a document the vault hasn't got, a link that still names a .md file once the page is written) are printed to stderr as warning: <message> lines, each naming the document it came from.
Every page ends with one line under the document, "Made with Plicine · plicine.com", linked to https://plicine.com/?ref=site. It's part of the site's frame, so it's there whatever theme the pages are in. "credit": false in .vault/vault.json takes it off the whole site, and credit: false in a document's frontmatter takes it off that page; see vault.json and frontmatter. The pages written for folders without an INDEX.md follow vault.json.
A block only runs if its content hash is approved on the machine doing the export, the same rule export-pdf follows: an unapproved block prints a placeholder and a warning. In CI that means running plicine trust <vault> --all first, then export-site --strict.
What comes out:
| Path | What it holds |
|---|---|
index.html |
The front page: the vault's own INDEX.md, or, for a vault without one, a page listing what's in it |
guides/welcome.html |
One page per document, at the document's own path with .md swapped for .html |
guides/index.html |
A folder's INDEX.md, or, for a folder without one, a page listing what's in the folder |
assets/ |
The vault's assets folder, copied whole, plus any other vault file a page points at |
_plicine/ |
The stylesheets, and each theme a page was rendered in with its templates and fonts |
_plicine is the one name the export reserves, and a vault with a folder of that name is refused. Every link in a page is relative, which is what makes file:// work. Two exports of the same vault produce the same bytes. There's no search, no 404 page, no sitemap and no dark mode yet, and the sidebar lists folders by name and documents by title.
The export writes into the folder given, adding and replacing files without removing anything, so a document that was renamed leaves its old page behind. Export into a new folder, or empty the old one first. The vault itself is never written to.
plicine validate [vault|doc.md|block-dir]
Checks documents, blocks and schemas without starting a server. The target defaults to .. If the target folder contains manifest.json, it's validated as a block source folder (see below); a .md file is resolved against its vault and only that document is checked; otherwise the target is opened as a vault and every document under it is checked.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--json |
boolean | off | Print one JSON object instead of text lines |
--trust |
boolean | off | Also compile and check blocks that aren't approved on this machine |
Without --trust, a block that isn't approved yet is reported as a warning (... is not approved on this machine, so its data wasn't checked (plicine trust, or --trust)) and skipped rather than compiled. --trust compiles and validates it anyway, without changing the trust store; this is what CI and agents typically want.
For a vault target, each document's frontmatter, block resolution warnings, and every non-code block's props are checked against its schema (core block schemas, or the compiled block's schema export), and its Print output is rendered once to catch render-time throws. A [[link]] that matches no document or asset in the vault is a warning on its own line, which is how a typo or a document that was renamed out from under a link shows up in CI. blocks.lock drift is reported as warnings (not errors).
plicine validate my-vault --trust
Text output is one line per problem, <file>[:<line>] <severity> <message>, followed by a summary line:
blocks.lock warning iso: not in blocks.lock (would add @solunify/iso@1.0.0)
Checked 30 documents: 0 errors, 1 warning
With --json, the same information is one object: { "ok": boolean, "summary": string, "problems": [{ "file", "line"?, "severity", "message" }] }.
The checks are the same everywhere, so validate, the gate block push and block edit run before writing, and the agent's validate_doc tool all report the same problems in the same shape.
For a block source folder (a directory with manifest.json at its root), validate packs the folder, compiles it, then runs every fenced example in BLOCK.md whose name matches the block's alias or id through the schema, Interactive and Print; Print is rendered twice per example and flagged as a warning if the two outputs differ, since Print must be deterministic. It also warns if the block's schema can't be turned into JSON Schema (so the app can't build an edit form for it).
plicine validate ~/blocks/price-grid --json
Exit codes
validate, lock --frozen, export-pdf --frozen/--strict and export-site --strict can all fail without throwing. In every command:
| Exit code | Meaning |
|---|---|
0 |
Success (or validate found no errors, only warnings) |
1 |
A checked condition failed: validate found at least one error, template apply found a missing value or a file in the way, lock --frozen/export-pdf --frozen found drift, export-pdf --strict or export-site --strict found warnings, block push/edit found a validation error or a conflict with the vault's file, agents-md found an AGENTS.md it didn't write, or an uncaught non-CLI error occurred (stack trace printed to stderr) |
2 |
A CLI usage error: missing argument, bad flag value, unknown command, block discard without --yes, agents-md --write --remove (plicine: <message> printed to stderr) |
plicine create-block <name>
Scaffolds a block source folder: manifest.json, package.json, tsconfig.json, BLOCK.md and src/index.tsx, all with a working example (a collapsible note block) already wired up. name must match /^[a-z][a-z0-9-]*$/.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--dir <parent> |
string | . |
Folder to create <name>/ inside |
--scope <@org> |
string | @local |
Scope prefix for the block id, must match /^@[a-z0-9][a-z0-9-]*$/; @core is reserved and refused |
--description <text> |
string | A <name> block. |
description field in manifest.json |
Fails if the target directory already exists.
plicine create-block price-grid --scope @acme --dir ~/blocks
Prints:
Created ~/blocks/price-grid
manifest.json
package.json
tsconfig.json
BLOCK.md
src/index.tsx
Next:
1. Edit src/index.tsx (schema, Interactive, Print) and the example in BLOCK.md
2. plicine validate ~/blocks/price-grid
3. plicine pack ~/blocks/price-grid --into <vault>
plicine pack <block-or-theme-dir>
Builds a .block or .theme file from a source folder, deterministically (see The .block package for how the hash and byte-identical zip are produced). Which kind it is gets decided by whether the folder has manifest.json or theme.json at its root; anything else fails.
| Flag | Type | Default | Meaning |
|---|---|---|---|
-o, --out <file> |
string | see below | Output file path |
--into <vault> |
string | current directory | Vault to pack into |
Without -o, a block is written to <into>/.vault/blocks/<alias>.block (or ./<alias>.block without --into); a theme is written to <into>/.vault/themes/<id>.theme (or ./<id>.theme).
plicine pack ~/blocks/quote-table --into my-vault
If the destination file already exists with byte-identical content, nothing is written:
@solunify/quote-table@2.1.0 unchanged at my-vault/.vault/blocks/quote-table.block (229eec57928d)
Otherwise it's written and reported with its size:
Packed @solunify/quote-table@2.1.0 into my-vault/.vault/blocks/quote-table.block (229eec57928d, 4.0 KB)
Warnings from packing a block folder (for example, a missing BLOCK.md example) are printed to stderr as warning: <message> before the result line.
plicine block
Checks an installed block's source out of a vault into an ordinary folder, edits it with normal tools, and writes it back over the original .block file in place, superseding it exactly like dropping in a new file would. See Editing an installed block for the walkthrough; this section is the flag reference.
plicine block checkout <name> [vault]
Resolves name (alias, @scope/id or @scope/id@range) against vault (default .) the same way a document would, and extracts the winning .block into a checkout folder. Running it again on the same folder resumes the existing checkout rather than re-extracting, so it's safe to run at the start of every session.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--dir <path> |
string | <app data>/dev/<vault-hash>/<alias> |
Checkout location |
--create |
boolean | off | Scaffold a new block (via scaffoldBlock, the same template as create-block) instead of failing when name doesn't exist yet |
--scope <@org> |
string | @local |
Scope for a new block's id, with --create |
--description <text> |
string | A <name> block. |
description for a new block's manifest, with --create |
--json |
boolean | off | Print { ok, dir, alias, id, version, target, baseHash, created, resumed } |
plicine block checkout quote-table my-vault
Checked out @solunify/quote-table@2.1.0 into /Users/you/Library/Application Support/plicine/dev/20c6593f1ded5d4d/quote-table
Next:
1. Edit the files in .../quote-table
2. plicine validate .../quote-table
3. plicine block push --dir .../quote-table
plicine block push [dir]
Runs the same checks validate runs on a block folder (compiling it, running every BLOCK.md example), and only if there are no errors, packs the checkout and writes it over the .block file it was checked out from. dir (or --dir) defaults to ., so cd into a checkout and run it bare.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--dir <path> |
string | dir positional, then . |
Checkout to push |
--bump patch|minor|major |
string | none | Bump manifest.json's version before packing |
--force |
boolean | off | Overwrite even if the vault's file changed since checkout |
--json |
boolean | off | Print { ok, target, id, version, hash, previousHash, changed, approved, pending?, warnings } |
Refuses to write, and exits 1, if the vault's .block file changed since this checkout was made or last pushed (someone else replaced it, or a previous push from elsewhere): the message names both hashes. --force overwrites anyway. If the checkout doesn't validate, push reports the same <file>:<line> <severity> <message> problems as plicine validate and writes nothing.
A pushed hash is approved automatically, without a trust prompt, only when the block's id was already approved with the exact same network/storage capabilities, or the block has never declared either capability at all; any capability change always needs plicine trust --approve (or the app's prompt) like any other new hash. Approving this way is recorded in trust.json same as any decision, but marked "via": "dev".
plicine block push --dir .../quote-table --bump patch
Pushed @solunify/quote-table@2.1.1 to my-vault/.vault/blocks/quote-table.block (a1b2c3d4e5f6)
Approved automatically (capabilities unchanged from the last approved version).
plicine block edit <name> [vault]
checkout, then push on every save: watches the checkout folder and re-validates and re-pushes on each change, debounced the same way the app's own vault watcher is. Takes every checkout flag, plus --bump. Runs until Ctrl+C.
plicine block edit quote-table my-vault
Watching .../quote-table. Save a file to validate and push. Press Ctrl+C to stop.
@solunify/quote-table@2.1.0 unchanged at my-vault/.vault/blocks/quote-table.block (229eec57928d)
Pushed @solunify/quote-table@2.1.0 to my-vault/.vault/blocks/quote-table.block (f00dcafe1234)
Approved automatically (capabilities unchanged from the last approved version).
With --json, each cycle prints one checkBlockDir-style problem report (if the edit doesn't validate) or one push result object, so a wrapper script or an agent can read one line per save.
plicine block status [dir] and plicine block diff [dir]
status compares a checkout against the package it started from and against the vault's current file: whether the checkout has local edits not yet pushed (dirty), and whether the vault's file changed independently since checkout (upstreamChanged, the condition push would refuse without --force). diff shows exactly which files changed and how, as a unified-style line diff per file (large files and binary files are reported as changed without inline detail).
| Flag | Type | Default | Meaning |
|---|---|---|---|
--dir <path> |
string | dir positional, then . |
Checkout to inspect |
--json |
boolean | off | status: { ok, alias, id, target, baseHash, currentHash, dirty, targetHash, targetMissing, upstreamChanged, packError? }. diff: { ok, files: [{ path, kind, lines? }] } |
plicine block diff --dir .../quote-table
--- src/index.tsx
const colours = {
- info: "#2f5bd3",
+ info: "var(--fx-color-accent, #2f5bd3)",
};
plicine block discard [dir]
Deletes a checkout folder, discarding any local edits that weren't pushed. There's no undo.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--dir <path> |
string | dir positional, then . |
Checkout to delete |
--yes |
boolean | off | Confirm the deletion; without it, the command fails with a reminder of what it would delete |
--json |
boolean | off | Print { ok, dir } |
plicine template
Fills a document's {{placeholders}} to make a new one. The syntax, what counts as a placeholder and how an adapter should shape its JSON are in Templates.
plicine template fields <doc.md>
Lists what a template asks for: each field's path (client.name, items[].price), its type, whether it's required, and the line it's first used on.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--json |
boolean | off | Print { ok, fields, schema, example } |
--schema |
boolean | off | Print only the JSON Schema (2020-12) the data should match |
--example |
boolean | off | Print only example data in the right shape, with empty values |
A type is text, flag (only ever tested with a section, so true or false), list (repeated with a section), object (reached into with dots) or any (the whole value of a block prop, so any JSON). A field only shown inside a section over itself, as in {{#note}}Note: {{note}}{{/note}}, and every flag, is optional. A malformed template, such as a section that's never closed, is a usage error naming the file and line.
plicine template apply <doc.md>
| Flag | Type | Default | Meaning |
|---|---|---|---|
-d, --data <file|-> |
string | none | JSON: one object, an array of objects, or JSON Lines. - reads stdin |
--set <path=text> |
string, repeatable | none | Set one value as text, on every record |
--set-json <path=json> |
string, repeatable | none | Set one value parsed as JSON, on every record |
-o, --out <file|pattern|-> |
string | required | Where to write. With more than one record, a pattern such as "out/{{client.name}}.md". - prints to stdout |
--allow-missing |
boolean | off | Write documents that still lack values, keeping those placeholders as written |
--force |
boolean | off | Overwrite files that exist |
--json |
boolean | off | Print { ok, documents } or { ok: false, problems } |
It needs --data or at least one --set. Every record is filled and checked before anything is written; a missing value, two records naming the same file, the template's own file or a file that exists (without --force) stops the run with exit code 1. Values in an --out pattern have / \ : * ? " < > | and control characters replaced by -, and leading and trailing dots and spaces dropped, and .md is added to a name without it.
plicine template apply proposal-template.md --data clients.json --out "proposals/{{client.name}}.md"
Wrote proposals/Lorem Ipsum Ltd.md
Wrote proposals/Dolor Sit Ltd.md
plicine trust [vault]
Lists every block used in the vault, or in its .vault/blocks/ folder, with its approval status, or changes approvals. vault defaults to ..
| Flag | Type | Default | Meaning |
|---|---|---|---|
--approve <id|alias|hash> |
string, repeatable | none | Approve the blocks matching each selector |
--deny <id|alias|hash> |
string, repeatable | none | Deny the blocks matching each selector |
--all |
boolean | off | Approve every block currently pending |
A selector matches a block if it equals the block's id, its alias, <id>@<version>, or a prefix of its content hash at least 8 characters long. If a given selector matches nothing, the command fails with No block matches <selectors>.
With no flags, it lists every block, one per line: approval status, <id>@<version>, a 12-character hash prefix, source (vault, library or community), declared capabilities and how many documents use it.
plicine trust my-vault
pending @solunify/iso@1.0.0 223ba97aa0b7 vault caps:- docs:1
pending @solunify/quote-table@2.1.0 229eec57928d vault caps:- docs:0
docs:0 means a block is installed but not currently used by any document.
Or approve everything pending in one go, which is what a CI export with --strict typically needs first:
plicine trust my-vault --all
Approved @solunify/iso@1.0.0 (223ba97aa0b7)
Approved @solunify/quote-table@2.1.0 (229eec57928d)
Approvals and denials are per machine, keyed by the block's content hash: see The app data folder for trust.json.
plicine lock [vault]
Recomputes blocks.lock from the vault's current documents and writes it only if it changed. vault defaults to ..
| Flag | Type | Default | Meaning |
|---|---|---|---|
--frozen |
boolean | off | Report drift instead of writing |
plicine lock my-vault
Updated blocks.lock
iso: not in blocks.lock (would add @solunify/iso@1.0.0)
or blocks.lock is up to date if nothing changed. With --frozen, drift is printed to stderr and the command exits 1; with no drift it prints blocks.lock is up to date and exits 0. This is the check to run in CI to make sure nobody committed a document that resolves to a different block than what's locked.
plicine agents-md [vault]
Writes, removes or reports the vault's AGENTS.md: the primer Plicine gives an agent, as a file, so an agent working from a terminal gets it with the app closed. vault defaults to ., and any folder inside a vault works, since the command walks up to the folder holding .vault/.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--write |
boolean | off | Turn agentsMd on in .vault/vault.json and write the file |
--remove |
boolean | off | Turn it off and delete the file |
--json |
boolean | off | Print one JSON object instead of text lines |
With neither flag it reports where things stand and what to run to change it. Passing both is a usage error (exit 2).
plicine agents-md my-vault --write
Wrote my-vault/AGENTS.md. Plicine keeps it up to date while agentsMd is on.
The setting is written before the file either way, because a running app watches vault.json and keeps the file in step with it. Writing again when nothing has changed leaves the file untouched and prints Left unchanged, the way lock behaves.
An AGENTS.md that Plicine didn't write is never overwritten or deleted. The generated file starts with <!-- Generated by Plicine. Edits are overwritten. -->, and a file without that line is left alone, reported to stderr, and the command exits 1. The same toggle sits in the palette's agent menu as Write AGENTS.md; see Using AI agents.
plicine install <file>
Copies a .block or .theme file into your app-wide library (or community folder), so it's available to every vault without being copied into each one. The file is validated (it must parse as a block or theme package) before being copied under its own file name.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--community |
boolean | off | Install into the community folder instead of the library |
plicine install ~/Downloads/quote-table.block
Installed quote-table.block into your library (/Users/you/Library/Application Support/plicine/library/quote-table.block)
install does not check any signature; see The app data folder and Security for what that means. Signing isn't built yet, so only install files from people you trust.
plicine chrome
Manages the browser PDF export renders in. Plicine looks for one in this order:
PLICINE_CHROME, orPUPPETEER_EXECUTABLE_PATHif that isn't set. A path that doesn't exist is an error, not a reason to keep looking.- The downloaded
chrome-headless-shellin the app data folder, if a previous run fetched it. - Google Chrome, Chromium, Microsoft Edge or Brave installed in the usual place for the platform.
Nothing is downloaded while one of those answers. When none does, the first export fetches the pinned chrome-headless-shell build from Chrome for Testing, about 100 MB, into <app data>/chrome/<version>/, and says so as it goes. plicine chrome download does the same thing on demand.
plicine chrome status
Prints which browser an export would use and whether anything has been downloaded. Exits 0 when there's a browser to render in and 1 when there isn't, so a script can check before it commits to an export.
plicine chrome status
PDF export renders with the downloaded chrome-headless-shell 153.0.8010.47: /Users/you/Library/Application Support/plicine/chrome/153.0.8010.47/chrome-headless-shell-mac-arm64/chrome-headless-shell
Downloaded into /Users/you/Library/Application Support/plicine/chrome/153.0.8010.47; "plicine chrome remove" deletes it.
plicine chrome download
Fetches and unpacks the pinned renderer, printing progress to stderr. It's a no-op if that version is already unpacked, and it downloads even when a browser is installed, which is how you get the pinned renderer on a machine that has Chrome but wants output to match everyone else's. The archive is checked against the SHA-256 pinned for the platform before it's unpacked, and one that doesn't match counts as a failed download. Exits 2 if the download fails, with an error naming the folder it tried to write and PLICINE_CHROME as the way round it.
plicine chrome download
plicine chrome remove
Deletes every downloaded version and the chrome/ folder itself. Exits 0 whether or not there was anything to delete. The next export downloads it again.
Environment variables
| Variable | Read by | Meaning |
|---|---|---|
PLICINE_DATA_DIR |
every command, and the app | Overrides the platform default app data directory (see The app data folder). Useful in CI, to keep a run's approvals and caches to itself. |
PLICINE_CHROME |
export-pdf, chrome, and the app |
Path to a Chrome, Chromium or Edge executable for export-pdf and the app's PDF export. Takes priority over PUPPETEER_EXECUTABLE_PATH and over the downloaded renderer. |
PUPPETEER_EXECUTABLE_PATH |
the same | Fallback for PLICINE_CHROME. |
Without either variable set, Plicine looks for a downloaded chrome-headless-shell in the app data folder, then for Google Chrome, Chromium, Microsoft Edge or Brave in the usual install locations (/Applications and ~/Applications on macOS, Program Files and %LOCALAPPDATA% on Windows). If PLICINE_CHROME or PUPPETEER_EXECUTABLE_PATH is set but the path doesn't exist, it fails immediately instead of searching on. Only an export downloads the pinned build; plicine chrome status never does.