An open API service indexing awesome lists of open source software.

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.

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


CI
crates.io
 
License

## Showcase


Web Capture
Terminal Capture


Web capture of this project's GitHub page
Terminal capture of CLI help


Local HTML
Markdown


Local HTML file rendered via headless Chrome
Markdown rendered as styled HTML via headless Chrome

## 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