Skip to main content

Host a program on a pseudo-terminal, then observe/control/render it (CUI or GUI)

Project description

tappty

Host a program on a pseudo-terminal, then observe, control, and render it — in a plain terminal (curses, CUI) or a green-phosphor window (GUI).

Software Architecture, Design & Engineering by Nicholas J. Kisseberth.
Code Synthesized via Anthropic Claude Code / Opus 4.8.
Code Review by OpenAI Codex / ChatGPT 5.5.

tappty is a small instrumented-terminal toolkit. A program's bytes (a subprocess on a PTY, or any in-process runner) flow into a fixed-size character Terminal; a Session fans that output out to any number of observers and routes input back. A renderer is just one more observer/controller — which is what lets a human and an automated client watch and drive the very same session. Several sessions tile into one window via the compositor.

Install

pip install tappty          # core (CUI works out of the box)
pip install 'tappty[sdl]'   # add the SDL/GUI window (installs pygame-ce)
pip install 'tappty[gl]' # add the OpenGL window (arcade; an alternative GUI backend)
pip install 'tappty[web]'    # add the browser renderer for --web (websockets)
pip install 'tappty[video]'  # render recordings to mp4/gif via --render (bundles ffmpeg)
pip install 'tappty[ansi]'  # add the full-ANSI/VT100+ backend (pyte) for --ansi
pip install 'tappty[win]'   # Windows: add the ConPTY source (pywinpty)
# from a checkout:
pip install -e '.[sdl,ansi,dev]'

pip install tapterm works too — it's a convenience alias that just pulls in tappty (which provides the tapterm command). The library, the extras, and the docs all live under tappty.

The tapterm program

tapterm                    # a regular terminal: your $SHELL, full-ANSI + raw keys
tapterm -e vim file        # run a command instead of the shell, xterm-style (or: -- vim file)
tapterm -geometry 100x30   # xterm-style size; -T/-title sets the title, -cd DIR the working dir
tapterm --cui -- bash      # force the curses character UI (takes over this terminal)
tapterm --gui -- bash      # force the SDL green-phosphor window (the 'sdl' extra)
tapterm --arcade -- bash   # same, on the arcade/OpenGL stack (the 'gl' extra)
tapterm --web -- bash      # serve it in a browser (the 'web' extra); open http://127.0.0.1:8023/
tapterm --headless -- ls   # run to completion, print the final screen (scripting/CI)
tapterm --cooked -- bash   # line-oriented instrument mode (local echo on the VT52 grid)
tapterm --play rec.cast    # replay a .cast / .ttyrec / .ans / .3a recording (--speed N, --loop)
tapterm --record out.cast -- bash       # record a session as you use it
tapterm --play rec.cast --render rec.mp4 # render a recording to a video (mp4/webm/gif)
tapterm --render rain.mp4 --seconds 5 -- cmatrix  # render a live program straight to video
tapterm --no-pty -- ls     # host over plain pipes, no pty (cross-platform, incl. Windows)

An interactive session behaves like a real terminal — full-ANSI rendering (the ansi extra, pyte) plus raw keys, so colors, line-editing, arrows, and full-screen apps work, and the window closes when the program exits, like xterm. Pass --cooked for the line-oriented instrument default instead (local echo on the dependency-free VT52 grid) — what the observe taps and the bus CMD capture expect. xterm-style flags are accepted where they fit: -e, -T/-title, -geometry, -cd, -hold.

--cui works anywhere; --gui needs the sdl extra. With no mode flag, tapterm picks GUI when the sdl extra is installed and a display is available (else CUI) — so it won't try to open a window over SSH/cron. On Windows the pty path uses ConPTY (the win extra), which emits VT100+.

Every flag, the modes, recordings, snapshots, recipes, and troubleshooting are in the tapterm user's guide.

Library

from tappty import Session, Terminal, PtySource, curses_ui

sess = Session(Terminal(cols=80, rows=24))
sess.source = PtySource(["bash"])
sess.claim_control("local", "human")
curses_ui.run(sess, None, title="bash")

Full API — classes, signatures, the observe/control contract, and worked examples — is in docs/REFERENCE.md.

The pieces:

  • Terminal / PyteTerminal — the screen model. Terminal is a fixed-size character grid (VT52 spirit: wrap/scroll, the common control chars, a handful of VT52 escapes), with scrollback and no deps. PyteTerminal is a drop-in full-ANSI/VT100+ backend (wraps pyte, the ansi extra) for programs that speak modern ANSI; same read interface (plus a cells() view of per-cell SGR color), so the GUI renderers show color while the rest is unchanged.
  • Source / PtySource / EngineSource / CastSource / TtyrecSource / AnsSource / ThreeASource / PipeSource / ConPtySource — byte producers. PtySource runs an external command on a real pty (POSIX); EngineSource wraps any in-process runner(emit, readline) callable; CastSource / TtyrecSource replay a recorded .cast / .ttyrec session, and AnsSource / ThreeASource play .ans / .3a art, through the same pipeline (original timing, speed/loop; replay_source(path) picks by extension); PipeSource hosts a command over plain pipes (no pty, any OS); ConPtySource hosts one on a Windows pseudo-console (ConPTY, the win extra).
  • Session — hosts a Source, drives the Terminal, and exposes observe taps (on_stream, on_frame, on_event) and control (send_input, feed_key) plus a talking-stick arbitration so exactly one controller types at a time.
  • Recorder / render_videoRecorder writes the session's output stream to a .cast or .ttyrec recording as it runs (the inverse of the replay sources; tapterm --record); render_video encodes a recording to a real video file (mp4/webm/gif) via ffmpeg, with size/zoom/font/speed and an area-of-interest crop (tapterm --play X --render out.mp4).
  • BusServer / BusClient — the same observe/control contract over a Unix-domain socket or TCP (a (host, port) tuple — works on Windows too), so an out-of-process client (a logger, an automated driver, a remote renderer) can attach to a session. It's a terminal control plane — trusted-local: the Unix socket is owner-only, TCP is loopback-only unless allow_remote=True, and a token= adds an optional shared-secret gate. Not a substitute for a tunnel on an untrusted network (no TLS).
  • curses_ui / pygame_ui / arcade_ui / web_ui — renderers; each exposes run(session, runner, title=…). pygame_ui and arcade_ui are two backends for the same green-phosphor window (SDL, or arcade/OpenGL); web_ui serves it in a browser over a WebSocket (the web extra).
  • compositor — tile several panels (SessionBacking for in-process, BusBacking for remote) in one GUI window, with per-tile pan/zoom and focus.

Demos & examples

demos/ holds runnable showpieces — the SGR color chart, the green digital rain, the compositor "mission control", drive_vim (a program driving a live vim over the control tap), and web_demo (the browser renderer). examples/ holds short, commented API examples — the observe taps, writing a custom Source, driving a session over the bus, and watch_and_drive (a bot that reads the output stream and types its decisions back). The gallery has screenshots and a clip of each.

Platform

The core (Terminal/Session/taps/talking-stick), EngineSource, CastSource, PipeSource, the renderers, and the TCP bus are cross-platform. PtySource uses pty/termios (POSIX-only); on Windows, host via ConPtySource (the win extra, ConPTY — paired with --ansi) or PipeSource (--no-pty), and use the TCP bus rather than a Unix socket. The Windows ConPTY path is implemented but not yet exercised on real Windows — finishing it is open work (see docs/DESIGN.md §11). The GUI needs a display; the CUI needs a terminal (and, on Windows, the win extra's windows-curses); --headless needs neither.

Documentation

The rendered docs site is at nyxcraft.github.io/tappty (built from docs/ by gh-pages/build_site.py and published via GitHub Actions). The sources:

  • docs/TAPTERM.md — the tapterm command in depth: every flag, the CUI / GUI / headless modes, the terminal model, recordings, snapshots, recipes, and troubleshooting. For using tapterm.
  • docs/REFERENCE.md — the programming/API reference: every public class and method with signatures, the observe/control contracts (snapshot dict, events, roles), and worked examples. For building on the library.
  • docs/DESIGN.md — the architecture: the Source → Terminal → Session → renderer/bus pipeline, the concurrency and security/trust models, and the design rationale. For modifying tappty.
  • CHANGELOG.md — dated history, newest first: when tappty started, when it first worked, and what's changed since. The remaining open work (publish to PyPI; verify Windows) is in docs/DESIGN.md §11.

Tests & tooling

pip install -e '.[dev]'            # quick: core suite (pyte + GUI tests skip)
pytest

pip install -e '.[dev,ansi,sdl]'   # full: also the ANSI backend + headless GUI smoke
pytest

ruff check src tests               # lint (E,F,W,I,B,UP); must be clean
ruff format src tests              # format (line-length 99, black-style)

# no install needed, straight from a checkout:
PYTHONPATH=src python3 -m tappty.cli --headless -- echo hello

Without the ansi/sdl extras, pytest skips the pyte and GUI tests (so it reports fewer passes plus a couple of skips); install .[dev,ansi,sdl] to run the whole suite. CI runs ruff + the full matrix on Python 3.9–3.13.

License

MIT © Nicholas J. Kisseberth. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tappty-0.1.0.tar.gz (1.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tappty-0.1.0-py3-none-any.whl (75.1 kB view details)

Uploaded Python 3

File details

Details for the file tappty-0.1.0.tar.gz.

File metadata

  • Download URL: tappty-0.1.0.tar.gz
  • Upload date:
  • Size: 1.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tappty-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b6fc3be56a9380a965ee6a1d2082e9e746778826d859fa9da8d266831558f808
MD5 b971bbab0ae86ed7c7631cac2b01843e
BLAKE2b-256 9b448453b72778fd2bb4f149cbd752925e21949a252b0415381bc4ef1a61d1dd

See more details on using hashes here.

Provenance

The following attestation bundles were made for tappty-0.1.0.tar.gz:

Publisher: publish.yml on nyxcraft/tappty

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tappty-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tappty-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 75.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tappty-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 033a2b21b5298fec027949632789981c29bf3881b43e23e755c498b92def832d
MD5 4243825a1f7dcf710fadbf7b3c1c5d37
BLAKE2b-256 956908dd5bad12ee54d5009b72ee88b573a8bc5e6cdb7d47e336d14214113f2f

See more details on using hashes here.

Provenance

The following attestation bundles were made for tappty-0.1.0-py3-none-any.whl:

Publisher: publish.yml on nyxcraft/tappty

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page