commit 6b3390f89dd90046f1703e560bbe3fc83858d219 Author: cottongin Date: Tue Mar 10 02:03:53 2026 -0400 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 diff --git a/docs/plans/2026-03-10-cursor-flasher-design.md b/docs/plans/2026-03-10-cursor-flasher-design.md new file mode 100644 index 0000000..fefc516 --- /dev/null +++ b/docs/plans/2026-03-10-cursor-flasher-design.md @@ -0,0 +1,102 @@ +# 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.