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.
- reterm-0.1.0/.github/workflows/ci.yml +39 -0
- reterm-0.1.0/.github/workflows/pages.yml +67 -0
- reterm-0.1.0/.github/workflows/release.yml +44 -0
- reterm-0.1.0/.gitignore +21 -0
- reterm-0.1.0/.python-version +1 -0
- reterm-0.1.0/CHANGELOG.md +72 -0
- reterm-0.1.0/LICENSE +21 -0
- reterm-0.1.0/PKG-INFO +416 -0
- reterm-0.1.0/README.md +384 -0
- reterm-0.1.0/assets/demo.gif +0 -0
- reterm-0.1.0/assets/demo.svg +1 -0
- reterm-0.1.0/examples/demo.reterm +32 -0
- reterm-0.1.0/examples/demoenv/.zshrc +6 -0
- reterm-0.1.0/examples/hero.reterm +24 -0
- reterm-0.1.0/examples/interactive.reterm +23 -0
- reterm-0.1.0/examples/player.reterm +14 -0
- reterm-0.1.0/packages/player/README.md +63 -0
- reterm-0.1.0/packages/player/demo/index.html +128 -0
- reterm-0.1.0/packages/player/demo/recording.json +282 -0
- reterm-0.1.0/packages/player/index.html +26 -0
- reterm-0.1.0/packages/player/mdx-test/index.html +60 -0
- reterm-0.1.0/packages/player/mdx-test/package.json +23 -0
- reterm-0.1.0/packages/player/mdx-test/public/recording.json +282 -0
- reterm-0.1.0/packages/player/mdx-test/src/demo.mdx +87 -0
- reterm-0.1.0/packages/player/mdx-test/src/main.tsx +12 -0
- reterm-0.1.0/packages/player/mdx-test/tsconfig.json +17 -0
- reterm-0.1.0/packages/player/mdx-test/vite.config.ts +10 -0
- reterm-0.1.0/packages/player/package.json +60 -0
- reterm-0.1.0/packages/player/play/index.html +76 -0
- reterm-0.1.0/packages/player/recordings/demo.json +15807 -0
- reterm-0.1.0/packages/player/scripts/dump-timeline.ts +52 -0
- reterm-0.1.0/packages/player/src/TerminalPlayer.tsx +343 -0
- reterm-0.1.0/packages/player/src/components/Controls.tsx +134 -0
- reterm-0.1.0/packages/player/src/components/ProgressBar.tsx +66 -0
- reterm-0.1.0/packages/player/src/components/Terminal.tsx +400 -0
- reterm-0.1.0/packages/player/src/components/WindowFrame.tsx +99 -0
- reterm-0.1.0/packages/player/src/demo.tsx +51 -0
- reterm-0.1.0/packages/player/src/hooks/usePlayback.ts +218 -0
- reterm-0.1.0/packages/player/src/hooks/useRecording.ts +66 -0
- reterm-0.1.0/packages/player/src/index.ts +40 -0
- reterm-0.1.0/packages/player/src/styles/terminal.css +56 -0
- reterm-0.1.0/packages/player/src/themes/index.ts +242 -0
- reterm-0.1.0/packages/player/src/types/recording.ts +73 -0
- reterm-0.1.0/packages/player/src/utils/timeline.ts +181 -0
- reterm-0.1.0/packages/player/tsconfig.json +25 -0
- reterm-0.1.0/packages/player/vite.config.ts +33 -0
- reterm-0.1.0/pyproject.toml +62 -0
- reterm-0.1.0/reterm/__init__.py +3 -0
- reterm-0.1.0/reterm/__main__.py +6 -0
- reterm-0.1.0/reterm/cli.py +338 -0
- reterm-0.1.0/reterm/core/__init__.py +1 -0
- reterm-0.1.0/reterm/core/engine.py +876 -0
- reterm-0.1.0/reterm/core/events.py +172 -0
- reterm-0.1.0/reterm/core/pty_manager.py +216 -0
- reterm-0.1.0/reterm/core/terminal.py +333 -0
- reterm-0.1.0/reterm/mcp/__init__.py +1 -0
- reterm-0.1.0/reterm/mcp/server.py +459 -0
- reterm-0.1.0/reterm/output/__init__.py +1 -0
- reterm-0.1.0/reterm/output/models.py +220 -0
- reterm-0.1.0/reterm/play.py +184 -0
- reterm-0.1.0/reterm/redact.py +114 -0
- reterm-0.1.0/reterm/render/__init__.py +1 -0
- reterm-0.1.0/reterm/render/frame.py +232 -0
- reterm-0.1.0/reterm/render/from_log.py +83 -0
- reterm-0.1.0/reterm/render/gif.py +166 -0
- reterm-0.1.0/reterm/render/svg.py +322 -0
- reterm-0.1.0/reterm/render/themes.py +276 -0
- reterm-0.1.0/reterm/script/__init__.py +1 -0
- reterm-0.1.0/reterm/script/parser.py +180 -0
- reterm-0.1.0/tests/__init__.py +0 -0
- reterm-0.1.0/tests/test_engine.py +181 -0
- reterm-0.1.0/tests/test_models.py +136 -0
- reterm-0.1.0/tests/test_svg.py +108 -0
- reterm-0.1.0/tests/test_terminal.py +127 -0
- 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
|
reterm-0.1.0/.gitignore
ADDED
|
@@ -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
|
+
[](https://www.npmjs.com/package/reterm-player)
|
|
36
|
+
[](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
|
+
[](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
|
+
# → [](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
|
+
``.
|
|
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
|