A pi extension that upgrades built-in tool output in the terminal and includes built-in FFF-powered search for find/grep.
Tool result bodies start collapsed (header + line count) and retain a blank row below their output. Use Pi Ctrl+O (app.tools.expand) on a tool block to show full output; Ctrl+Shift+O expands all. See Pi keybindings.
It currently enhances:
- user messages: a
❯prompt icon usinguserMessageText; live input follows the active thinking-level color for its icon read: syntax-highlighted text previews with line numbers,read docslabels for Pi documentation reads, plus inline image rendering when the terminal supports itbash: colored exit summary (exit 0/exit 1) with a preview body of command outputls: Nerd Font file icons with tree-oriented renderingfind/grep: built-in FFF-backed search with frecency-aware results, plus grouped/highlighted rendering- working indicator: an oh-my-pi-style shimmer sweep in the input's top-border
Working…row — flush-left, rotating phrases, per-session accent tint (see Working indicator) - thinking label: the hidden-thinking
Thinking...label gets the same shimmer treatment (see Thinking label)
Companion to @heyhuynhgiabuu/pi-diff for
write/editdiff rendering.
pi install npm:@heyhuynhgiabuu/pi-prettyLatest release: https://github.com/buddingnewinsights/pi-pretty/releases/latest
Or load locally:
pi -e ./src/index.ts
bash exit summary + output preview, and syntax-highlighted read text output.
ls/find/grep with Nerd Font icons and grouped/tree-oriented rendering.
read rendering an image inline in supported terminals.
Inline image previews are supported in Ghostty, Kitty, iTerm2, and WezTerm.
When running in tmux, pi-pretty uses passthrough escape sequences.
tmux must allow passthrough. Enable it with:
set -g allow-passthrough on(or run once in a session:
tmux set -g allow-passthrough on)
pi-pretty now bundles @ff-labs/fff-node and owns the built-in find / grep search behavior directly.
If you use bundled FFF mode, do not load pi-fff at the same time, because Pi extensions do not compositionally share ownership of the same built-in tool names.
FFF data is stored under a pi-pretty-specific path:
~/.pi/agent/pi-pretty/fff/
This makes it clear that the cache belongs to this extension rather than Pi core.
pi install npm:@heyhuynhgiabuu/pi-prettyDo not also load pi-fff in the same Pi setup.
cd /path/to/your/project
piOn session start, pi-pretty initializes the bundled FFF index for the current working directory.
You keep using the normal built-in tool names — pi-pretty owns them directly.
Examples:
find pattern="*.ts" path="src"
grep pattern="handleRequest" glob="*.ts"
read path="src/index.ts"
ls path="src"
pi-pretty also provides two maintenance commands:
/fff-health
/fff-rescan
Use them when:
- you want to confirm indexing is active
- the session started with a partial index warning
- you made large filesystem changes and want a fresh scan
findresults are frecency-aware, so files you touch more often can bubble up earlier.grepcan show a cursor notice when more results are available.- If you see a partial index warning, let the session settle or run
/fff-rescan. - When Pi starts in the exact home directory or filesystem root, FFF stays disabled by default and
find/grepuse Pi's SDK fallback instead of interrupting startup. Broader indexing requires explicit opt-in below.
To opt into home or root indexing for a session:
PRETTY_FFF_HOME_SCAN=1 pi
PRETTY_FFF_ROOT_SCAN=1 piThe equivalent Pi flags are --pretty-fff-home-scan and --pretty-fff-root-scan. Root scanning is intentionally never enabled by default because it can watch a very large filesystem and expose unrelated files to the agent.
Place a JSON file alongside Pi's settings.json to customize pi-pretty. Every option can also be set via environment variables, which take precedence over the config file: env var > pi-pretty.json > built-in default (the theme additionally falls back to ~/.pi/agent/settings.json's theme before the default).
{
"background": {
"tool": "#1e1e2e",
"error": "#2a1e1e"
},
"theme": "github-dark",
"icons": "nerd",
"enableTools": ["ls"],
"disableTools": ["grep"],
"maxHlChars": 80000,
"maxPreviewLines": 80,
"cacheLimit": 128,
"fff": {
"enableHomeScanning": false,
"enableRootScanning": false
},
"workingIndicator": {
"text": ["Working…", "Thinking…"]
}
}| Key | Type | Env var override | Default |
|---|---|---|---|
background.tool |
hex color | — | terminal default |
background.error |
hex color | — | background.tool |
theme |
Shiki theme name | PRETTY_THEME |
github-dark (after pi-pretty.json theme, then ~/.pi/agent/settings.json theme, when valid Shiki themes) |
icons |
nerd | none (or off) |
PRETTY_ICONS |
nerd |
enableTools |
string array | PRETTY_ENABLE_TOOLS |
[] (ls is opt-in) |
disableTools |
string array | PRETTY_DISABLE_TOOLS |
[] |
maxHlChars |
positive int | PRETTY_MAX_HL_CHARS |
80000 |
maxPreviewLines |
positive int | PRETTY_MAX_PREVIEW_LINES |
80 |
cacheLimit |
positive int | PRETTY_CACHE_LIMIT |
128 |
fff.enableHomeScanning |
boolean | PRETTY_FFF_HOME_SCAN (1/0) |
false |
fff.enableRootScanning |
boolean | PRETTY_FFF_ROOT_SCAN (1/0) |
false |
workingIndicator.enabled |
boolean | PRETTY_WORKING_INDICATOR (on/off) |
true |
workingIndicator.text |
string or string[] (phrases rotated per sweep; env accepts comma-separated) | PRETTY_WORKING_INDICATOR_TEXT |
["Working…"] |
workingIndicator.mode |
shimmer | kitt | static |
PRETTY_WORKING_INDICATOR_MODE |
shimmer |
workingIndicator.low |
theme color name or #hex |
— | dim |
workingIndicator.mid |
theme color name or #hex |
— | muted |
workingIndicator.high |
theme color name or #hex |
— | accent |
workingIndicator.bold |
boolean | — | true |
workingIndicator.hint |
boolean | — | deprecated (ignored) |
workingIndicator.sessionAccent |
boolean | — | true |
thinkingIndicator.enabled |
boolean | PRETTY_THINKING_INDICATOR (on/off) |
true |
- Config values take priority over theme-provided backgrounds (
toolBg/toolErrorBg). - Precedence for FFF scan scope is CLI flag > environment variable >
pi-pretty.json> safe default (false). - All options except
background.*are read once at startup; restart pi to apply changes to them (background.*applies live). - To override the config directory, set
PRETTY_CONFIG_DIRenv var.
While the agent streams, pi-pretty replaces pi's static Working... row with an
oh-my-pi-style shimmer: a bright accent band sweeps across the
text at 30 cells/second over a dim braille spinner, rendered flush-left in Pi's embedded
working-status row at the top border of the input. The custom row contains only the spinner and
shimmer text; it does not append Pi's interrupt hint or token-count status. On hosts that provide
Pi's embedded working-status API, pi-pretty supplies its custom frames to that row; older hosts
fall back to a zero-padding widget above the editor. Mode kitt swaps the sweep for a ping-ponging
scanner head with a decay trail; static renders a single unanimated frame. Tier colors resolve
#rrggbb hex first, then the active pi theme color name, then built-in fallbacks. Set
workingIndicator.enabled: false (or PRETTY_WORKING_INDICATOR=off) to restore pi's default
indicator. TUI sessions only; theme changes take effect on the next session.
text accepts a single phrase or an array — the sweep plays each phrase in order, one full band
sweep per phrase (e.g. ["Working…", "Thinking…", "Pondering…"]). The env var splits on commas.
When sessionAccent is on, the mid/high tiers and the spinner are tinted with a stable per-session
accent color derived from the session name (an OKLCH port of
oh-my-pi's session accent) — different windows get different
hues at uniform perceived brightness. Renaming the session re-tints the indicator live. Explicit
workingIndicator.mid/high colors disable the tint.
With thinking blocks hidden (pi's hideThinkingBlock setting), the label shows elapsed reasoning
time (Thinking... 12s) under the same shimmer: italic thinkingText base with the accent band
(and the session accent tint) sweeping through it. Both the active and completed labels have one
leading space for consistent output alignment. On the first text or tool delta it freezes as
Thought for 12s. Durations below one second use milliseconds (250ms instead of 0s); longer
durations retain compact whole-second units (12s, 1m 05s, 1h 02m 03s).
Each row keeps its own label: pi-pretty intercepts the host's per-row label fan-out
(AssistantMessageComponent.prototype.setHiddenThinkingLabel), so the streaming row animates while
completed rows stay frozen at their own Thought for 12s instead of every row mirroring the latest
write. Durations live for the current session (they are not persisted across restarts). A message
with several thinking runs (interleaved thinking → text → thinking, common on Gemini) shares one
label line per run, so those runs accumulate a single per-message total — a later run resumes the
count instead of rewinding to zero. If the host class is missing or reshaped, the intercept falls
back to pi's global-label behavior — including restoring the default Thinking... at message end so
older rows are never mislabeled. The 30fps ticker runs only while the current message's last block
is thinking, bounding the cost of setHiddenThinkingLabel(label) rebuilding chat children. Inherits
mode, bold, and the palette/accent from workingIndicator.
Optional environment variables:
PRETTY_THEME(overridespi-pretty.jsontheme, which overrides~/.pi/agent/settings.jsontheme; otherwise pi-pretty falls back to that setting beforegithub-dark)PRETTY_CONFIG_DIR— directory to readpi-pretty.jsonfrom (default:~/.pi/agent/)PRETTY_MAX_HL_CHARS(default:80000)PRETTY_MAX_PREVIEW_LINES(default:80)PRETTY_CACHE_LIMIT(default:128)PRETTY_ICONS(nerdby default, set tononeto disable icons)PRETTY_WORKING_INDICATOR(on/off, overridesworkingIndicator.enabled)PRETTY_WORKING_INDICATOR_MODE(shimmer/kitt/static)PRETTY_WORKING_INDICATOR_TEXT(indicator label)PRETTY_DISABLE_TOOLS— comma-separated list of tool names to skip during registration (e.g.read,grep). Explicit disables take precedence over enabled defaults.PRETTY_ENABLE_TOOLS— comma-separated list of opt-in tools.lsis disabled by default; setPRETTY_ENABLE_TOOLS=lsto register it.
Future pi-pretty custom-tool renderers should use customToolTitle(name) from src/tools/labels.ts; it returns ⚙ <name>. Built-in tool replacements keep their own labels.
npm install
npm run typecheck
npm run lint
npm testMIT — huynhgiabuu