Vaporware demos as code: render scripted terminal sessions to asciicast v3 (and GIF via agg)
Project description
mockcast
Vaporware demos as code. Describe an imaginary CLI in a YAML script, and
mockcast renders it into a real asciicast v3
.cast file — a scripted, fake terminal session you can play with the
asciinema player and share. No real product required,
no terminal recorded.
It ships with a headline demo for an imagined product, acme ai (a tool
that manages AI coding harnesses — Claude Code, Codex, Gemini CLI — plus
profiles and skills).
(Above: media/hero.gif, generated by mockcast from examples/acme-ai-day.yaml. See also media/styled.gif — the ANSI styling showcase.)
maya@laptop ~/acme $ acme ai profile install acme-team
Resolving acme-team…
✔ claude-code installed
✔ codex already up to date
✔ gemini-cli installed
✔ synced 7 skills · logged in as maya
Why generate instead of record?
A recording embeds wall-clock time and human timing, so it can't be tested or
reproduced. mockcast emits the v3 format directly from a definition, which
makes the output a pure function of its script:
- Deterministic — a seeded RNG drives typing "jitter," and the optional
timestampheader is omitted, so the same YAML always renders byte-identical bytes. - Diffable & reviewable — the demo lives in version control as readable YAML.
- Reshootable — tweak one line of timing or output and re-render; you never have to perform the demo again.
Requirements
- Python ≥ 3.12 and
uv asciinema≥ 3.0 for playback (v3 is an asciinema 3.x format; the legacy 2.x CLI can't play it)agg— only for thegifcommand (brew install agg)
Check your toolchain:
make check
Install
uv sync
This installs the package and the mockcast console script into the project
environment (run it via uv run mockcast …).
Usage
# Schema-check a demo script (friendly errors, no output written)
uv run mockcast validate examples/acme-ai-day.yaml
# Render to a .cast file
uv run mockcast render examples/acme-ai-day.yaml -o hero.cast
# Render to a temp file and play it in your terminal
uv run mockcast play examples/acme-ai-day.yaml
# Render to an animated GIF (via agg)
uv run mockcast gif examples/acme-ai-day.yaml -o hero.gif
uv run mockcast gif examples/styled-output.yaml -o styled.gif --speed 1.5 --theme dracula
gif accepts --speed <factor> and --theme <name> (passed through to agg;
themes include asciinema, dracula, monokai, nord, solarized-dark, …).
Then watch the real thing — timed playback with navigable chapter markers:
asciinema play hero.cast # press the marker keys to jump between chapters
Non-interactive sanity check (replays the timed stream to plain text — proves asciinema accepts the file):
asciinema convert -f txt hero.cast -
In asciinema 3.x,
catis a concatenation command (needs ≥2 files); useconvertto dump a single cast to text.
Writing a demo script
A demo is global settings (meta/vars) plus an ordered list of scenes,
each holding steps. Each scene name becomes a navigable chapter marker (when
auto_markers is on).
meta:
title: "acme ai — a day in the life"
term: { cols: 100, rows: 30, type: xterm-256color }
typing: { cps: 22, jitter: 0.35 } # chars/sec + randomness
prompt: "{user}@{host} {cwd} $ "
pauses: { after_command: 0.5, between_scenes: 1.2 }
idle_time_limit: 2.0
seed: 42 # makes jitter reproducible
auto_markers: true # one chapter marker per scene
vars: { user: maya, host: laptop, cwd: "~/acme" }
scenes:
- name: onboard
steps:
- type: "acme ai profile install acme-team"
- output: |
Resolving acme-team…
✔ claude-code installed
stream: lines # reveal line-by-line
- pause: 1.0
- name: teammate # the two-machine trick
vars: { user: sam, host: workstation } # swaps the prompt identity
prompt: "{user}@{host} $ "
steps:
- banner: "─── Sam's machine ───"
- type: "acme ai search recipe"
Step vocabulary
| Step | Behavior |
|---|---|
type: "<cmd>" |
renders the prompt, then "types" the command char-by-char (cps + seeded jitter), then Enter + an after_command pause |
output: "<text>" |
prints text; modifier stream: instant|lines|chars (default instant), optional delay: seconds before |
pause: <sec> |
idle gap (think time) |
banner: "<text>" |
prints a styled scene-divider line (set dressing) |
clear: true |
clears the screen |
marker: "<label>" |
drops a v3 m event → a navigable chapter marker |
Scene-level vars: / prompt: override the globals — that's how the
"Sam's machine" identity swap works. You can embed raw ANSI escapes in
output/banner strings for color.
How it maps to asciicast v3
- Header (line 1):
{"version": 3, "term": {"cols", "rows", "type"}, …}.timestampis omitted for reproducibility. - Events:
[interval, code, data], whereintervalis relative seconds since the previous event (the first event's interval is0.0), rounded to 3 decimals. Only two codes are emitted:o(output — every keystroke and printout) andm(marker).
Architecture
Three I/O-free units behind a thin CLI:
demo.yaml ──▶ [models.py] ──▶ [engine.py] ──▶ [writer.py] ──▶ out.cast ──▶ asciinema
(author) validate render serialize (play)
models.py— pydantic v2 schema + validation (what a valid demo is).engine.py— purerender(Demo) -> Cast: all timing, typing, prompt, and marker logic. No filesystem access (so it's golden-testable).writer.py— serializes aCastto newline-delimited v3 JSON.cli.py—render/validate/play; owns all I/O.
src/mockcast/{__init__,models,engine,writer,cli}.py
tests/{test_models,test_engine,test_writer,test_cli}.py
examples/acme-ai-day.yaml # the 10-beat hero cut
docs/superpowers/specs/ # design spec
docs/superpowers/plans/ # implementation plan
The hero cut
examples/acme-ai-day.yaml is one continuous "day in the life" of acme ai:
onboard → verify → launch → usage → search → author a skill → ship → promote →
Sam's machine (pull) → status. It renders to ~374 events with 10 chapter
markers, and a teammate scene swaps the prompt from maya@laptop to
sam@workstation to prove the producer→consumer loop in a single cast.
Styling with ANSI
examples/styled-output.yaml is a second demo showing bold, color, symbols,
and an ★ Insight box — the rich-terminal look. Styling is just raw ANSI in
output strings; the engine passes it straight through. Two rules:
- Use double-quoted YAML so
\ebecomes a real ESC byte (block scalars|keep text literal and won't interpret\e). - Instant
outputadds no trailing newline — end standalone lines with\n, or usestream: lines(which adds\r\nper line and reveals them one at a time). Keep each line self-contained (open and\e[0m-close its color on the same line).
uv run mockcast play examples/styled-output.yaml # see it in color
Development
just all # format, lint, typecheck, test
just test # pytest only
Tooling: uv (deps/packaging), ruff (format + lint), ty (typecheck),
pytest (tests), just (command runner). The test suite covers schema
validation, the engine's timing/marker/identity logic, deterministic output
(jitter: 0 exact intervals + render-twice byte equality), v3 serialization,
and the CLI.
License
MIT © Steve Morin
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mockcast-0.2.0.tar.gz.
File metadata
- Download URL: mockcast-0.2.0.tar.gz
- Upload date:
- Size: 7.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49afeeac40f19395b901e09a6fe1a652606ebc13c5820e0a5d0727f968e011e0
|
|
| MD5 |
ac259febdf68e9d2c91b68421742d5f9
|
|
| BLAKE2b-256 |
4d803c9662066136ab501550ad5dcd8e84eb25dbdbd90644fa59f2146fca16b0
|
File details
Details for the file mockcast-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mockcast-0.2.0-py3-none-any.whl
- Upload date:
- Size: 9.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
509ec248042055253a62604d75041622c2b3740c9c1b4505a229f81c14e71c70
|
|
| MD5 |
94d08d16aaca09bb565951f1e52fceb0
|
|
| BLAKE2b-256 |
f244ef0c8540fc7966cb46583fa4053ad846011bdad81158d353eaa46bb3cf9a
|