A component-based TUI screenshot library for OpenTUI/React
A screenshot library for TUIs built on OpenTUI, rendered with the ghostty terminal buffer.
You tag regions of your UI by name in component code; honeyshots crops the captured terminal output to each region and writes a PNG.
bun add honeyshotshoneyshots has no required runtime dependencies on React or OpenTUI. The honeyshots/opentui subpath provides an adapter for OpenTUI React apps.
Install the adapter once at startup. It's a no-op unless the HONEYSHOTS=1 env var is set:
import { createCliRenderer } from "@opentui/core";
import { createRoot } from "@opentui/react";
import { installAdapter } from "honeyshots/opentui";
const renderer = await createCliRenderer({...});
installAdapter(renderer);
createRoot(renderer).render(<App />);Tag any <box> you want to capture with an id prop prefixed with honeyshots::
function MainScreen() {
return (
<box id="honeyshots:main-screen" flexDirection="column">
{/* ... */}
</box>
);
}For components whose root is another React component (not a bare <box>), use the <Region> wrapper — it clones the child and attaches a ref:
import { Region } from "honeyshots/opentui";
<Region name="help-dialog">
<DialogFrame>{/* ... */}</DialogFrame>
</Region>import { TuiHarness } from "honeyshots";
const harness = new TuiHarness({
argv: ["bun", "run", "src/index.tsx"],
cols: 132,
rows: 43,
});
await harness.start();
await harness.waitForRegion("main-screen");
await harness.shoot("main-screen", "docs/shots/main-screen.png");
harness.send("?"); // "?" opens a help dialog
await harness.waitForRegion("help-dialog");
await harness.shoot("help-dialog", "docs/shots/help-dialog.png");
await harness.close();- Any Renderable whose
idbegins withhoneyshots:(or that is wrapped in<Region>) is tracked by the adapter. - On each render commit, the adapter walks the OpenTUI renderable tree, collects every tagged node's
(name, x, y, width, height), and emits a custom OSC sequence (OSC 5700) with a JSON payload. - The harness parses the OSC stream from the app's PTY output, strips the markers before feeding the stream into the ghostty terminal buffer, and keeps a live
name → rectmap. harness.shoot(name, path)looks up the rect, crops the captured terminal data, and renders a PNG with @takumi-rs/core.
honeyshots
├── TuiHarness // PTY wrapper + region tracker
├── renderTerminalToImage(data, opts) // TerminalData → PNG
├── cropTerminalData(data, rect) // rect-accurate cropping
└── MarkerProtocol // OSC 5700 parse/emit helpers
honeyshots/opentui
├── installAdapter(renderer) // installs the renderer hook
├── Region // <Region name="help-dialog">{child}</Region>
└── tagRenderable(node, name) // imperative tag helper
