# Load an extension with --extension flag
pi --extension examples/extensions/permission-gate.ts
# Or copy to extensions directory for auto-discovery
cp permission-gate.ts ~/.pi/agent/extensions/
Examples
Lifecycle & Safety
Extension
Description
permission-gate.ts
Prompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.)
project-trust.ts
Demonstrates the project_trust event for user/global and CLI extensions
protected-paths.ts
Blocks writes to protected paths (.env, .git/, node_modules/)
confirm-destructive.ts
Confirms before destructive session actions (clear, switch, fork)
dirty-repo-guard.ts
Prevents session changes with uncommitted git changes
sandbox/
OS-level sandboxing using @anthropic-ai/sandbox-runtime with per-project config
gondolin/
Route built-in tools and ! commands into a Gondolin micro-VM
Custom Tools
Extension
Description
todo.ts
Todo list tool + /todos command with custom rendering and state persistence
hello.ts
Minimal custom tool example
question.ts
Demonstrates ctx.ui.select() for asking the user questions with custom UI
questionnaire.ts
Multi-question input with tab bar navigation between questions
tool-override.ts
Override built-in tools (e.g., add logging/access control to read)
dynamic-tools.ts
Register tools after startup (session_start) and at runtime via command, with prompt snippets and tool-specific prompt guidelines
kimi-deferred-tools.ts
Search for and progressively activate tools for Kimi's deferred-tool loading protocol
structured-output.ts
Final structured-output tool that returns terminate: true so the agent can end on the tool call
built-in-tool-renderer.ts
Custom compact rendering for built-in tools (read, bash, edit, write) while keeping original behavior
minimal-mode.ts
Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode)
truncated-tool.ts
Wraps ripgrep with proper output truncation (50KB/2000 lines)
ssh.ts
Delegate all tools to a remote machine via SSH using pluggable operations
subagent/
Delegate tasks to specialized subagents with isolated context windows
Commands & UI
Extension
Description
preset.ts
Named presets for model, thinking level, tools, and instructions via --preset flag and /preset command
plan-mode/
Claude Code-style plan mode for read-only exploration with /plan command and step tracking
tools.ts
Interactive /tools command to enable/disable tools with session persistence
handoff.ts
Transfer context to a new focused session via /handoff <goal>
qna.ts
Extracts questions from last response into editor via ctx.ui.setEditorText()
status-line.ts
Shows turn progress in footer via ctx.ui.setStatus() with themed colors
github-issue-autocomplete.ts
Adds #1234 issue completions by stacking a custom autocomplete provider that preloads open issues from gh issue list
widget-placement.ts
Shows widgets above and below the editor via ctx.ui.setWidget() placement
hidden-thinking-label.ts
Customizes the collapsed thinking label via ctx.ui.setHiddenThinkingLabel()
working-indicator.ts
Customizes the streaming working indicator via ctx.ui.setWorkingIndicator()
model-status.ts
Shows model changes in status bar via model_select hook
snake.ts
Snake game with custom UI, keyboard handling, and session persistence
tic-tac-toe.ts
Tic-tac-toe vs the agent with executionMode: "sequential" tools to prevent race conditions on shared cursor state
send-user-message.ts
Demonstrates pi.sendUserMessage() for sending user messages from extensions
timed-confirm.ts
Demonstrates AbortSignal for auto-dismissing ctx.ui.confirm() and ctx.ui.select() dialogs
importtype{ExtensionAPI}from"@earendil-works/pi-coding-agent";import{Type}from"typebox";exportdefaultfunction(pi: ExtensionAPI){// Subscribe to lifecycle events
pi.on("tool_call",async(event,ctx)=>{if(event.toolName==="bash"&&event.input.command?.includes("rm -rf")){constok=awaitctx.ui.confirm("Dangerous!","Allow rm -rf?");if(!ok)return{block: true,reason:"Blocked by user"};}});// Register custom tools
pi.registerTool({name:"greet",label:"Greeting",description:"Generate a greeting",parameters: Type.Object({name: Type.String({description:"Name to greet"}),}),asyncexecute(toolCallId,params,signal,onUpdate,ctx){return{content:[{type:"text",text:`Hello, ${params.name}!`}],details:{},};},});// Register commands
pi.registerCommand("hello",{description:"Say hello",handler: async(args,ctx)=>{ctx.ui.notify("Hello!","info");},});}
Key Patterns
Use StringEnum for string parameters (required for Google API compatibility):
import{StringEnum}from"@earendil-works/pi-ai";// Good
action: StringEnum(["list","add"]asconst)// Bad - doesn't work with Google
action: Type.Union([Type.Literal("list"),Type.Literal("add")])
State persistence via details:
// Store state in tool result details for proper forking support
return{content:[{type:"text",text:"Done"}],details:{todos:[...todos],nextId},// Persisted in session
};// Reconstruct on session events
pi.on("session_start",async(_event,ctx)=>{for(constentryofctx.sessionManager.getBranch()){if(entry.type==="message"&&entry.message.toolName==="my_tool"){constdetails=entry.message.details;// Reconstruct state from details
}}});