Back to Pi Mono

Extension Examples

packages/coding-agent/examples/extensions/README.md

0.73.09.3 KB
Original Source

Extension Examples

Example extensions for pi-coding-agent.

Usage

bash
# 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

ExtensionDescription
permission-gate.tsPrompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.)
protected-paths.tsBlocks writes to protected paths (.env, .git/, node_modules/)
confirm-destructive.tsConfirms before destructive session actions (clear, switch, fork)
dirty-repo-guard.tsPrevents session changes with uncommitted git changes
sandbox/OS-level sandboxing using @anthropic-ai/sandbox-runtime with per-project config

Custom Tools

ExtensionDescription
todo.tsTodo list tool + /todos command with custom rendering and state persistence
hello.tsMinimal custom tool example
question.tsDemonstrates ctx.ui.select() for asking the user questions with custom UI
questionnaire.tsMulti-question input with tab bar navigation between questions
tool-override.tsOverride built-in tools (e.g., add logging/access control to read)
dynamic-tools.tsRegister tools after startup (session_start) and at runtime via command, with prompt snippets and tool-specific prompt guidelines
structured-output.tsFinal structured-output tool that returns terminate: true so the agent can end on the tool call
built-in-tool-renderer.tsCustom compact rendering for built-in tools (read, bash, edit, write) while keeping original behavior
minimal-mode.tsOverride built-in tool rendering for minimal display (only tool calls, no output in collapsed mode)
truncated-tool.tsWraps ripgrep with proper output truncation (50KB/2000 lines)
ssh.tsDelegate all tools to a remote machine via SSH using pluggable operations
subagent/Delegate tasks to specialized subagents with isolated context windows

Commands & UI

ExtensionDescription
preset.tsNamed 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.tsInteractive /tools command to enable/disable tools with session persistence
handoff.tsTransfer context to a new focused session via /handoff <goal>
qna.tsExtracts questions from last response into editor via ctx.ui.setEditorText()
status-line.tsShows turn progress in footer via ctx.ui.setStatus() with themed colors
github-issue-autocomplete.tsAdds #1234 issue completions by stacking a custom autocomplete provider that preloads open issues from gh issue list
widget-placement.tsShows widgets above and below the editor via ctx.ui.setWidget() placement
hidden-thinking-label.tsCustomizes the collapsed thinking label via ctx.ui.setHiddenThinkingLabel()
working-indicator.tsCustomizes the streaming working indicator via ctx.ui.setWorkingIndicator()
model-status.tsShows model changes in status bar via model_select hook
snake.tsSnake game with custom UI, keyboard handling, and session persistence
tic-tac-toe.tsTic-tac-toe vs the agent with executionMode: "sequential" tools to prevent race conditions on shared cursor state
send-user-message.tsDemonstrates pi.sendUserMessage() for sending user messages from extensions
timed-confirm.tsDemonstrates AbortSignal for auto-dismissing ctx.ui.confirm() and ctx.ui.select() dialogs
rpc-demo.tsExercises all RPC-supported extension UI methods; pair with examples/rpc-extension-ui.ts
modal-editor.tsCustom vim-like modal editor via ctx.ui.setEditorComponent()
rainbow-editor.tsAnimated rainbow text effect via custom editor
notify.tsDesktop notifications via OSC 777 when agent finishes (Ghostty, iTerm2, WezTerm)
titlebar-spinner.tsBraille spinner animation in terminal title while the agent is working
summarize.tsSummarize conversation with GPT-5.2 and show in transient UI
custom-footer.tsCustom footer with git branch and token stats via ctx.ui.setFooter()
custom-header.tsCustom header via ctx.ui.setHeader()
overlay-test.tsTest overlay compositing with inline text inputs and edge cases
overlay-qa-tests.tsComprehensive overlay QA tests: anchors, margins, stacking, overflow, animation
doom-overlay/DOOM game running as an overlay at 35 FPS (demonstrates real-time game rendering)
shutdown-command.tsAdds /quit command demonstrating ctx.shutdown()
reload-runtime.tsAdds /reload-runtime and reload_runtime tool showing safe reload flow
interactive-shell.tsRun interactive commands (vim, htop) with full terminal via user_bash hook
inline-bash.tsExpands !{command} patterns in prompts via input event transformation

Git Integration

ExtensionDescription
git-checkpoint.tsCreates git stash checkpoints at each turn for code restoration on fork
auto-commit-on-exit.tsAuto-commits on exit using last assistant message for commit message

System Prompt & Compaction

ExtensionDescription
pirate.tsDemonstrates systemPromptAppend to dynamically modify system prompt
claude-rules.tsScans .claude/rules/ folder and lists rules in system prompt
custom-compaction.tsCustom compaction that summarizes entire conversation
trigger-compact.tsTriggers compaction when context usage exceeds 100k tokens and adds /trigger-compact command

System Integration

ExtensionDescription
mac-system-theme.tsSyncs pi theme with macOS dark/light mode

Resources

ExtensionDescription
dynamic-resources/Loads skills, prompts, and themes using resources_discover

Messages & Communication

ExtensionDescription
message-renderer.tsCustom message rendering with colors and expandable details via registerMessageRenderer
event-bus.tsInter-extension communication via pi.events

Session Metadata

ExtensionDescription
session-name.tsName sessions for the session selector via setSessionName
bookmark.tsBookmark entries with labels for /tree navigation via setLabel

Custom Providers

ExtensionDescription
custom-provider-anthropic/Custom Anthropic provider with OAuth support and custom streaming implementation
custom-provider-gitlab-duo/GitLab Duo provider using pi-ai's built-in Anthropic/OpenAI streaming via proxy

External Dependencies

ExtensionDescription
with-deps/Extension with its own package.json and dependencies (demonstrates jiti module resolution)
file-trigger.tsWatches a trigger file and injects contents into conversation

Writing Extensions

See docs/extensions.md for full documentation.

typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";

export default function (pi: ExtensionAPI) {
  // Subscribe to lifecycle events
  pi.on("tool_call", async (event, ctx) => {
    if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
      const ok = await ctx.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" }),
    }),
    async execute(toolCallId, params, onUpdate, ctx, signal) {
      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):

typescript
import { StringEnum } from "@mariozechner/pi-ai";

// Good
action: StringEnum(["list", "add"] as const)

// Bad - doesn't work with Google
action: Type.Union([Type.Literal("list"), Type.Literal("add")])

State persistence via details:

typescript
// 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 (const entry of ctx.sessionManager.getBranch()) {
    if (entry.type === "message" && entry.message.toolName === "my_tool") {
      const details = entry.message.details;
      // Reconstruct state from details
    }
  }
});