# 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

```python
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

```python
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 
  cast
   and optional 
  gif
  , 
  mp4
  , 
  html
  , 
  ascii
  , 
  png
  , and 
  jpg
   paths. 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. Optional 
  display
   overrides 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 
  run
   calls 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

```python
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.
