A screenshot is normally sent to pi as a path, so the model gets its text and the reader gets nothing: pi renders image blocks in tool results but never on user messages. This extension closes both halves — the path collapses to "[Image #N]" as you type or on send, and the picture is drawn below the sent message. The rewrite is a terminal-input listener that re-reads a settled editor, joins word by word for paths with unescaped spaces, and reports a path that does not exist instead of eating the prompt. The drawing appends a custom session entry carrying the message's image blocks, one macrotask after message_end so it lands below the message pi persists afterwards, and renders it with pi-tui's inline image component under terminal.showImages and terminal.imageWidthCells. Entries stay out of the model context, so each image is still sent to the model once. Tests: npm test (21, node:test over a stand-in for pi: rewrite, send-time attachment, missing paths, append gate, renderer gating) and npm run typecheck. Co-Authored-By: nw-flash-flex <noreply@freno.me>
pi-image-paths
Drop a screenshot into pi's chat input — by path, by drag and drop, or pasted whole from anywhere — and the picture becomes a real attachment instead of a long line of text.
pi install npm:@mikefreno/pi-image-paths
you › look at ~/Desktop/Screenshot\ 2026-10-21\ at\ 10.42.11.png — why is the header clipped?
↓
you › look at [Image #1] — why is the header clipped?
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
█ (the screenshot, drawn in the chat) █
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
What it does
| Piece | Mechanism |
|---|---|
| Collapses the path as you type | A terminal-input listener re-reads the editor when it is idle: every token run that resolves to an image file is rewritten in place to [Image #N], the way pi collapses big pastes into [paste #N]. Dropping three files at once flips all three. |
| Attaches on send | The input event resolves the markers it wrote, then looks for any image path still in the prompt (pasted whole, RPC mode, rewritten too late), attaches every file it finds, and leaves [Image #N] in the text. |
| Draws what was sent | Pi renders image blocks on tool results but never on user messages, so the sent images ride in a custom session entry that a registered entry renderer draws with pi-tui's inline-image component. The entry is appended one macrotask after message_end — pi persists the user message after extension handlers return — which puts the picture directly below the message in the live chat and in the session log. |
Supported: png, jpg/jpeg, gif, webp, bmp; ~ expansion; backslash-escaped spaces,
surrounding quotes, file:// drops; bare filenames relative to the working directory; and paths
with unescaped spaces, which are joined word by word and disambiguated against the filesystem.
URLs are left alone, and a path that does not exist is reported as a warning instead of eating the
prompt.
Settings
The drawing honours pi's own settings, live:
| Setting | Effect |
|---|---|
terminal.showImages (default true) |
Off: no entry is appended and nothing is drawn — the images still reach the model. |
terminal.imageWidthCells (default 60) |
Width the picture is drawn at. |
terminal.images (default auto) |
No inline-image protocol: the renderer stays out of the way and the transcript keeps only the marker. |
Custom entries stay out of the model context, so the model still sees each image exactly once. The image data is stored in the entry as well as in the message, which roughly doubles a session's image bytes on disk.
Modes other than the interactive TUI attach images but draw nothing, and append no entries.
Off the terminal: The Nook
The Nook puts every coding agent you run in your Mac's notch, on your iPhone and on your Apple Watch. It renders the same image blocks, so a screenshot dropped into pi shows up there as a picture too.
Develop
npm test # node:test, driving the extension through a stand-in for pi
npm run typecheck
scripts/release-tag.sh # bump, tag and push; the v* tag publishes to npm