headless terminal CLI
CLI and skill that drive TUIs from an agent: runs vim, htop or a REPL in a background session, sends keys and snapshots the screen
- 122 ★ on the repository
- Last commit
- MIT
Broad access
Why this level
- Starts arbitrary programs and types keystrokes into them, including a shell.
Install
- Works in
Manual install
brew install montanaflynn/tap/htThe first command of the Install section. The skill follows: `npx skills add montanaflynn/headless-terminal --skill headless-terminal`.
Access and checks
How we reviewBroad accessThe tool can change data, run commands or handle secrets.
- Starts arbitrary programs and types keystrokes into them, including a shell.
This is third-party code. Review the repository files before installing.
What it does
headless terminal (the `ht` command) works as Puppeteer for terminal user interfaces. A Unix-socket daemon holds a pseudo-terminal per session and pipes output through libghostty-vt, the emulator from the Ghostty app. A program starts in the background, the agent sends keystrokes, waits for the redraw to finish and gets a screen snapshot as text or PNG. Examples: `ht run --name notes vim /tmp/notes.md` starts a session, `ht send notes "ihello from an agent<Esc>:wq<CR>" --view` types text and saves the file, `ht remove notes` deletes the session. Keys use vim-style notation (`<CR>`, `<Esc>`, `<C-c>`). The `--wait-idle 200ms` flag waits for output to settle and `ht view demo --format png` returns an image. This is how an agent handles `git add -p`, `gh auth login`, `create-next-app`, debuggers and vim itself.
Who it is for. Developers who want an agent to work with interactive terminal programs, and TUI testers.
Good fit when
- An agent must go through an interactive `git add -p` or `gh auth login`.
- Editing a file in vim or emacs must be automated.
- CI checks that a TUI renders as expected.
- A PNG screenshot of a terminal program is needed from a script.
Not a fit when
- Ordinary non-interactive commands are enough.
- You are on Windows: builds exist for macOS and Linux.
- The agent must not be allowed to run arbitrary programs in a terminal.
Example request
Start vim in a background session, type text into the file, save it and show the screen before exiting.Limitations
Prebuilt binaries exist for macOS (arm64) and Linux (amd64, arm64) and none for Windows. The daemon and client talk over a Unix socket. Through `ht` an agent can start any program and type any keys into it, including a shell, so the agent's rights on the machine are limited by the client's permission system and not by `ht` itself. Sessions stay in the background until removed (`ht remove`), so do not leave them with open access to secrets. The skill installs via `npx skills add` or by copying into Claude Code. The license is MIT.
How to disable. Remove sessions with `ht remove <name>`, the `ht` binary through `brew uninstall ht` (or by hand from `PATH`) and the skill folder from the skills directory.
FAQ
How do I install ht?
Run `brew install montanaflynn/tap/ht`. Release archives exist for darwin-arm64, linux-amd64 and linux-arm64 and are unpacked with `tar xz`.
How do I connect the skill?
With `npx skills add montanaflynn/headless-terminal --skill headless-terminal`, which picks per-agent paths for Claude Code, Codex, Cursor and Gemini. The fallback is to drop the folder into Claude Code by hand.
How is it different from running a command?
It creates a pseudo-terminal, tracks cursor and styles and can wait for redraws, which a shell without a terminal cannot.
Can a session be watched?
Yes. A session can be watched live from another shell.
What does it use to parse output?
libghostty-vt, the VT parser from the Ghostty emulator.
Discussion
No questions or reviews yet.
Related
CLI toolsA macOS terminal with the termio CLI and a skill: agents spawn sibling sessions and you see which one needs your answer
The tty7 skill teaches Claude Code and Codex to drive tty7 terminal panes: run commands in a real PTY, read the screen and hand work to another agent
term-cli runs interactive programs for an agent inside tmux sessions, while a human enters passwords and MFA through term-assist.
Rust CLI with a terminal switcher across coding agents: list, readiness check, active harness selection and command preview
A terminal shell for working with coding agents: embedded terminals, diffs, files, td tasks and worktrees side by side
An extension for Pi that runs vim, psql, ssh and other interactive CLIs in an overlay you can watch and take over.
README in short
The repository contains a Go CLI, a daemon and a skill with recipes and a key reference. Examples cover vim, CI checks and screenshots.
README badge
Original SKILL.md
--- name: headless-terminal description: Drive interactive terminal programs (vim, emacs, nethack, htop, CLI installers, REPLs, anything curses-based) headlessly via the `ht` CLI. Use when the task needs a program that expects a real TTY — won't run under plain pipes, draws to an alternate screen, or needs keystrokes like arrow keys, Ctrl-C, or function keys. Do not use for non-interactive commands that work fine with plain shell pipes. license: MIT compatibility: Requires the ht CLI (v0.1.0+) on macOS (Apple Silicon) or Linux. metadata: author: Montana Flynn version: "1.0" homepage: https://github.com/montanaflynn/headless-terminal --- # ht — headless terminal `ht` is a daemon that owns a pseudo-terminal per session and parses output with the same VT engine Ghostty uses. You can launch a TUI, send keystrokes, snapshot the rendered screen, and block until a screen condition is met. ## Install ht Check if the CLI is on `PATH`; if not, install it: ```sh command -v ht || brew install montanaflynn/tap/ht ``` Without Homebrew, grab a tarball from the [releases page](https://github.com/montanaflynn/headless-terminal/releases) and move the binary onto `PATH`. macOS (Apple Silicon) and Linux (x86_64/arm64) only. ## When to reach for this - Target program draws to the alternate screen / uses (n)curses / reads `$TERM`. - Needs real keystrokes (arrow keys, `<C-c>`, `<F5>`), not just stdin text. - You need to inspect the *rendered* screen state, not the raw output byte stream. If the program works with `echo ... | cmd`, use a plain pipe — don't reach for ht. ## Core workflow ``` ht run --name S <cmd...> # launch, prints session ID ht send S "<keys>" # type into it ht view S # snapshot screen (plain text) ht wait S --text "READY" # block until a condition ht stop S && ht remove S # cleanup ``` `ht run` prints a short hex session ID. Use `--name` to label it and reference the session by that name everywhere else — it's easier than juggling IDs. ## The one thing agents get wrong Keystrokes reach the PTY before the program has finished rendering a response. **Do not `view` immediately after `send`** — you'll snapshot a stale screen. Fold the wait into the send: