co-mono/packages/coding-agent/examples/hooks/README.md
Mario Zechner 42d7d9d9b6 Add before/after session events with cancellation support
- Merge branch event into session with before_branch/branch reasons
- Add before_switch, before_clear, shutdown reasons
- before_* events can be cancelled with { cancel: true }
- Update RPC commands to return cancelled status
- Add shutdown event on process exit
- New example hooks: confirm-destructive, dirty-repo-guard, auto-commit-on-exit

fixes #278
2025-12-22 18:18:38 +01:00

2.8 KiB

Hooks Examples

Example hooks for pi-coding-agent.

Examples

permission-gate.ts

Prompts for confirmation before running dangerous bash commands (rm -rf, sudo, chmod 777, etc.).

git-checkpoint.ts

Creates git stash checkpoints at each turn, allowing code restoration when branching.

protected-paths.ts

Blocks writes to protected paths (.env, .git/, node_modules/).

file-trigger.ts

Watches a trigger file and injects its contents into the conversation. Useful for external systems (CI, file watchers, webhooks) to send messages to the agent.

confirm-destructive.ts

Prompts for confirmation before destructive session actions (clear, switch, branch). Demonstrates how to cancel before_* session events.

dirty-repo-guard.ts

Prevents session changes when there are uncommitted git changes. Blocks clear/switch/branch until you commit.

auto-commit-on-exit.ts

Automatically commits changes when the agent exits (shutdown event). Uses the last assistant message to generate a commit message.

Usage

# Test directly
pi --hook examples/hooks/permission-gate.ts

# Or copy to hooks directory for persistent use
cp permission-gate.ts ~/.pi/agent/hooks/

Writing Hooks

See docs/hooks.md for full documentation.

Key Points

Hook structure:

import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";

export default function (pi: HookAPI) {
  pi.on("session", async (event, ctx) => {
    // event.reason: "start" | "before_switch" | "switch" | "before_clear" | "clear" |
    //               "before_branch" | "branch" | "shutdown"
    // event.targetTurnIndex: number (only for before_branch/branch)
    // ctx.ui, ctx.exec, ctx.cwd, ctx.sessionFile, ctx.hasUI

    // Cancel before_* actions:
    if (event.reason === "before_clear") {
      return { cancel: true };
    }
    return undefined;
  });

  pi.on("tool_call", async (event, ctx) => {
    // Can block tool execution
    if (dangerous) {
      return { block: true, reason: "Blocked" };
    }
    return undefined;
  });

  pi.on("tool_result", async (event, ctx) => {
    // Can modify result
    return { result: "modified result" };
  });
}

Available events:

  • session - lifecycle events with before/after variants (can cancel before_* actions)
  • agent_start / agent_end - per user prompt
  • turn_start / turn_end - per LLM turn
  • tool_call - before tool execution (can block)
  • tool_result - after tool execution (can modify)

UI methods:

const choice = await ctx.ui.select("Title", ["Option A", "Option B"]);
const confirmed = await ctx.ui.confirm("Title", "Are you sure?");
const input = await ctx.ui.input("Title", "placeholder");
ctx.ui.notify("Message", "info"); // or "warning", "error"

Sending messages:

pi.send("Message to inject into conversation");