Skip to content
robotadocs

Demo Recording Script

docs/demo.gif — the recording shown in README.md — is generated by scripts/record-demo.mjs. Regenerate it, do not re-improvise it:

pnpm --filter @robota-sdk/agent-cli build        # the recorder drives the BUILT binary
pnpm --filter @robota-sdk/agent-cli demo:record  # → docs/demo.gif

No asciinema, agg, terminalizer or ffmpeg install is required: everything the recorder needs is already a devDependency of this package.

What gets recorded

The scenario is fixed in the script, so two runs produce the same demo:

  1. robota boots in a throwaway project — a small HTTP "task board" the script writes into a fresh mkdtemp directory first, and removes when the run succeeds.
  2. The prompt Explain the main entry point of this project is typed keystroke by keystroke.
  3. The agent answers with a Read tool call, which really executes against src/index.ts in that project, and then explains the file that was read.
  4. The finished screen is held for three seconds so the looping GIF stays readable.

The tool call passes a project-relative path, so the throwaway directory's name never reaches the screen: the demo shows Read(src/index.ts) however the scratch directory happens to be named.

Why it needs no API key

The run boots with --session-log, which swaps the model provider for the offline replay provider: the two assistant turns come from a recorded session log embedded in the script. Only the model turns are replayed — the CLI, the TUI, the tool registry and the Read tool all run for real, in a real pseudo-terminal (the same PTY substrate the *.ptytest.ts suites use).

The child process is spawned with a deliberately minimal environment (PATH, a temp HOME, TERM), so no provider key, token or personal path from the recording machine can reach the terminal.

How the GIF is produced

StageWhat happens
record@homebridge/node-pty-prebuilt-multiarch drives the built bin/robota.cjs; raw terminal bytes are captured with timings.
renderThe capture is replayed into xterm.js inside headless Chromium (playwright); one screenshot per output change.
encodeFrames are quantized to a shared palette and written as an animated GIF (gifenc), unchanged pixels left transparent.

Frames are sampled from the recorded timeline rather than from wall-clock, so the render is reproducible; idle gaps are capped at 900 ms so a pause never stretches the GIF.

Safety check

Before anything is written, the recorder scans the captured terminal output and fails the run if it finds a home-directory path, a user@host string, the machine's hostname, or an API-key-shaped token. A published asset is the wrong place to discover a leak.

The scratch tree is created with mkdtemp (mode 0700, unguessable name) and every file inside it is written 0600 — a fixed path in a world-writable temp directory can be pre-created as a symlink by another user on a shared host, which is CodeQL's js/insecure-temporary-file.

Options

FlagDefaultPurpose
--out <path>docs/demo.gifOutput path.
--cast <path>(not written)Also write the raw capture as an asciicast-v2 file.
--cols/--rows90 / 33Terminal geometry — the committed GIF was recorded at 90×33.
--font-size14Render font size in pixels.
--colors <n>128GIF palette size; lower it if the file ever gets too large.

The current asset is 791×622, 17 frames, 74,786 bytes (73 KiB) — well inside the 5 MB budget the recorder enforces.

When to re-record

Re-record whenever the TUI's chrome changes (welcome banner, status bar, tool-call rendering), and look at the result: the GIF is the first thing a visitor sees on GitHub and npm.