A pure TUI display wrapper for the Pi coding agent.
This extension is derived from MasuRii/pi-tool-display.
@pure/pi-tool-display keeps tool rows compact, renders trustworthy tool-provided diffs, and improves the native user prompt box without changing tools, model context, messages, or sessions.
Earlier tool-display integrations commonly replaced or wrapped executable tool definitions to gain control of their presentation. That made behavior depend on extension registration order: two extensions targeting the same built-in tool could overwrite each other, wrap stale definitions, change ownership, or lose settings when tools were registered late or reloaded.
@pure/pi-tool-display instead attaches at Pi's final tool-row rendering seam. It does not re-register tools, wrap execute, replace schemas, change the active tool set, or mutate model/session data. The original extension remains the tool owner and continues to control execution; this extension only chooses how the existing call and result are displayed.
This separation provides practical advantages:
- No executable-tool ownership conflicts with extensions that customize Bash, MCP, permissions, background tasks, or other tools
- No registration-order race between this extension and the tool producer
- No execution or schema drift caused by cloning or wrapping tool definitions
- Reload-safe rendering without stacking wrappers or leaving stale registrations behind
- Fail-open compatibility: unsupported host shapes or renderer failures keep Pi's native rendering and execution
- Explicit third-party integration through display-only adapters that never expose or mutate executable definitions
- Compact built-in tool rendering for
read,grep,find,ls,bash,edit, andwrite - Opt-in MCP-style rendering with hidden, summary, and preview modes through custom tool overrides
- Opt-in custom tool overrides for noisy extension tools, defaulting to generic rendering unless
kind: "mcp"is selected - Adaptive edit/write diffs with split or unified layouts, syntax highlighting, inline emphasis, and narrow-pane width clamping
- Trustworthy diff rendering only from explicit patches or before/after data already supplied by the tool; missing diffs are never reconstructed
- Progressive collapsed diff hints that shorten automatically on small terminal widths instead of overflowing
- Hashline-anchor diff gutters that preserve
LINE#HASHlabels from anchored read/edit output when those lines are rendered in diffs - Three presets:
opencode,balanced, andverbose - Optional native user message box with markdown-aware rendering, configurable Theme-token border color, and safer ANSI/background handling
- Configurable tool-row separators for every tool, with dashed/solid styles and Theme-token colors
- Per-tool display toggles that never change tool ownership or execution
- Explicit third-party rendering through
customToolOverridesor producer adapters; MCP-like tools are never auto-detected for styling - Capability-aware RTK settings that appear only when the optimizer is available
- Adapter API for renderer consumers through the
@pure/pi-tool-display/tool-display-api-consumersubpath export
Place this folder in one of Pi's auto-discovery locations:
# Global default (when PI_CODING_AGENT_DIR is unset)
~/.pi/agent/extensions/pi-tool-display
# Project-specific
.pi/extensions/pi-tool-display
pi install git:github.com/liuli195/pi-tool-display@v0.1.0Open the settings modal:
/tool-display
The modal exposes the day-to-day controls most people change regularly:
- preset profile
- read and grep/find/ls output modes
- preview line count
- Bash output, command, and error modes with their line limits
- diff layout and indicator modes
- RTK compaction hints (when RTK is available)
- native user message box toggle and border color
- tool separator toggle, dashed/solid style, and color
JSON-only controls include the extension master switch, debug logging, built-in and custom-tool selection, the fallback output mode for explicit MCP producer adapters, expanded preview limit, split-width threshold, collapsed logical diff-line limit, diff wrapping, and truncation hints.
/tool-display show # Show the current display-policy summary
/tool-display reset # Reset to the default opencode preset
/tool-display preset opencode # Apply opencode preset
/tool-display preset balanced # Apply balanced preset
/tool-display preset verbose # Apply verbose preset
Other extensions that declare @pure/pi-tool-display as a direct dependency can opt into its rendering without depending on load order by importing the consumer helper:
import { registerRendererAdapter } from "@pure/pi-tool-display/tool-display-api-consumer";
const dispose = registerRendererAdapter({
id: "my-extension:mcp",
toolName: "my_mcp_tool",
kind: "mcp",
});
// Dispose during extension shutdown or reload.
dispose();Registration is display-only, deterministic, disposable, and does not expose or mutate the executable tool definition, schema, ownership, active state, or execution. Retain the returned disposer even when registering before pi-tool-display loads: after the pending intent is drained, that same disposer delegates to the live registration and remains idempotent before or after the drain.
The deprecated decorateToolForDisplay(tool, adapter) migration facade registers the same display intent and returns the exact original tool unchanged. Its registration lasts only for the current pi-tool-display load epoch; repeated calls for the same tool and adapter ID in that epoch replace the previous intent, and consumers must call it again after reload. New integrations should retain the disposer from registerRendererAdapter instead.
Supported Pi releases are 0.81.1 and later stable versions. The release matrix currently qualifies 0.81.1, 0.82.0, and the development runtime as representative points.
Versions below 0.81.1 and prerelease version strings are not supported. The peer dependency range and Host Adapter gate accept stable releases from 0.81.1 onward; the qualification matrix does not limit that support range.
Older, prerelease, or incompatible private TUI shapes emit one concise debug diagnostic and keep Pi's native rendering and execution.
| Preset | Read Output | Search Output | Explicit MCP Adapter Default | Bash Output | Preview Lines | Bash Lines |
|---|---|---|---|---|---|---|
opencode |
hidden | hidden | hidden | opencode | 8 | 10 |
balanced |
summary | count | summary | summary | 8 | 10 |
verbose |
preview | preview | preview | preview | 12 | 20 |
opencode(default): minimal inline-only display; tool results stay collapsedbalanced: compact summaries with line counts and match totals; bash shows line count onlyverbose: larger previews for read/search/MCP/bash output
| Mode | Behavior |
|---|---|
opencode |
Classic collapsed output using the bashCollapsedLines visual-row limit with expansion hint |
summary |
Shows only line count (e.g., "↳ 3 lines returned") — no output displayed |
preview |
Shows actual output lines using previewLines limit |
Runtime configuration is stored at:
Default global path: ~/.pi/agent/extensions/pi-tool-display/config.json
Actual global path: $PI_CODING_AGENT_DIR/extensions/pi-tool-display/config.json when PI_CODING_AGENT_DIR is set
A starter template is included at config/config.example.json.
Trusted projects can override global display settings by placing a config.json in their project-local extension directory:
<project>/.pi/extensions/pi-tool-display/config.json
Project-local config is read-only and only loaded when isProjectTrusted() is active (i.e., when the user has approved project-local files). It overlays the global config: any fields defined in the project config override the corresponding global values. Write operations through /tool-display always save to the global config.
The .pi directory name uses Pi's CONFIG_DIR_NAME constant and is not hardcoded.
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Master switch; set to false and reload to disable the extension |
debug |
boolean | false |
Opt-in file logging for extension diagnostics; preserved by /tool-display saves |
builtInToolDisplays |
object | all true |
Enable display formatting for each built-in tool |
customToolOverrides |
object | {} |
Explicit opt-in rendering rules for non-built-in extension tools |
enableNativeUserMessageBox |
boolean | true |
Enable bordered user prompt rendering |
enableToolSeparator |
boolean | true |
Add one separator after every tool row |
toolSeparatorStyle |
string | "dashed" |
Separator style: dashed or solid |
toolSeparatorColor |
string | "borderMuted" |
Separator Theme token: border, borderAccent, borderMuted, accent, muted, or dim |
userMessageBorderColor |
string | "border" |
USER box border Theme token from the same supported set |
readOutputMode |
string | "hidden" |
hidden, summary, or preview |
searchOutputMode |
string | "hidden" |
hidden, count, or preview |
mcpOutputMode |
string | "hidden" |
Fallback hidden, summary, or preview mode for explicitly registered MCP producer adapters; it does not discover or opt tools into rendering |
previewLines |
number | 8 |
Lines shown in collapsed preview mode |
expandedPreviewMaxLines |
number | 4000 |
Max expanded visual rows for non-Diff previews; for Diff it caps logical Diff lines |
bashOutputMode |
string | "opencode" |
opencode (collapse), summary (line count), or preview (show lines) |
bashCollapsedLines |
number | 10 |
Visual rows shown for collapsed Bash output in opencode mode |
bashCommandMode |
string | "preview" |
Bash command display: full, summary, or preview |
bashCommandPreviewLines |
number | 3 |
Visual command lines shown in preview mode; Ctrl+O expands the full command |
bashErrorOutputMode |
string | "preview" |
Failed Bash output: full, summary, or preview; the failure header always remains visible |
bashErrorPreviewLines |
number | 3 |
Visual error lines shown in preview mode; Ctrl+O expands the error output |
diffViewMode |
string | "auto" |
auto, split, or unified |
diffIndicatorMode |
string | "bars" |
bars (vertical indicators), classic (+/- markers), or none |
diffSplitMinWidth |
number | 120 |
Minimum width before auto mode prefers split diffs |
diffCollapsedLines |
number | 24 |
Logical diff content lines shown when collapsed; headers, metadata, and wrapped continuations do not consume the limit |
diffWordWrap |
boolean | true |
Wrap long diff lines when needed |
showTruncationHints |
boolean | false |
Show truncation indicators for compacted output |
showRtkCompactionHints |
boolean | false |
Show RTK compaction hints when RTK metadata exists |
Use builtInToolDisplays to select which built-in rows this extension formats:
{
"builtInToolDisplays": {
"read": true,
"grep": true,
"find": true,
"ls": true,
"bash": true,
"edit": true,
"write": true
}
}Set an entry to false to keep Pi's native renderer. Changes made through /tool-display apply immediately; manual config.json edits require /reload. Display selection never changes tool ownership, activation, definitions, or execution. Legacy registerToolOverrides input remains supported and is not rewritten merely by loading it.
Use customToolOverrides when another extension registers a noisy top-level tool and you want pi-tool-display to compact its result output. Custom overrides are explicit opt-in only: unlisted or disabled tools keep their original renderers, and native call renderers are preserved by default.
{
"customToolOverrides": {
"ide_find_symbol": {
"enabled": true,
"kind": "generic",
"outputMode": "summary"
},
"custom_mcp_gateway": {
"enabled": true,
"kind": "mcp",
"outputMode": "preview"
}
}
}Each entry supports:
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Whether pi-tool-display should decorate this custom tool |
kind |
string | "generic" |
generic for plain compact output, or mcp for MCP-style call labels and result handling |
outputMode |
string | "summary" |
hidden, summary, or preview for this custom tool's result output |
overrideCallRenderer |
boolean | false |
Replace the tool's native call renderer with the generic or MCP call renderer |
Boolean shorthand is also accepted:
{
"customToolOverrides": {
"ide_find_symbol": true,
"noisy_tool_to_leave_alone": false
}
}Notes:
- Built-in tool names (
read,grep,find,ls,bash,edit,write) are ignored here; usebuiltInToolDisplaysfor those. - With
overrideCallRenderer: true,genericcall rendering shows the tool name and argument count. - With
overrideCallRenderer: true,mcpcall rendering understands MCP proxy-style arguments such astool,server,search,describe, andconnect. - Overrides are selected at render time, so they work regardless of tool registration or extension load order.
{
"enabled": true,
"debug": false,
"builtInToolDisplays": {
"read": true,
"grep": true,
"find": true,
"ls": true,
"bash": true,
"edit": true,
"write": true
},
"customToolOverrides": {
"ide_find_symbol": {
"enabled": true,
"kind": "generic",
"outputMode": "summary"
},
"custom_mcp_gateway": {
"enabled": true,
"kind": "mcp",
"outputMode": "preview"
}
},
"enableNativeUserMessageBox": true,
"enableToolSeparator": true,
"toolSeparatorStyle": "dashed",
"toolSeparatorColor": "borderMuted",
"userMessageBorderColor": "border",
"readOutputMode": "summary",
"searchOutputMode": "count",
"mcpOutputMode": "summary",
"previewLines": 12,
"expandedPreviewMaxLines": 4000,
"bashOutputMode": "opencode",
"bashCollapsedLines": 15,
"bashCommandMode": "preview",
"bashCommandPreviewLines": 3,
"bashErrorOutputMode": "preview",
"bashErrorPreviewLines": 3,
"diffViewMode": "auto",
"diffIndicatorMode": "bars",
"diffSplitMinWidth": 120,
"diffCollapsedLines": 24,
"diffWordWrap": true,
"showTruncationHints": false,
"showRtkCompactionHints": false
}Debug logging is disabled by default. Set debug to true in the extension root config.json only when collecting diagnostics; missing or non-true values are treated as false. When enabled, diagnostics are appended to debug/debug.log under a runtime-created debug/ directory, and no debug output is written to the terminal.
edit and write results use the same diff renderer. In auto mode the extension chooses split or unified layout based on available width. Collapsed and expanded limits count logical Diff content lines, so headers, trusted omission metadata, and wrapped continuations do not consume the body budget. The extra omission hint reports the omitted visual Diff lines.
Partial edit calls can render a diff only from explicit old/new text supplied by the call. write calls show neutral content summaries unless the tool supplies trustworthy diff evidence. Rendering never reads the workspace to reconstruct a preimage or infer create/overwrite semantics.
When diff input includes Pi anchored read lines such as 12#AB:content, the renderer treats the anchor as line metadata and displays the LINE#HASH label in the gutter while keeping the content aligned for split, unified, and compact diff layouts.
When content is available, write call summaries include line count and byte size information inline so you can quickly see the size of the pending write before expanding the result.
When enabled, user prompts render inside a bordered box using Pi's native user message component. The renderer preserves markdown content more safely and normalizes ANSI/background handling to avoid odd nested background artifacts. userMessageBorderColor selects one of six stable Pi Theme foreground tokens; the user title keeps its accent styling.
When enableToolSeparator is enabled, every tool row—including partial updates and otherwise-native third-party tools—ends with one width-safe separator. toolSeparatorStyle selects dashed or solid, while toolSeparatorColor selects one of border, borderAccent, borderMuted, accent, muted, or dim. Separators are presentation-only and do not change tool renderers or execution.
The extension does not probe tool metadata to identify MCP tools. MCP-style rendering is enabled only by an exact customToolOverrides entry or an explicit producer adapter.
RTK controls remain capability-aware:
- RTK optimizer available: the modal exposes an RTK compaction-hints toggle
- RTK optimizer unavailable: the RTK control is hidden and RTK compaction hints are disabled
This keeps the RTK UI aligned with the current environment. Explicit producer adapters may still register dynamically; unconfigured tools remain native.
/reload is fully supported. On reload, the extension removes its display patches, timers, and Adapter registrations before reinstalling the current display policy. It never re-registers tools. No manual cleanup is needed.
Set builtInToolDisplays.<tool> to false to retain Pi's native renderer for that built-in. Display selection is independent of the executable tool's owner. Modal changes apply immediately; manual JSON edits require /reload. Use /tool-display show to inspect the effective display state.
If your settings are not being applied:
- Check that the global Pi tool-display config exists (default:
~/.pi/agent/extensions/pi-tool-display/config.json, respectsPI_CODING_AGENT_DIR) - Make sure the JSON is valid
- Run
/tool-display showto inspect the current display-policy summary
Add the exact third-party tool name under customToolOverrides. Use kind: "generic" for ordinary tools and kind: "mcp" for MCP proxy or direct tools. MCP tools are not detected or intercepted separately.
The RTK control appears only when the optimizer is available. MCP-style tools are configured manually under customToolOverrides; there is no auto-detected MCP control.
pi-tool-display/
├── index.ts # Extension entrypoint for Pi auto-discovery
├── src/
│ ├── index.ts # Bootstrap and extension registration
│ ├── capabilities.ts # RTK capability detection
│ ├── config-command.ts # Single lazy /tool-display registration entry
│ ├── config-modal.ts # /tool-display settings UI and command handling
│ ├── config-store.ts # Config load/save and normalization
│ ├── disposable.ts # Reload-safe cleanup registry for display patches and timers
│ ├── diff-renderer.ts # Edit/write diff rendering engine
│ ├── line-width-safety.ts # Width clamping helpers for narrow panes
│ ├── pi-host-adapter.ts # Supported Pi host-shape qualification
│ ├── presets.ts # Preset definitions and matching
│ ├── render-utils.ts # Shared rendering helpers
│ ├── tool-display-resolver.ts # Pure display-policy resolution
│ ├── tool-display-runtime.ts # Reload-safe renderer runtime
│ ├── tool-execution-patch.ts # Final tool-row display seam
│ ├── tool-overrides.ts # Built-in, MCP, and custom display renderers plus Adapter API
│ ├── types.ts # Shared config and type definitions
│ ├── user-message-box-markdown.ts # Markdown extraction for user message rendering
│ ├── user-message-box-native.ts # Native user message box registration
│ ├── user-message-box-patch.ts # Safe native render patching helpers
│ ├── user-message-box-renderer.ts # User message border renderer
│ ├── user-message-box-utils.ts # ANSI/background normalization helpers
│ ├── write-display-utils.ts # Write summary helpers
│ └── zellij-modal.ts # Modal UI primitives
├── config/
│ └── config.example.json # Starter config template
└── tests/
├── ansi-utils.test.ts # ANSI utility tests including foreground RGB preservation
├── bash-display.test.ts # Deterministic Bash display tests
├── capabilities-edge.test.ts # Capability detection edge cases
├── config-modal.test.ts # Config modal tests
├── custom-tool-overrides.test.ts # Opt-in custom tool override tests
├── debug-logger-edge.test.ts # Debug logger edge cases
├── diff-renderer-ansi.test.ts # ANSI/background handling tests for diff rendering
├── diff-renderer-edge.test.ts # Diff renderer edge case tests
├── diff-renderer-width.test.ts # Width and background coverage tests for diff rendering
├── index-integration.test.ts # Integration tests for extension lifecycle
├── presets-edge.test.ts # Preset edge case tests
├── reload-behavior.test.ts # Reload-safe cleanup and re-registration tests
└── tool-ui-utils.test.ts # Utility tests for user message and diff helpers
# Type check
npm run build
# Run the local real-runtime contract (missing optional runtimes are skipped)
npm run test:contract:local
# Standard local verification (missing optional Pi runtimes are skipped)
npm run check
npm run test:contract:local
npm run build
git diff --check
# Strict runtime qualification (requires every runtime root below)
PI_RUNTIME_DEV_ROOT=/path/to/pi-dev \
PI_RUNTIME_0_81_1_ROOT=/path/to/pi-0.81.1 \
PI_RUNTIME_0_82_0_ROOT=/path/to/pi-0.82.0 \
npm run test:contract:required