UNPKG

cursor-hooks

Version:

Type definitions and helpers for building Cursor agent hooks.

264 lines (185 loc) 6.34 kB
# cursor-hooks TypeScript definitions and helpers for building [Cursor agent hooks](https://cursor.com/docs/agent/hooks). ## Installation ### 1. Create a hooks directory in your project ```bash mkdir -p .cursor/hooks cd .cursor/hooks ``` ### 2. Initialize a Bun project Install [Bun](https://bun.sh/) if you haven't already, then initialize: ```bash bun init -y ``` ### 3. Install cursor-hooks ```bash bun install cursor-hooks ``` ### 4. Create a hooks.json configuration In your `.cursor` directory (at the project root), create a `hooks.json` file. **Important:** Hook command paths are relative to the `.cursor/hooks.json` file location. ```json { "version": 1, "hooks": { "beforeShellExecution": [ { "command": "bun run hooks/before-shell-execution.ts" } ], "afterFileEdit": [ { "command": "bun run hooks/after-file-edit.ts" } ] } } ``` This configuration assumes your hooks are in `.cursor/hooks/` and the `hooks.json` is at `.cursor/hooks.json`. ### 5. Enable JSON Schema validation (optional) You can enable schema validation in two ways: **Option A:** Add `$schema` directly to your `hooks.json`: ```json { "$schema": "https://unpkg.com/cursor-hooks@latest/schema/hooks.schema.json", "version": 1, "hooks": { "afterFileEdit": [ { "command": "bun run hooks/after-file-edit.ts" } ] } } ``` **Option B:** Configure it globally in your Cursor settings (`~/Library/Application Support/Cursor/User/settings.json` on macOS): ```json { "json.validate.enable": true, "json.format.enable": true, "json.schemaDownload.enable": true, "json.schemas": [ { "fileMatch": [".cursor/hooks.json"], "url": "https://unpkg.com/cursor-hooks/schema/hooks.schema.json" } ] } ``` ## Real-world Examples ### Example 1: Block npm commands, enforce bun `before-shell-execution.ts`: ```ts 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)); ``` ### Example 2: Gate prompts with a keyword `before-submit-prompt.ts`: ```ts import type { BeforeSubmitPromptPayload, BeforeSubmitPromptResponse } from "cursor-hooks"; const input: BeforeSubmitPromptPayload = await Bun.stdin.json(); const output: BeforeSubmitPromptResponse = { continue: input.prompt.includes("allow"), }; console.log(JSON.stringify(output, null, 2)); ``` ### Example 3: Auto-format and log file edits `after-file-edit.ts`: ```ts import type { AfterFileEditPayload } from "cursor-hooks"; const input: AfterFileEditPayload = await Bun.stdin.json(); if (input.file_path.endsWith(".ts")) { const result = await Bun.$`bunx @biomejs/biome lint --fix --unsafe --verbose ${input.file_path}` const output = { timestamp: new Date().toISOString(), ...input, stdout: result.stdout.toString(), stderr: result.stderr.toString(), exitCode: result.exitCode, } // only console.errors show in the logs console.error(JSON.stringify(output, null, 2)) } ``` ## Available Hook Types ### beforeShellExecution Intercept and control shell commands before execution. **Payload:** `BeforeShellExecutionPayload` - `command`: The shell command to execute - `workspace_roots`: Array of workspace root paths - `hook_event_name`: "beforeShellExecution" **Response:** `BeforeShellExecutionResponse` - `permission`: "allow" | "deny" | "ask" - `agentMessage?`: Message shown to the AI agent - `userMessage?`: Message shown to the user ### beforeSubmitPrompt Control whether prompts are submitted to the AI. **Payload:** `BeforeSubmitPromptPayload` - `prompt`: The user's prompt text - `workspace_roots`: Array of workspace root paths - `hook_event_name`: "beforeSubmitPrompt" **Response:** `BeforeSubmitPromptResponse` - `continue`: boolean - whether to allow the prompt ### afterFileEdit React to file edits made by the AI agent. **Payload:** `AfterFileEditPayload` - `file_path`: Path to the edited file - `edits`: Array of edit operations - `workspace_roots`: Array of workspace root paths - `hook_event_name`: "afterFileEdit" **Response:** None (this is a notification hook) ### beforeReadFile Control file access before the AI reads a file. **Payload:** `BeforeReadFilePayload` - `file_path`: Path to the file being read - `content`: The file contents - `workspace_roots`: Array of workspace root paths - `hook_event_name`: "beforeReadFile" **Response:** `BeforeReadFileResponse` - `permission`: "allow" | "deny" - `agentMessage?`: Message shown to the AI agent - `userMessage?`: Message shown to the user ### beforeMCPExecution Control MCP tool execution before it runs. **Payload:** `BeforeMCPExecutionPayload` - `tool_name`: Name of the MCP tool - `arguments`: Tool arguments - `workspace_roots`: Array of workspace root paths - `hook_event_name`: "beforeMCPExecution" **Response:** `BeforeMCPExecutionResponse` - `permission`: "allow" | "deny" | "ask" - `agentMessage?`: Message shown to the AI agent - `userMessage?`: Message shown to the user ### stop Notification when the AI agent finishes execution. **Payload:** `StopPayload` - `status`: The completion status - `workspace_roots`: Array of workspace root paths - `hook_event_name`: "stop" **Response:** None (this is a notification hook) ## Type Safety Helpers ### isHookPayloadOf Validate and narrow hook payload types: ```ts import { isHookPayloadOf } from "cursor-hooks"; const rawInput: unknown = await Bun.stdin.json(); if (isHookPayloadOf(rawInput, "beforeShellExecution")) { // TypeScript now knows rawInput is BeforeShellExecutionPayload console.log(rawInput.command); } ``` ## Development ### Build ```bash bun run build ``` ### Release - Follow [Conventional Commits](https://www.conventionalcommits.org/) for automatic versioning - CI automatically publishes to npm on pushes to `main` - Run `./scripts/setup-publish.sh` to configure npm token ## License MIT