pi-markdown-preview

extensionmaintained

Rendered markdown + LaTeX preview for pi, with terminal, browser, and PDF output

by — · v0.19.0 · published 1d ago

$ pi install npm:pi-markdown-preview
downloads/mo
0
stars
125
last push
1d ago
open issues
1

Signals

license: MITtestspi manifest: missinginstall size: —deps: 0peer deps: 0

Download trend

No downloads in the last 12 weeks.

README

pi-markdown-preview

Preview assistant responses and local Markdown, LaTeX, code, diff, and other text-based files from pi in the terminal, browser, or as PDF, with math rendering, syntax highlighting, Mermaid, and theme-aware styling.

Screenshots

Preview adapts to your pi theme. Examples with a custom theme and the built-in defaults:

Terminal preview (custom theme):

Custom theme terminal preview

Terminal preview (default dark):

Dark terminal preview

Terminal preview (default light):

Light terminal preview

Browser preview (default dark and light):

Default darkDefault light
Dark browser previewLight browser preview

Features

  • Terminal preview (default) — renders markdown as PNG images displayed inline (Kitty, iTerm2, Ghostty, WezTerm). Long responses are split across navigable pages at block boundaries when possible, with a fixed-height fallback for oversized content.
  • Browser preview — opens rendered HTML in your default browser as a single continuous scrollable document, with optional completion-level auto-refresh and response navigation via --watch (-w)
  • Local document links in watch mode — ordinary browser navigation for rendered text snapshots, native PDF viewing, and isolated HTML pages
  • PDF export — exports markdown to PDF via pandoc + LaTeX and opens it in your default PDF viewer
  • LLM-callable artifact export — lets pi render the latest response, supplied Markdown/LaTeX, or a local file to PDF, HTML, or PNG files for remote/headless workflows such as Telegram delivery
  • Mermaid diagrams — renders ```mermaid code blocks as SVG diagrams in terminal/browser previews, and as high-quality vector diagrams in PDF export when Mermaid CLI is available
  • LaTeX/math support — renders $inline$, $$display$$, \(...\), and \[...\] math via MathML with selective MathJax fallback for pandoc-unsupported browser/terminal equations, or native LaTeX (PDF)
  • Syntax highlighting — fenced code blocks in markdown and standalone code files are rendered with theme-aware syntax colouring via pandoc. Supports 50+ languages including TypeScript, Python, Rust, Go, C/C++, Julia, and more.
  • Code wrapping controls — preserve fixed-width layout by default; toggle wrapping for the whole terminal preview with w, or use global and per-block buttons in the browser. No special Markdown annotations are needed.
  • Annotation marker highlighting — inline [an: ...] markers are highlighted in terminal/browser/PDF previews as note-only chips (..., without the [an: ] wrapper) outside code blocks; long notes wrap correctly in PDF instead of running off the page
  • Theme-aware — matches your pi theme (dark/light inference, export page/card colours, Markdown colours, accent colours, syntax colours)
  • Response picker — select any past assistant response to preview, not just the latest
  • File preview — preview arbitrary Markdown files (including .md, .mdx, .rmd, and lightweight .qmd documents), LaTeX .tex files, diff/patch files, or code files (.py, .ts, .js, .rs, etc.) from the filesystem. Markdown HTML comments are omitted outside code, local image/PDF figures and basic Quarto/pandoc-crossref figure references are supported, and no Quarto computation is executed. LaTeX files are rendered as documents with full math and sectioning; diff files are rendered with coloured add/remove lines; code files are rendered with syntax highlighting. Use Quarto itself when full Quarto project, filter, subfigure, chapter-aware numbering, or execution semantics are required.
  • Caching — rendered pages are cached for instant re-display; refresh (r) bypasses cache

Prerequisites

  • Pandoc (brew install pandoc on macOS)
  • For terminal preview (/preview default): a Chromium-based browser executable (Chrome, Brave, Edge, Chromium). puppeteer-core is included as an extension dependency; no separate Puppeteer install is needed.
  • For terminal inline display: a terminal with image support (Ghostty, Kitty, iTerm2, WezTerm)
  • For PDF export (optional): a LaTeX engine, e.g. TeX Live (brew install --cask mactex on macOS, apt install texlive on Linux)
  • For Mermaid-in-PDF support (optional): Mermaid CLI (npm install -g @mermaid-js/mermaid-cli) and a Chromium browser accessible to Mermaid CLI. PDF icon nodes require Mermaid CLI 11.6+.

Mermaid icons

Mermaid flowcharts support optional lucide:* and logos:* icons. Keep each icon metadata declaration on one source line:

flowchart LR
  source@{ icon: "lucide:file-code-2", form: "rounded", label: "Source", pos: "b", h: 56 }
  github@{ icon: "logos:github-icon", form: "rounded", label: "GitHub", pos: "b", h: 56 }
  source -->|publish| github
  classDef unchanged fill:#f8f9fa,stroke:#868e96,stroke-width:2px
  classDef changed fill:#f3f0ff,stroke:#7950f2,stroke-width:2px
  class source unchanged
  class github changed

The browser renderer loads icon-pack JSON lazily from unpkg only when a diagram references that prefix, so first render requires network access. It contrast-corrects icon and shape labels against their rendered backgrounds while preserving semantic hues. PDF export forwards the same packs to Mermaid CLI 11.6+ only when a supported icon is present. Browser previews likewise load a pinned PDF.js module from jsDelivr only when a PDF figure is present; if it cannot load or render a figure, the native PDF embed remains available.

Install

pi install npm:pi-markdown-preview

Or from GitHub:

pi install https://github.com/omaclaren/pi-markdown-preview

Or try it without installing:

pi -e https://github.com/omaclaren/pi-markdown-preview

Usage

CommandDescription
/previewPreview the latest assistant response in terminal
/preview --pickSelect from all assistant responses
/preview <path/to/file>Preview a Markdown, LaTeX, diff, or code file
/preview --file <path/to/file>Preview a file (explicit flag)
/preview --browser (-b)Open preview in cmux when available, otherwise the system browser
/preview --font-size 14Preview with a custom terminal/browser font size in px (defaults: terminal 16, browser 15)
/preview-browserShortcut for a one-shot browser preview
/preview-browser <path/to/file>Open a file preview in browser
/preview-browser --watch (-w)Keep a browser preview updated after each completed assistant response
/preview-browser --watch <path>Start or reopen a browser watcher for a file
/preview-browser --listList active and starting browser preview watchers
/preview-browser --stop <path>Stop one file watcher
/preview-browser --stop --responsesStop the assistant-response watcher
/preview-browser --stop --allStop every browser preview watcher in this Pi session
/preview-browser --stopStop the watcher when zero or one is running; otherwise request a target
/preview --pdfExport to PDF and open
/preview-pdfShortcut for --pdf
/preview --pdf <path/to/file>Export a file to PDF
/preview-clear-cacheClear rendered preview cache
/preview --pick --browserPick a response, open in browser

Local images and Pandoc PDF figures are supported. In browser previews, a single-page PDF used with Markdown image syntax is rendered as a responsive page preview that links to the original PDF; multi-page PDFs retain the native browser viewer, as do single-page PDFs when page rendering is unavailable. Ordinary PDF links in watch mode use the native browser viewer (or download, according to browser settings); PDF export continues to use the original vector file. File previews resolve relative paths against the previewed file’s directory; assistant-response previews resolve them against pi’s current working directory. Absolute paths, file:, http(s):, and data: image URLs work in one-shot previews. In watch mode, exact local media references—including parent-relative paths such as ../figures/plot.png—are rewritten to opaque authenticated routes. The general relative-resource route remains restricted beneath the preview resource directory; watch mode never exposes an arbitrary filesystem route and serves only allowlisted image types, explicitly referenced PDFs, and supported linked documents.

In browser watch mode, local document links follow normal browser conventions: click to stay in the same tab; Cmd/Ctrl-click, middle-click or the native context menu to open another tab. Browser Back returns to the source revision and reading position. Linked text/HTML pages also offer Return to preview. Following a link never retargets the original watcher.

Markdown, LaTeX and common text/code links render as snapshots using the watcher's theme/font size, with images and further links resolved from each file's directory. Absolute paths (including outside the project), relative paths and local file:// URLs work; spaces and anchors are preserved. Refresh a linked page to reread its file, or use a file watcher for continuous updates. Text/HTML input is limited to 2 MiB of UTF-8. PDFs stream through authenticated, byte-range-capable routes to the native browser viewer; Office and other binary formats remain unsupported.

HTML links open as actual pages with View source / View page controls. /preview-browser ./page.html starts or reopens a managed file watcher even without --watch; use the normal list/stop commands to manage it. Terminal previews and exports still show HTML source. Other one-shot previews are unchanged.

Document routes require the watcher's browser cookie and an exact link in retained history or a recently opened document. There is no directory browser or arbitrary file-path endpoint. Anyone with a watcher's authenticated link can also follow its local document links, including links outside the project, so treat those URLs as private. Linked tabs share the source watcher's lifetime, do not add watchers, and may expire as retained history is pruned; reopen an expired tab from its source preview.

Authored HTML runs in an opaque-origin sandbox on a separate loopback server, never inside the authenticated response-preview DOM. The selected page is a snapshot; relative CSS, JS/modules, images, fonts and Wasm assets are served live from its directory (including subdirectories), with canonical containment checks. Dot paths, directory escapes, sibling HTML documents, PDFs and non-asset files are blocked on this asset server. HTTPS resources and requests from authored scripts are permitted, so this is not network isolation. The viewer itself does not upload local files to an online service. Its capability URLs grant access to the selected page and these allowed assets, so keep them private. Stopping the parent watcher stops this server too. Forms, nested frames, local storage and general web-app hosting are deliberately outside this page-viewing mode's scope.

Basic labelled-figure cross-references work consistently in terminal, browser, and PDF previews. Quarto syntax ({#fig-elephant} with @fig-elephant) and pandoc-crossref syntax ({#fig:elephant} with @fig:elephant) produce numbered captions and clickable Figure N references. This lightweight filter handles standalone captioned images and exact single references only; missing, duplicate, compound, or qualified references remain visibly unresolved. It does not emulate Quarto subfigures, project filters, chapter-aware numbering, or execution.

Bare <sup>…</sup> and <sub>…</sub> tags containing plain inline text are supported in terminal, browser, and PDF output, including affiliation markers such as Name<sup>1,2</sup>. Pandoc's native Name^1,2^ and H~2~O syntax also works. Code examples, escaped tags, and YAML front matter are preserved; attributed or nested HTML is not enabled by this compatibility support.

The short response-watch forms are /preview-browser -w and /preview -b -w. Use /preview-browser -w ./report.md or /preview -b -w --file ./report.md to watch a file. Quoted paths are supported; --file also makes reserved or dash-prefixed filenames explicit.

When pi is running inside cmux, browser previews automatically open as a focused cmux browser split in the caller’s workspace. If cmux is unavailable or declines the request, the normal system-browser opener is used instead.

The compact watch toolbar keeps Copy link visible beside Preview · current/total. Open Preview for the source filename, history navigation, and code wrapping. The panel overlays the document without moving it and stays open while you use Previous, Next, Latest, or wrapping controls. Use Tab to reach controls and Escape to close it, or click outside. Panel state is tab-local to each watcher; preserving it across page loads requires browser session storage. Touch targets are enlarged on touch devices.

The watch server removes its bootstrap token from the address bar after setting a browser-specific session cookie. Consequently, copying the cleaned address-bar URL into another browser is intentionally rejected. Use the watch toolbar’s Copy link control to request a fresh authenticated URL; it copies directly when browser permissions allow and otherwise selects the URL for manual copying.

Active watch tabs check for new rendered revisions every 200 ms using small, authenticated requests that finish immediately. Background tabs check less often and check again when brought to the foreground. Multiple tabs therefore do not tie up the browser's HTTP connection limit with permanent live-update streams. Unchanged revisions are neither re-rendered nor reloaded; history navigation remains immediate.

Response watch mode is deliberately completion-level rather than token-streaming: it performs one canonical Pandoc render after each settled agent run, and only for a new or changed latest assistant response. File watch mode debounces source-file changes, hashes the file contents, and renders only genuine changes; temporary read/render failures leave the last good preview visible. Linked asset changes alone do not trigger a render. Both modes use token-protected servers bound to 127.0.0.1 and stop through the browser-watch lifecycle commands or when the session shuts down. Non-HTML one-shot browser previews remain unchanged and do not start this server.

Up to eight browser preview watchers can run per Pi session: multiple canonical file paths plus at most one assistant-response watcher. Each has an independent loopback server, authentication token, history, resource root, render state, and cleanup lifecycle. Repeating the same source reopens its existing watcher rather than duplicating it, and file and response watchers may coexist. Use --list to inspect them, a path or --responses to stop one, and --all to stop every watcher. Bare --stop retains the convenient old behaviour when zero or one watcher exists but makes no change when several are running.

Watch history starts with the initial preview and retains up to the latest 20 completed responses or successfully rendered file versions, subject to a 32 MiB aggregate HTML cap per watcher; the newest successful revision is always retained. Use the browser’s Back/Forward buttons or the Previous, Next, and Latest controls to move between them. Option/Alt+Left and Option/Alt+Right are shortcuts for Previous and Next, and adding Shift jumps to the oldest retained or the latest revision, when focus is outside an editable field. Auto-follow continues while the latest preview is open; when viewing an older one, the page stays put, shows New beside the revision count, and marks Latest (new) inside the panel as new responses or file versions arrive. Use /preview --pick --browser for assistant responses from before a response watcher started. If the watcher becomes unavailable, the page shows a disconnected status beside its controls instead of silently going stale. It retries with a short backoff; hosts that reuse the same port and token can reconnect existing pages after a restart.

File watchers preserve your reading position across automatic updates, reloads, and navigation between versions of the same file. Restoration follows a nearby unchanged paragraph or heading, so edits above it do not move you elsewhere; if those anchors disappear, it falls back to relative scroll position. It accounts for delayed image/math layout, but stops adjusting as soon as you interact with the page. Positions are tab-local and isolated between watchers; if browser storage is unavailable, navigation still works without restoration. New assistant responses continue to start at the top, and browsing an older response remains undisturbed.

LLM-callable artifact export

The extension also registers a preview_export tool that pi can call directly. It renders Markdown/LaTeX content, a local file, or the latest assistant response to artifact files and returns their paths instead of requiring an interactive terminal/browser preview.

Supported formats:

  • pdf — writes a PDF file using the same pandoc + LaTeX path as /preview-pdf
  • html — writes a standalone rendered HTML preview
  • png — writes one PNG per rendered preview page, appending -1-of-N, -2-of-N, etc. for multi-page output

The tool accepts optional outputPath, fontSizePx, resourcePath, and open arguments. By default it only writes files and returns paths, so another integration (for example Telegram or an upload/send-file tool) can deliver them.

Example user requests pi can satisfy with preview_export:

Make the last answer a PDF and send it to me.
Render ./report.md as HTML.
Export this markdown as PNG pages.

Programmatic helper exports

Other pi extensions can import the preview helpers directly:

import {
  openPreview,
  openPreviewInBrowser,
  closeSharedPreviewBrowser,
} from "pi-markdown-preview";
  • openPreview(ctx, markdownOverride?, resourcePath?, isLatex?, fontSizePx?) opens the inline terminal preview.
  • openPreviewInBrowser(ctx, markdownOverride?, resourcePath?, isLatex?, fontSizePx?) writes and opens the browser HTML preview.
  • closeSharedPreviewBrowser() closes the shared headless Chromium instance used for terminal/PNG rendering. Importing extensions can call this from their own session_shutdown handler; the bundled extension also calls it on pi shutdown/reload/switch.

Additional accepted argument aliases:

  • Pick: -p, pick
  • File: -f
  • Browser target: -b, browser, --external, external, --browser-native, native
  • Browser watch: --watch, -w (assistant responses or one file), --stop
  • PDF target: pdf
  • Terminal target: terminal, --terminal (usually unnecessary because terminal is the default)
  • Font size: --font-size <px>, --font-size=<px>, --font-size-px <px>, --fs <px> (10–24 px; terminal/browser previews; defaults: terminal 16, browser 15)
  • Help: --help, -h, help
  • Note: --pick and --file cannot be used together

PDF export uses Pandoc plus a LaTeX PDF engine (xelatex by default). The PDF preamble uses optional styling packages when they are available (including light code-block backgrounds via framed) and falls back to simpler output otherwise. Long-running PDF subprocesses time out after 120 seconds by default; set PI_MARKDOWN_PREVIEW_PDF_TIMEOUT_MS to adjust this.

To validate command docs against implementation:

npm run check:readme-commands

Keyboard shortcuts (terminal preview)

KeyAction
← / →Navigate pages
rRefresh (re-render with current theme, keeping the wrapping choice)
wToggle code wrapping for this terminal preview (re-renders the images)
oOpen current preview in browser
EscClose preview

Code wrapping and copying

Code blocks start unwrapped so diagrams and column-aligned output retain their layout. In a browser, long lines scroll within the code block. Terminal previews and PNG files are static images, so content beyond a block's width cannot be scrolled there.

  • Terminal viewer: press w to toggle wrapping for all code blocks. The help line shows wrap code: off/on. The viewer keeps the previous images and setting if rendering fails; Esc also cancels an in-progress render. The choice lasts only until that viewer closes. This is an interactive key, not an argument: --watch / -w still means browser watch mode.
  • Browser: use Wrap all code: off/on/mixed at the top, or hover over a code block to reveal Wrap / Unwrap on its upper-right border. Per-block buttons also appear when reached with Tab, stay visible on touch devices, and do not add a row or cover the code text. The global button wraps all blocks when they are unwrapped or mixed, and unwraps all when they are wrapped; it clears individual exceptions. There is no bare-key browser shortcut.
  • Copy code: Copy sits beside the per-block wrapping button and copies plain code text, including indentation and logical line breaks, regardless of wrapping. It briefly shows Copied on success or Failed if clipboard access is unavailable; failures leave manual selection/copy available. Neither button labels nor visual line wrapping are included in the copied text.
  • Watch mode: the global control is Wrap: off/on/mixed inside the Preview panel, rather than a separate button above the document. The global browser choice survives refreshes and revision navigation in the same tab using session storage. Per-block exceptions reset on each loaded revision. Independent watchers and fresh one-shot documents start unwrapped. If browser storage is disabled, the buttons still work, but the choice does not survive reloads.

Wrapping changes presentation, not code text or authored line breaks. Buttons stay outside the code and are omitted from terminal/PNG rendering and browser printing. Browser and terminal choices are independent; opening a separate browser preview starts unwrapped. PDF export uses the separate Pandoc/LaTeX pipeline and is unchanged. Standalone PNG exports also retain the default unwrapped layout rather than inheriting a viewer's temporary state.

Configuration

The LLM-callable preview_export tool is registered by default. To omit that tool while keeping all /preview commands available, set this before starting pi:

export PI_MARKDOWN_PREVIEW_REGISTER_EXPORT_TOOL=false

The values 0, false, no, and off disable registration (case-insensitive). Unset or any other value keeps the tool enabled.

Set PANDOC_PATH if pandoc is not on your PATH:

export PANDOC_PATH=/usr/local/bin/pandoc

Pandoc HTML conversion is bounded to 30 seconds, 50 MiB of standard output, and 5 MiB of diagnostics so a broken subprocess cannot stall or exhaust a preview session. Stopping a browser watcher cancels its active Pandoc process tree.

Set PANDOC_PDF_ENGINE to override the LaTeX engine used for PDF export (default: xelatex):

export PANDOC_PDF_ENGINE=xelatex

Set PUPPETEER_EXECUTABLE_PATH to override Chromium detection for terminal preview rendering:

export PUPPETEER_EXECUTABLE_PATH=/path/to/chromium

On Windows, standard system and per-user Chrome, Edge, Brave, and Chromium installations are detected from ProgramW6432, PROGRAMFILES, PROGRAMFILES(X86), and LOCALAPPDATA, with the common C: locations as fallbacks.

Terminal preview uses the known-good fixed screenshot path: 1200px Chromium viewport at device scale 2. Set PI_MARKDOWN_PREVIEW_DEVICE_SCALE_FACTOR only if you want to experiment with screenshot density manually (default: 2; range: 1–2.5):

export PI_MARKDOWN_PREVIEW_DEVICE_SCALE_FACTOR=2

Set MERMAID_CLI_PATH if mmdc is not on your PATH:

export MERMAID_CLI_PATH=/path/to/mmdc

Set MERMAID_PDF_THEME for PDF Mermaid rendering (default, forest, dark, neutral; default: default):

export MERMAID_PDF_THEME=default

Cache

Rendered previews are cached at ~/.pi/cache/markdown-preview/ by default. When PI_CODING_AGENT_DIR is set, the cache is stored at $PI_CODING_AGENT_DIR/cache/markdown-preview/ instead. Clear it with:

/preview-clear-cache

Or manually:

rm -rf "${PI_CODING_AGENT_DIR:-$HOME/.pi}/cache/markdown-preview/"

License

MIT