Defines the approach for a macOS daemon that monitors Cursor's accessibility tree and shows a pulsing border overlay when the agent is waiting for user input. Made-with: Cursor
3.6 KiB
Cursor Flasher — Design Document
Date: 2026-03-10 Status: Approved
Problem
When Cursor's AI agent finishes its turn and is waiting for user input (approval, answering a question, or continuing the conversation), there's no visual or audible signal. The user has to keep checking the window manually.
Solution
A macOS background daemon that monitors Cursor's accessibility tree and displays a pulsing border overlay around the Cursor window when the agent is waiting for user input. Optionally plays a system sound.
Architecture
Detection: Accessibility Tree Polling
A Python process using pyobjc polls Cursor's accessibility tree every ~500ms via AXUIElement APIs.
Detection signals:
- Approval needed: Accept/Reject button elements appear in the a11y tree
- Agent turn complete: Stop/Cancel button disappears, or thinking indicator goes away
- Chat input active: Chat text area becomes the focused element after being inactive
State machine:
IDLE— not monitoring (Cursor not in chat or not running)AGENT_WORKING— agent is generating (Stop button visible, thinking indicator present)WAITING_FOR_USER— agent is done, user hasn't interacted yet → trigger flashUSER_INTERACTING— user started typing/clicking → dismiss flash, return to IDLE
The detection heuristics will be tuned during development after dumping Cursor's full a11y tree.
Visual Effect: Native macOS Overlay
- A borderless, transparent, non-interactive
NSWindowpositioned over Cursor's window frame - Draws only a pulsing border (interior is fully click-through)
- Core Animation drives the pulse: opacity oscillates between configurable min/max
- Default: ~4px amber border, 1.5s cycle
Dismissal triggers:
- Keyboard input to Cursor (via accessibility or
CGEventTap) - Mouse click in Cursor's chat area
- Timeout (default 5 minutes)
- Agent starts working again (Stop button reappears)
Sound
On transition to WAITING_FOR_USER, optionally plays a macOS system sound (default: "Glass"). Configurable sound name, volume, and on/off toggle.
Configuration
File: ~/.cursor-flasher/config.yaml
pulse:
color: "#FF9500"
width: 4
speed: 1.5
opacity_min: 0.3
opacity_max: 1.0
sound:
enabled: true
name: "Glass"
volume: 0.5
detection:
poll_interval: 0.5
cooldown: 3.0
timeout:
auto_dismiss: 300
Process Management
- CLI:
cursor-flasher start,cursor-flasher stop,cursor-flasher status - Runs as a background daemon
- No menu bar icon (MVP scope)
Tech Stack
- Python 3.10+
pyobjc-framework-Cocoa— NSWindow, NSApplication, Core Animationpyobjc-framework-Quartz— AXUIElement, CGEventTap, window managementPyYAML— configuration file parsing
Installation
pip install -e .from the project directory- Requires Accessibility permission: System Settings > Privacy & Security > Accessibility (grant to Terminal or Python)
Scope / Non-goals
- In scope: Detection, overlay, sound, CLI, config file
- Not in scope (MVP): Menu bar icon, auto-start on login, multi-monitor awareness, Linux/Windows support
Risks
- A11y tree fragility: Cursor UI updates could change element names/structure, breaking detection. Mitigation: make detection patterns configurable, log warnings on detection failures.
- Accessibility permissions: Users must grant permission manually. Mitigation: clear error message and instructions on first run.
- Performance: Polling a11y tree every 500ms could have CPU cost. Mitigation: only poll when Cursor is the frontmost app or recently active.