Tidy repo for public release
- Add MIT LICENSE - Polish README: tagline, permissions docs, clone URL - Add license, authors, readme, and repository URL to pyproject.toml - Remove stale docs/plans/ (relocated to .cursor/) and requirements.txt - Deduplicate and clean up .gitignore Made-with: Cursor
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,7 +8,7 @@ build/
|
|||||||
.venv/
|
.venv/
|
||||||
a11y_dump.txt
|
a11y_dump.txt
|
||||||
agent-tools/
|
agent-tools/
|
||||||
.pytest_cache/
|
|
||||||
.cursor/
|
.cursor/
|
||||||
|
docs/
|
||||||
chat-summaries/
|
chat-summaries/
|
||||||
|
|
||||||
|
|||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 cottongin
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# cursor-flasher
|
# cursor-flasher
|
||||||
|
|
||||||
Flash a colored border on the Cursor IDE window when the AI agent needs your attention — tool approval, questions, or task completion.
|
A macOS daemon that flashes a pulsing border and plays a sound when your [Cursor](https://cursor.com) AI agent needs attention.
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
@@ -16,13 +16,14 @@ Only tools in the `approval_tools` list trigger the pulse (default: `Shell`, `Wr
|
|||||||
- macOS
|
- macOS
|
||||||
- [uv](https://docs.astral.sh/uv/)
|
- [uv](https://docs.astral.sh/uv/)
|
||||||
- Cursor IDE
|
- Cursor IDE
|
||||||
- Accessibility permission for your terminal (System Settings → Privacy & Security → Accessibility)
|
- **Accessibility** permission for your terminal (System Settings → Privacy & Security → Accessibility) — needed for window enumeration
|
||||||
|
- **Input Monitoring** permission for the daemon process (System Settings → Privacy & Security → Input Monitoring) — needed for input-based pulse dismissal
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone and install
|
# Clone and install
|
||||||
git clone <repo-url> && cd cursor-flasher
|
git clone https://code.cottongin.xyz/cursor-flasher && cd cursor-flasher
|
||||||
uv sync
|
uv sync
|
||||||
|
|
||||||
# Install Cursor hooks (global, applies to all projects)
|
# Install Cursor hooks (global, applies to all projects)
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
# 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.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,9 @@ build-backend = "setuptools.build_meta"
|
|||||||
name = "cursor-flasher"
|
name = "cursor-flasher"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
description = "Flash Cursor's window when the AI agent needs attention"
|
description = "Flash Cursor's window when the AI agent needs attention"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
authors = [{ name = "cottongin" }]
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pyobjc-framework-applicationservices>=12.1",
|
"pyobjc-framework-applicationservices>=12.1",
|
||||||
@@ -17,6 +20,9 @@ dependencies = [
|
|||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = ["pytest", "pytest-mock"]
|
dev = ["pytest", "pytest-mock"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Repository = "https://code.cottongin.xyz/cursor-flasher"
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
cursor-flasher = "cursor_flasher.cli:main"
|
cursor-flasher = "cursor_flasher.cli:main"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
pyobjc-framework-ApplicationServices
|
|
||||||
pyobjc-framework-Cocoa
|
|
||||||
pyobjc-framework-Quartz
|
|
||||||
PyYAML
|
|
||||||
pytest
|
|
||||||
pytest-mock
|
|
||||||
Reference in New Issue
Block a user