Introduction
I have been exploring AI coding assistance, most recently using Cursor AI with Claude 4.0.
Although it often makes mistakes and produces substandard code, it also can be brilliant, correctly completing in seconds what would take minutes, hours, and yes, even days, without it.
So I continue to use it…but not without some frustrations. Lately it can take several minutes for it to finish responding to a question. I try to multitask, do something else while waiting, but then I need to return to the editor periodically to see if it’s ready. This frequent context switching kills my productivity.
💡 Quick Start: If you want to jump straight to implementation, see the Complete Setup Instructions at the end of this article.
The Problem Visualized
High Level Solution
Since my multitasking will likely bring me to an application other than Cursor, a visual notification in Cursor would not work for me. And since I am often working in coworking places where silence is required, an audio notification is not suitable either. So I need to find a way to show a visual notification, regardless of which application has focus.
But how would Cursor AI communicate to my system that the response was ready? Cursor operates in a sandbox with very limited ability to interact with the host system. There’s one thing that the Cursor AI Agent can do though, and it does it all the time – write to the filesystem – specifically, the project directory tree on the filesystem.
Solution Architecture
So the solution consisted of two parts:
- the Cursor AI signaling via the filesystem that the response was completed
- a script that watches the filesystem and flashes the image when signaled
Detailed Process Flow
The following diagram combines the system workflow shown above with the user’s interactions. (As with all the diagrams in this article, you may click or tap it to open a new tab containing only the diagram.)
Let’s discuss these parts one at a time…
Getting Cursor to Signal Response Completion
If you navigate the menus Cursor -> Settings -> Cursor Settings -> Rules, you can add “User Rules” that will apply to every request of every project you edit in Cursor. I added:
Before starting any response, delete .cursor_response_complete
in the project root if it exists. After completing that response, recreate (touch) it.
This rule requires the use of Agent mode, because in the other modes the AI will not
write to the filesystem. In addition, it requires that you enable the response to complete
without asking you for confirmation along the way. While I was originally unwilling to give it
this freedom, I’ve come around to allowing it, given that I git commit
frequently and can always
revert to the most recent commit if I want to revert the AI changes.
Flashing the Image On Response Completion
Now that we can detect the response completion by the presence of the .cursor_response_complete
file,
we need something to watch for that file and flash the image. I decided to let the AI do the
heavy lifting, especially since I am not expert in Swift or even bash scripting.
After it created its script, I realized this a nontrivial script like this is easier to read
and maintain in a scripting language than in shell script, so I asked it to translate the file
to Python. It wrote a working implementation, but it was repetitive and verbose. After a few
cycles of my giving it best practices guidance, it looked pretty good.
You can see it pasted at the end of this article.
The AI named the file file-watch-flash.py
, and I decided to put it in the scripts/
directory under my
project root so that it was version controlled and part of my project.
Another approach would be to put it in a single place on my system where all projects
can access it, and modifications can be made in one place.
The signal file to watch is specified as a constant (FILE_TO_WATCH
) in the script.
If you want to be able to run the script from any directory, then remember to use
an absolute filespec. I intend only to run it from the project root,
so I used a relative filespec. A bonus for that approach is that I don’t need
to modify the script if I copy it to other projects.
I decided it would be fun to have a goofy image pop up, so I found an image of Alfred E. Neuman, a very goofy character from Mad Magazine. (You can do a web search for ‘Alfred E. Neuman images’.) Now whenever a response completes, I get Alfred to make me laugh. Let’s see how my coworking colleagues like it – maybe it will be more disruptive than a sound!
Technical Implementation Details
The Python script architecture can be visualized as follows:
Smart Startup Behavior
The Python script deletes the watched file on startup to ensure only fresh file creation events trigger flashes. This prevents false positives if the script starts after a response has already completed.
Separate Scripts for File Watching and Image Display
The implementation uses a clean separation of concerns with two main components:
- A Python script (
file-watch-flash.py
) for file monitoring - A platform-specific flash script (
flash_screen
) for visual effects
This architecture allows different platforms to provide their own flash implementation without modifying the Python code. The current macOS implementation uses Swift with a shebang line for direct execution.
A huge benefit of this separation is that you now have a script you can use for desktop flashing for any other use case.
Cross-Platform Design
The flash script uses a generic filename without extension, allowing different platforms to provide their own implementation:
- macOS: Swift script with shebang (
#!/usr/bin/env swift
) - Windows: Could use a batch file or PowerShell script
- Linux: Could use a shell script or Python script. Complex due to desktop environment variety. Could use notify-send notifications or create fullscreen windows.
Help Output
Both scripts include comprehensive help:
- Python script:
./scripts/file-watch-flash.py --help
(or-h
) - Flash script:
./scripts/flash_screen --help
(or-h
)
Other Notes
Using a Fixed Image Name for Easy Changes
I tend to get tired of the same image after a while, so it’s helpful to have a scheme whereby I can change the image without changing the script configuration. The simplest approach is to always copy your desired image to the same filename:
Simple approach:
# Copy your current image to a fixed name
cp ~/Pictures/my-favorite-image.jpg ~/system-flash-image.jpg
# Later, to change the image, just copy a different one:
cp ~/Pictures/different-image.png ~/system-flash-image.jpg
Advanced: Using symbolic links (optional) If you’re comfortable with symbolic links, they’re even more convenient since you don’t need to copy large files:
ln -sf ~/Pictures/current-flash.jpg ~/system-flash-image.jpg
Note: The flash script accepts any image format that macOS can display (JPEG, PNG, etc.), so format conversion is usually unnecessary. Even copying a .png file to the .jpg filename seems to work; the system ignores the extension and examines the file to determine its type.
Deployment Options
Implementation Code
The complete implementation consists of two scripts:
Python File Watcher Script
This Python script handles file monitoring using the watchdog
library. Key features:
- Monitors
.cursor_response_complete
file creation - Configurable via command line arguments (
--file
,--image
,--verbose
) - Cross-platform file system event detection
- Executes the platform-specific flash script when triggered
Dependencies: pip install watchdog
📖 View Python script with syntax highlighting
#!/usr/bin/env python3
"""
File Watch Flash - Screen Flash Notification Script
This script monitors a specific file for changes and flashes the screen when events occur.
It's designed to provide visual feedback when certain file operations happen, particularly
useful for automated workflows or development environments.
WHAT IT DOES:
- Watches for changes to `.cursor_response_complete` file in the current directory
- Flashes the screen with a fullscreen image or white color when the file is created
- Optionally flashes on file deletion or modification (currently commented out)
- Uses a separate platform-specific flash script to create the fullscreen flash effect
DEPENDENCIES:
- Python 3.6+
- watchdog library: Install with `pip install watchdog`
- Platform-specific flash script (must be executable and in the same directory as this Python script)
- macOS: Requires Swift compiler for the included flash_screen script
- Optional: Image file for custom flash image
USAGE:
1. Make sure you have the watchdog library installed:
pip install watchdog
2. Run the script:
./scripts/file-watch-flash.py [--file FILE] [--image IMAGE] [--verbose]
Options:
--file FILE File to watch (default: .cursor_response_complete)
--image IMAGE Flash image path (default: ~/system-flash-image.jpg)
--verbose Enable verbose logging
3. The script will start monitoring and print status messages
4. Press Ctrl+C to stop the monitoring
The flash durations are currently defined in the platform-specific flash script and not yet configurable.
EVENTS:
- FILE CREATED: Triggers screen flash (active)
- FILE DELETED: Prints message only (flash commented out)
- FILE MODIFIED: Prints message only (flash commented out)
ARCHITECTURE:
- Python script handles file monitoring using the watchdog library
- Platform-specific flash script handles the actual screen flash visual effect
- The Python script executes the flash script when flash events occur
- The watched file is deleted on startup to ensure only fresh CREATED events trigger flashes
- Different platforms can provide their own flash script implementation without modifying Python code
This is a Python translation of the original bash script 'file-watch-flash' that used
fswatch for file monitoring. The Python version uses the cross-platform watchdog library
for better portability while maintaining the same core functionality.
"""
import os
import time
import subprocess
import argparse
import logging
from pathlib import Path
from datetime import datetime
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
# Default configuration
DEFAULT_FILE_TO_WATCH = ".cursor_response_complete"
DEFAULT_FLASH_IMAGE = "~/system-flash-image.jpg"
# Get the directory where this script is located
SCRIPT_DIR = Path(__file__).parent
FLASH_SCRIPT_PATH = SCRIPT_DIR / "flash_screen"
class FileWatcher(FileSystemEventHandler):
def __init__(self, file_to_watch, flash_image):
super().__init__()
self.file_to_watch = file_to_watch
self.filename = os.path.basename(file_to_watch)
self.flash_image = os.path.expanduser(flash_image)
logging.info(f"Watching file: {self.file_to_watch}")
logging.info(f"Flash image: {self.flash_image}")
logging.info(f"Flash script: {FLASH_SCRIPT_PATH}")
def _execute_flash_script(self, *args):
"""Execute flash script with optional arguments"""
try:
# Check if our flash script exists
if not FLASH_SCRIPT_PATH.exists():
logging.error(f"Flash script not found: {FLASH_SCRIPT_PATH}")
return
# Make sure the script is executable
if not os.access(FLASH_SCRIPT_PATH, os.X_OK):
logging.error(f"Flash script is not executable: {FLASH_SCRIPT_PATH}")
return
cmd = [str(FLASH_SCRIPT_PATH)] + list(args)
logging.debug(f"Executing flash command: {' '.join(cmd)}")
# Use Popen to avoid blocking, but capture stderr temporarily to check for immediate errors
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True)
# Give it a moment to start and check for immediate errors
try:
stdout, stderr = proc.communicate(timeout=0.1)
if stderr:
logging.error(f"Flash script execution error: {stderr}")
except subprocess.TimeoutExpired:
# This is expected - the flash app is running
logging.debug("Flash script started successfully")
except Exception as e:
logging.error(f"Error executing flash script: {e}")
def flash_screen(self):
"""Flash the screen with image or white color"""
if os.path.isfile(self.flash_image):
logging.debug(f"Flashing with image: {self.flash_image}")
self._execute_flash_script(os.path.abspath(self.flash_image))
else:
logging.debug("Flashing with white color")
self._execute_flash_script()
def _handle_file_event(self, event, event_type, should_flash=False):
"""Handle file events with common logic"""
if not event.is_directory and event.src_path.endswith(self.filename):
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f"{timestamp} - File {event_type}: {event.src_path}")
if should_flash:
self.flash_screen()
def on_created(self, event):
self._handle_file_event(event, "CREATED", should_flash=True)
def on_deleted(self, event):
self._handle_file_event(event, "DELETED", should_flash=False)
def on_modified(self, event):
self._handle_file_event(event, "MODIFIED", should_flash=False)
def setup_logging(verbose=False):
"""Configure logging based on verbosity level"""
level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(
level=level,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def parse_arguments():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(
description="Monitor file changes and flash screen notifications",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s # Use defaults
%(prog)s --file myfile.txt # Watch different file
%(prog)s --image ~/my-flash.png --verbose # Custom image with verbose output
"""
)
parser.add_argument(
'--file', '-f',
default=DEFAULT_FILE_TO_WATCH,
help=f'File to watch for changes (default: {DEFAULT_FILE_TO_WATCH})'
)
parser.add_argument(
'--image', '-i',
default=DEFAULT_FLASH_IMAGE,
help=f'Flash image path (default: {DEFAULT_FLASH_IMAGE})'
)
parser.add_argument(
'--verbose', '-v',
action='store_true',
help='Enable verbose logging'
)
return parser.parse_args()
def main():
"""Main function"""
args = parse_arguments()
setup_logging(args.verbose)
# Create the directory for the file if it doesn't exist
file_path = Path(args.file)
file_path.parent.mkdir(parents=True, exist_ok=True)
# Delete the file if it exists to ensure only freshly CREATED events are handled
file_path.unlink(missing_ok=True)
print(f"Watching for changes to: {args.file}")
print(f"Flash image: {args.image}")
print("Press Ctrl+C to stop...")
# Set up file watcher
event_handler = FileWatcher(args.file, args.image)
observer = Observer()
# Watch the directory containing the file
watch_dir = str(file_path.parent.absolute())
observer.schedule(event_handler, watch_dir, recursive=False)
try:
observer.start()
logging.info("File watcher started")
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nStopping file watcher...")
logging.info("File watcher stopped by user")
except Exception as e:
logging.error(f"Unexpected error: {e}")
finally:
observer.stop()
observer.join()
if __name__ == "__main__":
main()
📝 Note: After downloading, rename to file-watch-flash.py
and ensure it’s executable with chmod +x file-watch-flash.py
.
Swift Flash Script
This Swift script creates the visual flash effect using macOS NSWindow APIs. Key features:
- Displays fullscreen image for 0.5 seconds, or white flash for 0.1 seconds
- Supports any image format macOS can display (JPEG, PNG, etc.)
- Self-contained executable with help documentation
- Uses shebang line for direct execution (
#!/usr/bin/env swift
)
Requirements: macOS with Swift compiler
📖 View Swift script with syntax highlighting
#!/usr/bin/env swift
import Cocoa
// Flash duration constants
let IMAGE_FLASH_DURATION: TimeInterval = 1.0
let WHITE_FLASH_DURATION: TimeInterval = 0.1
// Help function
func showHelp() {
print("""
Flash Screen - Screen Flash Utility
USAGE:
./flash_screen [IMAGE_PATH]
swift flash_screen [IMAGE_PATH]
ARGUMENTS:
IMAGE_PATH Optional path to image file to display during flash
If not provided, displays a white flash
DESCRIPTION:
Creates a fullscreen flash effect for visual notifications.
- With image: Shows the image for \(IMAGE_FLASH_DURATION) seconds
- Without image: Shows white flash for \(WHITE_FLASH_DURATION) seconds
The flash window appears at maximum window level and ignores mouse events.
EXAMPLES:
./flash_screen # White flash
./flash_screen ~/Pictures/flash.jpg # Image flash
swift flash_screen /path/to/image.png # Image flash with swift command
OPTIONS:
-h, --help Show this help message and exit
""")
}
// Check for help flags first
let args = CommandLine.arguments
if args.count > 1 && (args[1] == "-h" || args[1] == "--help") {
showHelp()
exit(0)
}
let app = NSApplication.shared
app.setActivationPolicy(.regular)
let window = NSWindow(
contentRect: NSScreen.main!.frame,
styleMask: [.borderless],
backing: .buffered,
defer: false
)
window.backgroundColor = NSColor.white
window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
window.isOpaque = true
window.ignoresMouseEvents = true
// Check if we have an image path argument
if let imagePath = args.count > 1 ? args[1] : nil,
let image = NSImage(contentsOfFile: imagePath) {
let imageView = NSImageView(frame: window.contentView!.bounds)
imageView.image = image
imageView.imageScaling = .scaleProportionallyUpOrDown
window.contentView?.addSubview(imageView)
// Show image for longer duration
window.makeKeyAndOrderFront(nil)
app.activate(ignoringOtherApps: true)
DispatchQueue.main.asyncAfter(deadline: .now() + IMAGE_FLASH_DURATION) {
window.close()
app.terminate(nil)
}
} else {
// Show white flash for shorter duration
window.makeKeyAndOrderFront(nil)
app.activate(ignoringOtherApps: true)
DispatchQueue.main.asyncAfter(deadline: .now() + WHITE_FLASH_DURATION) {
window.close()
app.terminate(nil)
}
}
app.run()
📝 Note: After downloading, rename to flash_screen
and ensure it’s executable with chmod +x flash_screen
.
Complete Setup Instructions
Here’s everything you need to set up the desktop image flashing system:
Prerequisites
- macOS (for the current implementation)
- Python 3 (usually pre-installed on macOS)
- Cursor AI with Agent mode enabled
Step 1: Configure Cursor AI User Rules
- Open Cursor AI
- Navigate to: Cursor → Settings → Cursor Settings → Rules
-
Add this User Rule (applies to all projects):
Before starting any response, delete `.cursor_response_complete` in the project root if it exists. After completing that response, recreate (touch) it.
- Important: You must use Agent mode for this to work (other modes don’t write to filesystem)
- Important: Enable the AI to complete responses without asking for confirmation
Step 2: Install Python Dependencies
pip install watchdog
Step 3: Download and Setup Scripts
- Download the scripts using the download buttons in the Implementation Code section above
- Create a scripts directory in your project root:
mkdir scripts
- Place the downloaded files in the scripts directory:
- Rename
file-watch-flash.py.txt
tofile-watch-flash.py
- Rename
flash_screen.txt
toflash_screen
- Rename
- Make them executable:
chmod +x scripts/file-watch-flash.py chmod +x scripts/flash_screen
Step 4: Setup Your Flash Image
Choose one of these options:
Option A: Simple copy approach (recommended)
# Copy your image to a fixed filename
cp ~/Pictures/my-flash-image.jpg ~/system-flash-image.jpg
# Use that fixed filename with the script
./scripts/file-watch-flash.py --image ~/system-flash-image.jpg
Option B: Direct path approach
# Use any image file directly
./scripts/file-watch-flash.py --image ~/Pictures/my-flash-image.jpg
Option C: Symbolic links (advanced)
# Create a symbolic link for easy image swapping
ln -sf ~/Pictures/current-flash.jpg ~/system-flash-image.jpg
./scripts/file-watch-flash.py --image ~/system-flash-image.jpg
Step 5: Start the Monitoring Script
Basic usage:
cd /path/to/your/project
./scripts/file-watch-flash.py
With custom image:
./scripts/file-watch-flash.py --image ~/my-flash.png --verbose
With verbose logging only:
./scripts/file-watch-flash.py --verbose
Step 6: Test the Setup
- Test the flash script directly:
# Test with an image ./scripts/flash_screen ~/Pictures/test-image.jpg # Test with white flash ./scripts/flash_screen
- Test the complete workflow:
- Start the monitoring script
- Ask Cursor AI a question in Agent mode
- Wait for the response to complete
- You should see a flash when the response finishes
Troubleshooting
- Script not responding: Make sure you’re using Agent mode in Cursor AI
- Permission denied: Run
chmod +x
on the script files - Python module not found: Install watchdog with
pip install watchdog
- Image not displaying: Verify the image path and that macOS can display the format
- No flash appearing: Check that the
.cursor_response_complete
file is being created/deleted
Advanced Options
- Run from any directory: Use absolute paths in the script configuration
- Different flash duration: Modify the Swift script’s timer values
- Cross-platform: Create platform-specific flash scripts for Windows/Linux to replace
scripts/flash_screen
- Multiple projects: Place scripts in a system-wide location and use absolute paths
About the Author
This solution was developed by Keith Bennett of Bennett Business Solutions, Inc. Keith is an experienced software engineer and consultant who specializes in automation, development tooling, and creative technical solutions. He is currently open to work and available for employment and consulting engagements.
Contact: Through the website above or connect on professional networks.