beforeShellExecution in hooks.json required a JSON response we never provided, likely causing Cursor to silently break the entire hook pipeline. Commenting out those entries (and afterShellExecution) from HOOKS_CONFIG restores reliable preToolUse/postToolUse delivery. All Python handler code is retained as dead code for reference. Also reverts the is_cursor_frontmost() gate in _check_pending — pulses should fire unconditionally when the approval delay expires. Made-with: Cursor
Note
This project was developed entirely with AI coding assistance (Claude Opus 4.6 via Cursor IDE) and has not undergone manual review. It is provided as-is and may require adjustments for other environments.
cursor-flasher
A macOS daemon that flashes a pulsing border and plays a sound when your Cursor AI agent needs attention.
How It Works
Uses Cursor hooks for reliable detection:
**preToolUse**— fires when the agent wants to run a shell command, write a file, or use any tool that may need approval. Pulses the border continuously and plays a sound until you click the Cursor window.**stop** — fires when the agent loop ends. Flashes the border once, briefly.
Only tools in the approval_tools list trigger the pulse (default: Shell, Write, Delete). Auto-approved tools like Read and Grep are ignored.
Prerequisites
- macOS
- uv
- Cursor IDE
- Accessibility permission for your terminal (System Settings → Privacy & Security → Accessibility) — needed for window enumeration
- Input Monitoring permission for the daemon process (System Settings → Privacy & Security → Input Monitoring) — needed for input-based pulse dismissal
Installation
# Clone and install
git clone https://code.cottongin.xyz/cursor-flasher && cd cursor-flasher
uv sync
# Install Cursor hooks (global, applies to all projects)
uv run cursor-flasher install
# Start the daemon
uv run cursor-flasher start
The install command copies the hook script to ~/.cursor/hooks/ and adds entries to ~/.cursor/hooks.json. Cursor auto-reloads hooks.
Usage
uv run cursor-flasher start # background daemon
uv run cursor-flasher start --foreground # foreground (for debugging)
uv run cursor-flasher status
uv run cursor-flasher stop
Configuration
Optional config file at ~/.cursor-flasher/config.yaml:
theme: "auto" # "dark", "light", or "auto" (follows macOS appearance)
dark: # styles used when OS is in dark mode
running: # approval pulse (continuous until you interact)
color: "#FF9500" # border color (hex)
width: 4 # border thickness in pixels
opacity: 0.85 # max border opacity
pulse_speed: 1.5 # pulse cycle speed in seconds
sound: "Glass" # macOS system sound ("" to disable)
volume: 0.5 # 0.0 to 1.0
# sounds: Basso, Blow, Bottle, Frog, Funk, Glass, Hero,
# Morse, Ping, Pop, Purr, Sosumi, Submarine, Tink
completed: # agent stop flash (brief fade-in/out)
color: "#00FF00" # different color for completion
width: 4
opacity: 0.85
duration: 1.5 # flash duration in seconds
sound: "" # no sound by default (Cursor plays its own)
volume: 0.0
light: # styles used when OS is in light mode
running:
color: "#3B82F6"
width: 4
opacity: 0.9
pulse_speed: 1.5
sound: "Glass"
volume: 0.5
completed:
color: "#22C55E"
width: 4
opacity: 0.9
duration: 1.5
sound: ""
volume: 0.0
flash:
mode: "screen" # "window", "screen", or "allscreens"
# Tools that trigger the pulse + sound (approval mode).
# Others are silently ignored (e.g., Read, Grep, Glob, Task).
approval_tools:
- Shell
- Write
- Delete
general:
approval_delay: 2.5 # seconds to wait before pulsing (filters auto-approvals)
cooldown: 2.0 # minimum seconds between flashes
Styles are organized under dark and light theme sections, each containing running (approval pulse) and completed (stop flash) modes with their own color, border, and sound settings. The theme option controls which styles are active: set "auto" to follow macOS appearance in real-time, or force "dark" / "light". Set sound: "" to disable sound for a particular mode.
Uninstall
uv run cursor-flasher uninstall
uv run cursor-flasher stop
Troubleshooting
Flashing on every tool call (too noisy):
- Edit
~/.cursor-flasher/config.yamland narrow downapproval_toolsto justShell.
No flash at all:
- Check daemon:
uv run cursor-flasher status - Check hooks installed:
ls ~/.cursor/hooks/cursor-flasher-notify.py - Check Cursor Settings → Hooks tab for execution logs
Pulse doesn't stop:
- Click the Cursor window to bring it to focus — the pulse auto-dismisses when Cursor is the frontmost app.