Files
cursor-flasher/chat-summaries/2026-03-10_03-59-summary.md
cottongin 5b71b2275b Restructure config for per-mode style/sound and fix pulse dismiss
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
2026-03-10 07:01:52 -04:00

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 — added install/uninstall commands 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