Skip to main content

pi-coding-agent-extensions

Build and use custom extensions for Pi Coding Agent, the open-source alternative to Claude Code

Jump to install

Source facts

Repository
reason-machines/claude-code-skills
Last source activity
June 1, 2026 at 13:23
Detected SKILL.md language
English
Stars
4
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
pi-coding-agent-extensions
description
Build and use custom extensions for Pi Coding Agent, the open-source alternative to Claude Code
triggers
["how do I extend pi coding agent","create a custom pi extension","add a widget to pi agent","build a pi coding agent extension","customize pi agent ui","make a pi agent tool","orchestrate multiple pi agents","pi agent communication between agents"]
# Pi Coding Agent Extensions > Skill by [ara.so](https://ara.so) — Claude Code Skills collection. ## Overview Pi Coding Agent is an open-source terminal-based AI coding assistant similar to Claude Code. This project (`pi-vs-claude-code`) provides a collection of production-ready extensions that showcase Pi's customization capabilities: custom UI widgets, multi-agent orchestration, safety auditing, cross-agent integrations, and inter-agent communication. Extensions are TypeScript files that hook into Pi's lifecycle events to add tools, modify UI, intercept commands, and coordinate with other agents. ## Prerequisites All three are required: ```bash # Install Bun (runtime & package manager) curl -fsSL https://bun.sh/install | bash # Install just (task runner) brew install just # macOS # or cargo install just # cross-platform # Install Pi Coding Agent CLI # See https://github.com/mariozechner/pi-coding-agent ``` ## Installation ```bash # Clone the extensions repository git clone https://github.com/disler/pi-vs-claude-code.git cd pi-vs-claude-code # Install dependencies bun install # Copy environment template cp .env.sample .env # Edit .env and add your API keys: # OPENAI_API_KEY=sk-... # ANTHROPIC_API_KEY=sk-ant-... # GEMINI_API_KEY=... # OPENROUTER_API_KEY=... ``` ## API Key Setup Pi does NOT auto-load `.env` files. You must source keys before launching: **Option 1 - Manual source:** ```bash source .env && pi ``` **Option 2 - Shell alias (add to `~/.zshrc`):** ```bash alias pi='source $(pwd)/.env && pi' ``` **Option 3 - Use just (automatic):** ```bash just pi # .env is loaded automatically ``` ## Running Extensions ### Single Extension ```bash pi -e extensions/minimal.ts ``` ### Stack Multiple Extensions ```bash pi -e extensions/minimal.ts -e extensions/cross-agent.ts ``` ### Using Just Recipes ```bash # List all available recipes just # Common recipes just ext-minimal # Compact footer with context meter just ext-tool-counter # Rich footer with tool usage stats just ext-subagent-widget # Spawn background agents with live widgets just ext-agent-team # Multi-agent orchestration dashboard just ext-damage-control # Safety auditing with path controls just ext-pi-pi # Meta-agent that builds Pi agents just coms-net-server # Start agent-to-agent communication hub just coms # Connect to coms hub just all # Open every extension in separate terminals ``` ## Extension Architecture Extensions export a `PiExtension` object with lifecycle hooks: ```typescript import type { PiExtension, PiApp } from "pi"; export default { name: "my-extension", version: "1.0.0", // Called when extension loads async init(app: PiApp) { // Register tools, widgets, commands }, // Called before agent processes a turn async beforeTurn(app: PiApp, turn: Turn) { // Intercept or modify the turn }, // Called after agent completes a turn async afterTurn(app: PiApp, turn: Turn) { // Log, analyze, or trigger follow-up actions }, // Called on shutdown async destroy(app: PiApp) { // Cleanup resources } } satisfies PiExtension; ``` ## Key Extension Examples ### Minimal Footer Compact UI showing model name and context usage: ```typescript import type { PiExtension, PiApp } from "pi"; import { Widget } from "pi/ui"; export default { name: "minimal", version: "1.0.0", async init(app: PiApp) { const footer = new Widget({ position: "footer", render: () => { const model = app.session.model.name; const usage = app.session.contextUsage; const pct = Math.round(usage * 100); const filled = Math.round(usage * 10); const bar = "█".repeat(filled) + "░".repeat(10 - filled); return `${model} [${bar}] ${pct}%`; } }); app.ui.addWidget(footer); } } satisfies PiExtension; ``` ### Custom Tool Registration Add a new tool the agent can call: ```typescript import type { PiExtension, PiApp } from "pi"; export default { name: "custom-tool", version: "1.0.0", async init(app: PiApp) { app.registerTool({ name: "analyze_code_quality", description: "Analyze code quality metrics for a file", parameters: { type: "object", properties: { file_path: { type: "string", description: "Path to the file to analyze" } }, required: ["file_path"] }, handler: async (args: { file_path: string }) => { const content = await app.fs.readFile(args.file_path); // Run analysis logic return { lines: content.split("\n").length, complexity: calculateComplexity(content), issues: findIssues(content) }; } }); } } satisfies PiExtension; function calculateComplexity(code: string): number { // Simplified cyclomatic complexity const branches = (code.match(/if|while|for|case/g) || []).length; return branches + 1; } function findIssues(code: string): string[] { const issues: string[] = []; if (code.includes("eval(")) issues.push("Dangerous eval() usage"); if (code.includes("TODO")) issues.push("Unfinished TODO items"); return issues; } ``` ### Live Widget Above Editor Create a persistent widget that updates in real-time: ```typescript import type { PiExtension, PiApp, Turn } from "pi"; import { Widget } from "pi/ui"; const toolCounts = new Map<string, number>(); export default { name: "tool-counter-widget", version: "1.0.0", async init(app: PiApp) { const widget = new Widget({ position: "above-editor", height: 3, render: () => { const entries = Array.from(toolCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5); return entries .map(([tool, count]) => `${tool}: ${count}`) .join(" | ") || "No tools used yet"; } }); app.ui.addWidget(widget); }, async afterTurn(app: PiApp, turn: Turn) { // Count tool calls from this turn for (const call of turn.toolCalls || []) { toolCounts.set(call.name, (toolCounts.get(call.name) || 0) + 1); } app.ui.refresh(); } } satisfies PiExtension; ``` ### Safety Auditing (Damage Control) Intercept and block dangerous commands before execution: ```typescript import type { PiExtension, PiApp, Turn, ToolCall } from "pi"; import { readFileSync } from "fs"; import { parse } from "yaml"; interface DamageControlRules { blocked_patterns: string[]; allowed_paths: string[]; } export default { name: "damage-control", version: "1.0.0", async beforeTurn(app: PiApp, turn: Turn) { const rules: DamageControlRules = parse( readFileSync(".pi/damage-control-rules.yaml", "utf-8") ); for (const call of turn.toolCalls || []) { if (call.name === "bash") { const cmd = call.arguments.command; // Check blocked patterns for (const pattern of rules.blocked_patterns) { if (cmd.includes(pattern)) { throw new Error( `🚨 BLOCKED: Command contains dangerous pattern "${pattern}"` ); } } // Check path restrictions for file operations if (cmd.match(/rm|mv|cp|>|>>/) && call.arguments.path) { const isAllowed = rules.allowed_paths.some( allowed => call.arguments.path.startsWith(allowed) ); if (!isAllowed) { throw new Error( `🚨 BLOCKED: Path "${call.arguments.path}" is outside allowed directories` ); } } } } } } satisfies PiExtension; ``` Example `.pi/damage-control-rules.yaml`: ```yaml blocked_patterns: - "rm -rf /" - "dd if=" - ":(){ :|:& };:" - "curl | bash" - "wget | sh" allowed_paths: - "/tmp" - "./src" - "./tests" - "./extensions" ``` ## Multi-Agent Orchestration ### Subagent Spawning (`/sub`) Offload tasks to background Pi agents: ```typescript import type { PiExtension, PiApp } from "pi"; import { Widget } from "pi/ui"; import { spawn } from "child_process"; const subagents = new Map<string, any>(); export default { name: "subagent-widget", version: "1.0.0", async init(app: PiApp) { // Register /sub command app.registerCommand({ name: "sub", description: "Spawn a background Pi agent for a task", handler: async (args: string[]) => { const task = args.join(" "); const id = `sub-${Date.now()}`; const widget = new Widget({ position: "above-editor", height: 4, render: () => `🤖 ${id}\n${subagents.get(id)?.status || "Starting..."}` }); app.ui.addWidget(widget); // Spawn Pi subprocess const proc = spawn("pi", ["-p", task], { env: process.env, stdio: ["ignore", "pipe", "pipe"] }); subagents.set(id, { proc, status: "Running...", widget }); proc.stdout.on("data", (data) => { subagents.get(id).status = data.toString().slice(-200); app.ui.refresh(); }); proc.on("close", (code) => { subagents.get(id).status = `✓ Complete (exit ${code})`; app.ui.refresh(); setTimeout(() => { app.ui.removeWidget(widget); subagents.delete(id); }, 3000); }); } }); } } satisfies PiExtension; ``` Usage: ``` /sub implement user authentication with bcrypt ``` ### Agent Team Orchestration Dispatcher pattern with specialist agents: ```typescript import type { PiExtension, PiApp } from "pi"; import { readFileSync } from "fs"; import { parse } from "yaml"; import { execSync } from "child_process"; interface TeamConfig { agents: { [key: string]: { description: string; system_prompt: string; model?: string; } }; } export default { name: "agent-team", version: "1.0.0", async init(app: PiApp) { const config: TeamConfig = parse( readFileSync(".pi/agents/teams.yaml", "utf-8") ); app.registerTool({ name: "dispatch_agent", description: "Delegate a task to a specialist agent", parameters: { type: "object", properties: { agent_name: { type: "string", enum: Object.keys(config.agents), description: "Which specialist agent to use" }, task: { type: "string", description: "The task to delegate" } }, required: ["agent_name", "task"] }, handler: async (args: { agent_name: string; task: string }) => { const agent = config.agents[args.agent_name]; if (!agent) throw new Error(`Unknown agent: ${args.agent_name}`); // Write temporary agent config
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub