Skip to content

Extensions (TypeScript)

✓ Fresh · 2026-02-24

Extensions are TypeScript modules that add custom tools, commands, shortcuts, event handlers, and UI elements to Pi. This is Pi's primary extensibility mechanism.

What Extensions Can Do

Full capabilities list:

  • Custom and replacement tools
  • Sub-agents
  • Plan mode
  • Custom compaction logic
  • Permission gates and confirmation flows
  • Custom editors
  • Status lines and widgets
  • Git checkpointing
  • SSH and sandbox execution
  • MCP integration
  • Games and utilities

Creating an Extension

An extension is a TypeScript file that exports a default function receiving the ExtensionAPI:

typescript
export default function (pi: ExtensionAPI) {
  // Register a custom tool
  pi.registerTool({
    name: "deploy",
    description: "Deploy the application to production",
    parameters: {
      type: "object",
      properties: {
        environment: {
          type: "string",
          enum: ["staging", "production"],
          description: "Target environment"
        }
      },
      required: ["environment"]
    },
    async execute(args) {
      const { environment } = args;
      // Your deployment logic here
      return `Deployed to ${environment}`;
    }
  });

  // Register a custom command
  pi.registerCommand("stats", {
    description: "Show session statistics",
    async execute() {
      // Your stats logic here
    }
  });

  // Hook into events
  pi.on("tool_call", async (event, ctx) => {
    console.log(`Tool called: ${event.tool}`);
    // Modify, log, or gate tool calls
  });
}

Loading Extensions

Command Line

bash
# Single extension
pi -e extensions/my-ext.ts

# Stack multiple extensions (loaded in order)
pi -e ext1.ts -e ext2.ts

# Combine with session flags
pi -c -e extensions/damage-control.ts -e extensions/tool-counter.ts

Auto-Loading

Extensions placed in these directories are loaded automatically:

DirectoryScope
~/.pi/agent/extensions/Global (all projects)
.pi/extensions/Project-specific
Pi packagesInstalled via pi install

Using just Recipes

Create a justfile for convenient extension combos:

just
# Plain Pi, no extensions
pi:
    pi

# Pi with specific extension
ext name:
    pi -e extensions/{{name}}.ts

# Combine multiple extensions
full:
    pi -e extensions/damage-control.ts -e extensions/tool-counter.ts -e extensions/minimal.ts

Extension API Reference

pi.registerTool(toolDef)

Register a new tool the agent can call:

typescript
pi.registerTool({
  name: string,
  description: string,
  parameters: JSONSchema,
  execute: (args: any) => Promise<string>
});

pi.registerCommand(name, commandDef)

Register a slash command:

typescript
pi.registerCommand("mycommand", {
  description: string,
  execute: () => Promise<void>
});

pi.on(event, handler)

Subscribe to lifecycle events:

typescript
pi.on("tool_call", async (event, ctx) => {
  // event contains tool name, arguments, etc.
  // ctx provides session context
});

pi.on("session_start", async (event) => {
  // Runs when a session begins
});

See Hooks & Events for the complete event list.

Extension Loading Order

Best Practices

  1. Single responsibility — Each extension should do one thing well
  2. Use events — Hook into lifecycle events rather than replacing core behavior
  3. Stack extensions — Combine multiple focused extensions instead of building monoliths
  4. Share via packages — Bundle extensions as Pi packages for easy distribution
  5. Hot reload — Use /reload during development to refresh extensions without restarting

Example: Permission Gate

typescript
export default function (pi: ExtensionAPI) {
  pi.on("tool_call", async (event, ctx) => {
    if (event.tool === "bash" && event.args.command.includes("rm")) {
      const confirmed = await ctx.confirm(
        `Dangerous command detected: ${event.args.command}\nAllow?`
      );
      if (!confirmed) {
        return { abort: true, message: "Command blocked by permission gate." };
      }
    }
  });
}

Example: Custom Status Line

typescript
export default function (pi: ExtensionAPI) {
  let toolCount = 0;

  pi.on("tool_call", async () => {
    toolCount++;
    pi.setStatusLine(`Tools used: ${toolCount}`);
  });

  pi.on("session_start", async () => {
    toolCount = 0;
    pi.setStatusLine("Tools used: 0");
  });
}

Pi Coding Agent SOP Documentation