@yuru7/pi-print-stream

A Pi extension that streams non-interactive runs: realtime text, tool JSONL, transient thinking, and usage summary

Packages

Package details

extension

Install @yuru7/pi-print-stream from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@yuru7/pi-print-stream
Package
@yuru7/pi-print-stream
Version
0.2.1
Published
Sep 10, 2026
Downloads
464/mo · 464/wk
Author
yuru7
License
MIT
Types
extension
Size
52.3 KB
Dependencies
2 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./extensions/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-print-stream

A Pi extension that adds a --stream CLI flag for non-interactive runs.

pi -p "review this repository" --stream

While the turn runs:

  • Answer text streams to stdout in realtime and stays in the scrollback.
  • Tool calls stream as one JSON object per line and stay in the scrollback.
  • Thinking appears as a transient block of at most 8 screen rows (TTY only) and never pollutes the scrollback or redirected output.
  • When the run finishes, a usage summary shows tokens, elapsed time, TPS, and a command to continue the session.

How it works

--stream is a wrapper over pi --mode json -p. It intercepts the prompt, spawns a child pi --mode json -p "<prompt>" with the same model and flags, renders the child's JSONL event stream, and returns handled so Pi's own turn does not run twice. The child uses Pi's real print-mode path, so prompt handling, retries, and compaction behave exactly like a normal pi -p run.

pi -p "explain this repo" --stream      # recommended
pi --stream "explain this repo"         # also works (prompt recovered from argv)

Install

pi install npm:@yuru7/pi-print-stream

Local development:

pi -e ./extensions/index.ts -p "your prompt" --stream

Output

Answer text

text_delta events are written to stdout immediately, in arrival order. On a TTY the answer Markdown is rendered for the terminal with markdansi (headings, bold, lists, code blocks, tables, and more) as an append-only stream: completed lines are emitted as they arrive, while fenced code blocks and tables stay buffered until complete. Tool calls never reset the Markdown state. When stdout is not a TTY (redirect, pipe, tee, scripts), the raw Markdown is written unchanged with no ANSI sequences, keeping logs and pipes machine-readable.

Tool calls

Tool activity is emitted as compact JSONL, one event per line. On a TTY the lines are dimmed to stay unobtrusive; redirected output stays plain so grep/jq keep working:

{"type":"tool_start","id":"tool_1","name":"read","args":{"path":"src/index.ts"}}
{"type":"tool_end","id":"tool_1","name":"read","status":"success","elapsed_ms":42}

tool_end.status is "success" or "error". elapsed_ms is measured with a monotonic clock from tool_execution_start to tool_execution_end, so parallel tool calls are measured independently.

Tool result bodies are intentionally not printed: read, bash, grep, and web fetches can be very large. The stream shows the fact, name, args, status, and timing of each call.

Thinking (TTY only)

On a TTY, thinking deltas render as a transient block near the bottom of the screen:

────────────────────────────────────────
Thinking
  Need to inspect how message events are emitted.
  Tool execution should remain persistent.
  ...
────────────────────────────────────────
  • At most 8 screen rows are shown (wrapping-aware: CJK, emoji, and ANSI sequences are measured by display width, not string.length).
  • Older content scrolls off the top; only the latest rows are visible.
  • Tool calls alone never end the thinking session: the block is hidden, the tool line is written, and the thinking view is repainted below it.
  • The block is erased when answer text starts, and before errors or the final summary are written, so outputs never interleave.
  • The block is erased on resize and repainted with the new terminal width.
  • The thinking session ends when answer text takes over; tool-only message boundaries leave the view intact.

When stdout is not a TTY (redirect, pipe, tee, scripts), thinking is discarded completely and no ANSI control sequences are emitted. Only answer text, tool JSONL, and the final summary are written, keeping logs and pipes machine-readable.

Final summary

A compact 3-line block:

Done in 24.8s  TPS: 331.2 tok/s
Tokens: Input 12,481 / Cache read 48,220 / Output 3,842 / Cache write 0
To resume this session: pi --session 01a089b9-ae47-7772-8ce5-bd7cbb67bc29

The session ID comes from the leading {"type":"session",...} header of the child --mode json stream. The To resume this session line is omitted when no session ID is available (for example --no-session or a failure before the header arrives).

On failure the title is Failed and the statistics collected so far are shown. A missing trailing newline in the answer is added before the summary. On a TTY the block is dimmed; otherwise it is plain text.

TPS definition

TPS = Output tokens / Generation seconds

Generation time is the sum of message_startmessage_end intervals for assistant messages. Tool execution, network waits, and retries use wall-clock Elapsed time but are excluded from Generation and TPS, so the number reflects model generation speed rather than whole-agent latency. Usage is summed from each assistant message_end.message.usage (input, output, cacheRead, cacheWrite); missing fields count as zero. When generation time is zero, TPS is shown as -.

Behavior notes

  • Non-interactive only (print mode without UI). In interactive mode the TUI already renders the turn, so --stream is a no-op. Under --mode json or --mode rpc, Pi emits its own machine stream and --stream stays out of the way.
  • Malformed JSONL lines from the child never pollute stdout; a warning goes to stderr and streaming continues.
  • SIGINT / SIGTERM erase the transient thinking view, forward the same signal to the child, and exit with 130 / 143. The child exit code is otherwise propagated; a final assistant stopReason: "error" maps to exit 1 even when the JSON child itself exits 0.
  • All user-facing output is written to the real stdout (fd 1). Pi takes over process.stdout.write in print mode (forwarding extension output to stderr), so writing through process.stdout would lose redirected output. TTY detection and terminal width are likewise probed from fd 1, never from the replaced process.stdout object.
  • No TUI framework, syntax highlighting, tool-result dumps, or session restore is included. Output stays on stdout/ANSI by design.

License

MIT