Cast recorder
Write a terminal demo as a Starlark script. Type and execute each command with one call,
drive interactive programs through rec.terminal, and validate the saved recording using the same declaration.
Usage
rec = cast.recorder(title="Inspect a project", output="demo.cast")
rec.comment("List available stacks")
rec.run("atmos list stacks", expect="dev", reject=cast.regex(r"panic:"))
rec.generate()
Use this code in a custom command or workflow with type: script and
interpreter: starlark, or in a standalone .star script. No new command registration is needed.
Declarations do not execute commands. generate() executes them and validates the staged recording before replacing the output.
Interactive programs
rec = cast.recorder(output="interactive.cast", shell="/bin/sh")
rec.comment("Create a project")
rec.terminal.send("atmos init basic ./my-project")
rec.terminal.wait("Project name", timeout="10s")
rec.terminal.key("enter")
rec.terminal.wait("Which environments do you need?")
rec.terminal.key("enter")
rec.terminal.wait(cast.regex(r"Generated [0-9]+ files"))
rec.terminal.close()
rec.cd("my-project")
rec.run("atmos list stacks")
rec.generate()
The first terminal action opens a declared PTY block. Its actions share a shell, directory,
and environment until close(). Close it before ordinary recorder actions. A final terminal
block closes automatically on generation. send types and presses Enter; write types without Enter.
wait searches fresh output since the previous successful wait, with ANSI formatting removed.
It never matches the echo of a line the script typed and submitted with Enter, so a wait for a command's output is not satisfied by the terminal echoing the command itself.
It does not infer a command's exit status. Keys include enter, tab, arrow keys, and ctrl+c;
key("down", repeat=2, interval="200ms") sends repeated keys.
Recorder options
output- A cast path, or a mapping with
castand optionalgif,mp4,html,ascii,png, andjpgpaths. A cast path is required. working_directory,env,shell- Execution settings inherited by actions. Relative working directories resolve from the script invocation's working directory. Output paths also resolve from that invocation, independently of
rec.cd. defaults,width,height,rate,write_rate,key_interval- The existing cast recording and simulated typing settings. Ordinary recorded step output defaults to raw output without step labels.
policy- An optional function receiving raw recorded text, including ANSI color sequences. Call
fail()to reject it. Keep policies read-only. replacements- Literal output substitutions, for example an absolute checkout path mapped to
/absolute/path/to/repo. Substitutions handle text split across events and run before saved-recording assertions and policy checks. Source paths do not affect the portable declaration fingerprint.
Actions
run(command, expect=..., reject=..., exit_code=0)- Types and executes the same command. Assertions accept a string,
cast.regex(pattern), or a list of either. All expectations must match and no rejection may match. Assertions inspect ANSI-normalized command output, excluding typed input. Optionaldisplayoverrides the displayed text; step fields and timing overrides are accepted. comment(text),simulate(text),prompt(),pause(duration)- Present narration, simulated text, an idle prompt, or a delay. Comments receive a
#prefix automatically. Simulated text does not execute. cd(path)- Displays a directory transition and changes subsequent actions' working directory. Ordinary
runcalls are separate processes; use a terminal block for persistent shell state. screenshot(path)- Records a screenshot checkpoint. PNG rendering occurs after successful recording validation.
step(type, **fields)- Declares a registered step using its documented fields, including container operations. Workflow-only scheduling fields remain unsupported. Background services belong to the enclosing workflow.
terminal.hide(),terminal.show()- Exclude terminal output from the recording while retaining it for live waits. Hidden waits cannot be rechecked from saved output.
Setup and cleanup
def prepare(c):
c.step("workdir", source="./fixtures/demo", path="./work", reset=True)
def cleanup(c):
c.run("atmos emulator down aws")
rec = cast.recorder(output="demo.cast")
rec.setup(prepare)
rec.cleanup(cleanup)
rec.run("atmos version", expect="Atmos")
rec.generate()
Callbacks receive another declaration builder. They must declare actions rather than call
exec.run, steps.*, or other effectful functions directly. setup runs before recording;
hidden inserts an unrecorded group in the sequence. Cleanup runs even after partial setup
failure, failed assertions, or cancellation, with a 30-second grace period. Cleanup failures
prevent publication and are reported alongside the original failure.
Validate saved recordings
Replace the final rec.generate() with rec.validate(), or select between them using your
custom command's existing flags or explicit step environment. Use the same declaration for both.
Validation reads metadata, checks the declaration fingerprint, checks output assertions and
visible terminal waits, and runs the policy. It executes no declared actions and writes no artifacts.
Ordinary Starlark code outside the builder still executes normally.
Old recordings without declaration metadata, interrupted recordings, and recordings whose actions changed must be regenerated. Failed execution, cleanup, or validation preserves the previous cast. Artifact rendering runs after the valid cast is committed; a rendering failure does not roll it back. Generation is unavailable inside parallel script tasks because it owns the terminal and recorder. The host's dry-run mode parses the script without executing it.