Files
baudshell/CLAUDE.md
T
2026-09-04 15:04:37 -04:00

74 lines
3.4 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
A tool that runs an interactive shell behind a simulated serial (baud-rate) throttle,
so that terminal output animates one character at a time instead of appearing all at
once (e.g. to replay old VT100/ANSI animations that a fast terminal would render
instantly).
- `baudshell` — the program. Pure Python 3, self-contained: it launches `bash --norc`
in a PTY and throttles **both** directions of I/O in-process (no helper binary, no
`cc` build step).
- `baud.c` — an *optional* standalone C pipe filter with the same throttling idea
(reads stdin, re-emits it one char per `10/baud` seconds to stdout). It is **not**
used by `baudshell`; it exists for the "throttle a piped animation file" use case,
e.g. `cat anim.ans | baud 9600`. It compiles independently (`cc baud.c -o baud`);
the `baud` binary it produces is gitignored.
There is no build system, test suite, linter, or CI. Everything besides the two source
files (the `baud` binary, `.DS_Store`) is in `.gitignore`.
## Run
No build step is needed for the shell:
```sh
./baudshell # or: python3 baudshell
./baudshell 2400 # simulate 2400 baud instead of the default 9600
```
`argv[1]`, if given, is the **baud rate** (a positive integer; default 9600, lower is
slower). A non-integer or non-positive rate prints an error and exits 2.
The program puts the terminal into raw mode and restores it on exit, so keystrokes
reach the shell char-by-char. To leave, type `exit` in the shell (or close the
terminal); the program detects the shell's exit and cleans up.
## Architecture
`baudshell` multiplexes two file descriptors with `select()`: the terminal
(`sys.stdin`) and the PTY master. The single subprocess is `bash` (in its own session
via `preexec_fn=os.setsid`, on the PTY slave); the throttle itself runs in-process.
```
keyboard ─► fd_term ─► throttled_copy ─► master ─► shell (bash)
│
terminal ◄─ fd_term ◄─ throttled_copy ◄─ master ◄┘
```
- **Throttling is `throttled_copy(src, dst, baud)`**: read a chunk from `src`, then
write it to `dst` **one byte at a time, sleeping `10/baud` seconds between bytes**.
This runs for both directions — keystrokes are throttled into the shell, and the
shell's output is throttled out to the terminal, mirroring a real serial link.
- `delay_per_char(baud)` returns `10/baud` seconds. A serial byte is 10 bits
(1 start + 8 data + 1 stop), so a line at `baud` bits/s carries `baud/10` bytes/s;
this is the same formula the original `baud.c` used.
### End-of-stream / clean shutdown (the reason the old version hung)
The loop breaks as soon as either copy returns `True` (stream finished), which is how
the two termination cases are handled:
- **Shell exits** (`master` reaches EOF) → stop, then reap the shell and propagate its
exit status. This is the fix for the original bug: the previous version ignored the
copy result and spun forever after the shell exited.
- **stdin reaches EOF** (terminal closed / piped input drained) → stop, and
`shell.terminate()` kills the still-running shell.
A `finally` block always restores the terminal's termios settings. Throttle math:
rate must be `> 0`; unlike the C original, Python's `time.sleep` has no upper bound, so
very low rates are accepted rather than rejected.