Noureddine RAMDI / headless-terminal: programmatic control and synchronization of interactive TUIs in Go

Created Mon, 04 May 2026 10:23:01 +0000 Modified Sat, 23 May 2026 20:41:27 +0000

montanaflynn/headless-terminal

Interactive terminal user interfaces (TUIs) like vim, emacs, and htop are notoriously tricky to automate. The challenge is more than just sending keystrokes — you need accurate terminal emulation, state tracking, and crucially, synchronization to know when the UI has finished rendering. headless-terminal (ht) tackles this problem head-on by combining a real pseudo-terminal, precise VT parsing, and multiple wait strategies baked into a Unix-socket daemon architecture.

What headless-terminal does and how it’s built

headless-terminal is a Go command-line tool designed to provide programmatic control over interactive TUIs. It manages these TUIs by spawning processes attached to real pseudo-terminals, ensuring they behave exactly as if run in a normal terminal. This is vital because many TUIs detect terminal capabilities and behave differently without a proper pseudo-terminal.

Under the hood, ht runs a Unix-socket daemon that maintains pseudo-terminal sessions for each managed process. The output from these sessions is piped through libghostty-vt, a VT parsing library originally developed for the Ghostty terminal emulator. This library accurately interprets VT sequences — including cursor movements, scrollback, and style changes — allowing ht to track the terminal state with fidelity.

The project is written in Go, but relies on a two-phase build process involving CMake and Zig to compile the libghostty-vt static library. The final binary is statically linked (~6MB), depends only on libc, and targets Unix-like systems. The architecture cleanly separates terminal session management, VT parsing, and synchronization logic.

The synchronization problem: how ht drives TUIs reliably

What sets headless-terminal apart is its thoughtful handling of the synchronization challenge between sending keystrokes and knowing when the interface has fully rendered the resulting state. This race condition is a common stumbling block in TUI automation.

ht offers three waiting strategies:

  • Default pacing: waits 20ms between keystrokes, a simple throttle that works well for many apps.
  • –wait-duration: a fixed duration wait after sending input.
  • Composable condition waits like –wait-text and –wait-idle, which allow scripts to wait for specific text to appear or for the terminal to be idle.

This flexibility is critical because TUIs vary widely in rendering speed and behavior. The VT parsing engine ensures that cursor positions and style changes are tracked, so these wait conditions have accurate terminal state information to act on. The synchronization primitives give driver scripts a reliable way to know when it’s safe to continue sending input or capturing output.

The default pacing of 20ms per keystroke is a practical tradeoff between speed and reliability. More aggressive pacing risks missing intermediate renders and losing state accuracy. The composable waiting conditions provide fine-grained control when more precision is needed.

The codebase includes an agent skill for integration with AI coding assistants like Claude Code, Codex, and Cursor, supporting vim-style key notation and multiple wait strategies out of the box. This shows the project’s focus on practical automation use cases.

Quick start with headless-terminal

You can install the prebuilt binary on macOS or Linux from the releases page or via Homebrew:

brew install montanaflynn/tap/ht

Or download and install manually:

macOS (Apple Silicon)

curl -L https://github.com/montanaflynn/headless-terminal/releases/latest/download/ht-v0.1.0-darwin-arm64.tar.gz | tar xz
sudo mv ht /usr/local/bin/

Linux (x86_64)

curl -L https://github.com/montanaflynn/headless-terminal/releases/latest/download/ht-v0.1.0-linux-amd64.tar.gz | tar xz
sudo mv ht /usr/local/bin/

Linux (arm64)

curl -L https://github.com/montanaflynn/headless-terminal/releases/latest/download/ht-v0.1.0-linux-arm64.tar.gz | tar xz
sudo mv ht /usr/local/bin/

If you prefer to build from source, you’ll need Zig 0.15.2, CMake, pkg-config, and Go 1.22 or newer:

git clone https://github.com/montanaflynn/headless-terminal
cd headless-terminal
make build

The build process compiles libghostty-vt with Zig and CMake, then links it statically with Go via cgo.

verdict: who should consider headless-terminal

headless-terminal is relevant for developers and automation engineers who need reliable, programmatic control of interactive TUIs. Its real pseudo-terminal sessions combined with accurate VT parsing address the common pitfalls in driving terminal apps.

The synchronization primitives are especially valuable if you’ve struggled with race conditions and flaky automation scripts. The tradeoff is a slightly larger binary (~6MB) and some complexity in the build process due to the Zig and CMake dependencies.

While the tool targets Unix-like systems and depends on libc, the static linking and minimal runtime dependencies make it straightforward to deploy. The built-in agent skill for AI coding assistants hints at emerging use cases in AI-driven CLI automation.

If your work involves scripting interactions with vim, emacs, htop, or other TUIs and you’ve found existing tools lacking in synchronization or terminal fidelity, headless-terminal is worth a serious look. It’s not a drop-in replacement for all terminal automation tools but offers a solid foundation for robust TUI driving under the hood.


→ GitHub Repo: montanaflynn/headless-terminal ⭐ 92 · Go