Skip to main content

AI-native terminal recording tool with structured output

Project description

reterm

npm License: MIT

Record terminal sessions accurately, then embed the replay anywhere — GitHub READMEs, blogs, and product pages. One recording produces two outputs: a GIF for humans and a structured JSON log for AI tools and CLIs.

reterm demo

The SVG above animates inline on GitHub; click it for an interactive player (play/pause/seek). See Embed in your README.

The Python CLI is not on PyPI yet — install from source for now. Once published, the PyPI and CI badges (and the player link) go live.

Why reterm

You want to show a terminal workflow on a page and have it look right — and you want the same recording to be machine-readable so an AI or script can reason about exactly what ran.

  • Accurate recording. Each command's exact output (any length, with or without a trailing newline), real exit code, working directory, timing, and per-character colors are captured faithfully — via invisible OSC 133 shell-integration marks, the same mechanism iTerm2/WezTerm/VS Code use. Commands containing ! work too. (Shells without integration fall back to screen-based capture.)
  • Two outputs, one record. A polished GIF and a structured JSON log are produced from a single declarative script.
  • Built for sharing. Redact secrets and paths before posting, then drop an animated SVG/GIF in a README or play the JSON back interactively with the React player.

Which output goes where

Where you're posting Use Why
GitHub README Animated SVG or GIF (reterm run … -o demo.svg) GitHub strips JavaScript, so an animated image is the way to show a terminal inline. SVG is crisp, small, and selectable; GIF works everywhere.
GitHub README, interactive SVG poster → hosted player GitHub can't run a JS player inline, so link the inline poster to a hosted player for real play/pause/seek.
Blog / product page (React/MDX) reterm-player + the JSON log An interactive, seekable replay with real colors and playback controls.
Feeding an AI tool / CI JSON log (or the MCP server) Structured commands, exit codes, and output an agent can read directly.

Before posting anything public, run reterm redact to scrub secrets, tokens, and personal paths out of the recording.

Install

Python CLI (requires Python 3.11+, uv):

git clone https://github.com/dominic-righthere/reterm.git
cd reterm
uv sync
uv run reterm --help

# optional: install as a global `reterm` command
uv tool install .

PyPI publishing is planned — once live, uv tool install reterm and uvx reterm will work directly.

React player (published on npm):

npm install reterm-player

Quick Start

Create a .reterm script (or run reterm new hello.reterm for a template):

meta:
  name: "Hello World"

config:
  shell: /bin/zsh
  theme: dracula

steps:
  - run: echo "Hello from reterm!"
  - sleep: 500ms
  - run: ls -la
  - sleep: 1s

Run it:

# Generate GIF + JSON log (use `reterm …` directly if you ran `uv tool install .`)
uv run reterm run hello.reterm -o hello.gif -l hello.json

# JSON log only, no GIF (faster — ideal for AI/CLI use)
uv run reterm run hello.reterm --log-only -l hello.json

# Replay a recording in your terminal
uv run reterm play hello.json

CLI Commands

reterm run <script>          # Execute a script → GIF/SVG + JSON log
reterm new <file>            # Create a script from a template
reterm validate <script>     # Validate a script without executing it
reterm play <log>            # Replay a recording in the terminal
reterm render <log> -o <out> # Re-render a GIF or animated SVG from a log
reterm redact <log>          # Redact sensitive info from a log
reterm embed <poster>        # Print Markdown to embed a recording in a README
reterm serve                 # Start the MCP server for AI tools
reterm themes                # List available themes
reterm schema                # Print the JSON log schema

The visual format is chosen from the -o extension: .gif or .svg (an animated SVG you can embed inline in a GitHub README). In visual output, run commands animate their keystrokes (like the player); --log-only/MCP runs stay instant. run/render take --idle-limit N (default 2, 0 = off) to cap how long any static frame is held, so long pauses don't drag the loop.

reterm play supports --speed (e.g. --speed 2) and --idle-limit N to cap long pauses.

Redaction

Recordings are meant to be shared, so scrub anything sensitive first. Redaction operates on the JSON log (commands, output, working directories, captured variables, and terminal snapshots), and you can re-render a clean GIF afterward.

# Visible redaction — shows [HOME] in the output
reterm redact demo.json -p "/Users/me" -r "HOME" -o redacted.json

# Seamless — replaces without any visual indicator
reterm redact demo.json -p "/Users/me" -r "/Users/alice" --seamless -o clean.json

# Regex — e.g. API keys
reterm redact demo.json -p "sk-[a-zA-Z0-9]+" -r "API_KEY" --regex -o redacted.json

# Re-render a GIF from the redacted log
reterm render redacted.json -o redacted.gif

Embed in your README

A GitHub README can't run a live JS player — GitHub strips <script>/<iframe>. So the pattern is an animated SVG poster (which does animate inline, via <img>) that links to a hosted interactive player:

# 1. record an animated SVG poster
reterm run demo.reterm -o assets/demo.svg

# 2. print the Markdown (poster linked to the hosted player)
reterm embed assets/demo.svg --base https://you.github.io/reterm -r demo
# → [![terminal recording](assets/demo.svg)](https://you.github.io/reterm/play/?r=demo)

The hosted player is the bundled reterm-player deployed as a static page. This repo ships a GitHub Pages workflow (.github/workflows/pages.yml) that builds it and serves …/play/?r=<name> (reading recordings/<name>.json) and …/play/?src=<url>.

Turn it on once (the workflow is gated off by default so main stays green): set a repo variable ENABLE_PAGES=true (gh variable set ENABLE_PAGES --body true, or Settings → Secrets and variables → Actions → Variables). The next run auto-enables Pages and deploys. Pages needs a public repo or a paid plan; otherwise deploy the same site/ to Vercel/Netlify and point the poster link there. The inline SVG works regardless of hosting.

Just want the inline animation, no click-through? Use the SVG (or GIF) on its own: ![demo](assets/demo.svg).

React Player

Embed an interactive replay in a blog, docs site, or product page:

import { TerminalPlayer } from 'reterm-player';
import 'reterm-player/style.css';

// From inline data
<TerminalPlayer data={recording} autoPlay />

// From a URL
<TerminalPlayer src="/recordings/demo.json" showControls />

// Full options
<TerminalPlayer
  data={recording}
  autoPlay
  loop
  speed={1.5}
  theme="dracula"
  showControls
  showWindowFrame
  cursorStyle="block"
/>

Props

Prop Type Default Description
data RecordingLog Inline JSON recording data
src string URL to fetch a recording from
autoPlay boolean false Start playing automatically
loop boolean false Loop playback
speed number 1.0 Playback speed multiplier
theme string from log Theme override
showControls boolean true Show play/pause/seek/speed controls
showWindowFrame boolean true Show the macOS-style window frame
title string from log Window title (defaults to the script name or 'Terminal')
fit boolean false Fill the container width; scrolls horizontally if needed
cursorStyle 'block' | 'underline' | 'bar' 'block' Cursor appearance
showCursor boolean true Show the blinking cursor
fontSize number 14 Font size in pixels
fontFamily string monospace Terminal font family
typingSpeed number 50 Typing animation speed (ms per character)
onLoad (recording) => void Called when the recording loads
onComplete () => void Called when playback completes
onError (error) => void Called on load/playback error
className string Extra CSS class for the root element
style CSSProperties Inline styles for the root element

MCP Server

Expose reterm to AI tools over the Model Context Protocol.

Claude Code (from source — replace the path with your clone):

claude mcp add reterm -- uv run --directory /path/to/reterm reterm serve

# Available across all projects:
# claude mcp add --scope user reterm -- uv run --directory /path/to/reterm reterm serve

Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "reterm": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/reterm", "reterm", "serve"]
    }
  }
}

Once reterm is on PyPI, the command simplifies to uvx reterm serve. SSE transport is also available via reterm serve --transport sse.

Tools & resources

Tool Purpose
run_script Execute a .reterm script and return the structured log
run_command Run a single command and return its structured output
generate_script Build a .reterm script from a list of commands
validate_script Validate a script without executing it
format_as_markdown Render a log as shareable markdown (commands + output)
screenshot_terminal Return a PNG image of the terminal state
render_svg Return an animated SVG of the session (embeddable inline in a README)

Resources: reterm://schema (JSON log schema), reterm://themes, and reterm://example (an annotated example script).

Script Format

meta:
  name: "Script Name"
  description: "What this records"

config:
  shell: /bin/zsh        # Shell to use (default: $SHELL)
  theme: dracula         # Color theme
  size: [80, 24]         # Terminal size [cols, rows]
  typing_speed: 50ms     # Typing animation speed

env:
  GREETING: "Hello"      # Environment variables for the session

steps:
  - run: echo "$GREETING, world"   # Execute a command (captured in the log)
  - type: "ls -la"                 # Type with animation (visual only)
    then: enter                    #   …then press a key
  - sleep: 1s                      # Pause
  - key: ctrl+c                    # Send a special key
  - wait_for: "Ready"              # Wait for output before continuing
    timeout: 5s
    regex: false
  - screenshot: capture.png        # Capture a frame
  - note: "Not shown in terminal"  # Metadata-only note (log only)

  - run: ./deploy.sh               # Assertions + capture on a run step
    capture: deploy_id             #   save stdout to ${deploy_id}
    hidden: false                  #   hide from the GIF but keep in the log
    expect:
      exit_code: 0
      contains: "success"
      not_contains: "error"
      matches: "id=\\w+"           # regex

output:                            # Optional explicit output paths
  gif: demo.gif
  log: demo.json

Step types

Step Description
run Execute a command and capture it (command, exit code, output, timing)
type Type text with animation (visual only — not captured as a command)
sleep Pause for a duration
key Send a special key (ctrl+c, tab, enter, …)
wait_for Wait for an output pattern (with timeout, optional regex)
screenshot Capture a frame
note Metadata-only entry (appears in the log, not the terminal)

Modifiers on a run step: capture (save stdout to a ${var}), expect (assert exit_code / contains / not_contains / matches), and hidden (keep it in the log but out of the GIF). Reference captured variables and env vars with ${name}.

YAML tip: quote commands that look like YAML literals — use run: "false", run: "no", run: "yes" so they aren't parsed as booleans.

A couple of ready-to-run examples live in examples/.

JSON Log Output

Every recording produces a structured JSON log:

{
  "schema_version": "1.0.0",
  "metadata": {
    "terminal_size": [80, 24],
    "theme": "dracula",
    "total_duration_ms": 2500
  },
  "commands": [
    {
      "command": "echo hello",
      "exit_code": 0,
      "stdout": "hello",
      "duration_ms": 120,
      "terminal_before": { "screen_content": ["..."], "cursor_position": [0, 0] },
      "terminal_after": { "screen_content": ["..."], "cursor_position": [1, 0] }
    }
  ],
  "success": true,
  "all_commands_text": "echo hello",
  "all_output_text": "hello"
}

Beyond the basics, the log also carries success and failed_commands for quick status checks, captured_variables from capture: steps, and per-command styled_content (per-character colors used by the React player to reproduce the terminal faithfully). Run reterm schema for the full schema.

Development

uv sync --dev
uv run pytest            # Python tests
uv run ruff check .      # Lint
uv run mypy reterm/      # Type check

# React player
cd packages/player
npm install
npm run build

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reterm-0.1.0.tar.gz (349.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reterm-0.1.0-py3-none-any.whl (52.7 kB view details)

Uploaded Python 3

File details

Details for the file reterm-0.1.0.tar.gz.

File metadata

  • Download URL: reterm-0.1.0.tar.gz
  • Upload date:
  • Size: 349.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for reterm-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b1f13d65825be00562aca1845c5e105102bd211b04695e1f90cf155da288fec6
MD5 090c847e6b50b87e6b6ad1a850c5faf1
BLAKE2b-256 50a5507f99f7f277ec85eac7cd06cd88f7cc5013cb83a862cc2f578c117f9af2

See more details on using hashes here.

File details

Details for the file reterm-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: reterm-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 52.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for reterm-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7e2ede9707de71beca4096b7bc30f533a6651349a0713975a91a06d31f2e6072
MD5 891b732735d6985759abe14575b8720a
BLAKE2b-256 40f0432b38e27fcc862fd24f82b43f83dbbb80959cf0d932b567f366c705dac1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page