Terminal recordings¶
Every recording on this page is a real terminal session. A script called a
tape types into bash on a pseudo-terminal, runs the rich binary built
from this repository, and waits for the screen before each step. Nothing is
drawn by hand. Press play, pause anywhere, and select the text: the recordings
are asciinema casts, not videos.
The screenshots under each recording are taken from the same run. CI re-runs every tape and fails when any screenshot's text no longer matches, so this page cannot drift from what the CLI does. How it works is at the end.
Gallery¶
Point at a card, or move to it with Tab, to play its recording. With reduced motion turned on in your system settings the cards stay still.
One binary, every format
Watch a file
Page and search
Ask for a line
Components
Ask for more in a script
Explore a document
An interactive component
Overlays and chrome
Terminal apps
A game, played with held keys
A file manager
An oscilloscope
Stylesheets
Sorting and resizing a table
Lazy trees, tooltips, drag-and-drop
Flex alignment
Text mode and linear mode
A text area
A surface
ratatui's examples
Nested JSON Lines
Two-row micro assets
Review and jump
Banners and shell integration
Preview, then share
Dates and times
Org, AsciiDoc and man pages
Filter and group a log
Serve a program to a browser
A terminal pane
Charts from data
Dependency trees
Profile a data file
Schemas and ER diagrams
Crate features
Sizes, baselines and licence policy
Merge conflicts
DOT in Markdown
Micro assets
The guided tour
The 0.0.13 release
One binary, every format¶
Markdown, CSV and source code, each rendered from a plain rich FILE.
| Markdown | CSV | Code |
|---|---|---|
![]() |
![]() |
![]() |
Watch a file¶
rich --watch renders a file and renders it again on every save. Here the
tape edits service.json from outside the terminal while rich watches it.
| Before the edit | After the edit |
|---|---|
![]() |
![]() |
Page and search¶
rich view --pager numbers and highlights a file, then hands it to less:
page down, then /characters to search.
| Opened | Paged down | Searched |
|---|---|---|
![]() |
![]() |
![]() |
Ask for a line¶
rich input reads one line for a script. --placeholder shows a hint while
the line is empty, and --required refuses an empty answer with an error under
the line. The answer is captured with $(…).
| Placeholder | Required | Answered |
|---|---|---|
![]() |
![]() |
![]() |
Components¶
Every rs-rich-interact component, one after another, recorded in a small
fixture project:
- a fuzzy file picker with a highlighted preview;
- a multi-select;
- an input with suggestions and validation;
- a confirmation sheet with a diff and four choices;
- a form that reports an error under its field;
- a pager that searches.
| Select | Input | Confirm |
|---|---|---|
![]() |
![]() |
![]() |
| Form | MultiSelect | Pager |
![]() |
![]() |
![]() |
Ask for more in a script¶
rich write, rich file, rich color and rich asset, each captured into
a shell variable with $(…): several lines of a commit message, a path
picked from the fixture project with its preview, a colour from the
names and the palette, and an emoji and a box style used together. See
Ask in a script.
| rich write | rich file | rich file |
|---|---|---|
![]() |
![]() |
![]() |
| rich color | rich color | rich asset |
![]() |
![]() |
![]() |
write tape · cast · page · file tape · cast · page · color tape · cast · page · asset tape · cast · page
Explore a document¶
rich explore on a YAML file: a container opened, the breadcrumbs following
the cursor, the path copied, a search that keeps the match's ancestors, and
the JSONPath captured by the script. See
Explore it interactively.
| Folded | Opened | Searched |
|---|---|---|
![]() |
![]() |
![]() |
An interactive component¶
rs-rich-interact's Viewport, run as its example pager, pages a Markdown
file on the alternate screen. Each key repaints only the cells that changed.
Enter gives the terminal back, as it was, with the line it was left at. See
Interactive components.
| Opened | Paged down | Given back |
|---|---|---|
![]() |
![]() |
![]() |
Overlays and chrome¶
rs-rich-interact's overlays and chrome, run as its overlays example: a
file list wrapped in Overlays. Ctrl+O opens the command palette, which
lists every key the list has, by category, with its shortcut, and runs the
one picked. The help overlay groups the keys and searches them as you type.
Under the list, a status bar shows a badge, a spinner, a note and key hints
from the keymap. Breadcrumbs sit over the list, and Ctrl+K opens the
region's actions in a modal. See
Overlays and chrome.
| The palette | Searched | Run |
|---|---|---|
![]() |
![]() |
![]() |
| Help | Searched |
|---|---|
![]() |
![]() |
| The status bar | Region actions | A command ran |
|---|---|---|
![]() |
![]() |
![]() |
Terminal apps¶
rs-rich-intuituive's examples: the to-do app from the
tutorial, with a to-do added and one ticked
off; the same app with the inspector docked on the right, showing which
nodes drew; the screens example asking before it quits, in a modal; and
the inline example finishing in a few rows under the prompt, with the
scrollback above it kept. Then two examples of the newer parts: meters, a
board of meters that are widgets of their own (retained drawing, focus and
resize events, the pointer, a board that sees keys first), and planner,
built from the framework's components (a menu bar, a tree, split panes, a
calendar and a virtual list of a hundred thousand rows), with a toast and
then the command palette. See Terminal apps.
| To-do | Inspector |
|---|---|
![]() |
![]() |
| Modal | Inline |
![]() |
![]() |
| Widgets of your own | Components |
![]() |
![]() |
| Command palette | Menu bar |
![]() |
![]() |
A game, played with held keys¶
rs-rich-intuituive's ray caster, recorded at 35 frames a second, every
frame it draws: the title, the map, the keys and the pause menu as stills,
then a walk-through played with Hold, KeyDown and KeyUp, with the
kitty keyboard protocol reported (Set KittyKeyboard on) so the arrows'
releases reach the game. A second tape records the same walk-through with
the example drawing iTerm2 images, which the recorder decodes and draws
into the GIF. See the recorder's README
for the tape commands, Set Framerate and the graphics it follows.

| Title | Map |
|---|---|
![]() |
![]() |
Tape · GIF · APNG · In iTerm2 images (tape)
A file manager¶
rs-rich-intuituive's files example, a rebuild of the behaviour of
Yazi by sxyazi and contributors (MIT), in
the fixture project: Yazi's three columns, theme and status line, a Rust
file previewed with highlighting done on a worker thread, the which-key
pop-up after , and the sort by size it picks, the help, a selection, the
filter applied as it is typed, the confirmation before trashing, the
right-click menu, the command palette and a second tab. See
Porting a ratatui app.
| Browsing | Which-key |
|---|---|
![]() |
![]() |
| Help | Selection |
![]() |
![]() |
| Filter | Confirmation |
![]() |
![]() |
| Tabs | Right-click menu |
![]() |
![]() |
| Command palette | |
![]() |
An oscilloscope¶
rs-rich-intuituive's scope example, a rebuild of
scope-tui by alemi (MIT). It shows its test
signal moving in the oscilloscope, the vectorscope and the spectroscope,
then held still and paused in each for the screenshots, and finally its
keys. See
Porting a ratatui app.
| Oscilloscope | Vectorscope |
|---|---|
![]() |
![]() |
| Spectroscope | Keys |
![]() |
![]() |
Stylesheets¶
rs-rich-intuituive's styled example (0.0.18): a status page whose
colours, borders, sizes and docking come from a stylesheet, styled.tcss.
Rules select by kind (table), name (#title) and class (.card, and
.down while a service is down), with :focus and :hover. The tape then
edits the sheet from outside the terminal: the app reads it again, and a
line it cannot parse is reported in a toast while the last good sheet
stays. See Stylesheets.
| Styled | :focus and :hover |
|---|---|
![]() |
![]() |
| Reloaded | A mistake |
![]() |
![]() |
Sorting and resizing a table¶
rs-rich-intuituive's tables example (0.0.18), a process table built
with table_with: a click on a header sorts by it and a second click
reverses the order, ←/→ move the cell cursor and s sorts by its column,
and dragging the gap after a header resizes the column. See
Sorting, resizing and a cell cursor.
| Sorted by a click | The cell cursor, sorted with s |
Resized |
|---|---|---|
![]() |
![]() |
![]() |
Lazy trees, tooltips and drag-and-drop¶
rs-rich-intuituive's board example (0.0.18), a sprint board. Past
sprints sit in a tree_lazy that loads each one on another thread when it
is opened, showing "loading…" until it arrives and the error in red when it
fails. The pointer resting on a card shows its tooltip, and a card dragged
to another lane moves there. See The mouse.
| Loading | Loaded, and a failure |
|---|---|
![]() |
![]() |
| A tooltip | Dropped |
![]() |
![]() |
Flex alignment¶
rs-rich-intuituive's align example (0.0.19): a row of cards of
different sizes placed by align_items, justify, wrap and
align_content, each cycled with a key; the test card keeps to the end of
its line with align_self. See
Alignment and wrapping.
| Stretched | Centred |
|---|---|
![]() |
![]() |
| Space between | Wrapped |
![]() |
![]() |
| Lines centred | |
![]() |
Text mode and linear mode¶
rs-rich-intuituive's access example (0.0.18), the one the
screen reader checklist walks
through. In text mode (--accessible) boxes are blanks with their titles
kept, there is no colour, > marks the selected item, and the terminal's
cursor sits on the focus. In linear mode (--linear) there is no screen:
the app is written as lines of text, then only the lines that change. See
Accessibility.
| Text mode | Linear mode |
|---|---|
![]() |
![]() |
A text area¶
rs-rich-intuituive's editor example (0.0.19): Rust source highlighted
with line numbers, over a notes area that wraps and shows a placeholder.
A line is typed, a line selected with Shift, the typing undone as one step
with Ctrl+Z, and Escape then Tab moves on to the notes. See
Text areas.
| Highlighted | A line selected |
|---|---|
![]() |
![]() |
| Undone | Notes |
![]() |
![]() |
A surface¶
rs-rich-ext's guide_surface example (0.0.19): a panel, a table and a
status line drawn into rectangles of an inline Surface, eleven rows below
the prompt, and redrawn every half second. Each frame sends only the cells
that changed; the output above it stays, and the last frame stays on
screen when the program ends. See
Positioned drawing.
| Ticking | Done |
|---|---|
![]() |
![]() |
Dates and times¶
rich date (0.0.19): a day picked on a month grid, inside a range whose
days outside are out of reach, then a date and time whose time is refused
before the range's first moment, until it is moved into it. The answers
are printed in ISO 8601 form, for a script to read. See
Ask in a script.
| A day | Refused | Answered |
|---|---|---|
![]() |
![]() |
![]() |
Org, AsciiDoc and man pages¶
rich FILE (0.0.19) on an Org file, an AsciiDoc file and a man page, each
found by its extension (the man page by its section number and the roff
in it) and drawn in the look of rich's Markdown; then the man page's
source with --syntax. See
Org, AsciiDoc and man pages.
| Org | AsciiDoc | Man page | Source |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Filter and group a log¶
rich log (0.0.19) on one log in four formats (JSON Lines, logfmt, a
timestamped line and an access log, with a stack trace after an error),
read as records; then filtered by level and field, folded where a message
repeats, and grouped by trace id with a summary of the levels and the
traces that failed. See Logs: rich log.
| Records | Filtered | Folded | Groups |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
ratatui's examples on intuiTUIve¶
ratatui's demo and demo2, its widget, chart and layout examples, and
seven of its apps, rebuilt on intuiTUIve's API (0.0.19) with the mouse,
help and a command palette added. The design, layout and keys are
ratatui's (MIT, Florian Dehau and the Ratatui Developers); the code and
the sample data are ours. See
ratatui's examples.
The classic demo¶
| Graphs | World | Colours |
|---|---|---|
![]() |
![]() |
![]() |
The showcase¶
| About | Weather | |
|---|---|---|
![]() |
![]() |
![]() |
Widgets, charts and layout¶
| Blocks | Table | Popup |
|---|---|---|
![]() |
![]() |
![]() |
| Gauges | Chart | Canvas |
|---|---|---|
![]() |
![]() |
![]() |
| Explorer | Flex |
|---|---|
![]() |
![]() |
Apps¶
| Colours | Canvas | Form | Calendar |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Nested JSON Lines and style rules¶
rich profile (0.0.19) on JSON Lines, path by path with each path's mix
of types, then a table styled by value with --style-rule. See
Nested JSON Lines.
| Nested | Rules |
|---|---|
![]() |
![]() |
Two-row micro assets¶
Micro assets two rows high (0.0.19): a 2×2 icon in text and a table, and a 3×2 animation. See Micro assets.
| Icon | Animation |
|---|---|
![]() |
Review a queue and jump to anything¶
rich approve (0.0.19) walks a queue of items, approved, rejected with a
reason or skipped, and prints the decisions as JSON Lines; rich jump
finds a file, a heading, a symbol or a command from one fuzzy box. See
Review and jump.
| Queue | Reason | Decisions |
|---|---|---|
![]() |
![]() |
![]() |
| Groups | Narrowed | Answer |
|---|---|---|
![]() |
![]() |
![]() |
Banners and shell integration¶
rich banner (0.0.19) draws FIGlet text in a gradient, and rich shell
prints a status line after a command and links to files. See
Draw a banner and
Shell integration.
| Gradient | Vertical | Fit and status |
|---|---|---|
![]() |
![]() |
![]() |
Preview, then share¶
rich capture --redact --redact-preview (0.0.19) shows each secret a
redaction would mask, and its replacement, without writing anything; then
the redacted capture is shared as a PNG. See
Preview a redaction before you share.
| Preview | Shared |
|---|---|
![]() |
![]() |
Serve a program to a browser¶
rich serve (0.0.18, behind the CLI's serve feature) runs a program in
a browser tab, one copy per tab, and prints the address with its token. A
tape cannot drive a browser, so curl asks what a browser would: the page
without the token is refused and with it is served, and a WebSocket
upgrade from the server's own page starts a session while one from another
site is refused. Ctrl+C stops the server and the programs. See
rich serve.
| Started | The token and the origin checked |
|---|---|
![]() |
![]() |
A terminal pane¶
rs-rich-embed's embed example (0.0.18): a shell in a terminal pane
beside a web view, with a status line built from both panes' signals. Keys
go to the program in the focused pane; F6 moves to the web view, where w3m
shows a local page and Ctrl+L opens another. See
Embedding programs and pages.
| The shell | Another address |
|---|---|
![]() |
![]() |
A terminal web browser¶
Noctiluca (0.0.22, the reader phase): a page in reader mode, link hints, a followed link and back, the live F1 overlay of every action with the reason one cannot run, and the projection one key away. The tape replays Chrome's saved snapshots of the crate's fixture pages, since the tape job runs no browser. See the design note.
| Reader mode | Link hints | F1 | The projection |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
A live log beside a stream¶
Noctiluca (0.0.23) on stream-with-log.html, a local page built from
ordinary HTML: the stream playing in its region, a role=log chat beside
it with its authors in their colours and its badges and emotes drawn in
their lines, scrolled up (it holds still) and back, the about page's
terminal adaptations and codecs, and the faithful projection one key
away. It replays what Chrome supplied (the snapshot, the page's files,
Chrome's recorded frames of the video), so the log does not move here;
the live tests follow it in Chrome.
| The log beside the stream | Scrolled up | The adaptations | Faithful |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
A news page¶
Noctiluca (0.0.23) on news.html, a local page with our own pictures:
the page text as terminal text, each picture in Sixel where the page puts
it with its caption, a video's poster with its play mark that then plays
in its region, and further down the stories in a grid with an animated
GIF. Drawn by Noctiluca and intuiTUIve, the pictures by rs-rich-art; the
tape replays what Chrome supplied (the snapshot, the page's own picture
files, Chrome's recorded frames of the video), since the tape job runs no
browser.
| The page | Its video playing | The stories |
|---|---|---|
![]() |
![]() |
![]() |
A video page¶
Noctiluca (0.0.23) on media-grid.html: the player's poster, then the
video playing in its region, the channel's avatar beside its name, the
buttons as [ labels ], thumbnails side by side with their titles under
them, and a compact comment list with small avatars.
| The player | Playing | Up next and comments |
|---|---|---|
![]() |
![]() |
![]() |
Four more tapes over the same saved pages show
forms and controls,
reading, find and media,
keywords and site keys, and
the start page (0.0.24: the animated banner,
the lanternfish and the bookmarks). One more,
tapes/live/noctiluca-chrome.tape,
runs a real Chrome on the crate's test pages, served from 127.0.0.1. The
tape job runs no browser, so that one is recorded by hand and not checked;
its header says how. So are the tapes beside it that open real sites
(Wikipedia, Hacker News, MDN and others), shown on
Noctiluca on real sites. All of them are on
Noctiluca's guide pages.
Cornacai, a client for coding agents¶
Cornacai (0.0.24) against a fake agent, rs-rich-acp's replay of a recorded
conversation: an @ file mention completed from the workspace, a plan and
an answer streaming in as Markdown, the agent's edit reviewed as a diff and
accepted, a permission prompt for a command, and the finished turn. See
Cornacai's guide.
An @ mention |
An edit to review | A permission prompt | Done |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Charts from data¶
rich chart draws CSV, JSON or numbers piped in: bars labelled by a
column, every numeric column as a line, a sparkline from printf, a
heatmap, and a column that is not there refused with the ones that are. See
the charts guide.
| Bars | Lines | Piped in, and a mistake |
|---|---|---|
![]() |
![]() |
![]() |
Dependency trees¶
rich deps draws a Cargo workspace's dependencies from cargo metadata
(here a saved copy, so the recording never changes): a tree that marks
crates resolved at several versions, --why for what pulls a crate in, and
--graph for the same through the diagram layout. See
Dependencies and schemas.
| Tree | Why syn |
Graph |
|---|---|---|
![]() |
![]() |
![]() |
Crate features¶
rich deps --features shows which features each crate has enabled, what
each one turns on (other features, optional dependencies, features of
dependencies), and which crates asked for them (0.0.16). --package picks
one crate; here syn, resolved at two versions.

Sizes, baselines and licence policy¶
rich deps (0.0.19) as a CI gate: what each crate adds to a binary, from
cargo-bloat's JSON; a size regression against a saved baseline, which
exits 5; and a licence policy refusing a crate in an SBOM. Every input is
a fixture in the repository. See
Dependency sizes, baselines and licence policy.
| Sizes | Baseline | Policy |
|---|---|---|
![]() |
![]() |
![]() |
Profile a data file¶
rich profile says what each column of a CSV file holds: its type, nulls,
distinct values, statistics and distribution, and a map of where the nulls
are (0.0.16). rich FILE.csv --infer puts the same types on the table
itself. See Profile a data file.
| Profile | --infer |
|---|---|
![]() |
![]() |
Schemas and ER diagrams¶
rich schema reads SQL DDL as well as JSON Schema (and Arrow, with the
arrow feature): the tables as a tree, with what the reader skipped noted
under it, then --er for the same tables as an ER diagram, keys marked and
an edge per foreign key (0.0.16). See
Schemas.
| Tree | --er |
|---|---|
![]() |
![]() |
Merge conflicts¶
rich diff --conflicts shows a file's merge conflicts: ours, the base (for
a diff3-style conflict) and theirs side by side, with the lines around them
and a count at the end (0.0.16). See
Merge conflicts.

DOT in Markdown¶
A ```dot fence in a Markdown document is drawn in place of the
code block; --dot-backend off leaves it as code, as upstream renders it.
See the diagrams guide.
| Drawn | --dot-backend off |
|---|---|
![]() |
![]() |
Micro assets¶
Emoji-sized images in text, :micro:name:. A pseudo-terminal speaks no
image protocol, so these show each asset's emoji, or its image as
half-block cells with RICH_MICRO=blocks; a Kitty, iTerm2 or Sixel
terminal draws the image itself in the same cells. See
the built-in library for the images.
Making an asset: a 128×128 PNG previewed through the pipeline, written
into the user layer with rich micro create --add, listed and used.
| Through the pipeline | Added and used |
|---|---|
![]() |
![]() |
Which mode a terminal gets, and why: rich doctor with the variables
kitty and iTerm2 set.

rich micro: the built-in library listed, status/loading previewed, and
:micro: codes in --print --emoji text.
| List | Preview | In text |
|---|---|---|
![]() |
![]() |
![]() |
Micro assets in the interactive views: an icon per row, a badge in the
status bar, an icon on the first crumb and before each of the command
palette's categories, then rich explore --icons.
| Chrome | Palette | Explorer |
|---|---|---|
![]() |
![]() |
Tape · Cast · GIF · Page · Explorer tape
How it works¶
A tape is a short script, one step per line. This is the watch tape:
# rich --watch re-renders a file every time it is saved.
Set Title "rich --watch · re-render on save"
Set Size 90x22
Write service.json '{"service": "checkout", "replicas": 2, "healthy": true, "regions": ["eu-west-1", "us-east-1"]}'
Type "rich --watch service.json"
Sleep 300ms
Enter
Wait /"replicas": 2/
Sleep 1s
Screenshot before
Exec "sed -i 's/\"replicas\": 2/\"replicas\": 5/' service.json"
Wait /"replicas": 5/
Sleep 1s
Screenshot after
Ctrl+C
Wait /❯\s*$/
rich record runs it: the CLI's own recorder, built on the
rs-rich-record crate. It starts a shell
(bash unless the tape sets another) on a pseudo-terminal with a pinned
environment (a temporary home and working directory, TERM=xterm-256color,
truecolor, UTF-8, UTC), puts the binaries under test first on PATH, and
follows the screen with a terminal emulator. Wait blocks until the screen
shows the given text or matches a /regex/, so a tape never depends on a
fixed delay.
From one run it writes, under docs/media/tapes/<tape>/:
- for each
Screenshot: a PNG, an SVG with selectable text, and a plain-text grid of the screen. The SVG and the text grid come from the same frame as the rest of rich's export, so a screenshot looks like any other rich SVG; - the whole session as an asciinema cast, with the keys pressed;
- a GIF with the keys shown as they are pressed, an MP4 when FFmpeg is
installed, and, when asked (
Output apng,--format apng), an animated PNG (APNG, in full colour). Video holds every screen update the program finished, dropped to the tape's frame rate (Set Framerate, 12 a second by default) with identical frames merged, and the Kitty, Sixel and iTerm2 images the program drew, over their cells; - an HTML page (
<tape>.html, the Page links above): a small player and the screenshots, all inline, with text you can select. It fetches nothing and plays only when asked; provenance.json: the tape's fingerprint, the recorder andrichversions, the commit, and the screenshots written, so the next run removes only the ones the tape no longer takes.
Box-drawing characters are drawn as lines rather than font glyphs, so table
borders join between rows, and block, quadrant and braille characters are
drawn as shapes, so images and progress bars have no seams. Text uses DejaVu
Sans Mono and emoji use Twemoji, in colour, both embedded in the recorder, so
a PNG or GIF looks the same on every machine; --font FILE chooses another
text font. An emoji cluster such as 👩👧, 👍🏽, ❤️ or 🇺🇸 takes one cell as
wide as rich measures it, so the columns after it line up.
| Step | Meaning |
|---|---|
Set Size 100x28, Set Title "…", Set TypingDelay 40ms, Set Timeout 15s, Set Env NAME value |
Configure the session |
Set WindowFrame off, Set Caption "…", Set KeyOverlay off |
Presentation: leave out the window frame (title bar and buttons) around PNGs, SVGs and video; add a line of text under them and under the page's player; leave out the keys shown in video and the player. The frame and the overlay are on by default |
Output gif png, Output demo.html |
Write only these formats (png, svg, cast, gif, apng, mp4, html; text grids always; without an Output line, all of them but apng, which is written only when an Output line or --format names it), or name the file a per-tape format goes to, as in VHS. Several words or lines add up; --format can still narrow them |
Set Framerate 35, Set FontSize 10 |
Video: frames a second, 1 to 60 (12 by default; --fps overrides it), and the size of video text in pixels, 4 to 64 (16) |
Set KittyKeyboard on, Set KeyRepeat 200ms 33ms |
Keys: report the kitty keyboard protocol, so a program can turn it on and hear releases (off by default); a held key's autorepeat delay and interval (200ms 33ms) |
Set Audio on |
Sound: record what the program plays (off by default; --audio turns it on too). See Sound |
Set Shell zsh |
Run in bash (the default), zsh, fish or sh, each without your profile or rc files and with the same ❯ prompt. CI records in all four |
Write FILE "text", Exec "command" |
Prepare or change files, outside the terminal |
Type "text" |
Type into the terminal, one character at a time |
Enter, Tab, Shift+Tab, Space, Backspace, Delete, Insert, Escape, arrows, Home, End, PageUp, PageDown, F1 to F24, Ctrl+C, Alt+x, and the arrows, Home, End, PageUp, PageDown, Delete, Insert and function keys after any of Shift+, Alt+ and Ctrl+ (Alt+Left, Ctrl+Shift+PageDown) |
Press a key; a number after it repeats it. Modified keys go in xterm's form, CSI 1;3D for Alt+Left. F1 to F20 are sent as xterm sends them; F21 to F24 have no xterm form, so they go as the kitty keyboard protocol's codes, which an app reads once it has turned that protocol on |
Click 10 4, RightClick, MiddleClick, DoubleClick, MouseMove 10 4, ScrollUp 10 4 3, ScrollDown, Drag 2 4 20 4 |
Use the mouse at a cell (column and row, from 0 at the top left), as SGR mouse reports; a number after a click or scroll repeats it. Drag moves a cell at a time with the left button held. The app needs mouse reporting on, as intuiTUIve apps have |
KeyDown Up, KeyUp Up, Hold f 2s |
Hold a key (a name as above, or one character) down, let it go, or both with a wait between. A held key autorepeats while the steps after it run, and every key still held is let go when the tape ends. With Set KittyKeyboard on, presses, repeats and releases go as the protocol's escapes once the program has turned it on; without it, a release sends nothing, as on a legacy terminal |
Wait "text", Wait /regex/ |
Wait until the screen shows it, or has since the previous step began (so fast output that scrolls past is not missed) |
Sleep 500ms |
Pause the recording |
Screenshot NAME |
Save the screen |
Hide, Show |
Leave steps out of the recording |
Resize 80x24 |
Resize the terminal |
Mask /regex/ "text" |
Replace matches in the text grids --check compares, for output that differs on every run such as temporary paths or timings; images keep what was shown |
Shells and emoji¶
A tape can type and print emoji, and the recorder draws them as the program
measures them: rich, and modern terminals, give 👩👧 two cells. A shell's
line editor may not agree. bash, zsh and fish take character widths from the
C library, which counts 👩👧 as four, so moving the cursor back across a
joined emoji at the prompt (Left, Home, Backspace) redraws the line in
the wrong place, as it does in a real terminal. The command that runs is
still right; only the echoed line is garbled. Keep such text out of line
editing: put it in a file with Write and run that, or have the program
print it.
The docs' tapes use bash, as CI does. macOS ships bash 3.2, whose line
editing is older than CI's; rich record warns when it finds a bash older
than 4, and a newer one (brew install bash) first on PATH gives
recordings that match.
Regenerate everything, or check it as CI does:
cargo build -p rs-rich-cli -p rs-rich-interact -p rs-rich-intuituive -p rs-rich-embed \
--features rs-rich-cli/serve,rs-rich-intuituive/files,rs-rich-intuituive/archive,rs-rich-intuituive/watch,rs-rich-intuituive/art,rs-rich-intuituive/pcm,rs-rich-intuituive/viz \
--bins --examples
cargo build -p rs-rich-ext --example guide_surface
rich=target/debug/rich
$rich record --bin-dir target/debug --output docs/media/tapes docs/tapes/*.tape
$rich record --check --bin-dir target/debug --output docs/media/tapes docs/tapes/*.tape
The pager tape needs less, the serve tape curl and port 8080, and the
embed tape w3m; CI installs them.
Record your own¶
rich record is not limited to rich: a tape can drive any program you can
start from a shell, in bash, zsh, fish or sh (Set Shell). Write a
tape, then:
rich record demo.tape # writes recordings/demo/
rich record --format gif,png demo.tape # only the GIF and the PNGs
rich record --fps 30 game.tape # video at 30 frames a second
rich record --format html demo.tape # the page: a player and the screenshots
rich record --format all demo.tape # every format, the animated PNG too
rich record --window-frame off --caption 'Save to redraw' demo.tape
rich record --check demo.tape # fail if a screenshot changed
| Option | Meaning |
|---|---|
--output DIR |
Write to DIR/<tape>/ (default recordings) |
--check |
Compare each screenshot's text with DIR/<tape>/<name>.txt instead of writing, and report screenshots the last write listed that the tape no longer takes; exits non-zero on any difference |
--format LIST |
Any of png, svg, cast, gif, apng, mp4, html, or all (every format, apng included). Text grids are always written. Without it, a tape writes its Output formats, or every format but apng when it has none. A tape's Output narrows --format further |
--window-frame on\|off, --caption TEXT, --key-overlay on\|off |
Override the tape's Set WindowFrame, Set Caption and Set KeyOverlay |
--no-video |
Skip the GIF, the APNG, the MP4 and the HTML page's player |
--fps N |
Video frames a second, 1 to 60, over the tape's Set Framerate |
--audio |
Record the program's sound, as the tape's Set Audio on does |
--bin-dir DIR |
Put DIR first on the session's PATH (default: the directory of the running rich) |
--font FILE |
Draw PNG and GIF text in another font |
Sound¶
With Set Audio on in a tape, or rich record --audio, the recorder
records the program's sound as well as its screen. It is off by default,
and a recording without it is the same as before.
The recorder makes a directory and names it in the program's environment
as RICH_RECORD_AUDIO. A program that plays sound writes what it plays
there as well. intuiTUIve's audio outputs (audio::output,
audio::output_with, and a Mixer playing into one) do this by
themselves. Under the recorder they play on the sound card as usual and
copy every block the card plays into the recording, timed by the card's
own clock: its latency is counted, and an underrun stays a gap of
silence, so the sound keeps in step with the frames. With no sound card
(CI, a headless machine) they take their samples on the real clock and
write them to the recording alone. Any other
program can write the same files: each is a 24-byte header (the magic
RRAUDIO1, the rate as a u32, the channels as a u16, two zero bytes,
and when its first sample played, in nanoseconds since the Unix epoch, as a
u64, all little-endian), then the samples, interleaved little-endian
f32, in a file whose name ends .pcm. The format is documented in the
rs-rich-record crate's audio module.
When the tape ends, the sound is put on the recording's clock: what played
during Hide is cut, as the frames are, and the streams are mixed into one
track.
- An MP4 carries it as an AAC track, placed with FFmpeg's
-itsoffset. FFmpeg's built-inaacencoder is enough. - A GIF, an APNG, the HTML page and the cast have no sound of their own, so
a WAV goes beside them (
demo.gifgivesdemo.wav). It starts with the video, so the two play in step. - A program that played nothing while the recording was shown leaves no WAV and no track.
$REPO in a tape is the directory rich record was started in, so a tape
can copy fixtures (Exec "cp $REPO/fixtures/data.json ."). Linux and macOS
are supported; on Windows it builds through ConPTY but needs the tape's shell
on PATH, and is experimental. Use the recorder from Rust through the
rs-rich-record crate: tape::parse, record::record, then
record::write or record::check.
Limits, and what a tape can do¶
A tape is code: record only tapes you trust. Exec runs its command with
sh, and everything Type sends runs in the shell, with your user's
permissions. The recorder pins the environment and works in a temporary
directory so recordings repeat, not to contain the tape.
What the recorder does limit, so a mistake fails clearly rather than filling the disk or memory:
| Limit | |
|---|---|
| Terminal size | Set Size and Resize from 2x2 to 500x200 |
| Durations | Sleep, Wait and Set Timeout at most 3600s |
Write |
A relative path inside the working directory: no /…, no .. |
Exec |
60 seconds; the first 64 KiB of its error output is reported |
| Tape names | A tape's name is its output directory: ...tape (named ..) is refused |
| Stale screenshots | Removed only when provenance.json lists them from an earlier run; other files in the output directory are never touched |
| Frames | At most 60 a second (Set Framerate); within one frame only the last update is kept, and output over 32 KiB is kept as a repaint of the screen. Frames are stored packed, about 12 bytes a cell, with unchanged rows shared |
| Video | The first 5 minutes; a GIF, MP4 or HTML page of a longer recording is an error (use --no-video) |
| Images | At most 100 million pixels; a GIF at most 65535 pixels a side |







































































































































