https://github.com/urmzd/teasr
Capture showcase screenshots and GIFs from web apps, desktop, and terminal. Single Rust binary, no runtime deps.
https://github.com/urmzd/teasr
automation capture chrome-devtools-protocol cli developer-tools ffmpeg gif markdown rust screenshot showcase terminal video
Last synced: 18 days ago
JSON representation
Capture showcase screenshots and GIFs from web apps, desktop, and terminal. Single Rust binary, no runtime deps.
- Host: GitHub
- URL: https://github.com/urmzd/teasr
- Owner: urmzd
- License: apache-2.0
- Created: 2026-03-15T01:30:10.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-26T22:58:26.000Z (25 days ago)
- Last Synced: 2026-04-26T23:11:13.933Z (25 days ago)
- Topics: automation, capture, chrome-devtools-protocol, cli, developer-tools, ffmpeg, gif, markdown, rust, screenshot, showcase, terminal, video
- Language: Rust
- Size: 11 MB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
teasr
Automated project showcase capture — screenshots and GIFs from web apps, desktop, and terminal. Single binary, no runtime dependencies.
Download
·
Report Bug
·
CI Integration
## Showcase
Web Capture
Terminal Capture
Local HTML
Markdown
## Contents
- [Why teasr](#why-teasr)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Capture Modes](#capture-modes)
- [Configuration Reference](#configuration-reference)
- [CLI Reference](#cli-reference)
- [Output Formats](#output-formats)
- [CI Integration](#ci-integration)
- [Workspace](#workspace)
- [Agent Skill](#agent-skill)
- [License](#license)
## Why teasr
| | teasr | Node/Playwright approach |
|---|---|---|
| Runtime | Single binary | Node.js + npm install |
| Terminal render | Built-in (ANSI → SVG → PNG) | External tools |
| GIF encoding | gifski (pure Rust) | FFmpeg or ImageMagick |
| Config | `teasr.toml` | JS/TS config file |
| Server cleanup | Process group kill | Manual or best-effort |
## Installation
**Shell installer (recommended):**
```bash
curl -fsSL https://raw.githubusercontent.com/urmzd/teasr/main/install.sh | sh
```
**Cargo:**
```bash
cargo install teasr-cli
```
**GitHub Action:** see [CI Integration](#ci-integration) below.
## Quick Start
Create `teasr.toml` in your project root:
```toml
[server]
command = "npm run dev"
url = "http://localhost:3000"
timeout = 10000
[output]
dir = "./showcase"
formats = [{ output_type = "png" }]
[[scenes]]
type = "web"
uri = "/"
name = "homepage"
formats = [{ output_type = "gif" }, { output_type = "png" }]
[[scenes.interactions]]
type = "snapshot"
[[scenes.interactions]]
type = "click"
selector = "#get-started"
[[scenes.interactions]]
type = "snapshot"
[[scenes]]
type = "terminal"
name = "cli-help"
theme = "dracula"
cols = 90
rows = 24
formats = [{ output_type = "gif" }, { output_type = "png" }]
[[scenes.interactions]]
type = "type"
text = "teasr --help"
speed = 50
[[scenes.interactions]]
type = "key"
key = "enter"
[[scenes.interactions]]
type = "wait"
duration = 2000
```
Then run:
```bash
teasr run
```
Output files are written to `./showcase/`.
## Capture Modes
All three capture modes — `terminal`, `web`, `screen` — use a unified `[[scenes.interactions]]` syntax. Every interaction type is accepted by every mode; unsupported interactions are silently skipped (visible with `--verbose`). The `web` scene loads remote URLs, local files (HTML/SVG/PDF), or Markdown files through the same headless-Chrome renderer.
### Interaction Types
| Type | Fields | Description |
|------|--------|-------------|
| `type` | `text`, `speed` (ms per char, optional) | Type text (terminal: PTY input, web: keyboard events) |
| `key` | `key` (e.g. `"enter"`) | Press a key |
| `click` | `selector` (CSS selector, optional) | Click an element |
| `hover` | `selector` (CSS selector, optional) | Hover over an element |
| `scroll-to` | `selector` (CSS selector, optional) | Scroll an element into view |
| `wait` | `duration` (ms, default 1000) | Pause. Terminal/screen emit one frame at the end of the pause (`duration_ms = duration`); web emits nothing — follow with `snapshot` to capture post-pause state. |
| `snapshot` | `name` (optional) | Capture the current state as a frame |
Every interaction also accepts a `hidden` flag (default `false`). Hidden interactions execute normally but their frames are excluded from output — useful for setup steps (e.g. typing a command) that should not appear in the final GIF or screenshot.
```toml
[[scenes.interactions]]
type = "type"
text = "cd my-project"
hidden = true
[[scenes.interactions]]
type = "key"
key = "enter"
hidden = true
[[scenes.interactions]]
type = "wait"
duration = 500
hidden = true
```
### Web
Loads a URI in headless Chrome (via chromiumoxide). The `uri` field picks what to load:
- `http://` / `https://` — remote URL (a leading `/` joins against `[server].url` if configured)
- `*.md` / `*.markdown` — Markdown file rendered to styled HTML
- anything else — local file (HTML, SVG, PDF) loaded via `file://`
Requires Chrome or Chromium to be installed.
```toml
# Remote URL (or server-relative path with [server])
[[scenes]]
type = "web"
uri = "/dashboard"
name = "dashboard"
viewport = { width = 1440, height = 900 }
formats = [{ output_type = "png" }, { output_type = "gif" }]
[[scenes.interactions]]
type = "click"
selector = "#open-modal"
[[scenes.interactions]]
type = "snapshot"
name = "modal-open"
# Local HTML / SVG
[[scenes]]
type = "web"
uri = "./docs/preview.html"
name = "docs-preview"
[[scenes.interactions]]
type = "snapshot"
# PDF (page selection via the `page` field)
[[scenes]]
type = "web"
uri = "./spec.pdf"
page = 2
[[scenes.interactions]]
type = "snapshot"
# Markdown (rendered with the bundled GitHub-style template)
[[scenes]]
type = "web"
uri = "./README.md"
theme = "dark" # "light" (default) or "dark"
flavor = "github" # "github" (default), "commonmark", or "custom"
full_page = true
[[scenes.interactions]]
type = "snapshot"
```
**Web scene fields:**
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `uri` | string | required | Remote URL, server-relative path, local file, or Markdown file |
| `name` | string | uri value | Output filename base |
| `viewport` | object | `1280x720` | `{ width, height }` |
| `formats` | array | `output.formats` | Per-scene format override |
| `interactions` | array | `[]` | Sequence of interactions |
| `full_page` | boolean | `false` | Capture full page height instead of just the viewport |
| `frame_duration` | integer | `100` | Milliseconds per frame in GIF output |
| `page` | integer | `1` | PDF page to capture (only applies when `uri` is a PDF) |
| `theme` | string | `"light"` | Markdown theme: `"light"` or `"dark"` (Markdown only) |
| `flavor` | string | `"github"` | Markdown flavor: `"github"`, `"commonmark"`, `"custom"` (Markdown only) |
| `stylesheet` | string | — | Path to a custom CSS file appended after the default styles (Markdown only) |
| `template` | string | — | Path to a full HTML template with `{{content}}` placeholder; overrides the default template (Markdown only) |
**Markdown flavor details:**
| Flavor | Behavior |
|--------|----------|
| `github` | GitHub Flavored Markdown — tables, task lists, autolinks, strikethrough, footnotes |
| `commonmark` | Strict CommonMark — no extensions |
| `custom` | GFM extensions enabled; pair with `stylesheet` to apply your own visual style |
**Supported interactions:** `click`, `hover`, `scroll-to`, `wait`, `snapshot`, `type`, `key`
### Terminal
Scripts an interactive PTY session, captures frames at each interaction, and renders them as animated GIFs or PNGs with terminal chrome (title bar, traffic light buttons).
```toml
[[scenes]]
type = "terminal"
name = "test-output"
theme = "dracula"
cols = 100
rows = 24
formats = [{ output_type = "gif" }, { output_type = "png" }]
frame_duration = 80
[[scenes.interactions]]
type = "type"
text = "cargo test 2>&1"
speed = 50
[[scenes.interactions]]
type = "key"
key = "enter"
[[scenes.interactions]]
type = "wait"
duration = 2000
```
**Terminal scene fields:**
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | string | `"recording"` | Output filename base |
| `theme` | string | `"dracula"` | `"dracula"` or `"monokai"` |
| `cols` | integer | `80` | Terminal width in columns |
| `rows` | integer | `24` | Terminal height in rows |
| `interactions` | array | `[]` | Sequence of interactions |
| `frame_duration` | integer | `100` | Milliseconds per frame in GIF output |
| `formats` | array | `output.formats` | Per-scene format override |
**Supported interactions:** `type`, `key`, `wait`, `snapshot`
### Screen
Captures a display, window, or region using native screen capture (xcap). Screenshots are automatically wrapped in macOS-style window chrome (matching terminal output). Supports multi-frame GIF output when multiple `snapshot` + `wait` interactions are configured.
```toml
[[scenes]]
type = "screen"
name = "native-app"
setup = "open MyApp.app"
delay = 2000
theme = "dracula"
title = "My App"
formats = [{ output_type = "gif" }, { output_type = "png" }]
[[scenes.interactions]]
type = "snapshot"
[[scenes.interactions]]
type = "wait"
duration = 1000
[[scenes.interactions]]
type = "snapshot"
```
**Screen scene fields:**
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | string | `"screen"` | Output filename base |
| `display` | integer | primary | Display index (ignored if `window` is set) |
| `window` | string | — | Window title or app name substring (case-insensitive) |
| `region` | object | full display | `{ x, y, width, height }` |
| `setup` | string | — | Shell command run before capture |
| `delay` | integer | — | Milliseconds to wait after setup |
| `interactions` | array | `[]` | Sequence of interactions |
| `frame_duration` | integer | `100` | Milliseconds per frame in GIF output |
| `title` | string | `"Screen Capture"` | Title shown in chrome frame title bar |
| `theme` | string | `"dracula"` | Chrome frame theme: `"dracula"` or `"monokai"` |
| `formats` | array | `output.formats` | Per-scene format override |
**Supported interactions:** `snapshot`, `wait`
## Configuration Reference
### `[server]`
Optional. Starts a process before capture and health-polls it until ready. The process group is killed on exit — no orphaned processes.
```toml
[server]
command = "npm run dev"
url = "http://localhost:3000"
timeout = 10000 # ms to wait for server to be ready (default: 10000)
```
### `[output]`
```toml
[output]
dir = "./showcase" # default: "./teasr-output"
formats = [{ output_type = "png" }] # default: [{ output_type = "png" }]. Options: "png", "gif", "mp4"
```
### Top-level keys
```toml
fps = 24 # default: 24. Frames per second (sets default frame_duration = 1000/fps).
seconds = 2.5 # default: 2.5. Target output duration in seconds.
scene_timeout = 60 # default: 60. Per-scene wall-clock timeout in seconds.
outro_hold_ms = 1500 # default: 1500. Minimum hold time for the final frame of every scene
# before the GIF loops, so viewers can read the result. Set to 0 to disable.
```
The default `type` interaction speed is `80 ms` per character with ±20 % jitter, which reads as fast human typing rather than a uniform stream. Override per interaction with `speed = ` for a fixed cadence.
### `[[scenes]]`
Each `[[scenes]]` entry is one of the three types described above. The `type` field is required and must be `"web"`, `"terminal"`, or `"screen"`.
Config file discovery walks up from the current directory to the filesystem root, so running `teasr` from any subdirectory of your project will find `teasr.toml` at the root.
## CLI Reference
```
teasr [COMMAND]
Commands:
run Run capture scenes from teasr.toml (alias: `showme`)
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
### `teasr run`
```
teasr run [OPTIONS]
Options:
-c, --config Path to teasr.toml (default: auto-discover)
-o, --output Output directory (overrides config)
--formats Output formats: png, gif, mp4 (overrides config)
--verbose Enable debug logging
--timeout Global timeout in ms [default: 60000]
--fps Frames per second (overrides config)
--seconds Target output duration in seconds (overrides config)
--scene-timeout Per-scene wall-clock timeout in seconds (overrides config)
-h, --help Print help
```
`--formats` accepts comma-separated values: `--formats png,gif,mp4`
## Output Formats
| Format | Notes |
|--------|-------|
| `png` | Lossless screenshot. Native, no external tools required. |
| `gif` | Animated GIF from multi-frame session recording, encoded with gifski (pure Rust). |
| `mp4` | Video output from multi-frame session recording. |
## CI Integration
The GitHub Action downloads the appropriate pre-built binary from releases, installs Chrome, and runs `teasr run`. All configuration comes from `teasr.toml`.
```yaml
- uses: urmzd/teasr@v1
with:
version: "latest" # optional, pin to e.g. "0.11.0"
scenes: "web,terminal" # optional, default: all
install-chrome: "true" # optional, set "false" if Chrome is already available
install-fonts: "" # optional, space-separated font families
config: "" # optional, path to teasr.toml
args: "" # optional, extra flags for `teasr run`
```
**Supported runners:** `ubuntu-*`, `macos-*`, `windows-*` on x64 and ARM64.
## Workspace
teasr is a Cargo workspace with two crates:
| Crate | Description |
|-------|-------------|
| [`teasr-cli`](crates/teasr-cli) | CLI entry point (`teasr` binary) |
| [`teasr-core`](crates/teasr-core) | Capture, config, orchestration, and ANSI → SVG → PNG terminal rendering |
## Agent Skill
This repo's conventions are available as portable agent skills in [`skills/`](skills/).
## License
Apache-2.0