co-mono/packages/coding-agent/examples/extensions
Mario Zechner c6fc084534 Merge hooks and custom-tools into unified extensions system (#454)
Breaking changes:
- Settings: 'hooks' and 'customTools' arrays replaced with 'extensions'
- CLI: '--hook' and '--tool' flags replaced with '--extension' / '-e'
- API: HookMessage renamed to CustomMessage, role 'hookMessage' to 'custom'
- API: FileSlashCommand renamed to PromptTemplate
- API: discoverSlashCommands() renamed to discoverPromptTemplates()
- Directories: commands/ renamed to prompts/ for prompt templates

Migration:
- Session version bumped to 3 (auto-migrates v2 sessions)
- Old 'hookMessage' role entries converted to 'custom'

Structural changes:
- src/core/hooks/ and src/core/custom-tools/ merged into src/core/extensions/
- src/core/slash-commands.ts renamed to src/core/prompt-templates.ts
- examples/hooks/ and examples/custom-tools/ merged into examples/extensions/
- docs/hooks.md and docs/custom-tools.md merged into docs/extensions.md

New test coverage:
- test/extensions-runner.test.ts (10 tests)
- test/extensions-discovery.test.ts (26 tests)
- test/prompt-templates.test.ts
2026-01-05 01:43:35 +01:00
..
hello Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
question Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
subagent Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
with-deps Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
auto-commit-on-exit.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
chalk-logger.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
confirm-destructive.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
custom-compaction.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
dirty-repo-guard.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
file-trigger.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
git-checkpoint.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
handoff.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
permission-gate.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
pirate.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
plan-mode.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
protected-paths.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
qna.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
README.md Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
snake.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
status-line.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
todo.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00
tools.ts Merge hooks and custom-tools into unified extensions system (#454) 2026-01-05 01:43:35 +01:00

Extension Examples

Example extensions for pi-coding-agent.

Usage

# 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

Extension Description
permission-gate.ts Prompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.)
protected-paths.ts Blocks writes to protected paths (.env, .git/, node_modules/)
confirm-destructive.ts Confirms before destructive session actions (clear, switch, branch)
dirty-repo-guard.ts Prevents session changes with uncommitted git changes

Custom Tools

Extension Description
todo.ts Todo list tool + /todos command with custom rendering and state persistence
hello/ Minimal custom tool example
question/ Demonstrates pi.ui.select() for asking the user questions
subagent/ Delegate tasks to specialized subagents with isolated context windows

Commands & UI

Extension Description
plan-mode.ts Claude Code-style plan mode for read-only exploration with /plan command
tools.ts Interactive /tools command to enable/disable tools with session persistence
handoff.ts Transfer context to a new focused session via /handoff <goal>
qna.ts Extracts questions from last response into editor via ctx.ui.setEditorText()
status-line.ts Shows turn progress in footer via ctx.ui.setStatus() with themed colors
snake.ts Snake game with custom UI, keyboard handling, and session persistence

Git Integration

Extension Description
git-checkpoint.ts Creates git stash checkpoints at each turn for code restoration on branch
auto-commit-on-exit.ts Auto-commits on exit using last assistant message for commit message

System Prompt & Compaction

Extension Description
pirate.ts Demonstrates systemPromptAppend to dynamically modify system prompt
custom-compaction.ts Custom compaction that summarizes entire conversation

External Dependencies

Extension Description
chalk-logger.ts Uses chalk from parent node_modules (demonstrates jiti module resolution)
with-deps/ Extension with its own package.json and dependencies
file-trigger.ts Watches a trigger file and injects contents into conversation

Writing Extensions

See docs/extensions.md for full documentation.

import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/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):

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:

// Store state in tool result details for proper branching 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
    }
  }
});