Skip to content

CLI reference

One binary, liebstoeckel, with subcommands. A bare path is shorthand for live.

Terminal window
liebstoeckel <command> [args]
liebstoeckel <deck> # shorthand for: liebstoeckel live <deck>
liebstoeckel --help # the command surface
liebstoeckel <command> --help # arguments and options for one command
liebstoeckel --version

Every command has its own --help listing its arguments, options, and defaults. The short alias lst is interchangeable with liebstoeckel (e.g. lst build).

Scaffold a new deck. It materializes in the current directory as ./<name>; pass --dir <parent> to put it elsewhere (e.g. --dir presentations to drop it into a monorepo’s workspace glob).

Terminal window
liebstoeckel new <name> [--brand <brand>] [--dir <parent>] [--no-org-brand]
FlagMeaning
--brand <brand>brand theme to wire in (default: liebstoeckel, or your org’s default brand when logged in)
--dir <parent>parent directory for the deck (default: the current directory)
--no-org-branddon’t bake the logged-in org’s default brand; use the plain liebstoeckel brand

Generates index.html, main.tsx, build.ts (using buildDeck from @liebstoeckel/thumbnails/build), server.ts, bunfig.toml, and a starter slides/01-intro.tsx. The name must be lower-case letters/digits/hyphens; it won’t overwrite an existing directory.

When you’re logged in (and didn’t pass --brand), new automatically bakes your org’s default brand into the deck as owned source: brands/<name>.ts, wired into <Present brandThemes={…}>, with data-brand set, so new decks are on-brand instantly (see brand). Pass --no-org-brand to opt out, or --brand to choose a specific one. It’s best-effort: offline or signed-out, you just get the default liebstoeckel brand.

Scaffold registry items (charts, hooks, layouts, …) into a deck as owned source: the code is copied into your project, not imported from a package, so you can edit it freely. Leaf dependencies the items need (e.g. the relevant @visx/* packages) are installed into the deck.

Terminal window
liebstoeckel add bar-chart # one item into the current deck
liebstoeckel add chart bar-chart line-chart # several at once (optional category word)
liebstoeckel add donut-chart --dir ./presentations/demo
liebstoeckel add bar-chart --dry # show the plan, write nothing
FlagMeaning
--dir <deck>target deck directory (default: current directory)
--dryprint the plan (files + dependencies) without writing
--forceoverwrite files that already exist (default: skip them)
--no-installdon’t run bun add for the items’ dependencies
--jsonstructured plan/result output (default when stdout isn’t a TTY)

add always prints its plan before writing: which files it will create, overwrite, or skip, plus the dependencies it will install. Files land under the deck’s charts/ (or the item’s own path); existing files are skipped unless --force (so re-running is idempotent). Dependencies are installed with bun add --ignore-scripts (the registry trust model), or skipped with --no-install so you can run it yourself. With --json (or when piped), the plan (--dry) or the result emits as JSON for an agent to consume.

Items resolve through a registry namespace. A bare name (bar-chart) comes from the bundled default registry (@liebstoeckel); a scoped ref (@acme/heatmap) comes from a registry you map in the deck’s liebstoeckel.json under "registries", which today accepts a "default" keyword or a local path, with npm/git/HTTP transports planned. Categories are chart, hook, element, component, layout, motion; the leading category word is optional sugar.

Browse the chart/component registry: the discovery surface for add, and the machine-readable contract agents read before scaffolding.

Terminal window
liebstoeckel registry list # the catalog (name · type · data shape)
liebstoeckel registry view bar-chart # one item: exports, props, dataShape, example
liebstoeckel registry list --json # JSON (default when piped / not a TTY)

view returns each component’s dataShape, the exact type of its data prop, so you (or an agent) can wire data without reading the source. Output is JSON when --json is passed or stdout isn’t a TTY, and a compact human view otherwise. This reads the bundled @liebstoeckel registry.

Build a deck to a single self-contained .html (+ thumbnails), written to ./dist/<deck-slug>.html. The slug is the deck folder name, e.g. poll-demo/ → dist/poll-demo.html. By default the build also embeds a compressed copy of the deck’s own source (slides, package.json, etc., not node_modules), so the compiled .html can later be ejected back to an editable project. Source collection uses bun pm pack, so your deck’s package.json "files" list controls exactly what’s embedded.

The deck defaults to the current directory; pass it as a positional [dir] or with --dir <deck> (the convention shared by pack/live/export).

The build also embeds a third-party license notice built from the modules that actually end up in the bundle (React, Motion, Yjs, the fonts, and the MPL-2.0 engine). Minification strips license comments, so this puts the required attribution back. It is recomputed on every build, so it stays correct when you swap a font or a library. Read it with licenses.

The build requires a single copy of each @liebstoeckel/* package. A deck compiles into one file, so if two framework packages disagree on a version — for example after bumping @liebstoeckel/engine but leaving a plugin on an older release — the bundle would embed two incompatible copies side by side. The build stops, prints the conflicting versions, and asks you to update the framework packages together (e.g. bun update) so they resolve to one version. --check reports the same conflict without writing anything.

Terminal window
liebstoeckel build # the deck in the current directory
liebstoeckel build --dir <deck> # or a positional [dir]
liebstoeckel build [dir] --no-inline-package # build WITHOUT the recoverable source
liebstoeckel build [dir] --check # validate only: no artifact, structured errors
liebstoeckel build [dir] --check --visual # + render each slide and lint for cut-off text
liebstoeckel build [dir] --trust # pre-approve an unfamiliar deck's build-time code
LIEBSTOECKEL_NO_THUMBS=1 liebstoeckel build [dir] # skip thumbnails

Thumbnails need a headless Chromium; the build skips them (never fails) when none is found. See Chromium setup to install one or point at an existing binary with LIEBSTOECKEL_CHROMIUM.

FlagMeaning
--no-inline-packagedon’t embed the deck’s source (smaller file, not ejectable)
--no-inline-licensesdon’t embed the third-party license notices (not recommended)
--allow-secretoverride the secret gate that aborts if a packed file looks like a credential
--checkvalidate the deck bundles and resolves one copy of each framework package (no output written); reports diagnostics + exits non-zero on error
--visualwith --check: also render every slide headless and lint for cut-off or overflowing text (needs Chromium; skips with a reason when none is found)
--trustpre-approve this deck’s build-time code (a deck is code; remembered after the first build)
--jsonmachine-readable JSON result (default when stdout isn’t a TTY); progress goes to stderr

--check is the fast validation gate (no Chromium, no thumbnails, nothing written): it returns { ok, diagnostics }, each diagnostic carrying message/file/line, and exits non-zero when the deck doesn’t bundle. It checks that imports resolve and MDX/TSX transforms; it does not type-check. Output is JSON when piped or with --json.

--check --visual adds a rendered lint pass on top: it bundles a throwaway copy of the deck, steps through every slide in headless Chromium at the native 1280x720 stage, and flags text the audience would not see: content clipped by its container (clipped-text), chart labels sticking past their svg viewport (svg-clipped-text), and text extending beyond the stage (offstage-text). Findings land under visual in the JSON result, each with the slide index, a text snippet, a DOM path, and the overflow in pixels; any finding makes ok false and the exit code non-zero, so an agent can loop on it like a build error. Like thumbnails, it needs a headless Chromium; without one the pass is skipped with a reason (visual.skipped) and does not fail the check.

With --json (or piped), a normal write build prints the human progress to stderr and emits a single result object on stdout, so an agent can read the artifact path without scraping prose:

{ "ok": true, "artifact": "/abs/path/dist/my-talk.html", "outfile": "my-talk.html", "thumbnails": 6 }

thumbnails is null (with a thumbnailsSkipped reason) when no Chromium is available. A refused untrusted build emits { "ok": false, "error": "untrusted deck", "hint": "…" } and exits non-zero. Trusting a deck you didn’t write means agreeing to run its build-time code on your machine — a human decision; an AI agent should surface it rather than approve on its own. A human who trusts the deck approves it with --trust (or LIEBSTOECKEL_TRUST_BUILD=1).

Recover a built deck’s editable source from its .html.

Terminal window
liebstoeckel eject <deck.html> [outdir] [--force]

outdir defaults to <deck>-source/. Eject refuses a non-empty directory unless --force, and errors clearly if the HTML carries no embedded source (e.g. it was built --no-inline-package). Rebuilding runs the deck’s own build-time code (macros/build plugins) — --ignore-scripts only blocks npm lifecycle scripts, not that — so only rebuild a deck you trust. liebstoeckel build confirms an unfamiliar deck once (see the warning under build):

Terminal window
cd <outdir> && bun install --ignore-scripts && liebstoeckel build

For the full recover → edit (by hand or with an agent) → rebuild loop, see Editing a built deck.

Inspect, or emit, exactly the source a build would embed, without running a full build. The deck defaults to the current directory; pass it as [dir] or --dir <deck>.

Terminal window
liebstoeckel pack [dir] # print the file list (defaults to cwd)
liebstoeckel pack [dir] -o deck.tgz # write a gzip tarball (bun add-compatible)
liebstoeckel pack [dir] --allow-secret # override the secret gate

Report the third-party licenses bundled into a deck. Pass a built .html to print the notices it already carries, or a deck [dir] / --dir <deck> (default: cwd) to compute the report fresh from the deck’s real module graph.

Terminal window
liebstoeckel licenses dist/my-deck.html # print the notices embedded in a built deck
liebstoeckel licenses # compute the report for the deck in cwd
liebstoeckel licenses --dir <deck> # or a positional [dir]
liebstoeckel licenses [dir] --json # structured: { ok, packages, firstParty, flagged }
liebstoeckel licenses [dir] --check # exit non-zero if any non-standard license is bundled

The report lists only what is actually inlined into the .html: the client bundle and the embedded fonts. Build-time tools like the MDX compiler, Tailwind, and the syntax highlighter run during the build and aren’t redistributed, so they’re left out.

A built .html carries its notices as rendered text, so the command prints them (with --json, as { source, notices }). A deck directory is recomputed from a fresh bundle, so it gives the full structured report ({ ok, packages, firstParty, flagged }) and is the only input that works with --check. As with build --check, the output is JSON when piped or with --json.

--check is a CI gate: it fails when a bundled package carries a license outside the permissive/embeddable set (for example a copyleft chart library pulled in via add), so you catch it before shipping.

Useful for verifying your "files" allowlist before shipping. The -o tarball is standard gzip (bun add ./deck.tgz-installable); the in-HTML embed uses zstd internally.

Serve a deck for authoring: hot reload plus the dev shell (a sidebar beside the deck; the plain deck alone is behind /deck), whose first tool is live annotations an AI agent can apply to your slide source while you watch. With --dir, the server re-executes with the deck as its working directory so the deck’s own bunfig.toml plugins load; a port already in use fails with a message naming it.

Terminal window
liebstoeckel dev # the deck in the current directory
liebstoeckel dev --dir <deck> --port 3000
liebstoeckel dev --host 0.0.0.0 # expose beyond localhost (trusted networks only)
liebstoeckel dev poll # agent side: wait for an annotation batch
liebstoeckel dev poll --reply <batchId> done --data '{"applied":["<id>"],"files":["slides/01-title.mdx"],"notes":[]}'
FlagMeaning
--dir <deck>deck directory (default: current directory)
--port Nport (default: 3000)
--host <addr>bind hostname (default 127.0.0.1)
--jsonmachine-readable startup info

dev poll is the agent side. It blocks until the browser sends work (up to --timeout ms, default 600000), prints one JSON event, and exits; every event carries an _instructions field telling the agent exactly what to do next. Replies are done with a JSON result or error "reason".

FlagMeaning
--dir <deck>deck directory (default: current directory); the running server is found through its .liebstoeckel/dev/server.json
--timeout <ms>how long to wait for an event before printing a timeout event and exiting (default 600000)
--reply <id>reply to the event with this id instead of waiting; pair with a positional done or error "reason"
--data <json>the JSON result for a done reply: {"applied": [annotationIds], "files": [paths], "notes": [strings]}

Without a running dev server it prints {"error":"no_dev_server"} and exits 1.

The first run adds a small inert loader tag to the deck’s index.html. It only activates under dev, where it loads the bridge script that connects the framed deck to the sidebar, and every build strips it, so built decks are unaffected. Dev-mode state (annotations, screenshots, revert snapshots) lives under .liebstoeckel/dev/ in the deck and is git-ignored.

If liebstoeckel dev reports that it could not load @liebstoeckel/dev-server, the message names the underlying error; set LIEBSTOECKEL_DEBUG=1 to also print its stack trace.

Present a deck to a room. Builds the deck if given a project dir, and captures slide thumbnails by default (so the overview grid is instant); the capture is skipped, not failed, when no Chromium is available.

The deck defaults to the current directory; pass it as a positional or with --dir <deck> (the same convention as build/pack/export).

Terminal window
liebstoeckel live # the deck in the current directory
liebstoeckel live <deck.html | deck-dir> [--port N]
liebstoeckel live --dir <deck> --relay <url> --relay-token <token>
liebstoeckel live <deck> --no-thumbnails # skip the capture step
FlagMeaning
--port NLAN port (default: auto)
--relay <url>present through a relay instead of LAN
--relay-token <tok>relay account token (or LIEBSTOECKEL_RELAY_TOKEN)
--no-thumbnailsskip thumbnail capture (also via LIEBSTOECKEL_NO_THUMBS=1)
--format / --width / --quality / --scaletune thumbnails, as in thumbs

Run a public relay server.

Terminal window
liebstoeckel relay [--port N] [--tokens tok1,tok2] [--public-url https://…]
FlagMeaning
--port Nlisten port (or PORT)
--tokenscomma-separated account tokens (or PRESENT_RELAY_TOKENS); one is generated if omitted
--public-urlthe public https:// origin (so links/WebSocket use wss://)

(Re)generate thumbnails for a built deck.

Terminal window
liebstoeckel thumbs <built-deck.html> [--format webp|jpeg|png] [--width 640] [--quality 80] [--scale 2]

Needs Chromium and fails if none is found (Chromium setup).

Export slides to PNG files or a single PDF (one slide, a range, or the whole deck), rendered headless on the native 1280×720 canvas, the same pipeline as thumbnails. The input is a built .html or a deck source directory (built on demand); it defaults to the current directory, or pass it as a positional or with --dir <deck>.

Terminal window
liebstoeckel export [deck.html|deck-dir|--dir <deck>] [opts]
flagmeaning
--format png|pdfoutput format (default: inferred from -o’s extension, else png)
--slides <spec>1-based, inclusive: 3, 2-5, 1,3,5-7, 3-, -4 (default: all)
-o, --out <path>PDF: the .pdf file. PNG: an output directory
--width <n>viewport / page width (default 1280, the authoring canvas)
--scale <n>device-scale factor for PNG / raster PDF (default 2 → 2560×1440)
--rasterPDF only: image-per-page instead of the default vector PDF
--quality <n>JPEG quality for the raster PDF (default 92)
--page-size <size>PDF only: the paper. slide (default, the page is the 16:9 canvas), a3, a4, a5, letter, legal, or an explicit <w>x<h> with a unit (210x297mm, 8.5x11in)
--orientation landscape|portraitPDF only, with a paper page: default landscape
--margin <n><mm|cm|in|px>PDF only, with a paper page: clearance on every side (default 10mm)
Terminal window
# slide 3 of a built deck → PNG
liebstoeckel export ./dist/my-deck.html --slides 3 -o ./out
# whole deck as a PDF, building the source dir on demand
liebstoeckel export ./presentations/demo --format pdf -o demo.pdf
# printable handout: one slide per A4 page (landscape), 10 mm margin
liebstoeckel export ./presentations/demo --page-size a4 -o handout.pdf

PDF is vector by default: selectable, searchable text with embedded fonts and vector charts/gradients, produced in a single page.pdf() over a print view that stacks one slide per page (no merge, no extra dependency). Pass --raster for an image-per-page PDF instead: no text layer, but pixel-exact fidelity for a slide whose effects don’t reproduce under print.

Printing. By default each PDF page is exactly the 16:9 slide canvas, which prints poorly on paper. Pass --page-size a4 (or letter, a3, an explicit 210x297mm, …) to get physical pages instead: the slide is scaled to fit the printable area (the page minus --margin), centered, on white paper, aspect preserved. Landscape is the default since slides are landscape; --orientation portrait fills the width and leaves the lower part of the page empty. Vector text stays selectable, and --raster produces the same page geometry. A paper page sizes the slide itself, so it cannot be combined with --width.

PNGs come straight off the page (lossless, native resolution), named <deck>-slide-NN.png (1-based, zero-padded). Like thumbs, export needs Chromium and fails loudly if none is found (Chromium setup).

Install the liebstoeckel-deck agent skill into a project so AI coding agents can create and edit decks for you (scaffold → add charts → wire data → build). The skill ships inside the CLI package, version-pinned so it always matches your installed liebstoeckel, and is placed where each agent looks for it.

Terminal window
liebstoeckel skill install --dir ./my-deck # into one deck/project (writes AGENTS.md too)
liebstoeckel skill install --global # into your user account (~) — every project
liebstoeckel skill install --target codex --dir ./my-deck
liebstoeckel skill update # refresh the installed skill to the CLI's version
FlagMeaning
--scope <project|user>where to install, a project/deck, or your user account (~); --global is shorthand for --scope user
--target <list>(install) claude, codex, cursor, gemini, or all (default); comma-separated
--dir <deck>the deck/project for a project-scope install (default: current directory)

Scope. A project install writes the per-agent skill folders and the AGENTS.md fallback into the deck. A user install writes the folders under your home (~/.claude/skills/… and the rest) so every project sees the skill, and does not drop a bare ~/AGENTS.md. When you don’t pass a scope, an interactive terminal asks which you want; with --dir it’s a project install; and a non-interactive run refuses rather than scatter skill files into a directory that isn’t a deck — pass --scope user or --dir <deck> to be explicit.

It writes the same skill to each agent’s path (.claude/skills/, .agents/skills/ for Codex/Gemini, .cursor/skills/ + a rule) and merges a managed block into AGENTS.md, the universal fallback for agents without skill support. Re-running is idempotent. See the Scaffolding guide for how an agent uses it.

The installed skill is version-pinned to the CLI that wrote it. skill update rewrites the skill for the agent paths already present in the deck (plus the AGENTS.md block) without adding new ones. You rarely need to run it yourself: the installed skill is a pure function of the CLI version, so whenever an installed copy is older than the CLI you run, any command refreshes it in place and prints a one-line note on stderr. This covers project installs and a user-scope install under ~ alike.

Bring everything current in one command: the deck’s @liebstoeckel/* packages, a global CLI install, and every installed copy of the agent skill, whichever of those apply.

Terminal window
liebstoeckel update # the deck in the current directory (and/or the global CLI)
liebstoeckel update --dir <deck> # a specific deck
FlagMeaning
--dir <deck>the deck to update (default: current directory)

What it does, in order:

  1. Checks the registry live for the latest @liebstoeckel/cli (this command never uses the reminder cache; it also refreshes that cache with the answer).
  2. Deck-local install: runs bun update --latest for exactly the @liebstoeckel/* packages the deck declares, never your other dependencies. Updating the whole scope together keeps the framework packages on one version, which the build requires.
  3. The CLI’s own install root, when the running CLI lives in a different project than the deck (for example a project root holding @liebstoeckel/cli with decks in subdirectories): that root’s declared @liebstoeckel/* packages update too, so the CLI itself never stays behind. A CLI install the command cannot manage (not declared where it is installed) is called out explicitly instead of being skipped in silence.
  4. Global install (the CLI was installed with bun add -g): runs bun add -g @liebstoeckel/cli@latest. Shapes combine when several apply.
  5. Refreshes the agent skill wherever it is installed (the deck and your user account), using the freshly installed CLI, so the skill version matches the new CLI.

Outside a deck and without a global install there is nothing to act on, and the command says so instead of guessing.

After a deck update the command verifies that every @liebstoeckel/* package resolved to a single version. Releases bump dependents together, so a split is rare, but a package published against an older sibling can keep a stale nested copy that targeted updates never re-resolve; when that happens the command prints the affected packages and the clean reinstall (rm -rf node_modules bun.lock && bun install) that collapses them.

Check the build environment — the Bun version (must satisfy the CLI’s engines.bun) and whether a Chrome/Chromium was found for thumbnails and PNG/PDF export. Optionally install one.

Terminal window
liebstoeckel doctor # human summary (Bun + Chromium + config path)
liebstoeckel doctor --json # machine-readable (default when piped)
liebstoeckel doctor --install-chromium # download Playwright's Chromium, record its path
FlagMeaning
--install-chromiumInstall Playwright’s Chromium if none resolves, then record its path in ~/.config/liebstoeckel/config.json so every later build/export reuses it
--jsonMachine-readable output (default when piped)
--dir <deck>Deck to check for scaffold migrations (default: the current directory)

doctor resolves Chromium the same way builds do (see Chromium setup): an existing system Chrome is found automatically, so often nothing needs installing. Only export/thumbs require a browser; build skips thumbnails without one. When no browser is found, the human output lists every probed location, and --json adds them as probedCandidates.

The report also includes CLI staleness from the cached registry check (never a live lookup, so doctor stays network-free): --json carries cli: { version, latestKnown, updateAvailable }. Agents can gate on updateAvailable and suggest liebstoeckel update; latestKnown is null when no check has completed yet (for example offline).

Scaffolded deck source is yours, and the conventions it starts from keep improving. When a convention changes after a deck was created, doctor detects the gap instead of leaving the deck to drift: run it inside a deck (or pass --dir <deck>) and --json carries a migrations array with one entry per known migration:

"migrations": [
{
"id": "0001-hmr-entry-boundary",
"needed": true,
"since": "0.3.11",
"reference": "references/migrations/0001-hmr-entry-boundary.md",
"reason": "slide edits in dev mode reset the deck to slide 1 ...",
"autoPatchable": true
}
]

needed says the deck predates the convention; reference names the guide (shipped inside the installed agent skill) with the why and the manual edit; autoPatchable says liebstoeckel dev would apply it automatically. doctor itself never modifies anything.

When dev starts, it applies every needed migration whose file still matches the scaffolded shape, printing one notice per patched file (↻ migrated main.tsx (0001-hmr-entry-boundary): <reason>); the patch always includes a comment explaining itself. A file that diverged from the scaffold is never rewritten, only hinted about.

A deck that deliberately diverges can opt out per migration in its package.json, with a mandatory reason:

"liebstoeckel": {
"migrationOptOut": {
"0001-hmr-entry-boundary": "custom HMR via our own dispose handler"
}
}

A suppressed migration is never detected, hinted, or patched; doctor --json still lists it with suppressed: true and the reason, and warns (as migrationWarnings) about opt-out entries that suppress nothing (an empty reason, or an unknown id).

The CLI checks its registry for a newer @liebstoeckel/cli at most once an hour, in a detached background process, and prints a one-line note on a later run when an update exists. The check shells out to bun pm view, so it follows the same registry configuration as your installs (a scoped registry in .npmrc/bunfig.toml, or the public npm registry); no command ever waits on the network for it. Acting on the note is one command:

Terminal window
liebstoeckel update # CLI + the deck's @liebstoeckel/* packages + the agent skill

Reminders appear only on an interactive terminal. They are suppressed with --json, when output is piped (agents), when CI is set, and entirely with LIEBSTOECKEL_NO_UPDATE_CHECK=1. The check interval can be tuned with LIEBSTOECKEL_UPDATE_CHECK_INTERVAL=<seconds>. The agent-skill refresh is separate from these reminders: a stale installed skill is refreshed automatically on any command, in every mode (see skill).

The remaining commands talk to liebstoeckel cloud, the hosted control plane.

Sign in to liebstoeckel cloud (the hosted control plane) from the terminal using the OAuth 2.0 device-authorization grant (RFC 8628), with no password. The CLI prints a URL; open it in a browser, sign in with an email one-time code, and approve. The resulting token is stored in ~/.config/liebstoeckel/credentials.json (mode 600).

Terminal window
liebstoeckel login --api https://<your-control-plane-host>
FlagMeaning
--api <url>the control-plane base URL (or set LIEBSTOECKEL_API)

Upload a built single-file deck to your cloud dashboard. Authenticates with the token from login; the deck appears in your dashboard’s deck list, and for a team org it’s visible to every member (the shared library).

Terminal window
liebstoeckel build # produces ./dist/<deck-slug>.html
liebstoeckel push # no path → that built deck in ./dist
liebstoeckel push --org acme # push into a team you belong to
liebstoeckel push ./dist/my-deck.html --title "My deck" # explicit path + title override

Re-push = a new version. push identifies a deck by a stable key (the deck folder name, or --name <key>); pushing the same key again updates that deck in place at the same URL (a new version on Pro; the latest is replaced on free). --new forces a separate deck. With no path, push uploads the single built .html in ./dist (run build first). The title comes from --title → the deck’s own embedded <title> → the deck key: the server reads the <title> straight from the uploaded file, so any Unicode (em-dashes, smart quotes, emoji) just works. Only an explicit --title override travels as a header (URL-encoded). A cover is read from the deck’s built-in thumbnails.

FlagMeaning
--title <title>override the title (default: the deck’s embedded <title>, then its key)
--name <key>the deck key to upsert (default: the deck folder name)
--newcreate a separate deck instead of updating the matching one
--org <slug>push into this organization (default: the one set by orgs use, else personal)
--api <url>override the stored control-plane host

List the organizations (personal workspace + teams) you belong to, and choose which one push targets by default.

Terminal window
liebstoeckel orgs # list workspaces; → marks the push default
liebstoeckel orgs use acme # make "acme" the default for push

Teams, members, roles (owner / admin / member), and invitations are managed in the dashboard’s Team page; the CLI only needs to pick which org a deck lands in.

Share governed brand token sets across a team. Your org is an authenticated registry (the same protocol as add/registry); a brand is pulled into a deck as owned source (brands/<name>.ts) and baked at build, so the deck stays self-contained and WYSIWYG.

Terminal window
liebstoeckel brand list # brands in your org (→ marks the default)
liebstoeckel brand push ./brand.ts --default # upload a theme token file (admins/owners)
liebstoeckel brand pull acme # write brands/acme.ts into the current deck
liebstoeckel new my-deck # auto-applies the org's default brand

brand push accepts a defineTheme(...) module or a flat tokens JSON. Editing is also available in the dashboard’s Brand page. Brand sharing is a paid (Pro/Team) feature.

A brand’s fonts are picked from a curated Fontsource catalog in the Brand page. brand pull writes the import "@fontsource-variable/…" into brands/<name>.ts and runs bun add to install those font packages, so the webfont is inlined into your single-file deck at build (pass --no-install to install them yourself). A Custom… font carries no package and falls back to the system font unless you supply its own @font-face.

FlagMeaning
--default(push) make this the org default that new applies
--name <key>(push) brand name (default: the theme’s name, then the file name)
--dir <deck>(pull) the deck to write brands/<name>.ts into (default: cwd)
--no-install(pull) write the files but don’t bun add the brand’s font packages
--org <slug>the organization to act on

List your cloud decks in an organization, with view counts.

Terminal window
liebstoeckel decks # decks in your default workspace
liebstoeckel decks --org acme # decks in a team you belong to

--org works the same on every cloud command (push, decks, …): explicit --org wins, else the default from orgs use, else your personal workspace.

Comments

liebstoeckel

Code-first presentations your agent can author. One file out, no server.

© 2026 Leon Kaucher