npm package: @miclivs/pi-shot
Description: Beautiful pi session screenshots. Render any pi session as a PNG.
Latest version: 0.1.1

# pi-shot

Capture the live [pi](https://github.com/mariozechner/pi-coding-agent) TUI as a
pixel-perfect PNG. Compose synthetic conversations from CLI flags, drive real
slash commands, load existing sessions, swap themes — whatever you need to make
the screenshot you want.

![hero](examples/05-all-tools.png)

## Why

`pi` is a terminal-first AI coding agent and its TUI does a lot of work
(syntax-highlighted reads, real diffs on edits, colored bash backgrounds,
custom extension messages, theme support). Most screenshot tools either:

- snapshot a static frozen frame and lose all the live behavior, or
- run the real terminal and inherit your font/theme/window chrome.

pi-shot drives a real headless `pi` instance inside `node-pty` + `@xterm/headless`,
walks the buffer cell-by-cell, and renders the result through HTML +
Playwright. The output uses pi's actual ANSI colors, the actual theme JSON,
and a clean macOS-style window frame. The image is auto-trimmed to fit the
content so there's no dead space below the conversation.

## Install

```sh
npm install -g @miclivs/pi-shot
```

You also need `pi` itself installed and on your PATH:

```sh
npm install -g @mariozechner/pi-coding-agent
```

## Quick start

```sh
# render a synthetic two-turn chat with a bash tool call
pi-shot \
  --no-banner --pi-arg --offline \
  -m "user:list files" \
  -m "bash:ls -la:total 12\ndrwxr-xr-x  3 user staff  96 ." \
  -m "assistant:Three files." \
  -o chat.png
```

```sh
# capture an existing pi session as-is
pi-shot --session ~/.pi/agent/sessions/proj/abc.jsonl -o session.png
```

```sh
# pre-fill the editor box with a half-typed prompt
pi-shot --prompt "add tests for the auth flow" -o prompt.png
```

```sh
# drive an extension's slash command live
pi-shot --no-banner --keys "/psst-set" --keys "\r" -o ext.png
```

## Examples

The [`examples/`](examples/) folder has 15 self-contained scripts covering
every feature. Each one writes a numbered PNG next to itself. Run any of them
individually, or `bash run-all.sh` to generate them all.

| File | Shows |
|---|---|
| `01-empty.sh` | Empty pi at rest |
| `02-simple-chat.sh` | A two-turn user/assistant chat |
| `03-bash-tool.sh` | A bash tool call with multi-line output |
| `04-edit-tool.sh` | An edit tool rendered as a real unified diff |
| `05-all-tools.sh` | Every tool kind in one shot: read, write, edit, bash, grep, find, ls |
| `06-thinking.sh` | An assistant turn with a thinking block |
| `07-custom-message.sh` | A custom-typed message of the kind extensions emit |
| `08-prefilled-prompt.sh` | The editor box pre-filled with text |
| `09-slash-command.sh` | The slash-command autocomplete popup |
| `10-multi-step-keys.sh` | Multiple `--keys` bursts driving a multi-step flow |
| `11-real-session.sh` | An existing pi session JSONL rendered as-is |
| `12-light-theme.sh` | The same conversation in pi's built-in light theme |
| `13-custom-theme.sh` | A custom theme JSON file applied to pi |
| `14-with-extension.sh` | An extension's slash command rendered live |
| `15-pi-psst-end-to-end.sh` | Full pi-psst story with a real LLM call |

## Composing conversations

For quick captures, the `-m "kind:body"` shorthand covers everything:

```
user:<text>
assistant:<text>
thinking:<text>
bash:<command>:<output>
read:<path>:<content>
write:<path>:<content>
edit:<path>:<oldText>:<newText>
grep:<pattern>:<path>:<matches>
find:<pattern>:<path>:<results>
ls:<path>:<entries>
custom:<type>:<content>
model:<provider>:<model-id>
thinking_level:<off|minimal|low|medium|high|xhigh>
```

Use `\n` inside any field to insert a real newline.

For more complex flows (multi-content assistant turns, custom tool details),
write a JSON file matching the `ComposeMessage` union and pass `--compose
file.json` instead:

```jsonc
[
  { "kind": "user", "text": "hello" },
  { "kind": "assistant", "text": "hi", "thinking": "they greeted me" },
  { "kind": "bash", "command": "ls", "output": "file.txt" },
  { "kind": "edit", "path": "src/a.ts", "oldText": "foo", "newText": "bar" }
]
```

The compose builder uses pi's own typed `SessionManager` under the hood, so
the resulting JSONL is guaranteed to match pi's real session schema.

## Driving live pi

Anything you can type into pi, you can send via `--keys`. Each `--keys` flag
is one keystroke burst, and pi-shot waits for pi to settle between bursts so
multi-step flows work cleanly:

```sh
pi-shot \
  --no-banner \
  --keys "/psst-set DEMO_KEY sk-live-abc demo,prod\r" \
  --keys "echo the value of DEMO_KEY using bash\r" \
  --idle-ms 8000 \
  -o demo.png
```

Escape sequences supported: `\r`, `\n`, `\t`, `\e`, and `\xHH`.

## Theming

```sh
# built-in light theme
pi-shot --theme light -o light.png

# any pi theme JSON file
pi-shot --theme my-theme.json -o custom.png
```

The theme JSON schema is documented in pi's own theme directory. pi-shot
copies your real `~/.pi/agent` dir into a private temp dir and layers the
theme override on top, so all your packages, credentials, and model
preferences still apply.

## Output

pi-shot writes the **output file path to stdout** so you can pipe it:

```sh
convert "$(pi-shot -m 'user:hi' -o /tmp/x.png -q)" -resize 50% small.png
```

All status messages go to **stderr**. Use `-q`/`--quiet` to silence them
entirely. `NO_COLOR` is honored.

## Reference

```
USAGE
  pi-shot [INPUT] -o OUTPUT.png [OPTIONS]

INPUT (one of)
  --session <file>            open an existing pi session JSONL
  --compose <file>            load compose messages from a JSON array file
  -m, --message <kind:body>   compose a synthetic message (repeatable)

OUTPUT
  -o, --output <file>         output PNG path (required)

PROMPT BOX & DRIVING
  --prompt <text>             pre-fill the editor input box
  --keys <text>               send raw keystrokes (repeatable)
  --extension <path>          load a pi extension before capture (repeatable)

TERMINAL SIZE
  --cols <n>                  terminal columns (default: 120)
  --rows <n>                  terminal rows (default: 35)

CAPTURE TIMING
  --idle-ms <n>               ms of pty silence to consider pi idle (default: 2500)
  --step-timeout-ms <n>       hard timeout per message step (default: 30000)
  --delay <n>                 extra delay after final idle, before capture
  --capture <mode>            "viewport" (default) or "all" (full scrollback)

WINDOW CHROME
  --no-window                 disable the macOS-style window frame
  --padding <px>              padding inside the window (default: 20)
  --background <color>        window background color
  --border-radius <px>        window border radius (default: 8)

STARTUP CHROME
  --no-banner                 skip pi's startup overview (welcome banner,
                              [Context], [Extensions], [Skills], etc)

THEMING
  --theme <name|file>         pi theme: "dark", "light", or path to JSON
  --window-bg <color>         override the screen background only
  --font-family <css>         override the monospace font stack
  --font-size <px>            override font size (default: 14)
  --line-height <n>           override line-height multiplier (default: 1.15)

PI PASSTHROUGH
  --cwd <path>                working directory pi runs in
  --pi-arg <arg>              pass an extra argument to pi (repeatable)

GENERAL
  -q, --quiet                 suppress pi-shot's own status output
  -h, --help                  show full help
  --version                   print version
```

Run `pi-shot --help` for the complete reference with all message kinds and
inline examples.

## How it works

1. **PtyRunner** spawns `pi` inside `node-pty` with `COLORTERM=truecolor` and
   feeds its output into an `@xterm/headless` Terminal instance.
2. **SessionBuilder** uses pi's exported `SessionManager` to write a real
   session JSONL file from the compose messages, guaranteeing the shape pi
   expects.
3. The runner waits for pi to go idle (no output for `--idle-ms` ms), then
   any `--prompt` is typed into the editor and any `--keys` bursts are sent.
4. **HtmlRenderer** walks the xterm cell buffer, builds an HTML document with
   the exact ANSI colors pi emitted, integer-pixel row heights to avoid
   sub-pixel gaps in colored backgrounds, and auto-trims trailing empty rows.
5. **Playwright** screenshots the HTML and writes the PNG.

The whole pipeline takes 5-10 seconds for a typical capture.

## License

MIT