npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rwese/pi-hooks

v0.2.0

Published

Run user-defined hooks on pi events (input, agent_end)

Readme

pi-hooks

Run user-defined hooks on all pi events.

Features

  • Per-project hooks: Place hooks in .pi/pi-hooks/ in your project
  • Global hooks: Place hooks in ~/.pi/pi-hooks/ for all projects
  • All events: Hook into any pi Extension event
  • Multiple hooks per event: Each hook lives in its own directory
  • User confirmation: On hook failure, presents options to ignore or abort
  • Output persistence: Hook output saved to temp file for reference
  • Commands: List and configure hook settings
  • Execution visibility: Toggle "show hook execution" to see notifications when hooks run
  • Verbose output: See what hooks are doing via console.log/warn/error output

Supported Events

Each hook declares which event it listens to via the event export.

| Hook | Trigger | Can Block? | Can Modify? | Use Cases | |------|---------|------------|------------|-----------| | Agent ||| | before_agent_start | Before LLM dispatch | ✅ Yes | ✅ Transform | Validate, inject context | | agent_start | Agent starts | ❌ No | Logging, setup | | agent_start | Agent starts | ❌ No | Logging, setup | | agent_end | Agent completes | ❌ No | Post-processing, logging | | Turn ||| | turn_start | Turn begins | ❌ No | Per-turn setup | | turn_end | Turn ends | ❌ No | Per-turn cleanup | | Message ||| | message_start | Message starts | ❌ No | Track message flow | | message_update | Message updates (streaming) | ❌ No | Monitor streaming | | message_end | Message ends | ❌ No | Track completion | | Context |||| | context | Before LLM call | ❌ No | ✅ Transform | Filter/inject messages | | Tool Execution ||| | tool_execution_start | Tool starts | ❌ No | Track execution | | tool_execution_update | Tool progress | ❌ No | Monitor progress | | tool_execution_end | Tool ends | ❌ No | Track completion | | tool_call | Before tool runs | ✅ Yes | ✅ Transform | Block/modify tool arguments | | tool_result | After tool completes | ❌ No | ✅ Modify | Validate, log, modify output | | Session ||| | session_start | Session starts | ❌ No | Setup, clear state | | session_shutdown | Session ends | ❌ No | Cleanup | | session_before_switch | Before session switch | ❌ No | Warn on dirty state | | session_before_fork | Before fork | ❌ No | Prepare fork | | session_before_compact | Before compaction | ❌ No | Customize summary | | session_compact | After compaction | ❌ No | Post-compact actions | | session_before_tree | Before tree nav | ❌ No | Prepare navigation | | session_tree | After tree nav | ❌ No | Post-nav actions | | Model ||| | model_select | Model changes | ❌ No | Log model changes | | Provider ||| | before_provider_request | Before API call | ❌ No | Debug payloads | | after_provider_response | After API response | ❌ No | Debug responses | | Resource ||| | resources_discover | Resources discovered | ❌ No | Log resources | | User Bash ||| | user_bash | User runs ! or !! | ❌ No | Intercept commands |

Installation

pi install git:https://github.com/rwese/pi-hooks

Or copy the extension to ~/.pi/agent/extensions/pi-hooks/.

Creating Hooks

Location

Each hook lives in its own directory with an index.ts file that exports its event type.

Project-specific:

your-project/
├── .pi/
│   └── pi-hooks/
│       ├── my_input_validator/
│       │   └── index.ts
│       ├── block_dangerous/
│       │   └── index.ts
│       └── log_changes/
│           └── index.ts
└── ...

Global:

~/.pi/
└── pi-hooks/
    ├── my_input_validator/
    │   └── index.ts
    ├── block_dangerous/
    │   └── index.ts
    └── log_changes/
        └── index.ts

Hook Format

// ~/.pi/pi-hooks/log_changes/index.ts

// Declare which event this hook listens to
export const event = "tool_result" as const;

interface ToolResultPayload {
  event: "tool_result";
  toolCallId: string;
  toolName: string;       // "edit", "write", "bash", etc.
  input: object;           // Tool arguments
  content: Content[];     // Tool output
  details: object;        // Tool-specific details
  isError: boolean;
}

export default function logChangesHook(
  payload: ToolResultPayload
): boolean | void {
  const { toolName, input } = payload;

  // Log file modifications
  if (toolName === "edit" || toolName === "write") {
    const path = input.path as string;
    console.log(`[HOOK] ${toolName}: ${path}`);
  }

  return true; // Pass
  // return false; // Fail
}

before_agent_start Hook (Can Block & Transform)

// ~/.pi/pi-hooks/pre_dispatch/index.ts

export const event = "before_agent_start" as const;

interface BeforeAgentStartPayload {
  event: "before_agent_start";
  prompt: string;
  images: Image[];
  systemPrompt: string;
}

// Return false to block, or modified payload to transform
export default function preDispatchHook(
  payload: BeforeAgentStartPayload
): boolean | void | { prompt?: string; images?: Image[]; systemPrompt?: string } {
  const { prompt } = payload;

  // Auto-correct shorthand commands
  if (prompt.startsWith("test ")) {
    return {
      prompt: prompt.replace(/^test /, "test(unit): "),
    };
  }

  return true; // Pass unchanged
}

tool_call Hook (Can Block & Modify)

// ~/.pi/pi-hooks/block_dangerous/index.ts

export const event = "tool_call" as const;

interface ToolCallPayload {
  event: "tool_call";
  toolCallId: string;
  toolName: string;
  input: object;
}

// Return modified input to transform tool arguments
export default function blockDangerous(
  payload: ToolCallPayload
): boolean | void | { input: Record<string, unknown> } {
  const { toolName, input } = payload;

  // Block dangerous bash commands
  if (toolName === "bash") {
    const command = (input.command as string) || "";
    if (command.includes("rm -rf /")) {
      console.error("Blocking dangerous command!");
      return false; // Block
    }
  }

  // Modify tool arguments by returning new input
  if (toolName === "bash") {
    const command = input.command as string;
    if (!command.includes("--no-preserve-root")) {
      // Add safety flag
      return {
        passed: true,
        modified: true,
        input: {
          ...input,
          command: command + " --no-preserve-root",
        },
      };
    }
  }

  return true;
}

context Hook (Can Transform Messages)

// ~/.pi/pi-hooks/context_transformer/index.ts

export const event = "context" as const;

interface Message {
  role: "user" | "assistant" | "system";
  content: unknown;
}

interface ContextPayload {
  event: "context";
  messages: Message[];
}

export default function contextTransformerHook(
  payload: ContextPayload
): boolean | void | { messages: Message[] } {
  const { messages } = payload;

  // Example: Filter to last N messages
  const MAX = 20;
  if (messages.length > MAX) {
    return {
      passed: true,
      modified: true,
      messages: messages.slice(-MAX),
    };
  }

  return true; // Pass unchanged
}

tool_result Hook (Can Modify Output)

// ~/.pi/pi-hooks/validate_git/index.ts

export const event = "tool_result" as const;

interface ToolResultPayload {
  event: "tool_result";
  toolCallId: string;
  toolName: string;
  input: object;
  content: Content[];
  details: object;
  isError: boolean;
}

export default function validateGit(
  payload: ToolResultPayload
): boolean | void {
  const { toolName, content } = payload;

  // Validate git operations
  if (toolName === "bash") {
    const output = content
      .filter(c => c.type === "text")
      .map(c => c.text)
      .join("");

    if (output.includes("CONFLICT")) {
      console.error("[HOOK] Git conflict detected!");
    }
  }

  return true;
}

Example Hooks

See the examples/ directory for complete implementations:

| Example | Event | Description | |---------|-------|-------------| | before_agent_start/ | before_agent_start | Validates/modifies before LLM dispatch |

| block_dangerous_commands/ | tool_call | Blocks dangerous bash commands | | log_file_modifications/ | tool_result | Logs all file modifications | | trim_tool_whitespace/ | tool_call | Trims trailing whitespace from input | | dirty_edit_guard/ | tool_call | Prevents edits to modified files | | context_transformer/ | context | Filters/transforms messages before LLM call | | agent_end/ | agent_end | Logs after agent completes |

Commands

| Command | Description | |---------|-------------| | /hooks:list | List available hooks (shows name and event type) | | /hooks:show [on\|off] | Toggle hook execution visibility | | /hooks:verbose [on\|off] | Toggle verbose hook output | | /hooks:dirty-edit-clear | Clear tracked file states | | /hooks:disable <event> | Disable an event type for this session | | /hooks:enable <event> | Re-enable a disabled event type |

Hook Return Values

| Return | Meaning | |--------|---------| | true or undefined | Hook passed | | false | Hook failed (blocked for blocking events) | | throw new Error(msg) | Hook failed with message | | { prompt?, images?, systemPrompt? } | For before_agent_start: return transformed payload | | { input: {...} } | For tool_call: return transformed tool arguments | | { messages: [...] } | For context: return transformed messages | | { content, details, isError } | For tool_result: return modified result |

Notes

  • Hooks run with access to node_modules via npx tsx
  • 30-second timeout per hook
  • Hooks are skipped for non-interactive input (RPC, extensions)
  • tool_call and before_agent_start can block execution
  • Other hooks are informational only (can't block)
  • Use /hooks:list to see which hooks are active
  • Multiple hooks can listen to the same event type
  • Project hooks take precedence over global hooks with the same name