tappty 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 (87) hide show
  1. tappty-0.1.0/CHANGELOG.md +56 -0
  2. tappty-0.1.0/LICENSE +21 -0
  3. tappty-0.1.0/MANIFEST.in +8 -0
  4. tappty-0.1.0/PKG-INFO +224 -0
  5. tappty-0.1.0/README.md +174 -0
  6. tappty-0.1.0/demos/README.md +41 -0
  7. tappty-0.1.0/demos/color_chart.py +79 -0
  8. tappty-0.1.0/demos/drive_vim.py +111 -0
  9. tappty-0.1.0/demos/matrix_rain.py +78 -0
  10. tappty-0.1.0/demos/mission_control.py +122 -0
  11. tappty-0.1.0/demos/recordings/cbonsai.cast +197 -0
  12. tappty-0.1.0/demos/recordings/drive_vim.cast +16 -0
  13. tappty-0.1.0/demos/recordings/nyancat.cast +42 -0
  14. tappty-0.1.0/demos/web_demo.py +106 -0
  15. tappty-0.1.0/docs/DESIGN.md +733 -0
  16. tappty-0.1.0/docs/GALLERY.md +118 -0
  17. tappty-0.1.0/docs/LICENSE.md +28 -0
  18. tappty-0.1.0/docs/README.md +21 -0
  19. tappty-0.1.0/docs/REFERENCE.md +702 -0
  20. tappty-0.1.0/docs/TAPTERM.md +512 -0
  21. tappty-0.1.0/docs/media/cbonsai.png +0 -0
  22. tappty-0.1.0/docs/media/color_chart.png +0 -0
  23. tappty-0.1.0/docs/media/drive_vim.mp4 +0 -0
  24. tappty-0.1.0/docs/media/drive_vim.png +0 -0
  25. tappty-0.1.0/docs/media/matrix.mp4 +0 -0
  26. tappty-0.1.0/docs/media/matrix_rain.png +0 -0
  27. tappty-0.1.0/docs/media/mission_control.png +0 -0
  28. tappty-0.1.0/docs/media/nyancat.gif +0 -0
  29. tappty-0.1.0/docs/media/nyancat.png +0 -0
  30. tappty-0.1.0/docs/media/web_demo.png +0 -0
  31. tappty-0.1.0/examples/README.md +28 -0
  32. tappty-0.1.0/examples/bus_capture.py +45 -0
  33. tappty-0.1.0/examples/custom_source.py +58 -0
  34. tappty-0.1.0/examples/observe_tap.py +41 -0
  35. tappty-0.1.0/examples/watch_and_drive.py +86 -0
  36. tappty-0.1.0/pyproject.toml +74 -0
  37. tappty-0.1.0/setup.cfg +4 -0
  38. tappty-0.1.0/src/tappty/__init__.py +88 -0
  39. tappty-0.1.0/src/tappty/arcade_ui.py +339 -0
  40. tappty-0.1.0/src/tappty/bus.py +520 -0
  41. tappty-0.1.0/src/tappty/cli.py +534 -0
  42. tappty-0.1.0/src/tappty/compositor.py +425 -0
  43. tappty-0.1.0/src/tappty/curses_ui.py +289 -0
  44. tappty-0.1.0/src/tappty/keys.py +56 -0
  45. tappty-0.1.0/src/tappty/pygame_ui.py +180 -0
  46. tappty-0.1.0/src/tappty/pyte_terminal.py +156 -0
  47. tappty-0.1.0/src/tappty/recorder.py +203 -0
  48. tappty-0.1.0/src/tappty/session.py +334 -0
  49. tappty-0.1.0/src/tappty/source.py +717 -0
  50. tappty-0.1.0/src/tappty/style.py +141 -0
  51. tappty-0.1.0/src/tappty/terminal.py +144 -0
  52. tappty-0.1.0/src/tappty/video.py +221 -0
  53. tappty-0.1.0/src/tappty/web_ui.py +299 -0
  54. tappty-0.1.0/src/tappty.egg-info/PKG-INFO +224 -0
  55. tappty-0.1.0/src/tappty.egg-info/SOURCES.txt +85 -0
  56. tappty-0.1.0/src/tappty.egg-info/dependency_links.txt +1 -0
  57. tappty-0.1.0/src/tappty.egg-info/entry_points.txt +2 -0
  58. tappty-0.1.0/src/tappty.egg-info/requires.txt +25 -0
  59. tappty-0.1.0/src/tappty.egg-info/top_level.txt +1 -0
  60. tappty-0.1.0/tests/test_arcade_smoke.py +74 -0
  61. tappty-0.1.0/tests/test_bus_cmd.py +68 -0
  62. tappty-0.1.0/tests/test_bus_security.py +179 -0
  63. tappty-0.1.0/tests/test_bus_socket.py +288 -0
  64. tappty-0.1.0/tests/test_bus_tcp.py +38 -0
  65. tappty-0.1.0/tests/test_cast_source.py +146 -0
  66. tappty-0.1.0/tests/test_cli.py +84 -0
  67. tappty-0.1.0/tests/test_compositor_backings.py +71 -0
  68. tappty-0.1.0/tests/test_compositor_view.py +25 -0
  69. tappty-0.1.0/tests/test_curses_viewport.py +91 -0
  70. tappty-0.1.0/tests/test_demos_smoke.py +70 -0
  71. tappty-0.1.0/tests/test_error_handling.py +244 -0
  72. tappty-0.1.0/tests/test_examples.py +44 -0
  73. tappty-0.1.0/tests/test_gui_smoke.py +133 -0
  74. tappty-0.1.0/tests/test_keys.py +23 -0
  75. tappty-0.1.0/tests/test_pipe_source.py +46 -0
  76. tappty-0.1.0/tests/test_pty_source.py +43 -0
  77. tappty-0.1.0/tests/test_pyte_terminal.py +123 -0
  78. tappty-0.1.0/tests/test_recorder.py +201 -0
  79. tappty-0.1.0/tests/test_session_bus.py +69 -0
  80. tappty-0.1.0/tests/test_session_echo.py +20 -0
  81. tappty-0.1.0/tests/test_source_encoding.py +63 -0
  82. tappty-0.1.0/tests/test_style.py +90 -0
  83. tappty-0.1.0/tests/test_talking_stick.py +122 -0
  84. tappty-0.1.0/tests/test_term.py +80 -0
  85. tappty-0.1.0/tests/test_video.py +57 -0
  86. tappty-0.1.0/tests/test_video_bounds.py +40 -0
  87. tappty-0.1.0/tests/test_web_ui.py +241 -0
@@ -0,0 +1,56 @@
1
+ # Changelog
2
+
3
+ ## 2026-06-17 — And so it begins...
4
+
5
+ - **13:10** — Started: the fixed-size VT52 `Terminal` grid
6
+ - **14:29** — the first pygame renderer
7
+ - **22:51** — First end-to-end run of observe/control core
8
+
9
+ ## 2026-06-18 — standalone
10
+
11
+ - **23:30** — Extracted to a standalone package
12
+ - **23:35** — round-tripped a real subprocess through the pty into the grid.
13
+
14
+ ## 2026-06-19 — renderers, color, bus, packaging
15
+
16
+ - **10:54** — Runtime hardening
17
+ - **11:06** — Consolidated the byte-source reader loop into `Source._pump`.
18
+ - **12:08** — The detailed docs: `DESIGN.md`, `REFERENCE.md`, the `tapterm` guide.
19
+ - **13:14** — Windows: the `win` extra bundles `windows-curses` (curses CUI on Windows).
20
+ - **13:32** — `arcade_ui` renderer — the pyglet/OpenGL twin of `pygame_ui`.
21
+ - **14:23** — SGR **color** in the GUI, then in the curses CUI too.
22
+ - **14:55** — Raw-mode full-screen TUI input (`--raw`), so vim/htop work.
23
+ - **15:30** — `web_ui` renderer — the terminal in a browser over a websocket.
24
+ - **20:33** — Bold/italic/underline as real attributes; then strikethrough and blink.
25
+ - **20:46** — Packaging: `MANIFEST.in` + a validated (non-publishing) build.
26
+ - **21:10** — SGR color over the bus (styled snapshot + colored compositor panels).
27
+ - **21:37** — Wide CJK and single-code-point emoji at their true two columns.
28
+ - **22:44** — A GitHub Pages docs site: `docs/` sources, a `gh-pages/` builder, Actions deploy.
29
+ - **23:05** — Published a `tapterm` alias distribution alongside `tappty`.
30
+ - **23:50** — Docs-site nav and hero polish.
31
+
32
+ ## 2026-06-20 — recording formats, video, demos, the terminal
33
+
34
+ - **10:52** — A **gallery** of runnable demos (screenshots + source).
35
+ - **11:15** — `.ttyrec` read/write, and recording any live session to `.cast`/`.ttyrec`
36
+ - **11:47** — Host & record real ANSI programs; bundle `nyancat`/`cbonsai` recordings
37
+ - **12:01** — Play and export ANSI/BBS art `.ans` (CP437 + SAUCE) and animated `.3a`
38
+ - **12:27** — **Render to video** — any recording to mp4/webm/gif via ffmpeg
39
+ - **12:41** — render of a live command (record + render in one step).
40
+ - **13:14** — Split `demos/` (runnable showpieces) from `examples/` (API coding examples).
41
+ - **13:50** — Gallery goes visual: an embedded movie, a looping GIF, a digital-rain mp4.
42
+ - **14:06** — `tapterm` is a **regular terminal** by default
43
+ - **14:12** — `tapterm` takes xterm like arguments where useful
44
+ - **14:26** — Observe-and-control demos: an autopilot driving live `vim` (open loop)
45
+ - **14:35** — reactive bot demo that reads the stream and decides what to type (closed loop).
46
+ - **14:55** — A web-renderer demo with a real-browser (Playwright) screenshot.
47
+ - **15:42** — Named the GUI extras by graphics layer — `sdl` (pygame-ce) and `gl` (arcade).
48
+ - **16:40** — Docs-site polish
49
+ - **17:28** — Runtime hardening
50
+ - **17:33** — Fixed race conditions
51
+ - **17:40** — pypy packaging prep
52
+ - **18:11** — Bus/control hardening
53
+ - **18:16** — Improve replay/render robustness
54
+ - **18:19** — The CUI honors `--exit-when-done` / `-hold`
55
+ - **18:28** — Applied `ruff format`
56
+ - **18:29** — Across the tree; CI installs the `web` extra so the WebSocket/CSWSH tests run.
tappty-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nicholas J. Kisseberth
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.
@@ -0,0 +1,8 @@
1
+ # Files to include in the source distribution (sdist) beyond the defaults
2
+ # (pyproject.toml, README.md, and the LICENSE are included automatically).
3
+ include LICENSE CHANGELOG.md
4
+ recursive-include docs *.md
5
+ recursive-include docs/media *.png *.gif *.mp4
6
+ recursive-include demos *.py *.md *.cast
7
+ recursive-include examples *.py *.md
8
+ recursive-include tests *.py
tappty-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.4
2
+ Name: tappty
3
+ Version: 0.1.0
4
+ Summary: Host a program on a pseudo-terminal, then observe/control/render it (CUI or GUI)
5
+ Author: Nicholas J. Kisseberth
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nyxcraft/tappty
8
+ Project-URL: Documentation, https://nyxcraft.github.io/tappty/
9
+ Project-URL: Source, https://github.com/nyxcraft/tappty
10
+ Project-URL: Changelog, https://github.com/nyxcraft/tappty/blob/main/CHANGELOG.md
11
+ Keywords: terminal,pty,curses,pygame,tui,vt52,emulator,instrumentation
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console :: Curses
14
+ Classifier: Environment :: X11 Applications
15
+ Classifier: Environment :: Web Environment
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: POSIX
20
+ Classifier: Operating System :: POSIX :: Linux
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.9
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: Programming Language :: Python :: 3.13
28
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
+ Classifier: Topic :: Terminals :: Terminal Emulators/X Terminals
30
+ Requires-Python: >=3.9
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Provides-Extra: sdl
34
+ Requires-Dist: pygame-ce; extra == "sdl"
35
+ Provides-Extra: gl
36
+ Requires-Dist: arcade; extra == "gl"
37
+ Provides-Extra: web
38
+ Requires-Dist: websockets; extra == "web"
39
+ Provides-Extra: video
40
+ Requires-Dist: imageio-ffmpeg; extra == "video"
41
+ Provides-Extra: ansi
42
+ Requires-Dist: pyte; extra == "ansi"
43
+ Provides-Extra: win
44
+ Requires-Dist: pywinpty; platform_system == "Windows" and extra == "win"
45
+ Requires-Dist: windows-curses; platform_system == "Windows" and extra == "win"
46
+ Provides-Extra: dev
47
+ Requires-Dist: pytest; extra == "dev"
48
+ Requires-Dist: ruff; extra == "dev"
49
+ Dynamic: license-file
50
+
51
+ # tappty
52
+
53
+ **Host a program on a pseudo-terminal, then observe, control, and render it** — in a
54
+ plain terminal (curses, CUI) or a green-phosphor window (GUI).
55
+
56
+ Software Architecture, Design & Engineering by Nicholas J. Kisseberth.
57
+ Code Synthesized via Anthropic Claude Code / Opus 4.8.
58
+ Code Review by OpenAI Codex / ChatGPT 5.5.
59
+
60
+ `tappty` is a small instrumented-terminal toolkit. A program's bytes (a subprocess on a
61
+ PTY, or any in-process runner) flow into a fixed-size character `Terminal`; a `Session`
62
+ fans that output out to any number of observers and routes input back. A renderer is just
63
+ one more observer/controller — which is what lets a human *and* an automated client watch
64
+ and drive the very same session. Several sessions tile into one window via the compositor.
65
+
66
+ ## Install
67
+
68
+ ```sh
69
+ pip install tappty # core (CUI works out of the box)
70
+ pip install 'tappty[sdl]' # add the SDL/GUI window (installs pygame-ce)
71
+ pip install 'tappty[gl]' # add the OpenGL window (arcade; an alternative GUI backend)
72
+ pip install 'tappty[web]' # add the browser renderer for --web (websockets)
73
+ pip install 'tappty[video]' # render recordings to mp4/gif via --render (bundles ffmpeg)
74
+ pip install 'tappty[ansi]' # add the full-ANSI/VT100+ backend (pyte) for --ansi
75
+ pip install 'tappty[win]' # Windows: add the ConPTY source (pywinpty)
76
+ # from a checkout:
77
+ pip install -e '.[sdl,ansi,dev]'
78
+ ```
79
+
80
+ `pip install tapterm` works too — it's a convenience alias that just pulls in `tappty` (which
81
+ provides the `tapterm` command). The library, the extras, and the docs all live under `tappty`.
82
+
83
+ ## The `tapterm` program
84
+
85
+ ```sh
86
+ tapterm # a regular terminal: your $SHELL, full-ANSI + raw keys
87
+ tapterm -e vim file # run a command instead of the shell, xterm-style (or: -- vim file)
88
+ tapterm -geometry 100x30 # xterm-style size; -T/-title sets the title, -cd DIR the working dir
89
+ tapterm --cui -- bash # force the curses character UI (takes over this terminal)
90
+ tapterm --gui -- bash # force the SDL green-phosphor window (the 'sdl' extra)
91
+ tapterm --arcade -- bash # same, on the arcade/OpenGL stack (the 'gl' extra)
92
+ tapterm --web -- bash # serve it in a browser (the 'web' extra); open http://127.0.0.1:8023/
93
+ tapterm --headless -- ls # run to completion, print the final screen (scripting/CI)
94
+ tapterm --cooked -- bash # line-oriented instrument mode (local echo on the VT52 grid)
95
+ tapterm --play rec.cast # replay a .cast / .ttyrec / .ans / .3a recording (--speed N, --loop)
96
+ tapterm --record out.cast -- bash # record a session as you use it
97
+ tapterm --play rec.cast --render rec.mp4 # render a recording to a video (mp4/webm/gif)
98
+ tapterm --render rain.mp4 --seconds 5 -- cmatrix # render a live program straight to video
99
+ tapterm --no-pty -- ls # host over plain pipes, no pty (cross-platform, incl. Windows)
100
+ ```
101
+
102
+ An interactive session behaves like a **real terminal** — full-ANSI rendering (the `ansi` extra,
103
+ pyte) plus raw keys, so colors, line-editing, arrows, and full-screen apps work, and the window
104
+ closes when the program exits, like xterm. Pass `--cooked` for the line-oriented instrument
105
+ default instead (local echo on the dependency-free VT52 grid) — what the observe taps and the bus
106
+ `CMD` capture expect. xterm-style flags are accepted where they fit: `-e`, `-T`/`-title`,
107
+ `-geometry`, `-cd`, `-hold`.
108
+
109
+ `--cui` works anywhere; `--gui` needs the `sdl` extra. With no mode flag, `tapterm` picks GUI when
110
+ the `sdl` extra is installed *and a display is available* (else CUI) — so it won't try to open a
111
+ window over SSH/cron. On Windows the pty path uses ConPTY (the `win` extra), which emits VT100+.
112
+
113
+ Every flag, the modes, recordings, snapshots, recipes, and troubleshooting are in the
114
+ [tapterm user's guide](docs/TAPTERM.md).
115
+
116
+ ## Library
117
+
118
+ ```python
119
+ from tappty import Session, Terminal, PtySource, curses_ui
120
+
121
+ sess = Session(Terminal(cols=80, rows=24))
122
+ sess.source = PtySource(["bash"])
123
+ sess.claim_control("local", "human")
124
+ curses_ui.run(sess, None, title="bash")
125
+ ```
126
+
127
+ Full API — classes, signatures, the observe/control contract, and worked examples — is in
128
+ [docs/REFERENCE.md](docs/REFERENCE.md).
129
+
130
+ The pieces:
131
+
132
+ - **`Terminal` / `PyteTerminal`** — the screen model. `Terminal` is a fixed-size character
133
+ grid (VT52 spirit: wrap/scroll, the common control chars, a handful of VT52 escapes), with
134
+ scrollback and no deps. `PyteTerminal` is a drop-in full-ANSI/VT100+ backend (wraps `pyte`,
135
+ the `ansi` extra) for programs that speak modern ANSI; same read interface (plus a `cells()`
136
+ view of per-cell SGR color), so the GUI renderers show color while the rest is unchanged.
137
+ - **`Source` / `PtySource` / `EngineSource` / `CastSource` / `TtyrecSource` / `AnsSource` /
138
+ `ThreeASource` / `PipeSource` / `ConPtySource`** — byte producers. `PtySource` runs an external
139
+ command on a real pty (POSIX); `EngineSource` wraps any in-process `runner(emit, readline)`
140
+ callable; `CastSource` / `TtyrecSource` replay a recorded `.cast` / `.ttyrec` session, and
141
+ `AnsSource` / `ThreeASource` play `.ans` / `.3a` art, through the same pipeline (original
142
+ timing, `speed`/`loop`; `replay_source(path)` picks by extension); `PipeSource` hosts a command
143
+ over plain pipes (no pty, any OS); `ConPtySource` hosts one on a Windows pseudo-console (ConPTY,
144
+ the `win` extra).
145
+ - **`Session`** — hosts a Source, drives the Terminal, and exposes **observe taps**
146
+ (`on_stream`, `on_frame`, `on_event`) and **control** (`send_input`, `feed_key`) plus a
147
+ talking-stick arbitration so exactly one controller types at a time.
148
+ - **`Recorder` / `render_video`** — `Recorder` writes the session's output stream to a `.cast`
149
+ or `.ttyrec` recording as it runs (the inverse of the replay sources; `tapterm --record`);
150
+ `render_video` encodes a recording to a real video file (mp4/webm/gif) via ffmpeg, with
151
+ size/zoom/font/speed and an area-of-interest crop (`tapterm --play X --render out.mp4`).
152
+ - **`BusServer` / `BusClient`** — the same observe/control contract over a Unix-domain
153
+ socket *or* TCP (a `(host, port)` tuple — works on Windows too), so an out-of-process
154
+ client (a logger, an automated driver, a remote renderer) can attach to a session. It's a
155
+ terminal control plane — **trusted-local**: the Unix socket is owner-only, TCP is
156
+ loopback-only unless `allow_remote=True`, and a `token=` adds an optional shared-secret
157
+ gate. Not a substitute for a tunnel on an untrusted network (no TLS).
158
+ - **`curses_ui` / `pygame_ui` / `arcade_ui` / `web_ui`** — renderers; each exposes
159
+ `run(session, runner, title=…)`. `pygame_ui` and `arcade_ui` are two backends for the same
160
+ green-phosphor window (SDL, or arcade/OpenGL); `web_ui` serves it in a browser over a
161
+ WebSocket (the `web` extra).
162
+ - **`compositor`** — tile several panels (`SessionBacking` for in-process, `BusBacking`
163
+ for remote) in one GUI window, with per-tile pan/zoom and focus.
164
+
165
+ ## Demos & examples
166
+
167
+ `demos/` holds runnable showpieces — the SGR color chart, the green digital rain, the compositor
168
+ "mission control", `drive_vim` (a program driving a live `vim` over the control tap), and
169
+ `web_demo` (the browser renderer). `examples/` holds short, commented API examples — the observe
170
+ taps, writing a custom `Source`, driving a session over the bus, and `watch_and_drive` (a bot that
171
+ reads the output stream and types its decisions back). The [gallery](docs/GALLERY.md) has
172
+ screenshots and a clip of each.
173
+
174
+ ## Platform
175
+
176
+ The core (Terminal/Session/taps/talking-stick), `EngineSource`, `CastSource`, `PipeSource`,
177
+ the renderers, and the TCP bus are cross-platform. `PtySource` uses `pty`/`termios`
178
+ (POSIX-only); on Windows, host via `ConPtySource` (the `win` extra, ConPTY — paired with
179
+ `--ansi`) or `PipeSource` (`--no-pty`), and use the TCP bus rather than a Unix socket. The
180
+ Windows ConPTY path is implemented but not yet exercised on real Windows — finishing it is
181
+ open work (see [docs/DESIGN.md](docs/DESIGN.md) §11). The GUI needs a display; the CUI needs a
182
+ terminal (and, on Windows, the `win` extra's `windows-curses`); `--headless` needs neither.
183
+
184
+ ## Documentation
185
+
186
+ The rendered docs site is at **[nyxcraft.github.io/tappty](https://nyxcraft.github.io/tappty/)**
187
+ (built from `docs/` by `gh-pages/build_site.py` and published via GitHub Actions). The sources:
188
+
189
+ - **[docs/TAPTERM.md](docs/TAPTERM.md)** — the `tapterm` command in depth: every flag,
190
+ the CUI / GUI / headless modes, the terminal model, recordings, snapshots, recipes, and
191
+ troubleshooting. For *using* tapterm.
192
+ - **[docs/REFERENCE.md](docs/REFERENCE.md)** — the programming/API reference: every public
193
+ class and method with signatures, the observe/control contracts (snapshot dict, events,
194
+ roles), and worked examples. For *building on* the library.
195
+ - **[docs/DESIGN.md](docs/DESIGN.md)** — the architecture: the Source → Terminal → Session →
196
+ renderer/bus pipeline, the concurrency and security/trust models, and the design rationale.
197
+ For *modifying* tappty.
198
+ - **[CHANGELOG.md](CHANGELOG.md)** — dated history, newest first: when tappty started, when
199
+ it first worked, and what's changed since. The remaining open work (publish to PyPI; verify
200
+ Windows) is in [docs/DESIGN.md](docs/DESIGN.md) §11.
201
+
202
+ ## Tests & tooling
203
+
204
+ ```sh
205
+ pip install -e '.[dev]' # quick: core suite (pyte + GUI tests skip)
206
+ pytest
207
+
208
+ pip install -e '.[dev,ansi,sdl]' # full: also the ANSI backend + headless GUI smoke
209
+ pytest
210
+
211
+ ruff check src tests # lint (E,F,W,I,B,UP); must be clean
212
+ ruff format src tests # format (line-length 99, black-style)
213
+
214
+ # no install needed, straight from a checkout:
215
+ PYTHONPATH=src python3 -m tappty.cli --headless -- echo hello
216
+ ```
217
+
218
+ Without the `ansi`/`sdl` extras, `pytest` skips the pyte and GUI tests (so it reports
219
+ fewer passes plus a couple of skips); install `.[dev,ansi,sdl]` to run the whole suite. CI
220
+ runs ruff + the full matrix on Python 3.9–3.13.
221
+
222
+ ## License
223
+
224
+ MIT © Nicholas J. Kisseberth. See [LICENSE](LICENSE).
tappty-0.1.0/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # tappty
2
+
3
+ **Host a program on a pseudo-terminal, then observe, control, and render it** — in a
4
+ plain terminal (curses, CUI) or a green-phosphor window (GUI).
5
+
6
+ Software Architecture, Design & Engineering by Nicholas J. Kisseberth.
7
+ Code Synthesized via Anthropic Claude Code / Opus 4.8.
8
+ Code Review by OpenAI Codex / ChatGPT 5.5.
9
+
10
+ `tappty` is a small instrumented-terminal toolkit. A program's bytes (a subprocess on a
11
+ PTY, or any in-process runner) flow into a fixed-size character `Terminal`; a `Session`
12
+ fans that output out to any number of observers and routes input back. A renderer is just
13
+ one more observer/controller — which is what lets a human *and* an automated client watch
14
+ and drive the very same session. Several sessions tile into one window via the compositor.
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ pip install tappty # core (CUI works out of the box)
20
+ pip install 'tappty[sdl]' # add the SDL/GUI window (installs pygame-ce)
21
+ pip install 'tappty[gl]' # add the OpenGL window (arcade; an alternative GUI backend)
22
+ pip install 'tappty[web]' # add the browser renderer for --web (websockets)
23
+ pip install 'tappty[video]' # render recordings to mp4/gif via --render (bundles ffmpeg)
24
+ pip install 'tappty[ansi]' # add the full-ANSI/VT100+ backend (pyte) for --ansi
25
+ pip install 'tappty[win]' # Windows: add the ConPTY source (pywinpty)
26
+ # from a checkout:
27
+ pip install -e '.[sdl,ansi,dev]'
28
+ ```
29
+
30
+ `pip install tapterm` works too — it's a convenience alias that just pulls in `tappty` (which
31
+ provides the `tapterm` command). The library, the extras, and the docs all live under `tappty`.
32
+
33
+ ## The `tapterm` program
34
+
35
+ ```sh
36
+ tapterm # a regular terminal: your $SHELL, full-ANSI + raw keys
37
+ tapterm -e vim file # run a command instead of the shell, xterm-style (or: -- vim file)
38
+ tapterm -geometry 100x30 # xterm-style size; -T/-title sets the title, -cd DIR the working dir
39
+ tapterm --cui -- bash # force the curses character UI (takes over this terminal)
40
+ tapterm --gui -- bash # force the SDL green-phosphor window (the 'sdl' extra)
41
+ tapterm --arcade -- bash # same, on the arcade/OpenGL stack (the 'gl' extra)
42
+ tapterm --web -- bash # serve it in a browser (the 'web' extra); open http://127.0.0.1:8023/
43
+ tapterm --headless -- ls # run to completion, print the final screen (scripting/CI)
44
+ tapterm --cooked -- bash # line-oriented instrument mode (local echo on the VT52 grid)
45
+ tapterm --play rec.cast # replay a .cast / .ttyrec / .ans / .3a recording (--speed N, --loop)
46
+ tapterm --record out.cast -- bash # record a session as you use it
47
+ tapterm --play rec.cast --render rec.mp4 # render a recording to a video (mp4/webm/gif)
48
+ tapterm --render rain.mp4 --seconds 5 -- cmatrix # render a live program straight to video
49
+ tapterm --no-pty -- ls # host over plain pipes, no pty (cross-platform, incl. Windows)
50
+ ```
51
+
52
+ An interactive session behaves like a **real terminal** — full-ANSI rendering (the `ansi` extra,
53
+ pyte) plus raw keys, so colors, line-editing, arrows, and full-screen apps work, and the window
54
+ closes when the program exits, like xterm. Pass `--cooked` for the line-oriented instrument
55
+ default instead (local echo on the dependency-free VT52 grid) — what the observe taps and the bus
56
+ `CMD` capture expect. xterm-style flags are accepted where they fit: `-e`, `-T`/`-title`,
57
+ `-geometry`, `-cd`, `-hold`.
58
+
59
+ `--cui` works anywhere; `--gui` needs the `sdl` extra. With no mode flag, `tapterm` picks GUI when
60
+ the `sdl` extra is installed *and a display is available* (else CUI) — so it won't try to open a
61
+ window over SSH/cron. On Windows the pty path uses ConPTY (the `win` extra), which emits VT100+.
62
+
63
+ Every flag, the modes, recordings, snapshots, recipes, and troubleshooting are in the
64
+ [tapterm user's guide](docs/TAPTERM.md).
65
+
66
+ ## Library
67
+
68
+ ```python
69
+ from tappty import Session, Terminal, PtySource, curses_ui
70
+
71
+ sess = Session(Terminal(cols=80, rows=24))
72
+ sess.source = PtySource(["bash"])
73
+ sess.claim_control("local", "human")
74
+ curses_ui.run(sess, None, title="bash")
75
+ ```
76
+
77
+ Full API — classes, signatures, the observe/control contract, and worked examples — is in
78
+ [docs/REFERENCE.md](docs/REFERENCE.md).
79
+
80
+ The pieces:
81
+
82
+ - **`Terminal` / `PyteTerminal`** — the screen model. `Terminal` is a fixed-size character
83
+ grid (VT52 spirit: wrap/scroll, the common control chars, a handful of VT52 escapes), with
84
+ scrollback and no deps. `PyteTerminal` is a drop-in full-ANSI/VT100+ backend (wraps `pyte`,
85
+ the `ansi` extra) for programs that speak modern ANSI; same read interface (plus a `cells()`
86
+ view of per-cell SGR color), so the GUI renderers show color while the rest is unchanged.
87
+ - **`Source` / `PtySource` / `EngineSource` / `CastSource` / `TtyrecSource` / `AnsSource` /
88
+ `ThreeASource` / `PipeSource` / `ConPtySource`** — byte producers. `PtySource` runs an external
89
+ command on a real pty (POSIX); `EngineSource` wraps any in-process `runner(emit, readline)`
90
+ callable; `CastSource` / `TtyrecSource` replay a recorded `.cast` / `.ttyrec` session, and
91
+ `AnsSource` / `ThreeASource` play `.ans` / `.3a` art, through the same pipeline (original
92
+ timing, `speed`/`loop`; `replay_source(path)` picks by extension); `PipeSource` hosts a command
93
+ over plain pipes (no pty, any OS); `ConPtySource` hosts one on a Windows pseudo-console (ConPTY,
94
+ the `win` extra).
95
+ - **`Session`** — hosts a Source, drives the Terminal, and exposes **observe taps**
96
+ (`on_stream`, `on_frame`, `on_event`) and **control** (`send_input`, `feed_key`) plus a
97
+ talking-stick arbitration so exactly one controller types at a time.
98
+ - **`Recorder` / `render_video`** — `Recorder` writes the session's output stream to a `.cast`
99
+ or `.ttyrec` recording as it runs (the inverse of the replay sources; `tapterm --record`);
100
+ `render_video` encodes a recording to a real video file (mp4/webm/gif) via ffmpeg, with
101
+ size/zoom/font/speed and an area-of-interest crop (`tapterm --play X --render out.mp4`).
102
+ - **`BusServer` / `BusClient`** — the same observe/control contract over a Unix-domain
103
+ socket *or* TCP (a `(host, port)` tuple — works on Windows too), so an out-of-process
104
+ client (a logger, an automated driver, a remote renderer) can attach to a session. It's a
105
+ terminal control plane — **trusted-local**: the Unix socket is owner-only, TCP is
106
+ loopback-only unless `allow_remote=True`, and a `token=` adds an optional shared-secret
107
+ gate. Not a substitute for a tunnel on an untrusted network (no TLS).
108
+ - **`curses_ui` / `pygame_ui` / `arcade_ui` / `web_ui`** — renderers; each exposes
109
+ `run(session, runner, title=…)`. `pygame_ui` and `arcade_ui` are two backends for the same
110
+ green-phosphor window (SDL, or arcade/OpenGL); `web_ui` serves it in a browser over a
111
+ WebSocket (the `web` extra).
112
+ - **`compositor`** — tile several panels (`SessionBacking` for in-process, `BusBacking`
113
+ for remote) in one GUI window, with per-tile pan/zoom and focus.
114
+
115
+ ## Demos & examples
116
+
117
+ `demos/` holds runnable showpieces — the SGR color chart, the green digital rain, the compositor
118
+ "mission control", `drive_vim` (a program driving a live `vim` over the control tap), and
119
+ `web_demo` (the browser renderer). `examples/` holds short, commented API examples — the observe
120
+ taps, writing a custom `Source`, driving a session over the bus, and `watch_and_drive` (a bot that
121
+ reads the output stream and types its decisions back). The [gallery](docs/GALLERY.md) has
122
+ screenshots and a clip of each.
123
+
124
+ ## Platform
125
+
126
+ The core (Terminal/Session/taps/talking-stick), `EngineSource`, `CastSource`, `PipeSource`,
127
+ the renderers, and the TCP bus are cross-platform. `PtySource` uses `pty`/`termios`
128
+ (POSIX-only); on Windows, host via `ConPtySource` (the `win` extra, ConPTY — paired with
129
+ `--ansi`) or `PipeSource` (`--no-pty`), and use the TCP bus rather than a Unix socket. The
130
+ Windows ConPTY path is implemented but not yet exercised on real Windows — finishing it is
131
+ open work (see [docs/DESIGN.md](docs/DESIGN.md) §11). The GUI needs a display; the CUI needs a
132
+ terminal (and, on Windows, the `win` extra's `windows-curses`); `--headless` needs neither.
133
+
134
+ ## Documentation
135
+
136
+ The rendered docs site is at **[nyxcraft.github.io/tappty](https://nyxcraft.github.io/tappty/)**
137
+ (built from `docs/` by `gh-pages/build_site.py` and published via GitHub Actions). The sources:
138
+
139
+ - **[docs/TAPTERM.md](docs/TAPTERM.md)** — the `tapterm` command in depth: every flag,
140
+ the CUI / GUI / headless modes, the terminal model, recordings, snapshots, recipes, and
141
+ troubleshooting. For *using* tapterm.
142
+ - **[docs/REFERENCE.md](docs/REFERENCE.md)** — the programming/API reference: every public
143
+ class and method with signatures, the observe/control contracts (snapshot dict, events,
144
+ roles), and worked examples. For *building on* the library.
145
+ - **[docs/DESIGN.md](docs/DESIGN.md)** — the architecture: the Source → Terminal → Session →
146
+ renderer/bus pipeline, the concurrency and security/trust models, and the design rationale.
147
+ For *modifying* tappty.
148
+ - **[CHANGELOG.md](CHANGELOG.md)** — dated history, newest first: when tappty started, when
149
+ it first worked, and what's changed since. The remaining open work (publish to PyPI; verify
150
+ Windows) is in [docs/DESIGN.md](docs/DESIGN.md) §11.
151
+
152
+ ## Tests & tooling
153
+
154
+ ```sh
155
+ pip install -e '.[dev]' # quick: core suite (pyte + GUI tests skip)
156
+ pytest
157
+
158
+ pip install -e '.[dev,ansi,sdl]' # full: also the ANSI backend + headless GUI smoke
159
+ pytest
160
+
161
+ ruff check src tests # lint (E,F,W,I,B,UP); must be clean
162
+ ruff format src tests # format (line-length 99, black-style)
163
+
164
+ # no install needed, straight from a checkout:
165
+ PYTHONPATH=src python3 -m tappty.cli --headless -- echo hello
166
+ ```
167
+
168
+ Without the `ansi`/`sdl` extras, `pytest` skips the pyte and GUI tests (so it reports
169
+ fewer passes plus a couple of skips); install `.[dev,ansi,sdl]` to run the whole suite. CI
170
+ runs ruff + the full matrix on Python 3.9–3.13.
171
+
172
+ ## License
173
+
174
+ MIT © Nicholas J. Kisseberth. See [LICENSE](LICENSE).
@@ -0,0 +1,41 @@
1
+ # tappty demos
2
+
3
+ Runnable showpieces — single-file apps you run to *see* a feature in action. Most are
4
+ **in-process** (an `EngineSource`), so they need no external program; `drive_vim` hosts a real
5
+ `vim` to show off observe-and-control. (For coding-level examples of how to build on the API,
6
+ see [`../examples/`](../examples/).)
7
+
8
+ | Demo | What it shows | Run |
9
+ |---|---|---|
10
+ | [color_chart.py](color_chart.py) | SGR color + attributes — bold/italic/underline/strike/blink/reverse and a 256-color strip | `python demos/color_chart.py` |
11
+ | [matrix_rain.py](matrix_rain.py) | green-phosphor "digital rain" on the dependency-free VT52 backend | `python demos/matrix_rain.py` |
12
+ | [mission_control.py](mission_control.py) | the compositor — four live sessions tiled in one window | `python demos/mission_control.py` |
13
+ | [drive_vim.py](drive_vim.py) | a program driving a real terminal app — an autopilot types into live `vim` over the control tap | `python demos/drive_vim.py` |
14
+ | [web_demo.py](web_demo.py) | the **web renderer** — the live terminal served as an HTML canvas, painted in a browser tab | `python demos/web_demo.py` |
15
+
16
+ Install the matching extra first:
17
+
18
+ ```sh
19
+ pip install 'tappty[sdl,ansi]' # color_chart / mission_control / drive_vim (pygame + pyte)
20
+ pip install 'tappty[sdl]' # matrix_rain (pygame only; no color backend needed)
21
+ pip install 'tappty[web,ansi]' # web_demo (websockets + pyte); --shot also needs playwright
22
+ ```
23
+
24
+ `drive_vim` also needs `vim` (or `vi`) on your PATH; `web_demo --shot` needs Playwright +
25
+ Chromium (`pip install playwright && playwright install chromium`). To watch a closed loop that
26
+ **reads the screen and decides what to type**, see
27
+ [`../examples/watch_and_drive.py`](../examples/watch_and_drive.py).
28
+
29
+ Each demo also takes `--snapshot PATH`: instead of opening a window it renders
30
+ headless (SDL dummy driver) and writes a PNG. That's exactly how the
31
+ [documentation gallery](../docs/GALLERY.md) images are produced —
32
+ `gh-pages/screenshots.py` runs these same files with `--snapshot`.
33
+
34
+ `recordings/` holds short `.cast` sessions — real ANSI programs (`nyancat`, `cbonsai`) and the
35
+ autopilot-driven `vim` (`drive_vim.cast`) — recorded with `tapterm --record` and replayable with
36
+ **zero dependencies**:
37
+
38
+ ```sh
39
+ tapterm --play demos/recordings/nyancat.cast
40
+ tapterm --play demos/recordings/drive_vim.cast
41
+ ```
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env python3
2
+ """tappty demo — an ANSI color & SGR-attribute chart.
3
+
4
+ Hosts a tiny in-process program (an EngineSource) that prints a color and
5
+ attribute chart, so you can watch tappty render SGR. No external program needed.
6
+
7
+ pip install 'tappty[sdl,ansi]'
8
+ python demos/color_chart.py # open the green-phosphor window
9
+ python demos/color_chart.py --snapshot c.png # render headless, write c.png instead
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+
16
+ CSI = "\x1b["
17
+
18
+
19
+ def runner(emit, readline):
20
+ """An EngineSource program: print the chart, then wait for a keypress."""
21
+ emit(CSI + "2J" + CSI + "H")
22
+ emit(" tappty — SGR color & attributes\r\n\r\n")
23
+
24
+ emit(" foreground ")
25
+ for c in range(8):
26
+ emit(f"{CSI}3{c}m ██ {CSI}0m")
27
+ emit("\r\n (bold) ")
28
+ for c in range(8):
29
+ emit(f"{CSI}1;3{c}m ██ {CSI}0m")
30
+ emit("\r\n\r\n")
31
+
32
+ emit(" background ")
33
+ for c in range(8):
34
+ emit(f"{CSI}4{c}m {CSI}0m")
35
+ emit("\r\n\r\n")
36
+
37
+ emit(" attributes ")
38
+ emit(f"{CSI}1mbold{CSI}0m {CSI}3mitalic{CSI}0m {CSI}4munderline{CSI}0m ")
39
+ emit(f"{CSI}9mstrike{CSI}0m {CSI}5mblink{CSI}0m {CSI}7m reverse {CSI}0m\r\n\r\n")
40
+
41
+ emit(" 256-color ")
42
+ for i in range(16, 16 + 42): # a slice of the 6x6x6 cube
43
+ emit(f"{CSI}48;5;{i}m {CSI}0m")
44
+ emit("\r\n\r\n")
45
+
46
+ emit(" Uncolored text stays phosphor green — color appears only\r\n")
47
+ emit(" where the program asks for it.\r\n\r\n press a key to exit › ")
48
+ readline()
49
+
50
+
51
+ def main():
52
+ ap = argparse.ArgumentParser(description="tappty color/SGR chart demo")
53
+ ap.add_argument("--snapshot", metavar="PNG", help="render headless and write a PNG")
54
+ ap.add_argument("--seconds", type=float, default=2.0, help="snapshot render-time cap")
55
+ args = ap.parse_args()
56
+
57
+ if args.snapshot: # must precede the pygame import
58
+ import os
59
+
60
+ os.environ.setdefault("SDL_VIDEODRIVER", "dummy")
61
+ os.environ.setdefault("SDL_AUDIODRIVER", "dummy")
62
+ os.environ.setdefault("PYGAME_HIDE_SUPPORT_PROMPT", "1")
63
+
64
+ from tappty import Session, pygame_ui
65
+ from tappty.pyte_terminal import PyteTerminal
66
+ from tappty.source import EngineSource
67
+
68
+ session = Session(PyteTerminal(64, 22), source=EngineSource(runner))
69
+ if args.snapshot:
70
+ base = args.snapshot[:-4] if args.snapshot.endswith(".png") else args.snapshot
71
+ pygame_ui.run(
72
+ session, None, title="tappty color chart", snapshot_path=base, max_seconds=args.seconds
73
+ )
74
+ else:
75
+ pygame_ui.run(session, None, title="tappty color chart")
76
+
77
+
78
+ if __name__ == "__main__":
79
+ main()