.pi/skills/extension-dev-guide/SKILL.md
How to develop Pi extensions. Discovery workflow for TUI components, available APIs, composition patterns and common mistakes. Use when building or modifying extensions.
npx skillsauth add jitsusama/agentic-harness.pi extension-dev-guideInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Before building any UI, follow this sequence:
Read Pi's TUI docs for patterns and available
components. The main Pi documentation is listed in the
system prompt under "Pi documentation" — read docs/tui.md
from there. Follow links to related docs as needed.
Read the type declarations for the specific component
you need. The @mariozechner/pi-tui package's
dist/index.d.ts and dist/components/*.d.ts files show
the exact API surface.
Read the higher-level components exported by
@mariozechner/pi-coding-agent in its
dist/modes/interactive/components/index.d.ts.
Check this project's lib/ui/ for existing
abstractions built on top of Pi's primitives. The barrel
at lib/ui/index.ts shows the public surface. Don't
duplicate what's already there.
Browse Pi's examples for working implementations. The examples directory is listed in the system prompt.
Extensions and library code serve different roles. See AGENTS.md's "Integration Architecture" section for the full pattern. The short version:
lib/ holds domain logic: API clients, authentication,
renderers, types and UI components. This is reusable code
that other Pi packages can import.extensions/ holds Pi-specific wiring: tool
registration, renderCall/renderResult, slash commands,
confirmation gates, session state and lifecycle.When building an integration, the extension should be a thin consumer of its library. Caching belongs in the extension; the library stays stateless.
When building a guardian or workflow, shared logic goes in
lib/internal/. See AGENTS.md for the full guidelines on
public vs internal code.
From @mariozechner/pi-tui:
| Need | Component |
|------|-----------|
| Group children vertically | Container |
| Padded container with background | Box |
| Display text with word wrap | Text |
| Display text truncated to width | TruncatedText |
| Render markdown | Markdown |
| Empty vertical space | Spacer |
| Single-line text input | Input |
| Multi-line editor with autocomplete | Editor |
| Pick from a list | SelectList |
| Toggle settings | SettingsList |
| Spinner animation | Loader |
| Cancellable spinner with AbortSignal | CancellableLoader |
| Display an image | Image |
| Key detection | matchesKey, Key |
| Text width / truncation / wrapping | visibleWidth, truncateToWidth, wrapTextWithAnsi |
| Fuzzy search | fuzzyMatch, fuzzyFilter |
From @mariozechner/pi-coding-agent:
| Need | Component |
|------|-----------|
| Full-width border line | DynamicBorder |
| Bordered spinner with cancel | BorderedLoader |
| Custom editor with app keybindings | CustomEditor |
| Coloured diff output | renderDiff |
| Syntax highlighting | highlightCode, getLanguageFromPath |
| Pre-built themes for components | getMarkdownTheme, getSelectListTheme, getEditorTheme, getSettingsListTheme |
| Keybinding hint formatting | keyHint, appKeyHint, rawKeyHint |
| Visual line truncation | truncateToVisualLines |
From ctx.ui (ExtensionUIContext):
| Need | Method |
|------|--------|
| Selection dialog | select() |
| Yes/no confirmation | confirm() |
| Text input dialog | input() |
| Multi-line editor dialog | editor() |
| Toast notification | notify() |
| Footer status indicator | setStatus() |
| Loading message during streaming | setWorkingMessage() |
| Persistent widget above/below editor | setWidget() |
| Replace footer | setFooter() |
| Replace header | setHeader() |
| Full custom component | custom() |
| Overlay (floating component) | custom() with { overlay: true } |
| Replace the input editor | setEditorComponent() |
| Manipulate editor text | setEditorText(), getEditorText(), pasteToEditor() |
| Theme access | theme property |
| Tool output expansion | getToolsExpanded(), setToolsExpanded() |
| Terminal title | setTitle() |
| Raw terminal input | onTerminalInput() |
From this project's lib/ui/ (barrel: lib/ui/index.ts):
| Need | Export |
|------|--------|
| Interactive single-choice panel | promptSingle |
| Interactive tabbed panel | promptTabbed |
| Read-only content display | view |
| Stateful workspace prompt | workspace |
| Markdown/diff/code rendering | renderMarkdown, renderDiff, renderCode |
| Navigable list rendering | renderNavigableList, renderNavigableSections |
| Text wrapping | contentWrapWidth, wordWrap |
Internal files not in the barrel (redirect.ts,
tab-completion.ts, navigable-list.ts input handler,
text-layout.ts, panel-height.ts) can be imported
directly by extensions in this package when needed.
These are design decisions in Pi that aren't obvious from the type signatures. They'll cause real bugs when you miss them.
render(width) must return lines no wider than width visible
characters. Use truncateToWidth() on every line. ANSI escape
codes don't count toward width, but wide characters (CJK,
emoji) count as 2.
Never import theme directly. Pi's module caching means the
global theme may be undefined in extension code loaded via
jiti. Always grab the theme parameter from:
ctx.ui.custom((tui, theme, kb, done) => ...)renderCall(args, theme)renderResult(result, options, theme)// Correct: explicit type annotation
new DynamicBorder((s: string) => theme.fg("accent", s))
// Wrong: jiti inference fails
new DynamicBorder((s) => theme.fg("accent", s))
The TUI doesn't auto-render. After modifying state in
handleInput, call tui.requestRender() to trigger a
redraw.
When the theme changes, the TUI calls invalidate() on all
components. If you've baked theme colours into cached strings
(via theme.fg(), theme.bg()), you need to rebuild them in
invalidate(). See the "Rebuild on Invalidate" pattern in
Pi's tui.md.
Create fresh instances each time; never reuse a reference after the overlay closes because the component is disposed when it closes.
Container components that embed Input or Editor must
implement the Focusable interface and propagate the focused
property to the child. Without this, IME candidate windows
(CJK input) show up in the wrong position.
When placing Text inside a Box, use 0,0 padding on the
Text. The Box handles padding. Double-padding is a common
mistake.
All dialog methods (select, confirm, input) accept an
options object with timeout (auto-dismiss with countdown)
and signal (AbortSignal for manual dismiss).
development
Structure of a quest README and the documents that live under it: frontmatter shape, the four core and four optional body sections, emoji glyphs, ID format, alias notation, Cast bullets and Journey entries. Use when writing or editing a quest README, a plan, research, brief or report document under a quest. Pairs with quest-convention for choices like kind, promotion and reordering. Follow the prose-standard for voice.
tools
Operational conventions for the quest system: when to use a quest versus a subquest versus a sidequest, when to scaffold a plan or research document, how to reorder priorities, when to add optional sections, when to conclude versus retire, the resuscitate pattern. Use when driving the quest tool, deciding kind, promoting or parking work, or organising a project as quests. Pairs with quest-format for the on-disk shape.
development
Markdown structure rules: Title Case headings with their exceptions, the line-width target and its legitimate exceptions, reference-style links, fenced code blocks with language tags, tables and lists. Use when writing or editing any markdown file (README, AGENTS, docs, plans, skill files), or when adding a heading, link, table or code block. Owns markdown structure; pairs with prose-standard, which owns voice, grammar, spelling and punctuation.
tools
How to measure whether convention corrections keep recurring in the pi session logs, by category and by week. Use to record a baseline before the convention gates take effect and to re-run afterwards to confirm the recurring categories bend down. Pairs with the convention gates (pr-guardian, issue-guardian, commit-guardian, slack-integration) and the convention-context extension.