>_STRYKE-TERMINAL
pyte, one stryke pipe away. A headless VT100 / VT220 / TERM=linux terminal emulator for stryke — a faithful port of pyte. Feed it the raw byte stream a program writes (colors, cursor moves, erases, scroll regions, insert/delete, charsets, titles) and it maintains a full screen model: the character grid, cursor, per-cell colors and attributes, terminal modes, scroll margins, tab stops, and scrollback history. Then read the rendered screen instead of escape-laden bytes. The stryke side is a thin JSON-over-FFI wrapper; the emulator lives in the libstryke_terminal cdylib and sessions persist across calls.
Why this exists
strykelang already ships Tcl/Expect-style PTY automation as built-in functions — pty_spawn, pty_read, pty_send, pty_expect, pty_close, pty_strip_ansi. Those hand you the raw bytes a program writes, with only a dumb pty_strip_ansi to make sense of them — no cursor movement, no erase, no scroll, no insert/delete, no color/attribute state, no scrollback.
stryke-terminal is the missing piece: the screen model those bytes render into. Drive htop / vim / less headlessly through the pty_* builtins, pump their output into a Terminal, and read the exact screen a human would see. It does not re-implement PTY spawning — it consumes pty_read output.
pty_spawn("htop") —bytes—> Terminal::feed —> Screen model —> Terminal::display
(strykelang builtin) (this package) (grid+cursor+SGR) (rendered lines)
Install
The emulator is pure Rust — no network, no OS-side build deps beyond a Rust toolchain.
s pkg install -g github.com/MenkeTechnologies/stryke-terminal
Pin a specific release:
s pkg install -g github.com/MenkeTechnologies/stryke-terminal@v0.1.0
This fetches the prebuilt tarball for your host triple (aarch64-apple-darwin, x86_64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu), verifies its SHA-256, extracts into ~/.stryke/store/stryke-terminal@<version>/, and registers the cdylib for use Terminal. Verify the whole stack, permission-free, with terminal-test.
Quick start
Parse a stream of terminal output and read the rendered screen:
use Terminal
val $t = Terminal::new(columns => 80, lines => 24)
# feed the kind of bytes a program emits: colored text + a cursor move
Terminal::feed($t, "\x1b[1;32mBUILD OK\x1b[0m")
Terminal::feed($t, "\x1b[3;1Hline three")
for $line (Terminal::display($t)) {
p $line # the rendered screen, one string per row
}
val $cell = Terminal::cell($t, 0, 0)
p "$cell->{data} fg=$cell->{fg} bold=$cell->{bold}" # B fg=green bold=1
Terminal::destroy($t)
Driving a real program headless
Combine the strykelang pty_* builtins (which spawn and talk to the process) with Terminal (which renders what it draws). This is what "control a full terminal headless" means:
use Terminal
val $h = pty_spawn("vim -u NONE") # strykelang builtin — allocates a PTY
val $t = Terminal::new(columns => 80, lines => 24)
Terminal::drain($t, $h, 1) # pump pty_read -> Terminal::feed until idle
pty_send($h, "ihello\x1b") # type into vim
Terminal::drain($t, $h, 1)
p Terminal::display($t) # what vim's screen now shows
# some programs query the terminal (DA / DSR / cursor position); forward the
# emulator's replies back to the child so they keep working:
pty_send($h, Terminal::take_input($t))
pty_send($h, ":q!\x0d")
pty_close($h)
Terminal::drain is the core loop of headless terminal automation: spawn, drain, read the screen, act, repeat.
API surface
Every function takes a session id $t from Terminal::new. Full reference in the README.
| Terminal::new(columns, lines, kind, history, ratio) | create a session → id (kind => "history" for scrollback) |
| Terminal::feed($t, $data) | feed terminal output into the parser |
| Terminal::feed_bytes($t, $base64) | feed raw bytes when output is not valid UTF-8 |
| Terminal::display($t) | rendered screen, one string per line |
| Terminal::cell($t, $x, $y) | { data, fg, bg, bold, italics, underscore, strikethrough, reverse, blink } |
| Terminal::buffer($t) | full styled matrix (rows of cells) |
| Terminal::cursor($t) | { x, y, hidden, attrs } |
| Terminal::dirty($t) / clear_dirty($t) | changed line numbers / clear the set |
| Terminal::title($t) / icon_name($t) | OSC-set strings |
| Terminal::take_input($t) | drain device-report replies to forward with pty_send |
| Terminal::prev_page / next_page / history | scrollback paging + state (history sessions) |
| Terminal::pump / drain ($t, $pty, $timeout) | read one chunk / drain to EOF from a pty_spawn handle |
| Terminal::dis($data) | disassemble an escape string → [name, [args], {kwargs}] events |
Direct screen commands (drive the grid without escape sequences) are also exposed: draw, cursor_position, the cursor_* family, erase_in_line / erase_in_display, insert_lines / delete_lines, insert_characters / delete_characters, select_graphic_rendition (alias sgr), set_mode / reset_mode, set_margins, define_charset, and more.
Fidelity
This is a faithful port of pyte 0.8.2 — VT100 / VT220 / TERM=linux. The test suite generates golden fixtures by feeding the reference pyte the same inputs and asserts the Rust port reproduces the display, cursor, styled buffer, titles, device reports, and scrollback byte-for-byte. See the engineering report for coverage.