Skip to content

Cloud & teams

A built deck is one self-contained .html you can host anywhere. liebstoeckel cloud (the hosted control plane) adds a private, always-on home for your decks behind a login. For teams, it adds a shared library with members, roles, and invitations.

Authentication is passwordless. login runs the OAuth 2.0 device-authorization grant: the CLI prints a URL, you open it, sign in with a one-time email code, and approve.

Terminal window
liebstoeckel login --api https://<your-control-plane-host>

The token is stored in ~/.config/liebstoeckel/credentials.json (mode 600).

Terminal window
liebstoeckel build # → ./dist/<deck-slug>.html
liebstoeckel push # uploads the built deck in ./dist

The deck’s title comes from its embedded <title> (or an explicit --title override), and its cover thumbnail is read from the slide thumbnails already baked into the file, so there is nothing extra to send. The server reads both straight out of the uploaded file, which means titles with em-dashes, smart quotes, or emoji just work. The deck appears in your dashboard immediately.

Every account has a personal workspace (just you) and can create or join team organizations. A deck always belongs to exactly one organization, the active one.

  • In the dashboard, switch the active workspace with the switcher in the top-left.
  • From the CLI, list your workspaces and choose where push lands:
Terminal window
liebstoeckel orgs # list workspaces; → marks the push default
liebstoeckel orgs use acme # make the "acme" team the default
liebstoeckel push --org acme # …or target a team for one push

Every deck pushed to a team is visible to all its members as a shared library, with “uploaded by” attribution. Who can do what is governed by three roles:

RoleCan
ownereverything, incl. manage any deck, manage members, delete the org
adminmanage any deck, invite/remove members, change roles
memberupload decks; manage their own decks; view all team decks

Manage all of this on the dashboard’s Team page:

  • Invite a colleague by email. They get a link, sign in with a one-time code, and join automatically (still passwordless).
  • Change roles or remove members.
  • Leave a team you no longer need (your personal workspace always stays).

Any deck can be published to a time-limited public link served on a separate domain, with provenance chrome and an abuse-report path. Open a deck’s share action in the dashboard to set:

  • Expiry: how long the link lives (7 days on the free plan; longer, or no expiry, on paid).
  • White-label (paid): drop the “Published with liebstoeckel” banner. The abuse-report path always remains.

Turn any hosted deck into a live, interactive session straight from the browser, with no CLI and no local runtime. Open a deck’s ● present live action and Start live session:

  • A presenter view opens (you drive the slides), and a shareable audience link appears that you can copy or hand out. Your audience opens it on any device and follows along, voting in polls, asking questions, and reacting in real time.
  • Press End session when you’re done. The audience link stops working immediately.

The deck runs in an isolated sandbox for the audience, and the relay that carries the session never executes deck code, the same isolation as a public relay. Audience members can only interact through plugins (votes, questions, reactions); they can’t drive navigation or tamper with results.

Free gives you a real, usable live session with a capped audience and duration, and results aren’t kept once the session ends. Pro / Team raise the limits, drop the audience watermark, and persist results: open a past session under present live to see decoded poll tallies, the Q&A backlog, and reaction totals.

Your org is an authenticated registry, speaking the same protocol as liebstoeckel add but private to your team. A brand (a theme token set: colours, fonts, glow, and an ordered chart palette) is its first item type; custom company components can ride the same rails later.

  • Define it once: push a defineTheme(...) file (liebstoeckel brand push ./brand.ts --default) or edit it visually in the dashboard’s Brand page.
  • Use it: liebstoeckel brand pull <name> writes brands/<name>.ts into a deck, and liebstoeckel new auto-applies the org default brand. The brand is owned source, baked into the deck at build: self-contained and WYSIWYG, never a serve-time surprise.
  • Update it: change the brand centrally, then authors (or CI) re-pull + rebuild. Deliberate, not silent.

Shared brand is a Pro/Team feature.

Not sure where to start? The Brand page’s Generate button rolls a complete brand (colours, fonts, glow, and chart palette) in one click. It is constrained, not random: a single seed picks a base hue, a colour-harmony scheme (analogous / complementary / triadic / …), and a font-pairing recipe (minimal / editorial / modern / …), then every token is derived by rule in the perceptual OKLCH space, with each colour floored to a legible contrast on the dark background. Fonts are drawn from the catalog, so a generated brand always ships its webfonts on pull. Re-roll until you like it, then tweak any field by hand. The generated values are an editable starting point, never locked in.

The Brand page’s Heading, Body, and Mono fields are a picker over a curated catalog of Fontsource variable fonts (grouped sans / serif / display / monospace), with a Custom… option for any other font-family you type. The preview shows the real typeface as you choose.

When you liebstoeckel brand pull <name>, a catalog font comes with its webfont: the generated brands/<name>.ts imports the matching @fontsource-variable/* package, and pull runs bun add to install it, so the font is inlined into your single-file deck at build with no CDN involved. liebstoeckel new does the same for the org default brand. Pass --no-install to write the files and install the font packages yourself. A Custom… font carries no package; supply its @font-face yourself, or it falls back to the system font.

If you author a brand as a file instead (brand push ./brand.ts), any font family that isn’t in the catalog is accepted but flagged with a warning (with a did you mean when it’s close, e.g. "Inter""Inter Variable"), so the file path is as honest as the picker about which fonts actually ship a webfont.

A brand also carries an ordered chart-series palette (viz): the colours charts cycle through, exposed as --brand-viz-0..n. Edit it on the Brand page (add / reorder / remove swatches) or set viz: [...] in a pushed defineTheme(...). It’s baked into pulled decks so charts match the brand, not just the page chrome.

A deck has a stable identity across re-pushes. push keys a deck by its folder name (or --name), so editing and pushing again updates the same deck at the same URL; the share link and view counts carry over. --new starts a separate deck.

  • Free keeps the latest version (a re-push replaces it; the URL and stats are preserved).
  • Pro / Team keep full version history with one-click rollback: open a deck’s Versions panel to see every version, preview one (/d/:id?v=N), or make an older version current.

Every deck shows view counts (total and unique), for both private opens and public share links. Counts are privacy-preserving (no raw IP/UA is stored). Expand a deck to see a 14-day history strip on paid plans; the free plan shows counts only.

Public share links also carry a view cap on the free plan, a built-in guardrail against abuse that doubles as the upgrade nudge; paid plans are uncapped.

Workspaces are free by default. Paid plans (Pro / Team) unlock uncapped share views, view history with longer retention, white-label shares, longer/no-expiry links, and larger live sessions with persisted results. Plans attach to the organization (one subscription per workspace). The CLI shows your current plan under liebstoeckel orgs.

Comments

liebstoeckel

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

© 2026 Leon Kaucher