pi-image-paths: attach dropped image paths and draw them in the chat
All checks were successful
publish / publish (push) Successful in 15s

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>
This commit is contained in:
2026-10-21 15:20:12 -04:00
commit 1c83bd73e2
9 changed files with 1240 additions and 0 deletions

61
README.md Normal file
View File

@@ -0,0 +1,61 @@
# pi-image-paths
Drop a screenshot into [pi](https://pi.dev)'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.
```text
pi install npm:@mikefreno/pi-image-paths
```
```text
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](https://nook.freno.me) 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
```bash
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
```