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
103 lines
3.6 KiB
Markdown
103 lines
3.6 KiB
Markdown
# 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 flash**
|
|
- `USER_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 `NSWindow` positioned 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`
|
|
|
|
```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 Animation
|
|
- `pyobjc-framework-Quartz` — AXUIElement, CGEventTap, window management
|
|
- `PyYAML` — 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.
|