Major changes: - Add StyleConfig dataclass with independent color, width, opacity, duration, pulse_speed, sound, and volume per mode (running/completed) - Replace flat flash_*/sound_*/play_on config with running: and completed: YAML sections - Replace CGEventTap (silently fails in forked daemon) with CGEventSourceSecondsSinceLastEventType polling for reliable input-based pulse dismissal when Cursor is already frontmost - Update overlay, sound, and daemon to pass StyleConfig per call - Rewrite tests for new config shape and dismiss mechanism Made-with: Cursor
1.9 KiB
1.9 KiB
Rewrite: Hook-based detection architecture
Task
Replaced the unreliable macOS accessibility tree polling approach with Cursor's native hooks API for detecting when the agent needs user attention.
Problem
The a11y-based approach had fundamental issues:
- Cursor's "Stop" button during agent generation is NOT exposed in the accessibility tree
- Approval button text persists in chat history, causing false positives
- Required complex baseline tracking and state machine to mitigate, still unreliable
Solution
Switched to Cursor's lifecycle hooks (preToolUse, stop) which fire directly from Cursor when:
- The agent wants to use a tool (shell command, file write, etc.) that may need approval
- The agent loop completes (task finished, waiting for next prompt)
Architecture: Hook script → Unix domain socket → Daemon → Window flash overlay
Changes
- New:
hooks/notify.py— hook script that sends workspace/event info to daemon socket - New:
src/cursor_flasher/windows.py— window discovery and geometry (a11y used only for positions) - Rewritten:
src/cursor_flasher/daemon.py— Unix socket listener instead of a11y polling - Rewritten:
src/cursor_flasher/overlay.py— single brief flash instead of continuous pulse - Rewritten:
src/cursor_flasher/cli.py— addedinstall/uninstallcommands for hook management - Rewritten:
src/cursor_flasher/config.py— simplified config (no sound, no polling settings) - Deleted:
detector.py,state.py,sound.py,scripts/directory - Rewritten: All tests for new architecture (13 tests passing)
- Updated:
README.md,.gitignore,pyproject.toml(bumped to 0.2.0)
Follow-up
- The daemon is running and hooks are installed globally at
~/.cursor/hooks.json - Hooks fire on every
preToolUse— could add matcher filtering if too noisy - No sound from cursor-flasher; relies on Cursor's built-in sound