reterm 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. reterm-0.1.0/.github/workflows/ci.yml +39 -0
  2. reterm-0.1.0/.github/workflows/pages.yml +67 -0
  3. reterm-0.1.0/.github/workflows/release.yml +44 -0
  4. reterm-0.1.0/.gitignore +21 -0
  5. reterm-0.1.0/.python-version +1 -0
  6. reterm-0.1.0/CHANGELOG.md +72 -0
  7. reterm-0.1.0/LICENSE +21 -0
  8. reterm-0.1.0/PKG-INFO +416 -0
  9. reterm-0.1.0/README.md +384 -0
  10. reterm-0.1.0/assets/demo.gif +0 -0
  11. reterm-0.1.0/assets/demo.svg +1 -0
  12. reterm-0.1.0/examples/demo.reterm +32 -0
  13. reterm-0.1.0/examples/demoenv/.zshrc +6 -0
  14. reterm-0.1.0/examples/hero.reterm +24 -0
  15. reterm-0.1.0/examples/interactive.reterm +23 -0
  16. reterm-0.1.0/examples/player.reterm +14 -0
  17. reterm-0.1.0/packages/player/README.md +63 -0
  18. reterm-0.1.0/packages/player/demo/index.html +128 -0
  19. reterm-0.1.0/packages/player/demo/recording.json +282 -0
  20. reterm-0.1.0/packages/player/index.html +26 -0
  21. reterm-0.1.0/packages/player/mdx-test/index.html +60 -0
  22. reterm-0.1.0/packages/player/mdx-test/package.json +23 -0
  23. reterm-0.1.0/packages/player/mdx-test/public/recording.json +282 -0
  24. reterm-0.1.0/packages/player/mdx-test/src/demo.mdx +87 -0
  25. reterm-0.1.0/packages/player/mdx-test/src/main.tsx +12 -0
  26. reterm-0.1.0/packages/player/mdx-test/tsconfig.json +17 -0
  27. reterm-0.1.0/packages/player/mdx-test/vite.config.ts +10 -0
  28. reterm-0.1.0/packages/player/package.json +60 -0
  29. reterm-0.1.0/packages/player/play/index.html +76 -0
  30. reterm-0.1.0/packages/player/recordings/demo.json +15807 -0
  31. reterm-0.1.0/packages/player/scripts/dump-timeline.ts +52 -0
  32. reterm-0.1.0/packages/player/src/TerminalPlayer.tsx +343 -0
  33. reterm-0.1.0/packages/player/src/components/Controls.tsx +134 -0
  34. reterm-0.1.0/packages/player/src/components/ProgressBar.tsx +66 -0
  35. reterm-0.1.0/packages/player/src/components/Terminal.tsx +400 -0
  36. reterm-0.1.0/packages/player/src/components/WindowFrame.tsx +99 -0
  37. reterm-0.1.0/packages/player/src/demo.tsx +51 -0
  38. reterm-0.1.0/packages/player/src/hooks/usePlayback.ts +218 -0
  39. reterm-0.1.0/packages/player/src/hooks/useRecording.ts +66 -0
  40. reterm-0.1.0/packages/player/src/index.ts +40 -0
  41. reterm-0.1.0/packages/player/src/styles/terminal.css +56 -0
  42. reterm-0.1.0/packages/player/src/themes/index.ts +242 -0
  43. reterm-0.1.0/packages/player/src/types/recording.ts +73 -0
  44. reterm-0.1.0/packages/player/src/utils/timeline.ts +181 -0
  45. reterm-0.1.0/packages/player/tsconfig.json +25 -0
  46. reterm-0.1.0/packages/player/vite.config.ts +33 -0
  47. reterm-0.1.0/pyproject.toml +62 -0
  48. reterm-0.1.0/reterm/__init__.py +3 -0
  49. reterm-0.1.0/reterm/__main__.py +6 -0
  50. reterm-0.1.0/reterm/cli.py +338 -0
  51. reterm-0.1.0/reterm/core/__init__.py +1 -0
  52. reterm-0.1.0/reterm/core/engine.py +876 -0
  53. reterm-0.1.0/reterm/core/events.py +172 -0
  54. reterm-0.1.0/reterm/core/pty_manager.py +216 -0
  55. reterm-0.1.0/reterm/core/terminal.py +333 -0
  56. reterm-0.1.0/reterm/mcp/__init__.py +1 -0
  57. reterm-0.1.0/reterm/mcp/server.py +459 -0
  58. reterm-0.1.0/reterm/output/__init__.py +1 -0
  59. reterm-0.1.0/reterm/output/models.py +220 -0
  60. reterm-0.1.0/reterm/play.py +184 -0
  61. reterm-0.1.0/reterm/redact.py +114 -0
  62. reterm-0.1.0/reterm/render/__init__.py +1 -0
  63. reterm-0.1.0/reterm/render/frame.py +232 -0
  64. reterm-0.1.0/reterm/render/from_log.py +83 -0
  65. reterm-0.1.0/reterm/render/gif.py +166 -0
  66. reterm-0.1.0/reterm/render/svg.py +322 -0
  67. reterm-0.1.0/reterm/render/themes.py +276 -0
  68. reterm-0.1.0/reterm/script/__init__.py +1 -0
  69. reterm-0.1.0/reterm/script/parser.py +180 -0
  70. reterm-0.1.0/tests/__init__.py +0 -0
  71. reterm-0.1.0/tests/test_engine.py +181 -0
  72. reterm-0.1.0/tests/test_models.py +136 -0
  73. reterm-0.1.0/tests/test_svg.py +108 -0
  74. reterm-0.1.0/tests/test_terminal.py +127 -0
  75. reterm-0.1.0/uv.lock +2084 -0
@@ -0,0 +1,39 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ python:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v4
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - run: uv sync --dev
21
+ - run: uv run pytest tests/ -v
22
+ - run: uv run ruff check .
23
+ - run: uv run mypy reterm/
24
+
25
+ typescript:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: actions/setup-node@v4
30
+ with:
31
+ node-version: 20
32
+ cache: npm
33
+ cache-dependency-path: packages/player/package-lock.json
34
+ - run: npm ci
35
+ working-directory: packages/player
36
+ - run: npm run typecheck
37
+ working-directory: packages/player
38
+ - run: npm run build
39
+ working-directory: packages/player
@@ -0,0 +1,67 @@
1
+ name: Pages
2
+
3
+ # Builds the reterm-player into a small static site and deploys it to GitHub
4
+ # Pages, so the README's SVG poster can link to a live interactive player at
5
+ # https://<owner>.github.io/reterm/play/?r=demo
6
+ #
7
+ # Prerequisite: enable Pages once at Settings -> Pages -> Source: "GitHub Actions".
8
+ # Pages on a private repo needs a paid plan or a public repo; otherwise deploy the
9
+ # same `site/` to any static host (Vercel/Netlify) and point the poster link there.
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+ workflow_dispatch:
15
+
16
+ permissions:
17
+ contents: read
18
+ pages: write
19
+ id-token: write
20
+
21
+ concurrency:
22
+ group: pages
23
+ cancel-in-progress: false
24
+
25
+ jobs:
26
+ build:
27
+ # Only deploy when the ENABLE_PAGES repo variable is "true". Set it once the
28
+ # repo is public / Pages is enabled (Settings → Secrets and variables →
29
+ # Actions → Variables, or `gh variable set ENABLE_PAGES --body true`). Until
30
+ # then this job is skipped, so pushes to main stay green instead of failing
31
+ # on "Pages not enabled" (which can't be enabled on a private/free repo).
32
+ if: vars.ENABLE_PAGES == 'true'
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: actions/setup-node@v4
37
+ with:
38
+ node-version: 20
39
+ cache: npm
40
+ cache-dependency-path: packages/player/package-lock.json
41
+ - run: npm ci
42
+ working-directory: packages/player
43
+ - run: npm run build
44
+ working-directory: packages/player
45
+ - name: Assemble site
46
+ run: |
47
+ mkdir -p site/play site/dist site/recordings
48
+ cp packages/player/play/index.html site/play/index.html
49
+ cp -r packages/player/dist/* site/dist/
50
+ cp packages/player/recordings/*.json site/recordings/
51
+ printf '<!doctype html><meta http-equiv="refresh" content="0; url=play/">' > site/index.html
52
+ - uses: actions/configure-pages@v5
53
+ with:
54
+ enablement: true # auto-enable Pages (Actions source) on first run
55
+ - uses: actions/upload-pages-artifact@v3
56
+ with:
57
+ path: site
58
+
59
+ deploy:
60
+ needs: build
61
+ runs-on: ubuntu-latest
62
+ environment:
63
+ name: github-pages
64
+ url: ${{ steps.deployment.outputs.page_url }}
65
+ steps:
66
+ - id: deployment
67
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,44 @@
1
+ name: Release
2
+
3
+ # Publishes reterm to PyPI via Trusted Publishing (OIDC) — no API token stored.
4
+ #
5
+ # Triggered only by a published GitHub Release (no workflow_dispatch), so the
6
+ # privileged id-token:write job can't be dispatched from an arbitrary ref.
7
+ # Third-party actions are pinned to commit SHAs so a moved tag can't inject code
8
+ # into a job that can mint a PyPI token.
9
+ #
10
+ # One-time setup (you):
11
+ # 1. Create a PyPI account + enable 2FA.
12
+ # 2. On PyPI add a "pending publisher" for project `reterm`:
13
+ # Owner: dominic-righthere
14
+ # Repository: reterm
15
+ # Workflow: release.yml
16
+ # Environment: pypi
17
+ # 3. (Recommended) In repo Settings → Environments → `pypi`, add a protection
18
+ # rule: a required reviewer and/or restrict deployment to tags `v*`.
19
+ #
20
+ # To release: bump the version in pyproject.toml, then create a GitHub Release
21
+ # (tag e.g. v0.1.0). This workflow builds and publishes automatically.
22
+
23
+ on:
24
+ release:
25
+ types: [published]
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ jobs:
31
+ publish:
32
+ runs-on: ubuntu-latest
33
+ environment:
34
+ name: pypi
35
+ url: https://pypi.org/p/reterm
36
+ permissions:
37
+ id-token: write # required for Trusted Publishing (OIDC)
38
+ steps:
39
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
40
+ - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
41
+ with:
42
+ python-version: "3.12"
43
+ - run: uv build
44
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,21 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ .venv
9
+
10
+ # Node
11
+ node_modules/
12
+ packages/*/dist/
13
+ package-lock.json
14
+
15
+ # OS
16
+ .DS_Store
17
+
18
+ # IDE
19
+ .idea/
20
+ .vscode/
21
+ *.swp
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,72 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - **Animated SVG output.** `reterm run`/`render` now pick the visual format from
13
+ the `-o` extension (`.gif` or `.svg`). The SVG is a self-contained CSS flipbook
14
+ that animates inline on GitHub via `<img>` — crisp, small, and selectable —
15
+ with the final frame as a static fallback for non-animating viewers.
16
+ - **Hosted interactive player + `reterm embed`.** A GitHub Pages workflow
17
+ (`.github/workflows/pages.yml`) deploys `reterm-player` as a static page at
18
+ `…/play/?r=<name>` / `?src=<url>`; `reterm embed` prints the Markdown for an SVG
19
+ poster linked to it (GitHub can't run a JS player inline, so the poster links out).
20
+ - `reterm play` command to replay a recording in the terminal, with `--speed`
21
+ and `--idle-limit` controls
22
+ - MCP tools `format_as_markdown` (render a log as shareable markdown),
23
+ `screenshot_terminal` (return a PNG of the terminal state), and `render_svg`
24
+ (return an animated SVG embeddable inline in a README)
25
+
26
+ ### Changed
27
+
28
+ - **Viewer-friendly timing.** `reterm run`/`render` now cap how long any single
29
+ static frame is held in the GIF/SVG (`--idle-limit`, default 2s; `0` = real
30
+ timing), so long `sleep:` steps and idle stretches don't bake dead air into
31
+ the loop. And in visual output, `run` commands now animate their keystrokes —
32
+ matching the React player — while `--log-only`/MCP runs stay instant.
33
+ - **Per-command capture now uses OSC 133 shell-integration marks.** reterm
34
+ injects invisible `preexec`/`precmd` hooks into the recording shell (zsh, and
35
+ bash ≥ 4.4) that delimit each command's output and report its exit code — the
36
+ same mechanism iTerm2/WezTerm/kitty/VS Code use. Output is sliced exactly from
37
+ the raw byte stream between marks, so it captures **full output of any length**
38
+ (even when it scrolls past the screen) and output with **no trailing newline**,
39
+ and reads the **exact exit code** from the `D;<code>` mark. The marks are
40
+ invisible, so the GIF is unaffected. Shells without integration fall back to
41
+ the previous screen-based capture.
42
+
43
+ ### Fixed
44
+
45
+ - **Accurate exit codes.** The shell-state probe expanded `$__rc___` / `$PWD___`
46
+ as the (undefined) variables `__rc___` / `PWD___`, so every command recorded
47
+ `exit_code: -1` and `success` was always wrong. Now uses `${__rc}` / `${PWD}`.
48
+ - **Commands containing `!` no longer break recordings.** Interactive history
49
+ expansion (e.g. `echo "Done!"`) wedged the shell and corrupted every
50
+ following command. History expansion is now disabled in the recording shell.
51
+ - **Accurate output past one screen.** Output extraction diffed terminal
52
+ snapshots by absolute line index, which misattributed output once a recording
53
+ scrolled. It now locates the command on the post-command screen, making
54
+ capture scroll-tolerant.
55
+
56
+ ## [0.1.0] - 2025-12-01
57
+
58
+ ### Added
59
+
60
+ - YAML-based `.reterm` scripts for declarative terminal recording
61
+ - Dual output: GIF for humans + structured JSON log for AI tools
62
+ - CLI commands: `run`, `new`, `validate`, `redact`, `render`, `serve`, `themes`, `schema`
63
+ - Redaction support: visible, seamless, and regex-based
64
+ - MCP server for AI tool integration (stdio and SSE transports)
65
+ - React player component (`reterm-player` npm package)
66
+ - Playback controls: play/pause, seek, speed adjustment, looping
67
+ - macOS-style window frame with customizable title
68
+ - `fit` prop to scale terminal to container width
69
+ - Typing animation with configurable speed
70
+ - 6 built-in themes: Dracula, Nord, Monokai, Solarized Dark, GitHub Dark, One Dark
71
+ - Interactive `wait_for` step for prompt-driven flows
72
+ - Screenshot capture step
reterm-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dominic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
reterm-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,416 @@
1
+ Metadata-Version: 2.4
2
+ Name: reterm
3
+ Version: 0.1.0
4
+ Summary: AI-native terminal recording tool with structured output
5
+ Project-URL: Homepage, https://github.com/dominic-righthere/reterm
6
+ Project-URL: Repository, https://github.com/dominic-righthere/reterm
7
+ Project-URL: Issues, https://github.com/dominic-righthere/reterm/issues
8
+ Author-email: Dominic <dominic.righthere@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,cli,gif,mcp,recording,terminal
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Multimedia :: Video :: Capture
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: click>=8.1
23
+ Requires-Dist: fastmcp>=2.0
24
+ Requires-Dist: imageio>=2.31
25
+ Requires-Dist: pillow>=10.0
26
+ Requires-Dist: ptyprocess>=0.7
27
+ Requires-Dist: pydantic>=2.0
28
+ Requires-Dist: pyte>=0.8
29
+ Requires-Dist: pyyaml>=6.0
30
+ Requires-Dist: rich>=13.0
31
+ Description-Content-Type: text/markdown
32
+
33
+ # reterm
34
+
35
+ [![npm](https://img.shields.io/npm/v/reterm-player)](https://www.npmjs.com/package/reterm-player)
36
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
37
+
38
+ **Record terminal sessions accurately, then embed the replay anywhere** — GitHub
39
+ READMEs, blogs, and product pages. One recording produces two outputs: a **GIF**
40
+ for humans and a **structured JSON log** for AI tools and CLIs.
41
+
42
+ [![reterm demo](assets/demo.svg)](https://dominic-righthere.github.io/reterm/play/?r=demo)
43
+
44
+ > The SVG above animates inline on GitHub; **click it** for an interactive player
45
+ > (play/pause/seek). See [Embed in your README](#embed-in-your-readme).
46
+ >
47
+ > The Python CLI is not on PyPI yet — install [from source](#install) for now.
48
+ > Once published, the PyPI and CI badges (and the player link) go live.
49
+
50
+ ## Why reterm
51
+
52
+ You want to show a terminal workflow on a page and have it look right — and you
53
+ want the *same* recording to be machine-readable so an AI or script can reason
54
+ about exactly what ran.
55
+
56
+ - **Accurate recording.** Each command's exact output (any length, with or
57
+ without a trailing newline), real exit code, working directory, timing, and
58
+ per-character colors are captured faithfully — via invisible OSC 133
59
+ shell-integration marks, the same mechanism iTerm2/WezTerm/VS Code use.
60
+ Commands containing `!` work too. (Shells without integration fall back to
61
+ screen-based capture.)
62
+ - **Two outputs, one record.** A polished GIF and a structured JSON log are
63
+ produced from a single declarative script.
64
+ - **Built for sharing.** Redact secrets and paths before posting, then drop an
65
+ animated SVG/GIF in a README or play the JSON back interactively with the React player.
66
+
67
+ ## Which output goes where
68
+
69
+ | Where you're posting | Use | Why |
70
+ |---|---|---|
71
+ | **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. |
72
+ | **GitHub README, interactive** | **SVG poster → hosted player** | GitHub can't run a JS player inline, so link the inline poster to a [hosted player](#embed-in-your-readme) for real play/pause/seek. |
73
+ | **Blog / product page** (React/MDX) | **`reterm-player`** + the JSON log | An interactive, seekable replay with real colors and playback controls. |
74
+ | **Feeding an AI tool / CI** | **JSON log** (or the [MCP server](#mcp-server)) | Structured commands, exit codes, and output an agent can read directly. |
75
+
76
+ > **Before posting anything public, run [`reterm redact`](#redaction)** to scrub
77
+ > secrets, tokens, and personal paths out of the recording.
78
+
79
+ ## Install
80
+
81
+ **Python CLI** (requires Python 3.11+, [uv](https://docs.astral.sh/uv/)):
82
+
83
+ ```bash
84
+ git clone https://github.com/dominic-righthere/reterm.git
85
+ cd reterm
86
+ uv sync
87
+ uv run reterm --help
88
+
89
+ # optional: install as a global `reterm` command
90
+ uv tool install .
91
+ ```
92
+
93
+ > PyPI publishing is planned — once live, `uv tool install reterm` and
94
+ > `uvx reterm` will work directly.
95
+
96
+ **React player** (published on npm):
97
+
98
+ ```bash
99
+ npm install reterm-player
100
+ ```
101
+
102
+ ## Quick Start
103
+
104
+ Create a `.reterm` script (or run `reterm new hello.reterm` for a template):
105
+
106
+ ```yaml
107
+ meta:
108
+ name: "Hello World"
109
+
110
+ config:
111
+ shell: /bin/zsh
112
+ theme: dracula
113
+
114
+ steps:
115
+ - run: echo "Hello from reterm!"
116
+ - sleep: 500ms
117
+ - run: ls -la
118
+ - sleep: 1s
119
+ ```
120
+
121
+ Run it:
122
+
123
+ ```bash
124
+ # Generate GIF + JSON log (use `reterm …` directly if you ran `uv tool install .`)
125
+ uv run reterm run hello.reterm -o hello.gif -l hello.json
126
+
127
+ # JSON log only, no GIF (faster — ideal for AI/CLI use)
128
+ uv run reterm run hello.reterm --log-only -l hello.json
129
+
130
+ # Replay a recording in your terminal
131
+ uv run reterm play hello.json
132
+ ```
133
+
134
+ ## CLI Commands
135
+
136
+ ```bash
137
+ reterm run <script> # Execute a script → GIF/SVG + JSON log
138
+ reterm new <file> # Create a script from a template
139
+ reterm validate <script> # Validate a script without executing it
140
+ reterm play <log> # Replay a recording in the terminal
141
+ reterm render <log> -o <out> # Re-render a GIF or animated SVG from a log
142
+ reterm redact <log> # Redact sensitive info from a log
143
+ reterm embed <poster> # Print Markdown to embed a recording in a README
144
+ reterm serve # Start the MCP server for AI tools
145
+ reterm themes # List available themes
146
+ reterm schema # Print the JSON log schema
147
+ ```
148
+
149
+ The visual format is chosen from the `-o` extension: `.gif` or `.svg` (an
150
+ animated SVG you can embed inline in a GitHub README). In visual output, `run`
151
+ commands animate their keystrokes (like the player); `--log-only`/MCP runs stay
152
+ instant. `run`/`render` take `--idle-limit N` (default `2`, `0` = off) to cap how
153
+ long any static frame is held, so long pauses don't drag the loop.
154
+
155
+ `reterm play` supports `--speed` (e.g. `--speed 2`) and `--idle-limit N` to cap
156
+ long pauses.
157
+
158
+ ### Redaction
159
+
160
+ Recordings are meant to be shared, so scrub anything sensitive first. Redaction
161
+ operates on the JSON log (commands, output, working directories, captured
162
+ variables, and terminal snapshots), and you can re-render a clean GIF afterward.
163
+
164
+ ```bash
165
+ # Visible redaction — shows [HOME] in the output
166
+ reterm redact demo.json -p "/Users/me" -r "HOME" -o redacted.json
167
+
168
+ # Seamless — replaces without any visual indicator
169
+ reterm redact demo.json -p "/Users/me" -r "/Users/alice" --seamless -o clean.json
170
+
171
+ # Regex — e.g. API keys
172
+ reterm redact demo.json -p "sk-[a-zA-Z0-9]+" -r "API_KEY" --regex -o redacted.json
173
+
174
+ # Re-render a GIF from the redacted log
175
+ reterm render redacted.json -o redacted.gif
176
+ ```
177
+
178
+ ## Embed in your README
179
+
180
+ A GitHub README can't run a live JS player — GitHub strips `<script>`/`<iframe>`.
181
+ So the pattern is an **animated SVG poster** (which *does* animate inline, via
182
+ `<img>`) that **links to a hosted interactive player**:
183
+
184
+ ```bash
185
+ # 1. record an animated SVG poster
186
+ reterm run demo.reterm -o assets/demo.svg
187
+
188
+ # 2. print the Markdown (poster linked to the hosted player)
189
+ reterm embed assets/demo.svg --base https://you.github.io/reterm -r demo
190
+ # → [![terminal recording](assets/demo.svg)](https://you.github.io/reterm/play/?r=demo)
191
+ ```
192
+
193
+ The hosted player is the bundled `reterm-player` deployed as a static page. This
194
+ repo ships a GitHub Pages workflow (`.github/workflows/pages.yml`) that builds it
195
+ and serves `…/play/?r=<name>` (reading `recordings/<name>.json`) and `…/play/?src=<url>`.
196
+
197
+ > **Turn it on once** (the workflow is gated off by default so `main` stays green):
198
+ > set a repo variable `ENABLE_PAGES=true` (`gh variable set ENABLE_PAGES --body true`,
199
+ > or Settings → Secrets and variables → Actions → Variables). The next run
200
+ > auto-enables Pages and deploys. Pages needs a **public repo or a paid plan**;
201
+ > otherwise deploy the same `site/` to Vercel/Netlify and point the poster link
202
+ > there. The inline SVG works regardless of hosting.
203
+
204
+ Just want the inline animation, no click-through? Use the SVG (or GIF) on its own:
205
+ `![demo](assets/demo.svg)`.
206
+
207
+ ## React Player
208
+
209
+ Embed an interactive replay in a blog, docs site, or product page:
210
+
211
+ ```tsx
212
+ import { TerminalPlayer } from 'reterm-player';
213
+ import 'reterm-player/style.css';
214
+
215
+ // From inline data
216
+ <TerminalPlayer data={recording} autoPlay />
217
+
218
+ // From a URL
219
+ <TerminalPlayer src="/recordings/demo.json" showControls />
220
+
221
+ // Full options
222
+ <TerminalPlayer
223
+ data={recording}
224
+ autoPlay
225
+ loop
226
+ speed={1.5}
227
+ theme="dracula"
228
+ showControls
229
+ showWindowFrame
230
+ cursorStyle="block"
231
+ />
232
+ ```
233
+
234
+ ### Props
235
+
236
+ | Prop | Type | Default | Description |
237
+ |------|------|---------|-------------|
238
+ | `data` | `RecordingLog` | – | Inline JSON recording data |
239
+ | `src` | `string` | – | URL to fetch a recording from |
240
+ | `autoPlay` | `boolean` | `false` | Start playing automatically |
241
+ | `loop` | `boolean` | `false` | Loop playback |
242
+ | `speed` | `number` | `1.0` | Playback speed multiplier |
243
+ | `theme` | `string` | from log | Theme override |
244
+ | `showControls` | `boolean` | `true` | Show play/pause/seek/speed controls |
245
+ | `showWindowFrame` | `boolean` | `true` | Show the macOS-style window frame |
246
+ | `title` | `string` | from log | Window title (defaults to the script name or `'Terminal'`) |
247
+ | `fit` | `boolean` | `false` | Fill the container width; scrolls horizontally if needed |
248
+ | `cursorStyle` | `'block' \| 'underline' \| 'bar'` | `'block'` | Cursor appearance |
249
+ | `showCursor` | `boolean` | `true` | Show the blinking cursor |
250
+ | `fontSize` | `number` | `14` | Font size in pixels |
251
+ | `fontFamily` | `string` | monospace | Terminal font family |
252
+ | `typingSpeed` | `number` | `50` | Typing animation speed (ms per character) |
253
+ | `onLoad` | `(recording) => void` | – | Called when the recording loads |
254
+ | `onComplete` | `() => void` | – | Called when playback completes |
255
+ | `onError` | `(error) => void` | – | Called on load/playback error |
256
+ | `className` | `string` | – | Extra CSS class for the root element |
257
+ | `style` | `CSSProperties` | – | Inline styles for the root element |
258
+
259
+ ## MCP Server
260
+
261
+ Expose reterm to AI tools over the [Model Context Protocol](https://modelcontextprotocol.io).
262
+
263
+ **Claude Code** (from source — replace the path with your clone):
264
+
265
+ ```bash
266
+ claude mcp add reterm -- uv run --directory /path/to/reterm reterm serve
267
+
268
+ # Available across all projects:
269
+ # claude mcp add --scope user reterm -- uv run --directory /path/to/reterm reterm serve
270
+ ```
271
+
272
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
273
+
274
+ ```json
275
+ {
276
+ "mcpServers": {
277
+ "reterm": {
278
+ "command": "uv",
279
+ "args": ["run", "--directory", "/path/to/reterm", "reterm", "serve"]
280
+ }
281
+ }
282
+ }
283
+ ```
284
+
285
+ > Once reterm is on PyPI, the command simplifies to `uvx reterm serve`.
286
+ > SSE transport is also available via `reterm serve --transport sse`.
287
+
288
+ ### Tools & resources
289
+
290
+ | Tool | Purpose |
291
+ |------|---------|
292
+ | `run_script` | Execute a `.reterm` script and return the structured log |
293
+ | `run_command` | Run a single command and return its structured output |
294
+ | `generate_script` | Build a `.reterm` script from a list of commands |
295
+ | `validate_script` | Validate a script without executing it |
296
+ | `format_as_markdown` | Render a log as shareable markdown (commands + output) |
297
+ | `screenshot_terminal` | Return a PNG image of the terminal state |
298
+ | `render_svg` | Return an animated SVG of the session (embeddable inline in a README) |
299
+
300
+ Resources: `reterm://schema` (JSON log schema), `reterm://themes`, and
301
+ `reterm://example` (an annotated example script).
302
+
303
+ ## Script Format
304
+
305
+ ```yaml
306
+ meta:
307
+ name: "Script Name"
308
+ description: "What this records"
309
+
310
+ config:
311
+ shell: /bin/zsh # Shell to use (default: $SHELL)
312
+ theme: dracula # Color theme
313
+ size: [80, 24] # Terminal size [cols, rows]
314
+ typing_speed: 50ms # Typing animation speed
315
+
316
+ env:
317
+ GREETING: "Hello" # Environment variables for the session
318
+
319
+ steps:
320
+ - run: echo "$GREETING, world" # Execute a command (captured in the log)
321
+ - type: "ls -la" # Type with animation (visual only)
322
+ then: enter # …then press a key
323
+ - sleep: 1s # Pause
324
+ - key: ctrl+c # Send a special key
325
+ - wait_for: "Ready" # Wait for output before continuing
326
+ timeout: 5s
327
+ regex: false
328
+ - screenshot: capture.png # Capture a frame
329
+ - note: "Not shown in terminal" # Metadata-only note (log only)
330
+
331
+ - run: ./deploy.sh # Assertions + capture on a run step
332
+ capture: deploy_id # save stdout to ${deploy_id}
333
+ hidden: false # hide from the GIF but keep in the log
334
+ expect:
335
+ exit_code: 0
336
+ contains: "success"
337
+ not_contains: "error"
338
+ matches: "id=\\w+" # regex
339
+
340
+ output: # Optional explicit output paths
341
+ gif: demo.gif
342
+ log: demo.json
343
+ ```
344
+
345
+ ### Step types
346
+
347
+ | Step | Description |
348
+ |------|-------------|
349
+ | `run` | Execute a command and **capture** it (command, exit code, output, timing) |
350
+ | `type` | Type text with animation (visual only — not captured as a command) |
351
+ | `sleep` | Pause for a duration |
352
+ | `key` | Send a special key (`ctrl+c`, `tab`, `enter`, …) |
353
+ | `wait_for` | Wait for an output pattern (with `timeout`, optional `regex`) |
354
+ | `screenshot` | Capture a frame |
355
+ | `note` | Metadata-only entry (appears in the log, not the terminal) |
356
+
357
+ Modifiers on a `run` step: `capture` (save stdout to a `${var}`), `expect`
358
+ (assert `exit_code` / `contains` / `not_contains` / `matches`), and `hidden`
359
+ (keep it in the log but out of the GIF). Reference captured variables and env
360
+ vars with `${name}`.
361
+
362
+ > **YAML tip:** quote commands that look like YAML literals — use
363
+ > `run: "false"`, `run: "no"`, `run: "yes"` so they aren't parsed as booleans.
364
+
365
+ A couple of ready-to-run examples live in [`examples/`](examples/).
366
+
367
+ ## JSON Log Output
368
+
369
+ Every recording produces a structured JSON log:
370
+
371
+ ```json
372
+ {
373
+ "schema_version": "1.0.0",
374
+ "metadata": {
375
+ "terminal_size": [80, 24],
376
+ "theme": "dracula",
377
+ "total_duration_ms": 2500
378
+ },
379
+ "commands": [
380
+ {
381
+ "command": "echo hello",
382
+ "exit_code": 0,
383
+ "stdout": "hello",
384
+ "duration_ms": 120,
385
+ "terminal_before": { "screen_content": ["..."], "cursor_position": [0, 0] },
386
+ "terminal_after": { "screen_content": ["..."], "cursor_position": [1, 0] }
387
+ }
388
+ ],
389
+ "success": true,
390
+ "all_commands_text": "echo hello",
391
+ "all_output_text": "hello"
392
+ }
393
+ ```
394
+
395
+ Beyond the basics, the log also carries `success` and `failed_commands` for
396
+ quick status checks, `captured_variables` from `capture:` steps, and per-command
397
+ `styled_content` (per-character colors used by the React player to reproduce the
398
+ terminal faithfully). Run `reterm schema` for the full schema.
399
+
400
+ ## Development
401
+
402
+ ```bash
403
+ uv sync --dev
404
+ uv run pytest # Python tests
405
+ uv run ruff check . # Lint
406
+ uv run mypy reterm/ # Type check
407
+
408
+ # React player
409
+ cd packages/player
410
+ npm install
411
+ npm run build
412
+ ```
413
+
414
+ ## License
415
+
416
+ MIT