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.gifNo 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:
robotaboots in a throwaway project — a small HTTP "task board" the script writes into a freshmkdtempdirectory first, and removes when the run succeeds.- The prompt
Explain the main entry point of this projectis typed keystroke by keystroke. - The agent answers with a
Readtool call, which really executes againstsrc/index.tsin that project, and then explains the file that was read. - 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
| Stage | What happens |
|---|---|
| record | @homebridge/node-pty-prebuilt-multiarch drives the built bin/robota.cjs; raw terminal bytes are captured with timings. |
| render | The capture is replayed into xterm.js inside headless Chromium (playwright); one screenshot per output change. |
| encode | Frames 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
| Flag | Default | Purpose |
|---|---|---|
--out <path> | docs/demo.gif | Output path. |
--cast <path> | (not written) | Also write the raw capture as an asciicast-v2 file. |
--cols/--rows | 90 / 33 | Terminal geometry — the committed GIF was recorded at 90×33. |
--font-size | 14 | Render font size in pixels. |
--colors <n> | 128 | GIF 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.