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
Metadata
Release files for mockcast 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mockcast-0.2.0.tar.gz | 7.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mockcast-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 16.3 kB
Release files / mockcast-0.2.0.tar.gz
| Download URL | mockcast-0.2.0.tar.gz |
|---|---|
| Size | 7.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
49afeeac40f19395b901e09a6fe1a652606ebc13c5820e0a5d0727f968e011e0
|
|
BLAKE2b-256 checksum How to use checksums |
4d803c9662066136ab501550ad5dcd8e84eb25dbdbd90644fa59f2146fca16b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / mockcast-0.2.0-py3-none-any.whl
| Download URL | mockcast-0.2.0-py3-none-any.whl |
|---|---|
| Size | 9.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
509ec248042055253a62604d75041622c2b3740c9c1b4505a229f81c14e71c70
|
|
BLAKE2b-256 checksum How to use checksums |
f244ef0c8540fc7966cb46583fa4053ad846011bdad81158d353eaa46bb3cf9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|