Files
cursor-flasher/docs/plans/2026-03-10-cursor-flasher-design.md
cottongin 6b3390f89d Add cursor-flasher design document
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
2026-03-10 02:03:53 -04:00

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 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

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.