Documentation Index Fetch the complete documentation index at: https://mintlify.com/badlogic/pi-mono/llms.txt
Use this file to discover all available pages before exploring further.
Extensibility System
Pi’s extensibility system allows you to customize and extend its behavior without forking the codebase. The system provides four primary extension mechanisms: Extensions , Skills , Prompt Templates , and Themes .
Extension Mechanisms
Mechanism Format Purpose Access Level Extensions TypeScript/JavaScript Full agent customization Complete (tools, events, UI, commands) Skills Markdown Task-specific instructions Read-only (instructions for agent) Prompt Templates Markdown Reusable prompts Text substitution Themes JSON UI customization Visual styling
Extensions
Extensions are TypeScript or JavaScript modules that receive full access to the agent’s lifecycle, allowing you to:
Subscribe to lifecycle events
Register LLM-callable tools
Add slash commands
Define keyboard shortcuts
Customize the UI
Modify context before LLM calls
Intercept tool executions
Extension Structure
An extension is a module that exports a factory function:
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent/hooks' ;
import { Type } from '@sinclair/typebox' ;
export default async ( pi : ExtensionAPI ) => {
// Extension initialization code
console . log ( 'Extension loaded!' );
// Subscribe to events, register tools, etc.
};
Discovery and Loading
Extensions are discovered from:
Global : ~/.pi/agent/extensions/
Project : .pi/extensions/
Explicit : --extension path/to/extension.ts
Discovery rules :
Direct .ts or .js files in the extensions directory
Subdirectories with index.ts or index.js
Subdirectories with package.json containing a pi.extensions field
Direct File
Index File
Package Manifest
~ /.pi/agent/extensions/
my-extension.ts # ✓ Discovered
Extension API Reference
The ExtensionAPI provides methods for registering functionality and accessing the agent:
Event Subscription
pi . on ( 'session_start' , async ( event , ctx ) => {
console . log ( 'Session started' );
});
pi . on ( 'message_update' , async ( event , ctx ) => {
if ( event . assistantMessageEvent . type === 'text_delta' ) {
// Stream text to external logger
}
});
pi . on ( 'tool_call' , async ( event , ctx ) => {
// Block or log tool calls
if ( event . toolName === 'bash' && event . input . command . includes ( 'rm -rf' )) {
return { block: true , reason: 'Dangerous command blocked' };
}
});
Key event types :
session_start, session_switch, session_compact
agent_start, agent_end
turn_start, turn_end
message_start, message_update, message_end
tool_execution_start, tool_execution_update, tool_execution_end
tool_call, tool_result (interceptors)
context (modify messages before LLM)
model_select, input
pi . registerTool ({
name: 'search_docs' ,
label: 'Search Documentation' ,
description: 'Search project documentation' ,
parameters: Type . Object ({
query: Type . String ({ description: 'Search query' }),
limit: Type . Optional ( Type . Number ({ default: 10 }))
}),
execute : async ( toolCallId , params , signal , onUpdate , ctx ) => {
// Stream progress updates
onUpdate ?.({
content: [{ type: 'text' , text: 'Searching...' }],
details: { progress: 0.5 }
});
const results = await searchDocs ( params . query , params . limit );
return {
content: [{ type: 'text' , text: JSON . stringify ( results ) }],
details: { resultCount: results . length }
};
},
// Optional: Custom rendering
renderResult : ( result , options , theme ) => {
// Return a TUI Component for custom display
}
});
Command Registration
pi . registerCommand ( 'deploy' , {
description: 'Deploy the application' ,
getArgumentCompletions : ( prefix ) => {
return [
{ label: 'staging' , description: 'Deploy to staging' },
{ label: 'production' , description: 'Deploy to production' }
];
},
handler : async ( args , ctx ) => {
const target = args . trim () || 'staging' ;
ctx . ui . notify ( `Deploying to ${ target } ...` );
// Use ctx to access agent state
await ctx . waitForIdle ();
// Send a message to the agent
pi . sendUserMessage ( `Deploy to ${ target } completed` );
}
});
Keyboard Shortcuts
pi . registerShortcut ( 'ctrl+shift+d' , {
description: 'Toggle debug mode' ,
handler : async ( ctx ) => {
debugMode = ! debugMode ;
ctx . ui . notify ( `Debug mode: ${ debugMode ? 'ON' : 'OFF' } ` );
}
});
UI Customization
pi . on ( 'session_start' , async ( event , ctx ) => {
// Set a status indicator
ctx . ui . setStatus ( 'git-branch' , 'main' );
// Add a custom widget
ctx . ui . setWidget ( 'my-widget' , [
'Custom widget content' ,
'Line 2'
], { placement: 'aboveEditor' });
// Or use a component factory
ctx . ui . setWidget ( 'my-component' , ( tui , theme ) => {
return new MyCustomComponent ( tui , theme );
});
});
Provider Registration
pi . registerProvider ( 'my-proxy' , {
baseUrl: 'https://proxy.example.com' ,
apiKey: 'PROXY_API_KEY' ,
api: 'anthropic-messages' ,
models: [
{
id: 'claude-sonnet-4' ,
name: 'Claude 4 Sonnet (Proxied)' ,
reasoning: true ,
input: [ 'text' , 'image' ],
cost: { input: 3 , output: 15 , cacheRead: 0.3 , cacheWrite: 3.75 },
contextWindow: 200000 ,
maxTokens: 8192
}
]
});
Extension Context
The context object (ctx) passed to event handlers provides:
interface ExtensionContext {
// UI methods
ui : ExtensionUIContext ;
hasUI : boolean ;
// State
cwd : string ;
sessionManager : ReadonlySessionManager ;
model : Model < any > | undefined ;
// Control
isIdle () : boolean ;
abort () : void ;
shutdown () : void ;
// Context management
getContextUsage () : ContextUsage | undefined ;
compact ( options ?: CompactOptions ) : void ;
getSystemPrompt () : string ;
}
// Extended context for commands
interface ExtensionCommandContext extends ExtensionContext {
waitForIdle () : Promise < void >;
newSession () : Promise <{ cancelled : boolean }>;
fork ( entryId : string ) : Promise <{ cancelled : boolean }>;
navigateTree ( targetId : string ) : Promise <{ cancelled : boolean }>;
switchSession ( sessionPath : string ) : Promise <{ cancelled : boolean }>;
reload () : Promise < void >;
}
Extension Patterns
Pattern: Custom Tool with UI
Pattern: Context Modification
export default async ( pi : ExtensionAPI ) => {
// Inject project context before every LLM call
pi . on ( 'context' , async ( event , ctx ) => {
const projectInfo = await getProjectInfo ( ctx . cwd );
// Prepend a system message
return {
messages: [
{
role: 'system' as const ,
content: `Project: ${ projectInfo . name } \n Stack: ${ projectInfo . stack } ` ,
timestamp: Date . now ()
},
... event . messages
]
};
});
};
Pattern: Monitoring Extension
export default async ( pi : ExtensionAPI ) => {
let sessionStart = 0 ;
let tokenCount = { input: 0 , output: 0 };
pi . on ( 'session_start' , async ( event , ctx ) => {
sessionStart = Date . now ();
tokenCount = { input: 0 , output: 0 };
});
pi . on ( 'turn_end' , async ( event , ctx ) => {
const usage = event . message . usage ;
if ( usage ) {
tokenCount . input += usage . input ;
tokenCount . output += usage . output ;
const elapsed = Math . floor (( Date . now () - sessionStart ) / 1000 );
ctx . ui . setStatus ( 'session-stats' ,
` ${ elapsed } s | ${ tokenCount . input + tokenCount . output } tokens`
);
}
});
};
Skills
Skills are markdown files that provide task-specific instructions to the agent. They’re discovered automatically and presented in the system prompt or loaded explicitly via /skill commands.
Skill Structure
---
name : git-workflow
description : Follow best practices for Git workflows
---
# Git Workflow Instructions
When working with Git:
1. **Before making changes** : Always check current branch and status
```bash
git status
git branch
Commit messages : Use conventional commits format
feat: New feature
fix: Bug fix
docs: Documentation
refactor: Code restructuring
Before pushing : Review changes
### Skill Discovery
Skills are loaded from:
1. **Global**: `~/.pi/agent/skills/`
2. **Project**: `.pi/skills/`
3. **Explicit**: `--skill path/to/skill.md`
**Discovery rules**:
- Direct `.md` files in root (file name becomes skill name)
- Subdirectories with `SKILL.md` (directory name becomes skill name)
- Respects `.gitignore`, `.ignore`, `.fdignore` patterns
**Root Files:**
```bash
~/.pi/agent/skills/
python-debug.md # Skill name: python-debug
git-workflow.md # Skill name: git-workflow
Subdirectories:
.pi/skills/
docker-deploy/
SKILL.md # Skill name: docker-deploy
templates/
Dockerfile
Skill Frontmatter
---
name : skill-name # Optional: defaults to file/directory name
description : Short description # Required: shown in skill list
disable-model-invocation : true # Optional: prevent auto-loading in prompt
---
disable-model-invocation : When true, the skill is NOT automatically added to the system prompt. It can only be loaded explicitly via /skill:name commands. Useful for:
Large reference documents
Context-specific guides
Skills that conflict with default behavior
Using Skills
Automatic loading : Skills are presented in the system prompt:
The following skills provide specialized instructions for specific tasks.
Use the read tool to load a skill's file when the task matches its description.
< available_skills >
< skill >
< name > git-workflow </ name >
< description > Follow best practices for Git workflows </ description >
< location > /home/user/.pi/agent/skills/git-workflow.md </ location >
</ skill >
</ available_skills >
Manual invocation : Extensions can check for and load skills:
pi . on ( 'input' , async ( event , ctx ) => {
if ( event . text . startsWith ( '/skill:' )) {
const skillName = event . text . slice ( 7 );
// Load and inject skill content
return { action: 'handled' };
}
});
Prompt Templates
Prompt templates are reusable prompts with argument substitution. They’re useful for frequently used prompts or standardized workflows.
Template Structure
---
description : Explain code with detailed comments
---
Read the file at $1 and add detailed comments explaining:
- What the code does
- Why it's structured this way
- Any potential issues or improvements
$ARGUMENTS
Template Syntax
Positional arguments : $1, $2, $3, …
All arguments : $@ or $ARGUMENTS
Argument slicing : ${@:N} or ${@:N:L}
${@:2}: All arguments from 2nd onwards
${@:2:3}: 3 arguments starting from 2nd
Example: Code Review
Example: Refactor
---
description : Review code for issues
---
Review $1 for:
1. Bugs
2. Performance issues
3. Security vulnerabilities
Focus on: $ARGUMENTS
Template Discovery
Templates are loaded from:
Global : ~/.pi/agent/prompts/
Project : .pi/prompts/
Explicit : --prompt path/to/template.md
Usage :
# In Pi chat
/review src/app.ts security performance
# Expands to:
# Review src/app.ts for:
# 1. Bugs
# 2. Performance issues
# 3. Security vulnerabilities
#
# Focus on: security performance
Themes
Themes customize the visual appearance of Pi’s TUI. They’re JSON files that define colors and styles.
Theme Structure
{
"name" : "My Theme" ,
"colors" : {
"primary" : "#00ff00" ,
"secondary" : "#0088ff" ,
"error" : "#ff0000" ,
"warning" : "#ffaa00"
},
"chat" : {
"userMessageBorder" : "blue" ,
"assistantMessageBorder" : "green" ,
"toolCallBorder" : "yellow"
},
"editor" : {
"border" : "cyan" ,
"cursor" : "white"
},
"markdown" : {
"heading" : { "fg" : "cyan" , "bold" : true },
"code" : { "fg" : "yellow" },
"codeBlockBorder" : "gray"
}
}
Theme Discovery
Themes are loaded from:
Global : ~/.pi/agent/themes/
Project : .pi/themes/
Built-in : Shipped with pi-coding-agent
Usage :
pi --theme my-theme
# or
/theme my-theme
Pi Packages
Pi packages bundle extensions, skills, prompts, and themes into a single npm package for distribution.
Package Structure
{
"name" : "pi-package-example" ,
"version" : "1.0.0" ,
"pi" : {
"extensions" : [ "src/extension.ts" ],
"skills" : [ "skills/" ],
"prompts" : [ "prompts/" ],
"themes" : [ "themes/" ]
}
}
Installation
# Via npm
npm install -g pi-package-example
# Via git
git clone https://github.com/user/pi-package-example ~/.pi/agent/extensions/example
Creating a Package
Initialize package
mkdir my-pi-package
cd my-pi-package
npm init -y
Add package.json pi field
{
"pi" : {
"extensions" : [ "dist/extension.js" ],
"skills" : [ "skills/" ],
"prompts" : [ "prompts/" ]
}
}
Create extension
// src/extension.ts
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent/hooks' ;
export default async ( pi : ExtensionAPI ) => {
// Extension code
};
Build and publish
npm run build
npm publish
Extension Loading
Extensions are loaded using jiti, a just-in-time TypeScript compiler. This enables:
Writing extensions in TypeScript without pre-compilation
Hot module reloading during development
Support for ES modules and CommonJS
Automatic dependency resolution
Module Resolution
Extensions have access to bundled packages:
@mariozechner/pi-coding-agent
@mariozechner/pi-agent-core
@mariozechner/pi-ai
@mariozechner/pi-tui
@sinclair/typebox
Example :
import { Type } from '@sinclair/typebox' ;
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent/hooks' ;
import { getModel } from '@mariozechner/pi-ai' ;
export default async ( pi : ExtensionAPI ) => {
// Use bundled packages directly
};
Error Handling
Extension errors are caught and logged without crashing Pi:
[Extension Error] my-extension.ts
Failed to execute handler for 'session_start': TypeError: Cannot read property 'x' of undefined
Check ~/.pi/agent/logs/ for detailed error logs.
Best Practices
Keep extensions focused on a single responsibility
Use event handlers for observation, tools for capabilities
Provide clear descriptions for commands and tools
Handle errors gracefully (extensions shouldn’t crash Pi)
Use TypeScript for type safety
Write clear, actionable instructions
Include code examples where relevant
Keep skills focused on a specific task or domain
Use descriptive names (lowercase, hyphens)
Test skills by invoking manually first
Use positional args for required parameters
Use $ARGUMENTS for optional/flexible content
Provide good default behavior
Include examples in the description
Next Steps
Architecture Understand the overall system design
Packages Explore package capabilities