Lesson

Enforce Cursor Tooling Standards with beforeShellExecution

Learn how to create a custom 'beforeShellExecution' hook in Cursor to programmatically block 'npm' commands and automatically guide the AI to use 'bun' instead.

Access
Included
Transcript
Needs source

Relying on documentation or repeated corrections to enforce project tooling standards is tedious and unreliable. The AI might default to common commands like npm instead of your preferred tool, and you find yourself constantly correcting it. There's a better way to enforce these standards automatically.

This lesson shows you how to build a beforeShellExecution hook that intercepts every shell command the AI attempts to run. You'll inspect the command, decide whether to allow or deny it, and send custom feedback that guides the AI to use the correct tools—all automatically, with zero manual intervention.

The Goal: Automated Tooling Enforcement

The hook you'll build accomplishes three things:

  1. Detects when the AI attempts to run a command using npm
  2. Denies permission for that command to execute
  3. Sends a custom message back to the AI explaining that bun must be used instead

The AI receives this feedback instantly and retries the command using the correct tool. Your project standards are enforced automatically, with no manual corrections needed.

Hook Implementation

The process involves creating a TypeScript file for our hook's logic and registering it in Cursor's hooks.json configuration.

First, we register the hook in .cursor/hooks.json:

{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      {
        "command": "bun run hooks/before-shell-execution.ts"
      }
    ]
  }
}

The hook logic in hooks/before-shell-execution.ts receives a payload with the command to be executed and returns a response with permission ("allow" or "deny") and an optional agentMessage.

A simple startsWith check isn't enough—commands can be chained like cd /path/to/project && npm install. The solution is to check both whether the command starts with npm and whether it contains npm (with spaces) anywhere in the string.

Here's the complete implementation:

import type { BeforeShellExecutionPayload, BeforeShellExecutionResponse } from "cursor-hooks";

const input: BeforeShellExecutionPayload = await Bun.stdin.json();

const startsWithNpm = input.command.startsWith("npm") || input.command.includes(" npm ");

const output: BeforeShellExecutionResponse = {
  permission: startsWithNpm ? "deny" : "allow",
  agentMessage: startsWithNpm ? "npm is not allowed, always use bun instead" : undefined,
};

console.log(JSON.stringify(output, null, 2));

Seeing it in Action

When you ask the AI to install a package, it might default to using npm:

npm install lodash

The hook intercepts the command, denies execution, and sends back the custom message explaining that bun is required. The AI reads this feedback and immediately retries the command using bun—all automatically.

This demonstrates the power of intelligent guardrails: the AI learns your project's rules on the fly, adapting its behavior without any manual corrections. Your standards are enforced seamlessly, creating a smoother, more reliable development experience.