[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-3472":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":13,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":10,"pushedAt":10,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":15,"starSnapshotCount":15,"syncStatus":14,"lastSyncTime":25,"discoverSource":26},3472,"headless-terminal","montanaflynn\u002Fheadless-terminal","montanaflynn","Headless terminal — puppeteer for TUIs (vim\u002Femacs\u002Fhtop\u002Fnethack) with a Go CLI backed by libghostty-vt","",null,"Go",111,1,2,0,7,42.1,"MIT License",false,"main",[],"2026-06-12 04:00:18","\u003Ch1 align=\"center\">headless terminal\u003C\u002Fh1>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"#examples\">Examples\u003C\u002Fa> ·\n  \u003Ca href=\"#install\">Install\u003C\u002Fa> ·\n  \u003Ca href=\"#agent-skill\">Agent Skill\u003C\u002Fa> ·\n  \u003Ca href=\"#commands\">Commands\u003C\u002Fa> ·\n  \u003Ca href=\"#architecture\">Architecture\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"screenshot.png\" alt=\"Claude Code driving nvim via ht\">\n\u003C\u002Fp>\n\nPuppeteer for terminal UIs. Drive `vim`, `emacs`, `htop`, `nethack`, or any\nother interactive TUI from a CLI (or an AI agent) — spawn the program in a\nbackground session, send keystrokes, snapshot the screen, and watch the whole\nthing live from another shell.\n\nUnder the hood: a Unix-socket daemon owns a pseudo-terminal per session and\npipes its output through [libghostty-vt](https:\u002F\u002Fgithub.com\u002Fghostty-org\u002Fghostty)\nfor VT parsing. You get the terminal emulation used by the Ghostty app, in a \ndetached, scriptable, snapshot-able form.\n\n## Why\n\nInteractive TUIs assume a human at a keyboard. If you want an agent — or a\ntest, or a debugger, or a recorder — to drive one, you need three things the\nshell doesn't give you:\n\n- A pseudo-terminal so the TUI thinks it's attached to a real tty.\n- A VT parser that tracks cursor position, scrollback, styles, etc. from the\n  TUI's output stream.\n- Synchronization primitives so the driver knows when the TUI has finished\n  redrawing.\n\n`ht` is those three things, wrapped in a simple CLI with a daemon.\n\n## Examples\n\n**Agentic coding.** Let an agent drive interactive CLIs it otherwise can't —\n  `git add -p`, `gh auth login`, `create-next-app`, REPLs, debuggers, even\n  `vim` for surgical edits.\n\n```shell\n# Start a headless vim session, returns a short session ID.\nht run --name notes vim \u002Ftmp\u002Fnotes.md\n\n# Drive it. Keys use vim-style notation (\u003CCR>, \u003CEsc>, \u003CC-c>, \u003CF1>, …).\nht send notes \"ihello from an agent\u003CEsc>:wq\u003CCR>\" --view # return view\n\n# Session exited and the file is saved:\ncat \u002Ftmp\u002Fnotes.md\n# → hello from an agent\n\n# Remove the session completely\nht remove notes\n```\n\n**CI tests for TUIs.** Script an interactive program in GitHub Actions:\n  run it, send keys, assert against the rendered screen (as text or a PNG\n  snapshot). Covers paths `expect`\u002F`pexpect` can't — alternate screen,\n  colors, cursor position.\n\n```shell\n# Boot a TUI, send keys, fail the build if the screen doesn't match.\nht run --name smoke vim \u002Ftmp\u002Fdemo.md\nht send smoke \"ihello from CI\u003CEsc>\" --wait-idle 200ms\nht view smoke | grep -q \"hello from CI\" || { echo \"render failed\"; exit 1; }\nht send smoke \":q!\u003CCR>\"\n```\n\n**Demo and doc generation.** Record keystroke-perfect asciicasts with\n  `ht record`, render to GIF via `agg`, or grab a one-shot PNG of the\n  current frame for a README or bug report.\n\n```shell\n# Drive a session, then grab a PNG of the current frame for a README or bug report.\nht run --name demo bash\nht send demo \"echo 'headless terminal'\u003CCR>\" --wait-idle 200ms\nht view demo --format png > screenshot.png\nht stop demo\n```\n\n**Follow-along debugging.** `ht watch` streams a session live to another\n  pane, so a human can shoulder-surf whatever an agent (or a detached\n  process) is driving — handy during skill development or pair debugging.\n\n```shell\n# Pane A: the watcher blocks until a matching session is created.\nht watch nethack-demo\n\n# Pane B (or an agent): create the session the watcher is waiting for.\nht run --size 78x46 --name nethack-demo nethack -u Claude\nht send nethack-demo \"y\" --wait-duration 150ms --view\n# (pane A now shows nethack, live)\n```\n\n## Install\n\n```shell\nbrew install montanaflynn\u002Ftap\u002Fht\n```\n\n\u003Cdetails>\n\u003Csummary>From release\u003C\u002Fsummary>\n\nGrab a tarball from the [releases page](https:\u002F\u002Fgithub.com\u002Fmontanaflynn\u002Fheadless-terminal\u002Freleases),\nor extract + install in place:\n\n**macOS (Apple Silicon)**\n\n```shell\ncurl -L https:\u002F\u002Fgithub.com\u002Fmontanaflynn\u002Fheadless-terminal\u002Freleases\u002Flatest\u002Fdownload\u002Fht-v0.1.0-darwin-arm64.tar.gz | tar xz\nsudo mv ht \u002Fusr\u002Flocal\u002Fbin\u002F\n```\n\n**Linux (x86_64)**\n\n```shell\ncurl -L https:\u002F\u002Fgithub.com\u002Fmontanaflynn\u002Fheadless-terminal\u002Freleases\u002Flatest\u002Fdownload\u002Fht-v0.1.0-linux-amd64.tar.gz | tar xz\nsudo mv ht \u002Fusr\u002Flocal\u002Fbin\u002F\n```\n\n**Linux (arm64)**\n\n```shell\ncurl -L https:\u002F\u002Fgithub.com\u002Fmontanaflynn\u002Fheadless-terminal\u002Freleases\u002Flatest\u002Fdownload\u002Fht-v0.1.0-linux-arm64.tar.gz | tar xz\nsudo mv ht \u002Fusr\u002Flocal\u002Fbin\u002F\n```\n\nBump the version segment when newer releases drop. The binary is ~6MB,\nstatically links `libghostty-vt`, and depends only on libc.\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>From source\u003C\u002Fsummary>\n\nRequires [Zig](https:\u002F\u002Fziglang.org) 0.15.2, CMake, pkg-config, and Go 1.22+.\n\n```shell\ngit clone https:\u002F\u002Fgithub.com\u002Fmontanaflynn\u002Fheadless-terminal\ncd headless-terminal\nmake build\n```\n\n`make` orchestrates two phases: CMake fetches [ghostty](https:\u002F\u002Fgithub.com\u002Fghostty-org\u002Fghostty)\nat a pinned commit and builds `libghostty-vt.a` with Zig; then Go builds\n`.\u002Fht` with cgo, linking that static lib via pkg-config.\n\n\u003C\u002Fdetails>\n\n\n## Agent Skill\n\nAn `ht`-aware skill lives in [`skills\u002Fheadless-terminal\u002F`](skills\u002Fheadless-terminal\u002F). It teaches an agent\nwhen to reach for `ht`, the vim-style key notation, the wait-strategy decision\ntree (the part agents get wrong), and common recipes.\n\nPreferred — [skills CLI](https:\u002F\u002Fskills.sh) (handles per-agent paths for\nClaude Code, Codex, Cursor, Gemini, etc.):\n\n```shell\nnpx skills add montanaflynn\u002Fheadless-terminal --skill headless-terminal\n```\n\nFallback — drop it into Claude Code directly:\n\n```shell\ncp -r skills\u002Fheadless-terminal ~\u002F.claude\u002Fskills\u002Fheadless-terminal\n```\n\nThe skill uses Anthropic's standard skills format — other agent frameworks\nthat consume the same layout can point their loader at `skills\u002Fheadless-terminal\u002F` or copy\nit into their equivalent directory. Progressive disclosure: only the short\n`SKILL.md` is always in context; reference docs load on demand.\n\n## Commands\n\n```\nht run \u003Ccmd...>       start a session (returns a session ID)\nht list               list sessions (table in a tty, JSON when piped)\nht view \u003Csid>         snapshot current screen (plain | ansi | html | png | json)\nht send \u003Csid> \u003Ckeys>  send keystrokes; optional --view \u002F --wait-* \u002F --rate\nht wait \u003Csid> ...     block until a condition is met\nht watch \u003Csid>        live-stream a session; blocks until it exists\nht record \u003Csid>       record session as asciicast (pipe to agg for GIFs)\nht stop \u003Csid>         graceful shutdown (SIGTERM, escalates)\nht kill \u003Csid>         immediate SIGKILL\nht remove \u003Csid>       delete an exited session's record\nht daemon [stop]      manual daemon control (normally auto-started)\n```\n\nSession IDs can be the short 8-hex ID, an unambiguous prefix, or `--name`.\n\n### Key notation\n\nVim-style. Literals pass through; angle-bracket specials are recognized:\n\n```\n\u003CCR> \u003CEnter> \u003CEsc> \u003CTab> \u003CBS> \u003CSpace> \u003CDel> \u003CIns>\n\u003CUp> \u003CDown> \u003CLeft> \u003CRight> \u003CHome> \u003CEnd> \u003CPageUp> \u003CPageDown>\n\u003CF1>…\u003CF12>\n\u003CC-x>          ctrl+x (letters a–z only)\n\u003CM-x>          meta\u002Falt+x  (alias \u003CA-x>)\n\u003CS-Tab>        shift+Tab\n\u003CC-M-x>        combine modifiers in any order\n\u003Clt>           literal '\u003C'\n```\n\n### Send synchronization\n\nThe keystroke-to-snapshot problem in a nutshell: the child process hasn't\nnecessarily processed your keys by the time the daemon returns. `ht send`\ngives you three ways to deal with this:\n\n- **Pacing (default: 20ms\u002Fkeystroke).** The daemon writes one keystroke at a\n  time with a 20ms gap and a trailing gap. Fast TUIs reliably echo their\n  reaction in that window. Override with `--rate 10ms` or `--rate 0`.\n- **`--wait-duration DUR`**: a plain post-send sleep. Use when a single key\n  triggers slow work (character creation in nethack, emacs startup).\n- **`--wait-text`\u002F`--wait-cursor`\u002F`--wait-idle`\u002F`--wait-change`\u002F`--wait-exit`**:\n  block on a deterministic condition. Compose with AND — e.g.\n  `--wait-text READY --wait-idle 200ms` waits for `READY` to appear AND\n  output to be quiet for 200ms.\n\nAll of these are also available as standalone `ht wait` subcommand flags.\n\n### Exit codes\n\n| Code | Meaning |\n|---|---|\n| 0 | success |\n| 1 | runtime error (session missing, IO, daemon unreachable) |\n| 2 | usage error (bad flags) |\n| 3 | `wait` timeout |\n\n## Architecture\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│                         ht CLI                               │\n│  (run \u002F send \u002F view \u002F watch \u002F wait \u002F list \u002F stop \u002F …)        │\n└──────────────────────────────┬───────────────────────────────┘\n                               │  Unix socket, JSON lines\n┌──────────────────────────────┴───────────────────────────────┐\n│                        ht daemon                             │\n│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐        │\n│  │ Session      │  │ Session      │  │ Session      │   …    │\n│  │ ┌──────────┐ │  │ ┌──────────┐ │  │ ┌──────────┐ │        │\n│  │ │ PTY      │ │  │ │ PTY      │ │  │ │ PTY      │ │        │\n│  │ │ ↓        │ │  │ │ ↓        │ │  │ │ ↓        │ │        │\n│  │ │ vim\u002Fetc  │ │  │ │ nethack  │ │  │ │ emacs    │ │        │\n│  │ └──────────┘ │  │ └──────────┘ │  │ └──────────┘ │        │\n│  │ libghostty-vt│  │ libghostty-vt│  │ libghostty-vt│        │\n│  │ (grid)       │  │ (grid)       │  │ (grid)       │        │\n│  └──────────────┘  └──────────────┘  └──────────────┘        │\n└──────────────────────────────────────────────────────────────┘\n```\n\nEach session owns a PTY master, a libghostty terminal (the authoritative\nscreen model), and a set of subscriber channels for `ht watch`. All three\nare serialized behind a single mutex.\n","headless terminal 是一个用于控制终端用户界面（TUI）的工具，支持通过CLI或AI代理驱动如vim、emacs、htop等交互式程序。其核心功能包括在后台会话中启动程序、发送按键指令、截取屏幕快照，并允许从另一个shell实时观察整个过程。该工具基于Go语言开发，底层使用libghostty-vt库进行VT解析，提供伪终端以模拟真实tty环境，同时具备同步机制确保驱动程序与TUI之间的协调工作。适用于需要自动化测试、调试、记录或者由AI代理操作TUI的应用场景，比如CI\u002FCD中的自动化测试、生成文档示例以及远程调试等。","2026-06-11 02:54:37","CREATED_QUERY"]