Recipes
The demos people actually need — a landing-page snippet, a README SVG, an error message, real commands, an existing VHS tape.
Each recipe is a job someone has, the smallest file that does it, and the result playing. Every player on this page was compiled from the file under its “Source”.
An install snippet for a landing page
A hero wants the terminal and nothing else: no title bar, no control bar in the way. The controls stay reachable — hover the demo or tab to it — because a loop that never stops needs a way to stop it.
<TerminalDemo demo={install} autoplay loop chrome="none" controls="hover" />
chrome none, controls on hover
Source: site/src/demos/hero.terminal.yaml
version: 1
terminal:
title: castwright
cols: 72
rows: 12
theme: catppuccin-mocha
steps:
- run: pnpm add -D @casoon/castwright
- output: |
{green}✓{/} added 3 packages
- wait: 500
- run: castwright build demo.terminal.yaml
- output: |
{dim}dist/demo.cast{/}
- wait: 1500$ pnpm add -D @casoon/castwright ✓ added 3 packages $ castwright build demo.terminal.yaml dist/demo.cast
Keep it short: two or three commands, a result that reads as success, and a wait at the
end so the final frame holds before the loop starts over.
An animated demo in a README
GitHub runs no script in a README, and a GIF of a terminal is large and blurry. An SVG is neither: text stays text, and the animation is CSS.
castwright build demo.terminal.yaml --format svg -o .github/readme
<img src=".github/readme/demo.svg" alt="What the demo shows, in one sentence." width="720">
The SVG honours reduced motion — a visitor who has it switched on sees the finished
screen, standing still. --no-chrome drops the title bar; --loop-delay <ms> sets how
long the last frame holds. For places that take no SVG (chat, social cards),
--format gif renders the same cast through agg.
An error message, and what fixes it
Documentation of a CLI is mostly documentation of what goes wrong. Show the real message
rather than a paraphrase of it: run the failing command once with exec: and record it.
A typo, reported and fixed
Source: site/src/demos/error-fix.terminal.yaml
# Scenario: an error, and its fix. Recorded like first-demo: real commands,
# run once with `castwright build --allow-exec --record`.
version: 1
terminal:
title: ~/my-site
cols: 116
rows: 20
theme: catppuccin-mocha
steps:
- run: castwright validate demos/deploy.terminal.yaml
- output:
raw: |
demos/deploy.terminal.yaml:5:5: unknown key 'sped' in step 'run' — known keys: run, speed, jitter, pause, prompt
sped: 30
^
delay: 16
- wait: 2500
- run: perl -pi -e 's/sped:/speed:/' demos/deploy.terminal.yaml
- wait: 400
- run: castwright validate demos/deploy.terminal.yaml && echo "✓ valid"
- output:
raw: |
✓ valid
- wait: 800
- run: castwright build demos/deploy.terminal.yaml --format svg
- output:
raw: |
dist/deploy.svg
- wait: 2500$ castwright validate demos/deploy.terminal.yaml
demos/deploy.terminal.yaml:5:5: unknown key 'sped' in step 'run' — known keys: run, speed, jitter, pause, prompt
sped: 30
^
$ perl -pi -e 's/sped:/speed:/' demos/deploy.terminal.yaml
$ castwright validate demos/deploy.terminal.yaml && echo "✓ valid"
✓ valid
$ castwright build demos/deploy.terminal.yaml --format svg
dist/deploy.svgReal command output, without running it on every build
Writing out a long, coloured output by hand is error-prone. exec: runs the command in a
pseudo-terminal and records what it printed, with its timing:
steps:
- exec: castwright validate broken.terminal.yaml
castwright build demo.terminal.yaml --allow-exec --record # once
castwright build demo.terminal.yaml # every build after
--record rewrites the file: the exec: step becomes the command and exactly what it
printed. From then on nothing runs, and every build produces the same bytes. Commands
never run without --allow-exec, and the build warns when the output contains something
that looks like a token or your home directory.
An exec: step, after --record
Source: examples/recorded.terminal.yaml
version: 1
terminal:
title: castwright validate
cols: 110
rows: 9
theme: catppuccin-mocha
steps:
# Recorded, not written: this was an exec: step, and
# `castwright build --allow-exec --record` ran it once and replaced it
# with the command and exactly what it printed.
- run: castwright validate broken.terminal.yaml
- output:
raw: |
broken.terminal.yaml:5:5: unknown key 'sped' in step 'run' — known keys: run, speed, jitter, pause, prompt
sped: 30
^
delay: 50
- wait: 1500$ castwright validate broken.terminal.yaml
broken.terminal.yaml:5:5: unknown key 'sped' in step 'run' — known keys: run, speed, jitter, pause, prompt
sped: 30
^A config file, shown in the terminal
show: puts a file on screen, highlighted with Shiki at build time — the output of a
cat without copying the file into the demo. Edit the file and the demo follows.
steps:
- run: cat astro.config.ts
- show: snippets/astro.config.ts
show: with Shiki
Source: examples/show.terminal.yaml
version: 1
terminal:
title: show
cols: 64
rows: 12
theme: catppuccin-mocha
steps:
- run: cat astro.config.ts
- show: snippets/astro.config.ts
- wait: 1500$ cat astro.config.ts
import castwright from '@casoon/astro-castwright';
import { defineConfig } from 'astro/config';
// Compiles every *.terminal.yaml at build time.
export default defineConfig({
integrations: [castwright()],
});An existing VHS tape
If you already have VHS tapes, build them as they
are. With --allow-exec their commands run and their output is recorded, as VHS would do
it — without a headless browser.
castwright build demo.tape --format svg --allow-exec
examples/vhs.tape, as is
The commands in this tape ran during this site's build; the hidden cd included.
Source: examples/vhs.tape
# A VHS tape, built by castwright as is:
# castwright build examples/vhs.tape --format svg --allow-exec
Set FontSize 20
Set Width 900
Set Height 360
Set Theme "Catppuccin Mocha"
Set TypingSpeed 40ms
Hide
Type "cd snippets && clear" Enter
Show
Type "ls" Sleep 300ms Enter
Sleep 1s
Type "head -3 astro.config.ts" Sleep 300ms Enter
Sleep 2s> ls
astro.config.ts broken.terminal.yaml
> head -3 astro.config.ts
import castwright from '@casoon/astro-castwright';
import { defineConfig } from 'astro/config';
> VHS tapes lists what is translated and what is not.
The same demo in another theme
A cast stores colours as palette indices and carries its palette in the header, so a
theme is one line — terminal.theme — and a light-mode page gets a light terminal.
theme: github-light
$ pnpm add -D @casoon/castwright ✓ added 3 packages $ castwright build demo.terminal.yaml dist/demo.cast
The seven bundled themes are listed in Theming.