@konaito/terminal-browser
v0.1.0
Published
A real browser inside your terminal — TypeScript reimplementation using kitty graphics protocol + CDP
Downloads
174
Readme
@konaito/terminal-browser
A real browser that lives inside your terminal. Pure TypeScript.
A clean-room reimplementation inspired by zenbu-labs/terminal-browser (MIT). Where the original ships Electron + a rust engine + a swift helper app as a 129MB macOS-only binary, this rebuilds the same idea with nothing but TypeScript, headless Chromium and terminal escape sequences — ~26KB on npm.
npx @konaito/terminal-browser open example.comHow it works
- Rendering — headless Chromium streams PNG frames over CDP
Page.startScreencast, and each frame is drawn straight into the terminal with the kitty graphics protocol. Frames are double-buffered over alternating image ids (draw new, then delete old) and unchanged frames are skipped, so the screen never flickers. - Mouse — SGR mouse reporting (
CSI ?1003h+?1006h) delivers clicks, motion and wheel events; cell coordinates are converted to pixels and forwarded as CDPInput.dispatchMouseEvent. - Keyboard — raw-mode stdin is parsed incrementally and forwarded as CDP
Input.dispatchKeyEvent.
No Electron, no rust, no swift. Everything the terminal already provides.
Requirements
- A kitty-graphics-capable terminal (ghostty / kitty / WezTerm / VS Code 1.80+ …)
- Node.js 18+ (Chromium is downloaded automatically by puppeteer)
Usage
terminal-browser open <url> # open a url (localhost:3000 and local html paths work too)
terminal-browser open <url> --split right # open in a split pane (inside tmux)
terminal-browser ls # list running instances
terminal-browser action -- snapshot # drive it from an agent (click/fill/eval/read/tab...)
terminal-browser open <url> --headless # serve the action API without a TUIScreen layout
The top two rows are pixel-rendered browser chrome. The chrome itself is an HTML/CSS page rendered by the same Chromium and displayed via kitty graphics, so rounded tabs, hover states and the focus ring look and behave like a real browser.
- Tab strip: click to switch, × to close (closing the last tab quits), + for a new tab
- Nav bar: ◀ ▶ ⟳ buttons; click the URL field and edit it like any input (Enter navigates, Esc blurs)
- Click hit-testing happens inside the chrome page's own JS — there is no coordinate logic for it on the TypeScript side
Key bindings
| Key | Action |
|---|---|
| Ctrl+L | Focus the URL bar (Enter navigates, Esc cancels) |
| Ctrl+R | Reload |
| Ctrl+T | New tab (opens the URL bar) |
| Ctrl+N | Next tab |
| Ctrl+W | Close tab (quits on the last one) |
| Ctrl+C / Ctrl+Q | Quit |
| Mouse | Forwarded to the page; on the chrome rows it operates the UI (hover works) |
| Everything else | Forwarded to the page — form input just works |
vs. the original
| | original (zenbu-labs) | this |
|---|---|---|
| Rendering | Electron offscreen + rust engine | puppeteer + CDP screencast |
| Input | terminal + swift background app | terminal escape sequences only |
| Smooth scrolling | yes (raw trackpad events) | wheel-step |
| Tabs | yes | yes (Ctrl+T / Ctrl+N, action tab) |
| Split panes | built-in | delegated to tmux (--split right) |
| Agent control | action CLI | action CLI (snapshot/click/fill/eval/read/tab) |
| Distribution | 129MB curl installer | npm (~26KB of code, Chromium on first run) |
Development
bun install
bun test # unit + integration tests (132, written test-first)
bun run build # tsc → dist/
bun src/main.ts open example.com # run from sourceLicense
MIT © konaito
