gfxsh · Rust · a VT emulator · Kitty graphics

gfxsh

gfxsh draws an unmodified zsh session as graphics. It runs zsh in a pseudo-terminal, follows it with its own terminal emulator, and paints the screen through the Kitty graphics protocol: each command becomes a panel showing its exit status and duration, the prompt is a card that responds as you type, and an animated background sits behind it.

gfxsh                         # your $SHELL; zsh gets semantic blocks
gfxsh --background nebula
gfxsh settings                # live editor; the session behind it is the preview
gfxsh demo --out demo.png     # an offscreen render, no terminal needed
gfxsh doctor                  # what this terminal supports

Three looks

gfxsh with the aurora background and glass panes: a git log block with a green success badge, and a running make test block with an amber badge and a white caret.
aurora · glass card · bloom caret
gfxsh with the synthwave background, scanlines and an aperture-grille mask: the same two blocks with a CRT-style card and an ember caret throwing sparks.
synthwave · CRT scanlines and mask · ember caret
gfxsh with the bokeh background, soft blurred lights behind the glass panes, and a green comet caret with a trail.
bokeh · holographic card · comet caret · light sweep

Rendered for this page by gfxsh demo from three configs; each image is the lower half of the scripted session.

How it works

gfxsh is a terminal inside your terminal. Nothing it draws is text: each screen row is a cell-aligned image, so it can give a command's output rounded glass, drop shadows and light, while your shell, its theme and your programs see an ordinary terminal.

How gfxsh draws a shell gfxsh's pipeline. Your zsh starts with a temporary ZDOTDIR that loads your normal startup files and then adds hooks emitting OSC 133 semantic prompt marks, the working directory, the command line and its duration. The shell runs in a pseudo-terminal followed by a full VT emulator, which anchors each mark to the absolute line where it arrived, so command blocks survive scrolling and resizes. The renderer draws each screen row as one cell-aligned image, cached by content, with panels as signed-distance shapes and overlays (caret, glow, stream-in) on separate layers, diffs the scene against what is already placed, and sends only the changes over the Kitty graphics protocol to the terminal: Kitty, Ghostty or WezTerm, directly or inside herdr or tmux. zshyour dotfiles+ gfxsh hooks pty VT emulatormarks on absolute lines blockscommand · outputexit · duration rendererrows as cached imagesSDF panels · overlays terminalKitty · Ghostty · WezTermherdr · tmux backgrounda seamless loop, off-thread OSC 133 · OSC 7 Kitty graphicsonly changes your keys
Your dotfiles are never modified. The hooks are appended after your .zshrc through a temporary ZDOTDIR, from precmd, preexec and a composing zle hook, so prompt themes such as powerlevel10k work unchanged.
From marks to a block How OSC 133 semantic prompt marks become a gfxsh command block. A marks the start of the prompt, B the start of the command you type, C the moment it runs and its output begins, and D the end, with its exit status. gfxsh also reports the command line and its wall-clock duration. The bytes between the marks become a glass pane: a header with the command line, the output, and a badge with the exit status and duration, with a status stripe that is amber while running, green on success and red on failure. A prompt ~/tools ❯ B command make test C output running 42 tests … ok D;0 done · exit 0 ~/tools ❯ make test ✓ 4.1s running 42 tests test blocks::lifecycle ... ok
Blocks come from the shell, not guesses. gfxsh's marks are tagged as its own; any other shell integration's OSC 133 in the same shell is ignored.

Command blocks

Because gfxsh knows where every command and its output are, it can act on them. Ctrl+G, or a click on a command's header, enters block mode: j/k move between commands, y copies an output, Y the command and output, c the command line, and e puts a command back on the prompt to edit. A right-click menu does the same, and copying goes through OSC 52 so it reaches the right clipboard over SSH.

Inside tmux, tmux -CC makes gfxsh the tmux client, as iTerm2 does: each pane gets its own emulator and renderer, with its own command blocks and caret, and every key goes through tmux's own key tables, so your prefix and bindings work. In herdr, images are padded to whole cells; in tmux each placement is cropped to its pane and hidden when the pane isn't visible.

Configuration

Backgroundsaurora · nebula · waves · grid · stars · plasma · bokeh · synthwave · rain · fireflies · static · none
Prompt cardsglass · neon · outline · solid · minimal · pill · holo · rail · crt · hud
Caretsbloom · beam · block · underline · outline · halo · orb · comet · prism · pulse · ember · plasma
Running lightorbit · sweep · pulse, in amber, the accent, a rainbow or any colour
Lightingglyph shadows, glow, chromatic fringes, a glass sheen, light leaks, vignette, film grain
CRTscanlines with a rolling refresh band, an aperture-grille mask, a curved frame
Finisha green or red border flash; optionally a glitch on a failed command's rows

gfxsh settings edits all of it live: inside a gfxsh session the running shell behind the editor is the preview, and a preview column replays a scripted session (a listing, a failing test run, a streaming build) with the real effects.