CtrlK
BlogDocsLog inGet started
Tessl Logo

engineer-hook

Creation and configuration of event hooks/plugins to manage agent behavior, command filtering, auditing, and automation. Works across IDEs (Cursor, Claude Code, OpenCode). Use when you need to: (1) Create a new hook (e.g., for formatting or security checks), (2) Configure hooks/plugins, (3) Implement logic for blocking or modifying agent actions via scripts.

69

Quality

85%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Hook / Plugin Creator

Overview

This skill helps design and implement event hooks (or plugins). Hooks allow intercepting agent actions (command execution, file read/write, tool usage) and applying rules: allow, block (with explanation), request confirmation, or modify input data.

IDE Detection and Hook Placement

Hook Paths by IDE

IDEUser HooksProject HooksFormat
Cursor~/.cursor/hooks.json.cursor/hooks.jsonJSON config + shell scripts
Claude Code~/.claude/settings.json.claude/settings.json
.claude/settings.local.json
JSON config (4 hook types)
OpenCode~/.config/opencode/plugins/*.{js,ts}.opencode/plugins/*.{js,ts}
opencode.json plugin (npm)
JS/TS modules (event-based)

Detection Strategy

  1. Check for IDE-specific markers:
    • .cursor/ directory → Cursor
    • .claude/ directory → Claude Code
    • .opencode/ directory or opencode.json → OpenCode
  2. If multiple detected or none → ask the user

IMPORTANT: After detecting the IDE, read ONLY the corresponding reference file. Do not load all references.

Hook Type Availability Matrix

TypeCursorClaude CodeOpenCode
Command (shell script)YesYesNo (use plugin code)
Prompt (LLM-evaluated)YesYes (8 events)No
HTTP (POST to URL)NoYesNo
Agent (multi-turn subagent)NoYes (8 events)No
Programmatic (JS/TS code)NoNoYes

Cross-IDE Event Mapping

Cursor EventClaude Code EventOpenCode Equivalent
beforeShellExecutionPreToolUse (matcher: "Bash")tool.execute.before
afterShellExecutionPostToolUse (matcher: "Bash")tool.execute.after
preToolUsePreToolUsetool.execute.before
postToolUsePostToolUsetool.execute.after
postToolUseFailurePostToolUseFailureeventtool.execute.after
sessionStartSessionStarteventsession.created
sessionEndSessionEndeventsession.deleted
subagentStartSubagentStart
subagentStopSubagentStop
stopStopeventsession.idle
preCompactPreCompactexperimental.session.compacting
afterFileEditPostToolUse (matcher: "Edit|Write")eventfile.edited
beforeSubmitPromptUserPromptSubmitchat.message
beforeMCPExecutionPreToolUse (matcher: "mcp__.*")tool.execute.before
afterMCPExecutionPostToolUse (matcher: "mcp__.*")tool.execute.after
beforeReadFilePreToolUse (matcher: "Read")tool.execute.before
PermissionRequestpermission.ask
Notificationevent → various
TeammateIdle
TaskCompletedeventtodo.updated
ConfigChange
InstructionsLoaded
WorktreeCreate / WorktreeRemove

Main Workflow

  1. Detect IDE using markers above
  2. Read the IDE-specific reference (see Resources below)
  3. Define the Event: Choose from the mapping table above
  4. Choose Implementation Type per availability matrix
  5. Configure: Add config to the correct location
  6. Implement Logic: Create the script/plugin
  7. Test: Verify the hook fires correctly

Cursor Hooks

JSON config + shell scripts. Two types: command and prompt.

Configuration

In .cursor/hooks.json:

{
  "version": 1,
  "hooks": {
    "afterFileEdit": [{ "command": ".cursor/hooks/format.sh" }],
    "beforeShellExecution": [
      { "command": ".cursor/hooks/guard.sh", "matcher": "rm " }
    ]
  }
}

Implementation Types

  1. Command-based: Shell script receiving JSON via stdin, returning JSON via stdout. Exit 0 = success, 2 = deny.
  2. Prompt-based: LLM-evaluated condition.
    { "type": "prompt", "prompt": "Is this command safe?", "timeout": 10 }

Full Reference

For events list, input/output schemas, env vars, matcher values: see hooks_api.md.

Example: Blocking Dangerous Commands

guard.sh:

#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.command')
if [[ "$command" == *"rm -rf"* ]]; then
  echo '{"permission": "ask", "user_message": "Are you sure?", "agent_message": "rm -rf requires confirmation."}'
else
  echo '{"permission": "allow"}'
fi

Claude Code Hooks

JSON config in settings.json. Four types: command, http, prompt, agent.

Configuration

In .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/guard.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Note the nested structure: each event maps to an array of matcher groups, each containing a hooks array.

Implementation Types

  1. Command: Shell script. Exit 0 = allow, exit 2 = block (stderr = reason).
  2. HTTP: POST event data to URL. Response body parsed as JSON.
  3. Prompt: Single-turn LLM evaluation with $ARGUMENTS placeholder.
  4. Agent: Multi-turn subagent verification with tool access (up to 50 turns).

Prompt/agent types only available for 8 events: PermissionRequest, PostToolUse, PostToolUseFailure, PreToolUse, Stop, SubagentStop, TaskCompleted, UserPromptSubmit.

Full Reference

For all 18 events, input/output schemas, env vars, exit code behavior per event: see claude_code_hooks_api.md.

Example: Blocking Dangerous Commands

guard.sh (same script works for both Cursor and Claude Code):

#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
tool_input=$(echo "$input" | jq -r '.tool_input.command // empty')
if [[ "$tool_input" == *"rm -rf"* ]]; then
  echo "Blocked: rm -rf requires manual confirmation" >&2
  exit 2
fi
exit 0

OpenCode Plugins

JS/TS modules returning a Hooks object. No JSON config — all logic is code.

Plugin Structure

import type { Plugin } from "@opencode-ai/plugin"

export default (async ({ project, client, $, directory, worktree }) => {
  return {
    "tool.execute.before": async (input) => {
      // inspect/modify tool arguments
      return input
    },
    event: async (event) => {
      // handle any system event
    },
  }
}) satisfies Plugin

Distribution

  • Local: .opencode/plugins/*.ts
  • npm: opencode.json"plugin": ["package-name"]

Full Reference

For all hooks, events, tool() helper, examples: see opencode_plugins_api.md.

Example: Blocking Dangerous Commands

import type { Plugin } from "@opencode-ai/plugin"

export default (async ({ client }) => ({
  "tool.execute.before": async (input) => {
    if (input.tool === "bash" && input.args?.command?.includes("rm -rf")) {
      client.app.log({ body: { level: "warn", message: "Blocked rm -rf" } })
      return { ...input, args: { command: "echo 'Blocked: rm -rf not allowed'" } }
    }
    return input
  },
})) satisfies Plugin

Resources

  • hooks_api.md — Cursor: full event list, I/O formats, env vars
  • claude_code_hooks_api.md — Claude Code: 18 events, 4 hook types, settings.json format
  • opencode_plugins_api.md — OpenCode: plugin API, hooks, events, tool() helper
  • assets/hook_template.sh — Bash script template for command-type hooks

Tips

  • All IDEs: Paths in hook configs are relative to project root
  • Cursor: Use matcher so hooks only fire for relevant commands
  • Cursor: Debug via "Hooks" tab in settings or "Hooks" output channel
  • Cursor: Set failClosed: true for security-critical hooks
  • Claude Code: Use claude --debug or Ctrl+O for verbose hook logging
  • Claude Code: Interactive /hooks menu available for managing hooks
  • Claude Code: Check stop_hook_active in Stop hooks to prevent infinite loops
  • Claude Code: PermissionRequest hooks don't fire in non-interactive mode (-p)
  • OpenCode: Plugin dependencies go in .opencode/package.json (Bun auto-installs)
  • OpenCode: Plugin tools override built-in tools with matching names
Repository
korchasa/flowai-plugins
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.