@epeer1/htui
v0.6.0
Published
Horizontal Terminal UI — turns terminal output into horizontally paged cards
Maintainers
Readme
htui is a local MCP server that gives your coding agent a real terminal: structured JSON output, parallel runs, and searchable history across the session. It also runs a read-only TUI (htui watch) so you can see every command the agent fires, live, in a sidebar terminal. Zero runtime dependencies, one command to install, works with any MCP-capable client.
htui — watch ~/work/api-server ╭ ● connected ╮ 3 cards ▶ follow agent
────────────────────────────────────────────────────────────────────────────────────────────────
╭─ ✔ npm test ──────────────╮ ╭─ ⠹ tsc --noEmit ──────────╮ ╭─ ✘ eslint . ──────────────╮
│ PASS src/parser.test.ts │ │ src/api.ts:42:7 │ │ │
│ PASS src/runner.test.ts │ │ error TS2345: Argument │ │ /src/cli.ts │
│ PASS src/store.test.ts │ │ of type 'string' is not │ │ 18:1 error Unexpected │
│ │ │ assignable to parameter │ │ console statement │
│ Tests: 24 passed, 24 │ │ of type 'number'. │ │ │
│ Snapshots: 0 total │ │ │ │ ✖ 1 problem (1 error, 0 │
│ Time: 1.84s │ │ Found 1 error in 1 file. │ │ warnings) │
│ │ │ │ │ │
├───────────────────────────┤ ├───────────────────────────┤ ├────────────────────────────┤
│ done 1.8s ⏎ 0 │ │ active 2.1s │ │ failed 0.4s ⏎ 1 │
╰───────────────────────────╯ ╰───────────────────────────╯ ╰────────────────────────────╯
● ○ ◉ 1–3 / 3
← → move Enter expand f follow / filter g/G jump q quitWhy htui
Your agent runs commands in your terminal. You can't see what it ran; it can't reuse what came back; both of you burn tokens parsing walls of ANSI.
- Structured output the agent can actually use.
htui_execreturns{ stdout[], stderr[], exitCode, durationMs, truncated }with ANSI stripped, CRLF normalized, progress bars collapsed. No more re-prompting the model to "ignore the spinner frames." - Real parallelism.
htui_runreturns acardIdimmediately. Firenpm test,tsc --noEmit, andeslint .at once, thenhtui_tailwhichever finishes first. Built-inrun_in_terminaltools block the agent's whole turn on a single command. - Search across the whole session.
htui_search "TS2345"regex-scans every card the agent has run this session and returns matching lines with cardId + line number. No re-running the suite, no re-reading 4 KB of logs.
Compatibility
Works with: GitHub Copilot in VS Code. Runs on: Windows · macOS · Linux · WSL. Requires: Node ≥ 18.
30-second install
# Pick one:
npm i -D @epeer1/htui # project-local (recommended)
npm i -g @epeer1/htui # global
npx @epeer1/htui init # zero-install
# Then, from your project root:
npx htui init --yes # writes .vscode/mcp.json + .github/copilot-instructions.md
# Reload VS Code (or restart your agent) to pick up the MCP server.
# Optional: watch what the agent is doing, in any side terminal:
npx htui watchWhat you get
What the agent sees
{
"ok": true,
"cardId": "c_8f2a",
"exitCode": 0,
"status": "done",
"durationMs": 1843,
"stdout": [
"PASS src/parser.test.ts",
"PASS src/runner.test.ts",
"Tests: 24 passed, 24 total"
],
"stderr": [],
"truncated": false,
"stdoutTotalLines": 18,
"stderrTotalLines": 0
}What you see
A new card slides in when the agent starts a command. Status turns green on exit 0, red on non-zero. Press Enter on any card to expand its full output.
Same data. Both audiences happy.
The 9 MCP tools
| Tool | Description | When to use |
|---|---|---|
| htui_exec | Run a command and wait. Returns stdout, stderr, exitCode, durationMs, cardId. | Quick one-shot, want the output |
| htui_run | Start a command in the background. Returns cardId immediately. | Long-running or parallel work |
| htui_tail | Block until a card has new output or finishes. | Poll a running command |
| htui_get | Fetch card output by cardId. Supports range: [start, end], stream: 'stdout' \| 'stderr' \| 'stdin' \| 'both' \| 'all', and reports waitingForInput / waitingReason / idleSinceMs / stdinOpen. | Need everything one card produced, or check whether it's blocked at a prompt |
| htui_search | Regex / substring search across cards. | Find an error you saw earlier |
| htui_list | List cards with status, title, exit code, duration. | "Which tests have I run?" |
| htui_kill | Terminate an active card (SIGTERM / SIGKILL; Windows uses taskkill /T /F). | Cancel a runaway process |
| htui_send | Write a line to an active card's stdin. { cardId, input, appendNewline?, secret? }. | Answer a prompt: OTP, [y/N], password |
| htui_summary | Counts by status plus the 5 most recent cards. | Status check at end of turn |
Interactive prompts (npm publish OTP, gh auth login)
For commands that pause and ask for input, htui_run keeps the child alive across tool calls. Poll htui_get to detect the wait, then reply with htui_send:
// 1. start the command in the background
htui_run({ command: "npm publish", cwd: "/repo" })
// → { ok: true, cardId: "c_5", startedAt: ... }
// 2. poll roughly every 2s
htui_get({ cardId: "c_5", tailLines: 20 })
// → { ..., waitingForInput: true, waitingReason: "prompt",
// idleSinceMs: 1900, stdinOpen: true,
// lines: [..., { stream: "stdout", text: "This operation requires a one-time password." },
// { stream: "stdout", text: "Enter OTP: " }] }
// 3. ask the user for the OTP, then send it. Treat OTPs / passwords as secrets.
htui_send({ cardId: "c_5", input: "123456", secret: true })
// → { ok: true, bytesSent: 7 }
// (stored stdin echo is "[input redacted, 7 bytes]" — the value never lands in the buffer.)
// 4. keep polling until the card terminates.Same pattern works for gh auth login, sudo, ssh host-key prompts, and any [y/N] confirmation. htui_send returns errorCode: 'card-not-active' if the child already exited and 'stdin-closed' if its stdin pipe is closed.
CLI commands
| Command | What it does |
|---|---|
| htui init | Configure MCP, Copilot instructions, and the watch script for VS Code. --yes / -y / --no-prompt skips the watch prompt. |
| htui mcp [--workspace <path>] | Run as an MCP stdio server. Normally invoked by your agent, not by humans. |
| htui watch [--workspace <path>] | Live TUI view of agent terminal activity. |
| htui exec "<command>" | Fallback: run a command and print a structured JSON result. |
| htui --api | Legacy: interactive JSON-over-stdio API for scripted agents. |
htui init auto-detects how htui is installed and writes one of these command entries into .vscode/mcp.json:
Global: <abs-path-to-node> <abs-path-to-cli.js> mcp --workspace ${workspaceFolder}
Local: node node_modules/@epeer1/htui/dist/cli.js mcp --workspace ${workspaceFolder}
npx: npx -y @epeer1/htui mcp --workspace ${workspaceFolder}For global installs, htui resolves both the Node binary and its own CLI script to absolute, symlink-followed paths so the MCP client can spawn it without relying on inherited PATH (e.g. when VS Code is launched from Finder or the Dock on macOS). Re-run htui init if you switch Node versions.
An existing .vscode/mcp.json is merged in place: other MCP servers are preserved, the file's tab/space indent is preserved, and JSONC comments are stripped on parse.
htui watch controls
| Key | Action |
|---|---|
| ← → | Move selection |
| Enter | Expand focused card |
| Esc | Collapse expanded view |
| f | Toggle follow (auto-jump to newest) |
| g / G | Jump to first / last card |
| / | Filter by title; Esc clears |
| q / Ctrl-C | Quit |
How it works
htui mcp runs as a stdio JSON-RPC server. Each command becomes a card in an in-process ring buffer (200 cards × 5000 lines each); cards are streamed to any attached htui watch client over a per-workspace local socket (named pipe on Windows, Unix domain socket under $XDG_RUNTIME_DIR elsewhere). Process trees are killed cleanly via POSIX signals or taskkill /T /F on Windows.
Configuration
--workspace <path>— override the workspace root (otherwise CWD).HTUI_WORKSPACE— same, as env var. Used byhtui watchto find the right socket.HTUI_NO_ANIM=1— disable card slide-in / pulse animations inhtui watch.
FAQ / Troubleshooting
- Agent doesn't see the htui tools. Reload your editor window after
htui initso the MCP server is spawned. In VS Code: Developer: Reload Window. htui watchsays "waiting for agent". The MCP server is started on demand by your client. Open Copilot Chat once — that triggers the spawn — andwatchwill connect.- Does it work in WSL? Yes. Run htui inside the WSL distro; it behaves as Linux and uses
$XDG_RUNTIME_DIRfor the socket. - Can two devs share one watch session? No. Sockets are scoped to the local user and workspace by hash; there's no network transport.
License
MIT — see LICENSE.
