DSL reference
Every key of the castwright DSL.
File extension: *.terminal.yaml. Keywords are English and lowercase. Unknown keys are
errors, not warnings — a silently ignored typo in a demo file is discovered at review
time, which is too late.
Every example on this page is playing: each player below was compiled from the file under its “Source”, during this site’s build.
A whole file: examples/basic.terminal.yaml
Source: examples/basic.terminal.yaml
version: 1
terminal:
title: castwright
cols: 80
rows: 20
steps:
- run: npx casoon create my-app
- output: |
{green}✓{/} Creating project
{green}✓{/} Installing dependencies
{green}✓{/} Project ready
delay: 250
- wait: 800
- run: cd my-app && npm run dev
- output: "{dim}ready in 412ms{/}\n\n {cyan}➜{/} Local: http://localhost:4321/\n"
- wait: 2000$ npx casoon create my-app ✓ Creating project ✓ Installing dependencies ✓ Project ready $ cd my-app && npm run dev ready in 412ms ➜ Local: http://localhost:4321/
Document
version: 1 # required, must be 1
terminal: # required
title: Casoon CLI
cols: 90
rows: 24
theme: catppuccin-mocha
prompt: "$ "
cursor: block
defaults: # optional
speed: 45 # ms per character
pause: 300 # ms after each step
seed: 42 # required only when a step uses jitter
steps: # required
- run: npm install
terminal
| Key | Default | Notes |
|---|---|---|
title |
— | Shown in the window chrome, not inside the terminal. |
cols |
80 |
Written to the asciicast header. |
rows |
24 |
Written to the asciicast header. |
theme |
default |
One of the seven bundled names; see Theming. |
prompt |
"$ " |
Supports markup, so a coloured prompt is a one-liner. |
cursor |
block |
block, bar or underline; a mapping adds blink: false. |
defaults
| Key | Default | Notes |
|---|---|---|
speed |
45 |
Milliseconds per character for run and type. |
pause |
300 |
Milliseconds after each step. |
Steps
Exactly one primary key per step.
| Key | Value | Meaning |
|---|---|---|
run |
string | Write the prompt, type the text, press Enter. Sugar for type + key: enter. |
type |
string | Type text at the cursor. No prompt, no Enter. |
key |
see below | Send a single key. |
output |
string or { raw } |
Emit program output. Not typed. |
show |
path | Emit a file’s contents, syntax-highlighted. See below. |
exec |
command | Run a real command and record its output. See below. |
wait |
ms | Pause. |
clear |
true |
Clear the screen. |
prompt |
string | Change the prompt from here on. |
marker |
string | Named chapter marker, written to the cast as an asciicast m event. |
key accepts enter, tab, escape, backspace, up, down, left, right,
ctrl+c, ctrl+d and ctrl+l.
type, key and wait — with seeded jitter
Source: site/src/demos/keys.terminal.yaml
version: 1
terminal: { cols: 56, rows: 7 }
seed: 7
steps:
- type: npm ru
jitter: 0.4
- key: tab
- wait: 250
- type: "n build"
- key: enter
- output: |
{green}✓{/} built in 412ms
- wait: 1200npm ru n build ✓ built in 412ms
Modifiers
| Key | Applies to | Meaning |
|---|---|---|
speed |
run, type |
Milliseconds per character. 0 is instant. |
jitter |
run, type |
0–1, varies the per-character delay. Requires a top-level seed. |
delay |
output, show |
Milliseconds between output lines. Default 0 — the whole block at once. |
pause |
any | Milliseconds after this step. |
prompt |
run, type |
false suppresses the prompt for that step. |
delay: output one line at a time
Source: site/src/demos/output-delay.terminal.yaml
version: 1
terminal: { cols: 56, rows: 9 }
steps:
- run: npm test
- output: |
{green}✓{/} parser
{green}✓{/} compiler
{green}✓{/} player
{dim}3 passed{/}
delay: 400
- wait: 1200$ npm test ✓ parser ✓ compiler ✓ player 3 passed
Ranges
Every numeric key is range-checked at parse time, with the file, line and column of the offending value.
| Key | Accepts |
|---|---|
terminal.cols, terminal.rows |
Whole numbers from 1 to 1000. |
speed, pause, delay, wait |
Milliseconds from 0 to 3600000 (one hour). |
jitter |
0 to 1. |
seed |
A whole number from 0 to 4294967295. |
.inf and .nan are numbers as far as YAML is concerned and are rejected here.
Note:
promptmeans two things depending on company. Alongsiderunortypeit is the boolean modifier above. Alone, it is the primary step that changes the prompt from there on. The presence ofrun/typeis what decides.
show
show puts a file on screen with syntax highlighting — the output of a cat, without
writing it out by hand in output:.
steps:
- run: cat astro.config.ts
- show: snippets/astro.config.ts # relative to this .terminal.yaml
lines: 1-7 # optional: a line or a range, 1-based
lang: ts # optional: default is the file extension
theme: github-dark # optional: any Shiki theme
Highlighting happens at build time with Shiki and is written into
the cast as 24-bit colour, so nothing extra reaches the browser. The default theme is the
Shiki theme matching terminal.theme (default uses dark-plus, default-light
uses light-plus).
Shiki is an optional peer dependency: projects that never use show do not install it,
and one that does gets a positioned error naming the fix.
pnpm add -D shiki
A missing file, a range past its end or an unknown language is reported with the file, line and column of the step. The Vite plugin registers the shown file as a dependency of the demo, so editing it recompiles the demo in dev.
A compiled cast depends on the Shiki version as well as the file: the project’s lockfile is what keeps it byte-identical from one build to the next.
For code outside a terminal, use your site’s ordinary code blocks — show is for a file
that appears as part of a terminal session.
show: a file, highlighted at build time
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()],
});exec
exec runs a real command and puts what it printed into the demo, with the timing it
actually had. The command is typed at the prompt like run, then executed with
/bin/sh -c in a pseudo-terminal the size of the demo, so programs see a real terminal
and colour their output.
steps:
- exec: npm test
cwd: example-app # relative to this .terminal.yaml
env: { CI: '1' }
timeout: 120000 # ms before the command is killed; default 60000
idle: 1500 # longest pause kept between two pieces of output
The command is taken literally — no colour markup, since {…} is common in shell
commands. speed, prompt and pause work as they do for run.
Nothing runs unless you allow it: castwright build --allow-exec, castwright dev --allow-exec, or castwright({ allowExec: true }) in the Vite plugin. Without it a file
containing exec fails to build, pointing at the step. A demo file is not a script
anyone should be surprised to find executing.
Record once, replay forever. Real output depends on the machine, the time and the
network, so a demo that runs exec on every build is not reproducible.
castwright build --allow-exec --record runs the commands once and rewrites the file:
each exec becomes a run with the command and an output: { raw } with exactly what it
printed. From then on the demo builds without running anything, byte for byte the same.
Comments in the file are kept.
exec:, after --record
This was an exec: step. --record ran it once and replaced it with the command and exactly what it printed.
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
^Output ends up in a published demo, so the build warns when it contains something that
looks like a token or a private key, or your home directory path — check those before
publishing. It also warns when a command exits non-zero (the output is recorded anyway),
and when exec runs in CI.
exec needs node-pty, an optional peer dependency with a native build:
pnpm add -D node-pty
Caution: node-pty 1.1.0 ships its macOS helper without the executable bit. If
execfails with “posix_spawnp failed”, the error names the file;chmod +xit once.
Colour and style
Real ESC bytes cannot be written comfortably in YAML, so output and prompt take inline
markup. {/} closes the most recent tag, {//} resets everything, tags nest, and a
literal brace is {{.
- output: |
{red}red{/} {green}green{/} {yellow}yellow{/} {blue}blue{/}
{bright-magenta}bright-magenta{/} {bg-blue} on blue {/}
{#f38ba8}true colour{/} {bold}bold{/} {dim}dim{/} {underline}underline{/}
{green}nested {bold}bold green{/} still green{/}
Tags: the eight base colours (black, red, green, yellow, blue, magenta,
cyan, white) and their bright- variants, bg-<colour> for backgrounds, #rrggbb
for true colour, plus bold, dim, italic and underline.
Named colours compile to palette indices, so a demo follows the theme it is played with.
#rrggbb is absolute and ignores it.
Markup, compiled
Source: site/src/demos/colours.terminal.yaml
version: 1
terminal: { cols: 56, rows: 9 }
steps:
- output: |
{red}red{/} {green}green{/} {yellow}yellow{/} {blue}blue{/}
{bright-magenta}bright-magenta{/} {cyan}cyan{/}
{bg-blue} on blue {/} {#f38ba8}#f38ba8{/}
{bold}bold{/} {dim}dim{/} {italic}italic{/} {underline}underline{/}
{green}nested {bold}bold green{/} still green{/}
- wait: 1500red green yellow blue bright-magenta cyan on blue #f38ba8 bold dim italic underline nested bold green still green
Raw ANSI
For pasting genuine program output that already contains escape sequences:
- output:
raw: "\e[1;36mmy-cli\e[0m \e[2mv2.1.0\e[0m\r\n"
Supports \e, \n, \r, \t, \xNN, \uNNNN and \\. Markup is not processed in
raw.
Validation
Enforced by castwright validate and by every code path that parses a file:
- Exactly one primary key per step. Two is an error, not a precedence question.
- Unknown keys are errors.
jitter > 0without a top-levelseedis an error.- Unbalanced or unknown markup tags are errors, with the offending column.
- An unknown
terminal.themeis an error, listing the known names. - An
outputline wider thancolsis a warning, not an error.
Every error carries the file, the line, the column and the offending source line:
demo.terminal.yaml:4:13: unknown markup tag '{nope}'
- output: "{nope}broken{/}"
^
An error, and its fix
Real castwright validate output, recorded: the position, the known keys, a caret under the typo.
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.svg