Pi Agent Extension Getting Started

Extensions are the most powerful way to customize Pi Agent—you can write custom tools, commands, and event listeners in TypeScript.


What is an Extension

An Extension is a TypeScript module that can do the following.

CapabilityDescriptionTypical Use
RegisterCustom ToolsAdd new callable functions for AIQuery internal systems, read/write databases, wrap third-party APIs
RegisterCustom CommandsInvoked by users via /command-nameWrap common prompts, execute fixed workflows with one click
ListenLifecycle EventsInsert logic at various stages of AI workIntercept dangerous commands, inject context, record audit logs
RegisterKeyboard ShortcutsBind keys in the terminal UIQuickly switch panels, trigger high-frequency operations
DisplayCustom UIRender interactive interfaces in the terminalComponents such as progress bars, lists, and confirmation dialogs

Your First Extension: Hello World

The following extension demonstrates three capabilities at once: event listening, tool registration, and command registration.

Example

// File path: ~/.pi/agent/extensions/my-extension.ts
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";

export default function (pi: ExtensionAPI) {
  // Triggered when the session starts
  pi.on("session_start", async (_event, ctx) => {
    ctx.ui.notify("My extension has loaded!", "info");
  });

  // Intercept dangerous commands
  pi.on("tool_call", async (event, ctx) => {
    if (event.toolName === "bash"
        && event.input.command?.includes("rm -rf")) {
      const ok = await ctx.ui.confirm("Dangerous operation!", "Are you sure you want to run rm -rf?");
      if (!ok) return { block: true, reason: "User blocked" };
    }
  });

  // Register a custom tool
  pi.registerTool({
    name: "greet",
    label: "Greet",
    description: "Greet the specified person",
    parameters: Type.Object({
      name: Type.String({ description: "The name of the person to greet" }),
    }),
    async execute(toolCallId, params) {
      return {
        content: [{ type: "text", text:`Hello, ${params.name}!` }],
        details: {},
      };
    },
  });

  // Register a custom command
  pi.registerCommand("hello", {
    description: "Greet the world",
    handler: async (args, ctx) => {
      ctx.ui.notify(`Hello ${args || "World"}!`, "info");
    },
  });
}

The event check on line 13 uses the simplestevent.toolNameapproach; you can also use type guards to narrow the event to a specific event type, see the Event System chapter.

Start Pi Agent with the pi -e parameter to load this extension for testing:

$ pi -e ~/.pi/agent/extensions/my-extension.ts
Loading extension: /Users/example/.pi/agent/extensions/my-extension.ts
my-extension 加载成功(1 个工具、1 个命令)
我的扩展已加载!

The last line is the notification popped up by ctx.ui.notify in the session_start event, which indicates the extension has taken effect.


Extension Loading Locations

Pi Agent automatically discovers extensions in several fixed directories, and you can also explicitly specify paths.

LocationScopeDescription
~/.pi/agent/extensions/*.tsGlobalEffective for all projects, auto-discovered
~/.pi/agent/extensions/*/index.tsGlobalExtensions with index.ts in subdirectories
.pi/extensions/*.tsProjectCurrent project only, requires project trust
.pi/extensions/*/index.tsProjectProject subdirectory extensions

In addition to command-line arguments, you can also write an array of paths in the extensions field of settings.json; directory paths are expanded according to the extension directory rules.

Example

{
  "extensions": [
    "/Users/example/.pi/agent/extensions/quick-notify.ts",
    "/Users/example/.pi/agent/extensions/team-tools"
  ]
}

The first element is a single-file extension, and the second is a directory extension; replace them with your own extension locations in actual use.


Extension File Structure

An Extension can be a single .ts file or a directory with an entry file.

Single-file Extension

The simplest form, suitable for small extensions:

~/.pi/agent/extensions/
└── my-extension.ts

Directory Extension (with index.ts)

Suitable for multi-file extensions:

~/.pi/agent/extensions/
└── my-extension/
    ├── index.ts        # 入口文件(导出默认函数)
    ├── tools.ts        # 辅助模块
    └── utils.ts        # 工具函数

Extension with Dependencies

Suitable for large extensions that require npm packages:

~/.pi/agent/extensions/
└── my-extension/
    ├── package.json    # 声明依赖和入口
    ├── node_modules/   # npm install 后生成
    └── src/
        └── index.ts

Extensions run with your system permissions and can execute arbitrary code.

Only install extensions from trusted sources.。


Extension Loading Mechanism

Extensions are loaded viajitiloading; TypeScript code can run directly without compilation.

If your factory function returns a Promise, Pi Agent waits for it to complete before continuing the startup process.

This means you can perform asynchronous operations during extension initialization, such as fetching remote configuration.


Available Import Packages

These packages are built into Pi Agent and can be imported directly in extensions.

Package NamePurpose
@earendil-works/pi-coding-agentExtension types (ExtensionAPI, ExtensionContext, and various event types)
typeboxSchema definition for tool parameters
@earendil-works/pi-aiAI tools (such as StringEnum, for Google-compatible enums)
@earendil-works/pi-tuiTUI components, for custom rendering

These built-in core packages are provided by Pi itself; just list them as peerDependencies, and do not bundle them into your extension.

Your own runtime dependencies must be written in the dependencies field of package.json.

When Pi installs extension packages, it performs a production install by default (npm install --omit=dev), so devDependencies are not available at runtime.

Other npm packages can also be used—add a package.json in the extension directory and run npm install.


Development Tips

These pieces of experience can help you avoid detours, especially when choosing the loading method.

ApproachReason
Put the extension in~/.pi/agent/extensions/the directorySupports /reload hot reload, no need to restart the session after changes
pi -e ./path.tsOnly for temporary testingFor long-term use, put the extension in the ~/.pi/agent/extensions/ directory and let Pi Agent auto-discover it.
Do not start background resources in the extension factory functionProcesses, sockets, file watchers, timers, etc. should be started in the session_start event
Clean up session-level resources in the session_shutdown eventAvoid leftover handles and listeners after switching sessions

Further Integration

Besides writing extensions, the official team also provides two programmatic integration routes.

Node or TypeScript projects can directly use@earendil-works/pi-coding-agentthe exported SDK: use createAgentSession to create sessions, subscribe to events, and prompt to send messages.

This approach runs in the same process as Pi and has complete type definitions.

For cross-language calls or when process isolation is needed, switch topi --mode rpc, and communicate with the Pi process via the JSONL protocol.

Both routes share the same event model as extensions; which one to choose mainly depends on whether you need an independent process.

Other Extensions