{"id":47288130,"url":"https://github.com/urmzd/teasr","last_synced_at":"2026-05-04T03:04:47.233Z","repository":{"id":344508423,"uuid":"1182059327","full_name":"urmzd/teasr","owner":"urmzd","description":"Capture showcase screenshots and GIFs from web apps, desktop, and terminal. Single Rust binary, no runtime deps.","archived":false,"fork":false,"pushed_at":"2026-04-26T22:58:26.000Z","size":11553,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T23:11:13.933Z","etag":null,"topics":["automation","capture","chrome-devtools-protocol","cli","developer-tools","ffmpeg","gif","markdown","rust","screenshot","showcase","terminal","video"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/urmzd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-15T01:30:10.000Z","updated_at":"2026-04-26T22:58:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/urmzd/teasr","commit_stats":null,"previous_names":["urmzd/tease","urmzd/teasr"],"tags_count":58,"template":false,"template_full_name":null,"purl":"pkg:github/urmzd/teasr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fteasr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fteasr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fteasr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fteasr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/urmzd","download_url":"https://codeload.github.com/urmzd/teasr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fteasr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32592720,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"online","status_checked_at":"2026-05-04T02:00:06.625Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","capture","chrome-devtools-protocol","cli","developer-tools","ffmpeg","gif","markdown","rust","screenshot","showcase","terminal","video"],"created_at":"2026-03-16T06:28:41.243Z","updated_at":"2026-05-04T03:04:47.227Z","avatar_url":"https://github.com/urmzd.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003eteasr\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\n    Automated project showcase capture — screenshots and GIFs from web apps, desktop, and terminal. Single binary, no runtime dependencies.\n    \u003cbr /\u003e\u003cbr /\u003e\n    \u003ca href=\"https://github.com/urmzd/teasr/releases\"\u003eDownload\u003c/a\u003e\n    \u0026middot;\n    \u003ca href=\"https://github.com/urmzd/teasr/issues\"\u003eReport Bug\u003c/a\u003e\n    \u0026middot;\n    \u003ca href=\"https://github.com/urmzd/teasr/blob/main/action.yml\"\u003eCI Integration\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/urmzd/teasr/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/urmzd/teasr/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://crates.io/crates/teasr\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/teasr\" alt=\"crates.io\"\u003e\u003c/a\u003e\n  \u0026nbsp;\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/urmzd/teasr\" alt=\"License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n## Showcase\n\n\u003ctable align=\"center\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cstrong\u003eWeb Capture\u003c/strong\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cstrong\u003eTerminal Capture\u003c/strong\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"showcase/github.gif\" alt=\"Web capture of this project's GitHub page\" width=\"400\"\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"showcase/cli-help.gif\" alt=\"Terminal capture of CLI help\" width=\"400\"\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cstrong\u003eLocal HTML\u003c/strong\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cstrong\u003eMarkdown\u003c/strong\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"showcase/file-demo.png\" alt=\"Local HTML file rendered via headless Chrome\" width=\"400\"\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"showcase/markdown-demo.png\" alt=\"Markdown rendered as styled HTML via headless Chrome\" width=\"400\"\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## Contents\n\n- [Why teasr](#why-teasr)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Capture Modes](#capture-modes)\n- [Configuration Reference](#configuration-reference)\n- [CLI Reference](#cli-reference)\n- [Output Formats](#output-formats)\n- [CI Integration](#ci-integration)\n- [Workspace](#workspace)\n- [Agent Skill](#agent-skill)\n- [License](#license)\n\n## Why teasr\n\n| | teasr | Node/Playwright approach |\n|---|---|---|\n| Runtime | Single binary | Node.js + npm install |\n| Terminal render | Built-in (ANSI → SVG → PNG) | External tools |\n| GIF encoding | gifski (pure Rust) | FFmpeg or ImageMagick |\n| Config | `teasr.toml` | JS/TS config file |\n| Server cleanup | Process group kill | Manual or best-effort |\n\n## Installation\n\n**Shell installer (recommended):**\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/urmzd/teasr/main/install.sh | sh\n```\n\n**Cargo:**\n\n```bash\ncargo install teasr-cli\n```\n\n**GitHub Action:** see [CI Integration](#ci-integration) below.\n\n## Quick Start\n\nCreate `teasr.toml` in your project root:\n\n```toml\n[server]\ncommand = \"npm run dev\"\nurl = \"http://localhost:3000\"\ntimeout = 10000\n\n[output]\ndir = \"./showcase\"\nformats = [{ output_type = \"png\" }]\n\n[[scenes]]\ntype = \"web\"\nuri = \"/\"\nname = \"homepage\"\nformats = [{ output_type = \"gif\" }, { output_type = \"png\" }]\n\n[[scenes.interactions]]\ntype = \"snapshot\"\n\n[[scenes.interactions]]\ntype = \"click\"\nselector = \"#get-started\"\n\n[[scenes.interactions]]\ntype = \"snapshot\"\n\n[[scenes]]\ntype = \"terminal\"\nname = \"cli-help\"\ntheme = \"dracula\"\ncols = 90\nrows = 24\nformats = [{ output_type = \"gif\" }, { output_type = \"png\" }]\n\n[[scenes.interactions]]\ntype = \"type\"\ntext = \"teasr --help\"\nspeed = 50\n\n[[scenes.interactions]]\ntype = \"key\"\nkey = \"enter\"\n\n[[scenes.interactions]]\ntype = \"wait\"\nduration = 2000\n```\n\nThen run:\n\n```bash\nteasr run\n```\n\nOutput files are written to `./showcase/`.\n\n## Capture Modes\n\nAll 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.\n\n### Interaction Types\n\n| Type | Fields | Description |\n|------|--------|-------------|\n| `type` | `text`, `speed` (ms per char, optional) | Type text (terminal: PTY input, web: keyboard events) |\n| `key` | `key` (e.g. `\"enter\"`) | Press a key |\n| `click` | `selector` (CSS selector, optional) | Click an element |\n| `hover` | `selector` (CSS selector, optional) | Hover over an element |\n| `scroll-to` | `selector` (CSS selector, optional) | Scroll an element into view |\n| `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. |\n| `snapshot` | `name` (optional) | Capture the current state as a frame |\n\nEvery 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.\n\n```toml\n[[scenes.interactions]]\ntype = \"type\"\ntext = \"cd my-project\"\nhidden = true\n\n[[scenes.interactions]]\ntype = \"key\"\nkey = \"enter\"\nhidden = true\n\n[[scenes.interactions]]\ntype = \"wait\"\nduration = 500\nhidden = true\n```\n\n### Web\n\nLoads a URI in headless Chrome (via chromiumoxide). The `uri` field picks what to load:\n\n- `http://` / `https://` — remote URL (a leading `/` joins against `[server].url` if configured)\n- `*.md` / `*.markdown` — Markdown file rendered to styled HTML\n- anything else — local file (HTML, SVG, PDF) loaded via `file://`\n\nRequires Chrome or Chromium to be installed.\n\n```toml\n# Remote URL (or server-relative path with [server])\n[[scenes]]\ntype = \"web\"\nuri = \"/dashboard\"\nname = \"dashboard\"\nviewport = { width = 1440, height = 900 }\nformats = [{ output_type = \"png\" }, { output_type = \"gif\" }]\n\n[[scenes.interactions]]\ntype = \"click\"\nselector = \"#open-modal\"\n\n[[scenes.interactions]]\ntype = \"snapshot\"\nname = \"modal-open\"\n\n# Local HTML / SVG\n[[scenes]]\ntype = \"web\"\nuri = \"./docs/preview.html\"\nname = \"docs-preview\"\n\n[[scenes.interactions]]\ntype = \"snapshot\"\n\n# PDF (page selection via the `page` field)\n[[scenes]]\ntype = \"web\"\nuri = \"./spec.pdf\"\npage = 2\n\n[[scenes.interactions]]\ntype = \"snapshot\"\n\n# Markdown (rendered with the bundled GitHub-style template)\n[[scenes]]\ntype = \"web\"\nuri = \"./README.md\"\ntheme = \"dark\"           # \"light\" (default) or \"dark\"\nflavor = \"github\"        # \"github\" (default), \"commonmark\", or \"custom\"\nfull_page = true\n\n[[scenes.interactions]]\ntype = \"snapshot\"\n```\n\n**Web scene fields:**\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `uri` | string | required | Remote URL, server-relative path, local file, or Markdown file |\n| `name` | string | uri value | Output filename base |\n| `viewport` | object | `1280x720` | `{ width, height }` |\n| `formats` | array | `output.formats` | Per-scene format override |\n| `interactions` | array | `[]` | Sequence of interactions |\n| `full_page` | boolean | `false` | Capture full page height instead of just the viewport |\n| `frame_duration` | integer | `100` | Milliseconds per frame in GIF output |\n| `page` | integer | `1` | PDF page to capture (only applies when `uri` is a PDF) |\n| `theme` | string | `\"light\"` | Markdown theme: `\"light\"` or `\"dark\"` (Markdown only) |\n| `flavor` | string | `\"github\"` | Markdown flavor: `\"github\"`, `\"commonmark\"`, `\"custom\"` (Markdown only) |\n| `stylesheet` | string | — | Path to a custom CSS file appended after the default styles (Markdown only) |\n| `template` | string | — | Path to a full HTML template with `{{content}}` placeholder; overrides the default template (Markdown only) |\n\n**Markdown flavor details:**\n\n| Flavor | Behavior |\n|--------|----------|\n| `github` | GitHub Flavored Markdown — tables, task lists, autolinks, strikethrough, footnotes |\n| `commonmark` | Strict CommonMark — no extensions |\n| `custom` | GFM extensions enabled; pair with `stylesheet` to apply your own visual style |\n\n**Supported interactions:** `click`, `hover`, `scroll-to`, `wait`, `snapshot`, `type`, `key`\n\n### Terminal\n\nScripts 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).\n\n```toml\n[[scenes]]\ntype = \"terminal\"\nname = \"test-output\"\ntheme = \"dracula\"\ncols = 100\nrows = 24\nformats = [{ output_type = \"gif\" }, { output_type = \"png\" }]\nframe_duration = 80\n\n[[scenes.interactions]]\ntype = \"type\"\ntext = \"cargo test 2\u003e\u00261\"\nspeed = 50\n\n[[scenes.interactions]]\ntype = \"key\"\nkey = \"enter\"\n\n[[scenes.interactions]]\ntype = \"wait\"\nduration = 2000\n```\n\n**Terminal scene fields:**\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `name` | string | `\"recording\"` | Output filename base |\n| `theme` | string | `\"dracula\"` | `\"dracula\"` or `\"monokai\"` |\n| `cols` | integer | `80` | Terminal width in columns |\n| `rows` | integer | `24` | Terminal height in rows |\n| `interactions` | array | `[]` | Sequence of interactions |\n| `frame_duration` | integer | `100` | Milliseconds per frame in GIF output |\n| `formats` | array | `output.formats` | Per-scene format override |\n\n**Supported interactions:** `type`, `key`, `wait`, `snapshot`\n\n### Screen\n\nCaptures 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.\n\n```toml\n[[scenes]]\ntype = \"screen\"\nname = \"native-app\"\nsetup = \"open MyApp.app\"\ndelay = 2000\ntheme = \"dracula\"\ntitle = \"My App\"\nformats = [{ output_type = \"gif\" }, { output_type = \"png\" }]\n\n[[scenes.interactions]]\ntype = \"snapshot\"\n\n[[scenes.interactions]]\ntype = \"wait\"\nduration = 1000\n\n[[scenes.interactions]]\ntype = \"snapshot\"\n```\n\n**Screen scene fields:**\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `name` | string | `\"screen\"` | Output filename base |\n| `display` | integer | primary | Display index (ignored if `window` is set) |\n| `window` | string | — | Window title or app name substring (case-insensitive) |\n| `region` | object | full display | `{ x, y, width, height }` |\n| `setup` | string | — | Shell command run before capture |\n| `delay` | integer | — | Milliseconds to wait after setup |\n| `interactions` | array | `[]` | Sequence of interactions |\n| `frame_duration` | integer | `100` | Milliseconds per frame in GIF output |\n| `title` | string | `\"Screen Capture\"` | Title shown in chrome frame title bar |\n| `theme` | string | `\"dracula\"` | Chrome frame theme: `\"dracula\"` or `\"monokai\"` |\n| `formats` | array | `output.formats` | Per-scene format override |\n\n**Supported interactions:** `snapshot`, `wait`\n\n## Configuration Reference\n\n### `[server]`\n\nOptional. Starts a process before capture and health-polls it until ready. The process group is killed on exit — no orphaned processes.\n\n```toml\n[server]\ncommand = \"npm run dev\"\nurl = \"http://localhost:3000\"\ntimeout = 10000          # ms to wait for server to be ready (default: 10000)\n```\n\n### `[output]`\n\n```toml\n[output]\ndir = \"./showcase\"       # default: \"./teasr-output\"\nformats = [{ output_type = \"png\" }]  # default: [{ output_type = \"png\" }]. Options: \"png\", \"gif\", \"mp4\"\n```\n\n### Top-level keys\n\n```toml\nfps = 24                 # default: 24. Frames per second (sets default frame_duration = 1000/fps).\nseconds = 2.5            # default: 2.5. Target output duration in seconds.\nscene_timeout = 60       # default: 60. Per-scene wall-clock timeout in seconds.\noutro_hold_ms = 1500     # default: 1500. Minimum hold time for the final frame of every scene\n                         # before the GIF loops, so viewers can read the result. Set to 0 to disable.\n```\n\nThe 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 = \u003cms\u003e` for a fixed cadence.\n\n### `[[scenes]]`\n\nEach `[[scenes]]` entry is one of the three types described above. The `type` field is required and must be `\"web\"`, `\"terminal\"`, or `\"screen\"`.\n\nConfig 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.\n\n## CLI Reference\n\n```\nteasr [COMMAND]\n\nCommands:\n  run     Run capture scenes from teasr.toml (alias: `showme`)\n  help    Print this message or the help of the given subcommand(s)\n\nOptions:\n  -h, --help     Print help\n  -V, --version  Print version\n```\n\n### `teasr run`\n\n```\nteasr run [OPTIONS]\n\nOptions:\n  -c, --config \u003cPATH\u003e      Path to teasr.toml (default: auto-discover)\n  -o, --output \u003cDIR\u003e       Output directory (overrides config)\n      --formats \u003cFMT,...\u003e  Output formats: png, gif, mp4 (overrides config)\n      --verbose            Enable debug logging\n      --timeout \u003cMS\u003e       Global timeout in ms [default: 60000]\n      --fps \u003cN\u003e            Frames per second (overrides config)\n      --seconds \u003cN\u003e        Target output duration in seconds (overrides config)\n      --scene-timeout \u003cN\u003e  Per-scene wall-clock timeout in seconds (overrides config)\n  -h, --help               Print help\n```\n\n`--formats` accepts comma-separated values: `--formats png,gif,mp4`\n\n## Output Formats\n\n| Format | Notes |\n|--------|-------|\n| `png` | Lossless screenshot. Native, no external tools required. |\n| `gif` | Animated GIF from multi-frame session recording, encoded with gifski (pure Rust). |\n| `mp4` | Video output from multi-frame session recording. |\n\n## CI Integration\n\nThe GitHub Action downloads the appropriate pre-built binary from releases, installs Chrome, and runs `teasr run`. All configuration comes from `teasr.toml`.\n\n```yaml\n- uses: urmzd/teasr@v1\n  with:\n    version: \"latest\"        # optional, pin to e.g. \"0.11.0\"\n    scenes: \"web,terminal\"   # optional, default: all\n    install-chrome: \"true\"   # optional, set \"false\" if Chrome is already available\n    install-fonts: \"\"        # optional, space-separated font families\n    config: \"\"               # optional, path to teasr.toml\n    args: \"\"                 # optional, extra flags for `teasr run`\n```\n\n**Supported runners:** `ubuntu-*`, `macos-*`, `windows-*` on x64 and ARM64.\n\n## Workspace\n\nteasr is a Cargo workspace with two crates:\n\n| Crate | Description |\n|-------|-------------|\n| [`teasr-cli`](crates/teasr-cli) | CLI entry point (`teasr` binary) |\n| [`teasr-core`](crates/teasr-core) | Capture, config, orchestration, and ANSI → SVG → PNG terminal rendering |\n\n## Agent Skill\n\nThis repo's conventions are available as portable agent skills in [`skills/`](skills/).\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furmzd%2Fteasr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Furmzd%2Fteasr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furmzd%2Fteasr/lists"}