Skip to main content

Vaporware demos as code: render scripted terminal sessions to asciicast v3 (and GIF via agg)

Project description

mockcast

CI

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).

mockcast rendering the acme ai demo

(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 timestamp header 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 the gif command (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, cat is a concatenation command (needs ≥2 files); use convert to 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"}, …}. timestamp is omitted for reproducibility.
  • Events: [interval, code, data], where interval is relative seconds since the previous event (the first event's interval is 0.0), rounded to 3 decimals. Only two codes are emitted: o (output — every keystroke and printout) and m (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 — pure render(Demo) -> Cast: all timing, typing, prompt, and marker logic. No filesystem access (so it's golden-testable).
  • writer.py — serializes a Cast to newline-delimited v3 JSON.
  • cli.pyrender / 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:

  1. Use double-quoted YAML so \e becomes a real ESC byte (block scalars | keep text literal and won't interpret \e).
  2. Instant output adds no trailing newline — end standalone lines with \n, or use stream: lines (which adds \r\n per 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mockcast-0.2.0.tar.gz (7.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mockcast-0.2.0-py3-none-any.whl (9.0 kB view details)

Uploaded Python 3

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

Hashes for mockcast-0.2.0.tar.gz
Algorithm Hash digest
SHA256 49afeeac40f19395b901e09a6fe1a652606ebc13c5820e0a5d0727f968e011e0
MD5 ac259febdf68e9d2c91b68421742d5f9
BLAKE2b-256 4d803c9662066136ab501550ad5dcd8e84eb25dbdbd90644fa59f2146fca16b0

See more details on using hashes here.

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

Hashes for mockcast-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 509ec248042055253a62604d75041622c2b3740c9c1b4505a229f81c14e71c70
MD5 94d08d16aaca09bb565951f1e52fceb0
BLAKE2b-256 f244ef0c8540fc7966cb46583fa4053ad846011bdad81158d353eaa46bb3cf9a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page