💡 Quick Start: If you want to jump straight to implementation, see the Complete Setup Instructions at the end of this article.


Quick Access: View and Download Scripts

(Remove the '.txt' extension after downloading.)
  • 📖 View file_watcher.py with syntax highlighting
      #!/usr/bin/env python3
    
    """
    File Watcher - A generic file monitoring and command execution script
    
    This script monitors a specific file and executes a given command when it appears.
    It's designed to be a simple, dependency-free tool for automated workflows.
    
    WHAT IT DOES:
    - Polls for the existence of a specified file every second.
    - When the file is found, it executes a user-defined command and waits for it to complete.
    - Checks the exit code of the command to determine success or failure.
    - Deletes the file after the command is run to prepare for the next event.
    
    DEPENDENCIES:
    - Python 3.6+
    - No external libraries required.
    
    USAGE:
    1. Run the script with a command to execute:
       ./scripts/file-watcher.py --file FILE [--delay SECONDS] [--verbose] -- [COMMAND] [ARGS...]
       
       The '--' is recommended to separate script arguments from the command.
    
    2. The script will start polling.
    3. Press Ctrl+C to stop.
    """
    
    import time
    import subprocess
    import argparse
    import logging
    from pathlib import Path
    from datetime import datetime
    
    # Default configuration
    DEFAULT_DELAY_SECONDS = 0.5
    
    def execute_command(command):
        try:
            logging.debug(f"Executing command: {' '.join(command)}")
            result = subprocess.run(
                command,
                capture_output=True,
                text=True
            )
    
            if result.returncode != 0:
                logging.error(f"Command failed with exit code {result.returncode}")
                if result.stderr:
                    logging.error(f"Stderr: {result.stderr.strip()}")
            else:
                logging.info("Command executed successfully.")
                if result.stdout:
                    logging.debug(f"Stdout: {result.stdout.strip()}")
                if result.stderr: # Log non-fatal stderr output too for diagnostics
                    logging.debug(f"Stderr (non-fatal): {result.stderr.strip()}")
    
        except FileNotFoundError:
            logging.error(f"Command not found: {command[0]}")
        except Exception as e:
            logging.error(f"Error executing command: {e}")
    
    
    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="Poll for a file and execute a command when it appears.",
            formatter_class=argparse.RawDescriptionHelpFormatter,
            epilog="""
    Examples:
      # Flash screen when file appears, checking every 0.2 seconds
      %(prog)s --file .cursor_response_complete --delay 0.2 -- ./scripts/FlashScreen
    
      # Watch a different file with default delay
      %(prog)s --file state.log --verbose -- ls -l
            """
        )
        
        parser.add_argument(
            '-f', '--file',
            required=True,
            help='File to watch for.'
        )
        
        parser.add_argument(
            '-d', '--delay',
            type=float,
            default=DEFAULT_DELAY_SECONDS,
            help=f'Delay in seconds between polling checks (default: {DEFAULT_DELAY_SECONDS})'
        )
        
        parser.add_argument(
            '-v', '--verbose',
            action='store_true',
            help='Enable verbose logging'
        )
        
        parser.add_argument(
            'command',
            nargs=argparse.REMAINDER,
            help='Command to execute when file is found. Use -- to separate from script args.'
        )
        
        args = parser.parse_args()
    
        if args.command and args.command[0] == '--':
            args.command = args.command[1:]
    
        if not args.command:
            parser.error(
                "No command supplied. You must provide a command to execute after the script options.\n"
                "Example: %(prog)s --file ... -- your_command_here"
            )
    
        return args
    
    
    def delete_file(filespec):
        try:
            filespec.unlink()
            logging.debug(f"Deleted file: {filespec}")
        except OSError as e:
            logging.error(f"Error deleting file {filespec}: {e}")
    
    
    def process_file_presence(file_to_watch, command):
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print(f"{timestamp} - File FOUND: {file_to_watch}")
        
        execute_command(command)
        
    
    def main():
        args = parse_arguments()
        setup_logging(args.verbose)
        
        file_to_watch = Path(args.file).resolve()
        
        print(f"Polling for file: {file_to_watch}")
        print(f"Command to run: {' '.join(args.command)}")
        print("Press Ctrl+C to stop...")
        
        try:
            while True:
                if file_to_watch.exists():
                    process_file_presence(file_to_watch, args.command)
                    delete_file(file_to_watch)
                time.sleep(args.delay)
        except KeyboardInterrupt:
            print("\nStopping file watcher.")
        except Exception as e:
            logging.error(f"An unexpected error occurred: {e}")
    
    if __name__ == "__main__":
        main() 
      
  • 📖 View FlashScreen with syntax highlighting
      #!/usr/bin/env swift
    
    import Cocoa
    
    // Flash duration constants
    let IMAGE_FLASH_DURATION: TimeInterval = 1.0
    let SOLID_COLOR_FLASH_DURATION: TimeInterval = 0.3
    
    // Default image path constants
    let DEFAULT_IMAGE_PATH_DISPLAY = "~/system-flash-image.jpg"
    let DEFAULT_IMAGE_PATH = NSString(string: DEFAULT_IMAGE_PATH_DISPLAY).expandingTildeInPath
    
    // Color parsing function
    func parseColor(_ colorString: String) -> NSColor {
        let lowercased = colorString.lowercased()
        
        // Handle named colors
        switch lowercased {
        case "white": return NSColor.white
        case "black": return NSColor.black
        case "red": return NSColor.red
        case "green": return NSColor.green
        case "blue": return NSColor.blue
        case "yellow": return NSColor.yellow
        case "orange": return NSColor.orange
        case "purple": return NSColor.purple
        case "cyan": return NSColor.cyan
        case "magenta": return NSColor.magenta
        case "gray", "grey": return NSColor.gray
        default:
            // Try to parse as hex color
            if colorString.hasPrefix("#") {
                let hex = String(colorString.dropFirst())
                if hex.count == 6, let rgbValue = UInt32(hex, radix: 16) {
                    let red = CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0
                    let green = CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0
                    let blue = CGFloat(rgbValue & 0x0000FF) / 255.0
                    return NSColor(red: red, green: green, blue: blue, alpha: 1.0)
                }
            }
            // Default to white if parsing fails
            return NSColor.white
        }
    }
    
    // Help function
    func showHelp() {
        print("""
    Flash Screen - Screen Flash Utility
    
    USAGE:
        ./FlashScreen [OPTIONS] [IMAGE_PATH]
        swift FlashScreen [OPTIONS] [IMAGE_PATH]
    
    ARGUMENTS:
        IMAGE_PATH    Optional path to image file to display during flash
                      If not provided, defaults to \(DEFAULT_IMAGE_PATH_DISPLAY)
                      If default image doesn't exist, displays a solid color flash
    
    DESCRIPTION:
        Creates a fullscreen flash effect for visual notifications.
        - With image: Shows the image for \(IMAGE_FLASH_DURATION) seconds
        - Without image: Shows solid color flash for \(SOLID_COLOR_FLASH_DURATION) seconds
        - Default: Checks for \(DEFAULT_IMAGE_PATH_DISPLAY) if no path specified
        
        The flash window appears at maximum window level and ignores mouse events.
    
    EXAMPLES:
        ./FlashScreen                            # Default image or solid color flash
        ./FlashScreen -n                         # Force solid color flash (ignore default image)
        ./FlashScreen -c red                     # Force red color flash
        ./FlashScreen --color "#FF5500"          # Force orange color flash using hex
        ./FlashScreen ~/Pictures/flash.jpg       # Image flash
        swift FlashScreen /path/to/image.png     # Image flash with swift command
    
    OPTIONS:
        -n, --no-image    Force solid color flash, ignoring default image
        -c, --color COLOR Specify flash color (named color or hex like #FF0000)
                          Named colors: white, black, red, green, blue, yellow, orange, purple, cyan, magenta, gray
        -h, --help        Show this help message and exit
    """)
    }
    
    // Parse command line arguments
    let args = CommandLine.arguments
    var forceNoImage = false
    var imagePath: String? = nil
    var flashColor: NSColor = NSColor.white
    
    // Process arguments
    var i = 1
    while i < args.count {
        let arg = args[i]
        if arg == "-h" || arg == "--help" {
            showHelp()
            exit(0)
        } else if arg == "-n" || arg == "--no-image" {
            forceNoImage = true
            i += 1
        } else if arg == "-c" || arg == "--color" {
            if i + 1 < args.count {
                flashColor = parseColor(args[i + 1])
                forceNoImage = true  // Color option implies no image
                i += 2
            } else {
                print("Error: -c/--color requires a color value")
                exit(1)
            }
        } else if !arg.hasPrefix("-") {
            // This is an image path
            imagePath = arg
            break
        } else {
            print("Error: Unknown option \(arg)")
            exit(1)
        }
    }
    
    let app = NSApplication.shared
    app.setActivationPolicy(.regular)
    
    let window = NSWindow(
        contentRect: NSScreen.main!.frame,
        styleMask: [.borderless],
        backing: .buffered,
        defer: false
    )
    
    window.backgroundColor = flashColor
    window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
    window.isOpaque = true
    window.ignoresMouseEvents = true
    
    // Determine final image path based on arguments and flags
    if forceNoImage {
        // Force solid color flash, ignore any image path or default
        imagePath = nil
    } else if imagePath == nil {
        // No explicit image path provided, check for default
        imagePath = FileManager.default.fileExists(atPath: DEFAULT_IMAGE_PATH) ? DEFAULT_IMAGE_PATH : nil
    }
    
    // Try to load and display image
    if let path = imagePath,
       let image = NSImage(contentsOfFile: path) {
        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 solid color flash for shorter duration
        window.makeKeyAndOrderFront(nil)
        app.activate(ignoringOtherApps: true)
        DispatchQueue.main.asyncAfter(deadline: .now() + SOLID_COLOR_FLASH_DURATION) {
            window.close()
            app.terminate(nil)
        }
    }
    
    app.run() 
      
  • 📖 View sample_notifier.py with syntax highlighting
      #!/usr/bin/env python3
    
    """
    This script provides a sample notification by flashing the screen,
    speaking a confirmation message, and sending a native macOS notification.
    """
    
    import subprocess
    from pathlib import Path
    
    # --- Configuration ---
    
    # Get the directory where this script is located
    SCRIPT_DIR = Path(__file__).parent.resolve()
    
    # Infer the project's absolute path from the script's location (one dir up).
    PROJECT_DIR_ABSOLUTE = SCRIPT_DIR.parent
    
    # Abbreviate the home directory with ~ for a cleaner path if applicable
    try:
        PROJECT_DIR_DISPLAY = f"~/{PROJECT_DIR_ABSOLUTE.relative_to(Path.home())}"
    except ValueError:
        PROJECT_DIR_DISPLAY = str(PROJECT_DIR_ABSOLUTE)
    
    # --- Notification Functions ---
    
    def flash():
        """
        Flashes the screen. The FlashScreen script handles default image logic internally.
        """
        flash_script_path = SCRIPT_DIR / "FlashScreen"
        if not flash_script_path.is_file():
            print(f"Error: FlashScreen script not found at {flash_script_path}")
            return
    
        # Use Popen to run in the background, so other notifications can proceed.
        subprocess.Popen([str(flash_script_path)])
    
    def speak():
        """Speaks a confirmation message."""
        try:
            subprocess.run(["say", "AI response complete"], check=True)
        except subprocess.CalledProcessError as e:
            print(f"Error with text-to-speech: {e}")
        except FileNotFoundError:
            print("Error: 'say' command not found (macOS only)")
    
    def terminal_notifier_installed():
        """Check if terminal-notifier is available (only once)."""
        if not hasattr(terminal_notifier_installed, '_checked'):
            terminal_notifier_installed._checked = True
            try:
                subprocess.run(["which", "terminal-notifier"],
                             capture_output=True, text=True, check=True)
                return True
            except subprocess.CalledProcessError:
                print("Error: terminal-notifier not found. Install with: brew install terminal-notifier")
                return False
        return True
    
    def os_notify():
        """Sends a native macOS notification using terminal-notifier."""
        if not terminal_notifier_installed():
            return
        
        message = f"AI response complete in project: {PROJECT_DIR_DISPLAY}"
        title = "AI Response Complete"
        
        # Use terminal-notifier for reliable notifications on macOS 15+; Claude Opus reports that 
        # "macOS 15+ requires explicit notification permissions for command-line tools,
        # and osascript's basic display notification doesn't always trigger the permission request properly."
        process = subprocess.run(
            ["terminal-notifier", "-message", message, "-title", title],
            capture_output=True, text=True
        )
        if process.returncode != 0:
            print(f"Error sending notification: {process.stderr.strip()}")
    
    # --- Main Execution ---
    if __name__ == "__main__":
        flash()
        os_notify()
        speak()
     
      
  • 📖 View watch with syntax highlighting
      scripts/file_watcher.py -f .cursor_response_complete -v scripts/sample_notifier.py
    
    
      

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 can also be brilliant, correctly completing in seconds what would otherwise take minutes, hours, or even days.

So I continue to use it…but not without some frustrations. For various reasons, my requests sometimes perform quite slowly, even taking several minutes sometimes. While I’m waiting I multitask, processing my email inbox for example, but then I need to return to Cursor periodically to see if the response has completed. This frequent context switching kills my productivity.

High Level Solution

Since my multitasking will likely bring me to an application other than Cursor, a visual notification inside Cursor would not work for me. And since I am often working in coworking places where silence is required, an audio notification is not always suitable either. So I needed to find a way to show a visual notification when the response completed, regardless of which application had focus. I decided to look into flashing the desktop with an image or solid color, and this met my need well.

Later I realized it would be nice to support the other notification types as well, so the solution is notification-type-independent. You can configure whatever notification actions you want, but for your convenience I provided a sample-notifier.py script which demonstrates how to implement the following notification types:

  • flash the desktop with an image or solid color
  • show a notification in the Notification Center (requires terminal-notifier)
  • speak text (using macOS say command)

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 consists of two parts:

  • the Cursor AI signaling via the filesystem that the response was completed
  • a script that watches the filesystem and performs the notification(s) when signaled

Let’s discuss these parts one at a time…

Configuring Cursor to Signal Response Completion By Creating a Signal File

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 other modes the AI will not write to the filesystem.

While I was originally unwilling to give it this freedom, I’ve come around to allowing it, since I git commit frequently and can always revert to the most recent commit if I want to undo the AI’s changes.

Flashing the Image On Signal File Creation

Now that response completion is signalled by the presence of the .cursor_response_complete file, we need something to watch for that file and flash the image. The file_watcher.py script watches for any file and execute any command. 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 the system where all projects can access it, and modifications can be made in one place.

Technical Implementation Details

Separate Scripts for File Watching and Image Display

The implementation uses a clean separation of concerns with two main components:

  • File monitoring:
    • A Python script (file_watcher.py) for file monitoring
  • Notification:
    • A platform-specific script (FlashScreen) for displaying the visual flash effect
    • A convenience script (sample_notifier.py) that includes flashing, text-to-speech, and notifications

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 scripts you can use for file watching and desktop flashing in any other use case.

Adapting for Other Platforms

The file_watcher.py script is fully cross-platform since it’s written in Python. The platform-specific components are the notification scripts it is instructed to execute.

This post provides macOS-specific examples:

  • FlashScreen: A Swift script using native macOS APIs for the visual flash.
  • sample_notifier.py: A Python script that orchestrates FlashScreen, the macOS say command, and terminal-notifier.

To implement this solution on Windows or Linux, you would keep file_watcher.py and create your own notification script.

Example approaches for other platforms:

  • Windows: A PowerShell script (.ps1) could use .NET libraries to create a fullscreen window and the BurntToast module to send native notifications.
  • Linux: A shell script could use notify-send for system notifications and a simple Python script with a GUI library (like Tkinter) to create a fullscreen flash effect.

You would then pass your custom script to the file watcher:

# Example for a custom Linux script
./scripts/file_watcher.py --file .cursor_response_complete -- ./scripts/my-linux-notifier.sh

Help Output

Both scripts include comprehensive help:

Python script: ./scripts/file_watcher.py --help (or -h)

usage: file_watcher.py [-h] -f FILE [-d DELAY] [-v] ...

Poll for a file and execute a command when it appears.

positional arguments:
  command            Command to execute when file is found. Use -- to separate
                     from script args.

options:
  -h, --help         show this help message and exit
  -f, --file FILE    File to watch for.
  -d, --delay DELAY  Delay in seconds between polling checks (default: 0.5)
  -v, --verbose      Enable verbose logging

Examples:
  # Flash screen when file appears, checking every 0.2 seconds
  file_watcher.py --file .cursor_response_complete --delay 0.2 -- ./scripts/flash_screen

  # Watch a different file with default delay
  file_watcher.py --file state.log --verbose -- ls -l

Flash script: ./scripts/FlashScreen --help (or -h)

Flash Screen - Screen Flash Utility

USAGE:
    ./FlashScreen [OPTIONS] [IMAGE_PATH]
    swift FlashScreen [OPTIONS] [IMAGE_PATH]

ARGUMENTS:
    IMAGE_PATH    Optional path to image file to display during flash
                  If not provided, defaults to ~/system-flash-image.jpg
                  If default image doesn't exist, displays a solid color flash

DESCRIPTION:
    Creates a fullscreen flash effect for visual notifications.
    - With image: Shows the image for 1.0 seconds
    - Without image: Shows solid color flash for 0.3 seconds
    - Default: Checks for ~/system-flash-image.jpg if no path specified
    
    The flash window appears at maximum window level and ignores mouse events.

EXAMPLES:
    ./FlashScreen                            # Default image or solid color flash
    ./FlashScreen -n                         # Force solid color flash (ignore default image)
    ./FlashScreen -c red                     # Force red color flash
    ./FlashScreen --color "#FF5500"          # Force orange color flash using hex
    ./FlashScreen ~/Pictures/flash.jpg       # Image flash
    swift FlashScreen /path/to/image.png     # Image flash with swift command

OPTIONS:
    -n, --no-image    Force solid color flash, ignoring default image
    -c, --color COLOR Specify flash color (named color or hex like #FF0000)
                      Named colors: white, black, red, green, blue, yellow, orange, purple, cyan, magenta, gray
    -h, --help        Show this help message and exit

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 that lets me 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

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.

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.

Implementation Code

The complete implementation consists of two scripts:

Python File Watcher Script

This Python script handles file monitoring using a simple polling approach. Key features:

  • Monitors any specified file creation with --file argument
  • Executes any command when the file appears
  • Configurable polling delay with --delay argument (default: 0.5 seconds)
  • No external dependencies - uses only the Python standard library
  • Cross-platform compatible

Dependencies: None - uses only the Python 3.6+ standard library

📖 View file_watcher.py with syntax highlighting
  #!/usr/bin/env python3

"""
File Watcher - A generic file monitoring and command execution script

This script monitors a specific file and executes a given command when it appears.
It's designed to be a simple, dependency-free tool for automated workflows.

WHAT IT DOES:
- Polls for the existence of a specified file every second.
- When the file is found, it executes a user-defined command and waits for it to complete.
- Checks the exit code of the command to determine success or failure.
- Deletes the file after the command is run to prepare for the next event.

DEPENDENCIES:
- Python 3.6+
- No external libraries required.

USAGE:
1. Run the script with a command to execute:
   ./scripts/file-watcher.py --file FILE [--delay SECONDS] [--verbose] -- [COMMAND] [ARGS...]
   
   The '--' is recommended to separate script arguments from the command.

2. The script will start polling.
3. Press Ctrl+C to stop.
"""

import time
import subprocess
import argparse
import logging
from pathlib import Path
from datetime import datetime

# Default configuration
DEFAULT_DELAY_SECONDS = 0.5

def execute_command(command):
    try:
        logging.debug(f"Executing command: {' '.join(command)}")
        result = subprocess.run(
            command,
            capture_output=True,
            text=True
        )

        if result.returncode != 0:
            logging.error(f"Command failed with exit code {result.returncode}")
            if result.stderr:
                logging.error(f"Stderr: {result.stderr.strip()}")
        else:
            logging.info("Command executed successfully.")
            if result.stdout:
                logging.debug(f"Stdout: {result.stdout.strip()}")
            if result.stderr: # Log non-fatal stderr output too for diagnostics
                logging.debug(f"Stderr (non-fatal): {result.stderr.strip()}")

    except FileNotFoundError:
        logging.error(f"Command not found: {command[0]}")
    except Exception as e:
        logging.error(f"Error executing command: {e}")


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="Poll for a file and execute a command when it appears.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Flash screen when file appears, checking every 0.2 seconds
  %(prog)s --file .cursor_response_complete --delay 0.2 -- ./scripts/FlashScreen

  # Watch a different file with default delay
  %(prog)s --file state.log --verbose -- ls -l
        """
    )
    
    parser.add_argument(
        '-f', '--file',
        required=True,
        help='File to watch for.'
    )
    
    parser.add_argument(
        '-d', '--delay',
        type=float,
        default=DEFAULT_DELAY_SECONDS,
        help=f'Delay in seconds between polling checks (default: {DEFAULT_DELAY_SECONDS})'
    )
    
    parser.add_argument(
        '-v', '--verbose',
        action='store_true',
        help='Enable verbose logging'
    )
    
    parser.add_argument(
        'command',
        nargs=argparse.REMAINDER,
        help='Command to execute when file is found. Use -- to separate from script args.'
    )
    
    args = parser.parse_args()

    if args.command and args.command[0] == '--':
        args.command = args.command[1:]

    if not args.command:
        parser.error(
            "No command supplied. You must provide a command to execute after the script options.\n"
            "Example: %(prog)s --file ... -- your_command_here"
        )

    return args


def delete_file(filespec):
    try:
        filespec.unlink()
        logging.debug(f"Deleted file: {filespec}")
    except OSError as e:
        logging.error(f"Error deleting file {filespec}: {e}")


def process_file_presence(file_to_watch, command):
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    print(f"{timestamp} - File FOUND: {file_to_watch}")
    
    execute_command(command)
    

def main():
    args = parse_arguments()
    setup_logging(args.verbose)
    
    file_to_watch = Path(args.file).resolve()
    
    print(f"Polling for file: {file_to_watch}")
    print(f"Command to run: {' '.join(args.command)}")
    print("Press Ctrl+C to stop...")
    
    try:
        while True:
            if file_to_watch.exists():
                process_file_presence(file_to_watch, args.command)
                delete_file(file_to_watch)
            time.sleep(args.delay)
    except KeyboardInterrupt:
        print("\nStopping file watcher.")
    except Exception as e:
        logging.error(f"An unexpected error occurred: {e}")

if __name__ == "__main__":
    main() 
  

📝 Note: After downloading, rename to file_watcher.py and ensure it’s executable with chmod +x file_watcher.py.

Swift Flash Script

This Swift script creates the visual flash effect using macOS NSWindow APIs. Key features:

  • Displays fullscreen image for 1.0 second, or solid color flash for 0.3 seconds
  • Supports any image format macOS can display (JPEG, PNG, etc.)
  • Supports custom colors with -c/--color option (named colors or hex values)
  • Force solid color mode with -n/--no-image option
  • Default image path: ~/system-flash-image.jpg
  • Self-contained executable with help documentation
  • Uses shebang line for direct execution (#!/usr/bin/env swift)

Requirements: macOS with Swift compiler

📖 View FlashScreen with syntax highlighting
  #!/usr/bin/env swift

import Cocoa

// Flash duration constants
let IMAGE_FLASH_DURATION: TimeInterval = 1.0
let SOLID_COLOR_FLASH_DURATION: TimeInterval = 0.3

// Default image path constants
let DEFAULT_IMAGE_PATH_DISPLAY = "~/system-flash-image.jpg"
let DEFAULT_IMAGE_PATH = NSString(string: DEFAULT_IMAGE_PATH_DISPLAY).expandingTildeInPath

// Color parsing function
func parseColor(_ colorString: String) -> NSColor {
    let lowercased = colorString.lowercased()
    
    // Handle named colors
    switch lowercased {
    case "white": return NSColor.white
    case "black": return NSColor.black
    case "red": return NSColor.red
    case "green": return NSColor.green
    case "blue": return NSColor.blue
    case "yellow": return NSColor.yellow
    case "orange": return NSColor.orange
    case "purple": return NSColor.purple
    case "cyan": return NSColor.cyan
    case "magenta": return NSColor.magenta
    case "gray", "grey": return NSColor.gray
    default:
        // Try to parse as hex color
        if colorString.hasPrefix("#") {
            let hex = String(colorString.dropFirst())
            if hex.count == 6, let rgbValue = UInt32(hex, radix: 16) {
                let red = CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0
                let green = CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0
                let blue = CGFloat(rgbValue & 0x0000FF) / 255.0
                return NSColor(red: red, green: green, blue: blue, alpha: 1.0)
            }
        }
        // Default to white if parsing fails
        return NSColor.white
    }
}

// Help function
func showHelp() {
    print("""
Flash Screen - Screen Flash Utility

USAGE:
    ./FlashScreen [OPTIONS] [IMAGE_PATH]
    swift FlashScreen [OPTIONS] [IMAGE_PATH]

ARGUMENTS:
    IMAGE_PATH    Optional path to image file to display during flash
                  If not provided, defaults to \(DEFAULT_IMAGE_PATH_DISPLAY)
                  If default image doesn't exist, displays a solid color flash

DESCRIPTION:
    Creates a fullscreen flash effect for visual notifications.
    - With image: Shows the image for \(IMAGE_FLASH_DURATION) seconds
    - Without image: Shows solid color flash for \(SOLID_COLOR_FLASH_DURATION) seconds
    - Default: Checks for \(DEFAULT_IMAGE_PATH_DISPLAY) if no path specified
    
    The flash window appears at maximum window level and ignores mouse events.

EXAMPLES:
    ./FlashScreen                            # Default image or solid color flash
    ./FlashScreen -n                         # Force solid color flash (ignore default image)
    ./FlashScreen -c red                     # Force red color flash
    ./FlashScreen --color "#FF5500"          # Force orange color flash using hex
    ./FlashScreen ~/Pictures/flash.jpg       # Image flash
    swift FlashScreen /path/to/image.png     # Image flash with swift command

OPTIONS:
    -n, --no-image    Force solid color flash, ignoring default image
    -c, --color COLOR Specify flash color (named color or hex like #FF0000)
                      Named colors: white, black, red, green, blue, yellow, orange, purple, cyan, magenta, gray
    -h, --help        Show this help message and exit
""")
}

// Parse command line arguments
let args = CommandLine.arguments
var forceNoImage = false
var imagePath: String? = nil
var flashColor: NSColor = NSColor.white

// Process arguments
var i = 1
while i < args.count {
    let arg = args[i]
    if arg == "-h" || arg == "--help" {
        showHelp()
        exit(0)
    } else if arg == "-n" || arg == "--no-image" {
        forceNoImage = true
        i += 1
    } else if arg == "-c" || arg == "--color" {
        if i + 1 < args.count {
            flashColor = parseColor(args[i + 1])
            forceNoImage = true  // Color option implies no image
            i += 2
        } else {
            print("Error: -c/--color requires a color value")
            exit(1)
        }
    } else if !arg.hasPrefix("-") {
        // This is an image path
        imagePath = arg
        break
    } else {
        print("Error: Unknown option \(arg)")
        exit(1)
    }
}

let app = NSApplication.shared
app.setActivationPolicy(.regular)

let window = NSWindow(
    contentRect: NSScreen.main!.frame,
    styleMask: [.borderless],
    backing: .buffered,
    defer: false
)

window.backgroundColor = flashColor
window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
window.isOpaque = true
window.ignoresMouseEvents = true

// Determine final image path based on arguments and flags
if forceNoImage {
    // Force solid color flash, ignore any image path or default
    imagePath = nil
} else if imagePath == nil {
    // No explicit image path provided, check for default
    imagePath = FileManager.default.fileExists(atPath: DEFAULT_IMAGE_PATH) ? DEFAULT_IMAGE_PATH : nil
}

// Try to load and display image
if let path = imagePath,
   let image = NSImage(contentsOfFile: path) {
    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 solid color flash for shorter duration
    window.makeKeyAndOrderFront(nil)
    app.activate(ignoringOtherApps: true)
    DispatchQueue.main.asyncAfter(deadline: .now() + SOLID_COLOR_FLASH_DURATION) {
        window.close()
        app.terminate(nil)
    }
}

app.run() 
  

📝 Note: After downloading, rename to FlashScreen and ensure it’s executable with chmod +x FlashScreen.

Sample Notifier Script

For users who want more comprehensive notifications including screen flash, text-to-speech, and native macOS notifications, we provide a sample notifier script. This Python script demonstrates how to combine multiple notification types:

Key features:

  • Flashes the screen using the FlashScreen script
  • Speaks “AI response complete” using macOS say command
  • Sends a native macOS notification using terminal-notifier
  • Shows the project directory in notifications for context
  • Kicks off all three notifications in quick succession for a near-parallel effect

Requirements:

  • macOS (for say command)
  • terminal-notifier (install with: brew install terminal-notifier)
📖 View sample_notifier.py with syntax highlighting
  #!/usr/bin/env python3

"""
This script provides a sample notification by flashing the screen,
speaking a confirmation message, and sending a native macOS notification.
"""

import subprocess
from pathlib import Path

# --- Configuration ---

# Get the directory where this script is located
SCRIPT_DIR = Path(__file__).parent.resolve()

# Infer the project's absolute path from the script's location (one dir up).
PROJECT_DIR_ABSOLUTE = SCRIPT_DIR.parent

# Abbreviate the home directory with ~ for a cleaner path if applicable
try:
    PROJECT_DIR_DISPLAY = f"~/{PROJECT_DIR_ABSOLUTE.relative_to(Path.home())}"
except ValueError:
    PROJECT_DIR_DISPLAY = str(PROJECT_DIR_ABSOLUTE)

# --- Notification Functions ---

def flash():
    """
    Flashes the screen. The FlashScreen script handles default image logic internally.
    """
    flash_script_path = SCRIPT_DIR / "FlashScreen"
    if not flash_script_path.is_file():
        print(f"Error: FlashScreen script not found at {flash_script_path}")
        return

    # Use Popen to run in the background, so other notifications can proceed.
    subprocess.Popen([str(flash_script_path)])

def speak():
    """Speaks a confirmation message."""
    try:
        subprocess.run(["say", "AI response complete"], check=True)
    except subprocess.CalledProcessError as e:
        print(f"Error with text-to-speech: {e}")
    except FileNotFoundError:
        print("Error: 'say' command not found (macOS only)")

def terminal_notifier_installed():
    """Check if terminal-notifier is available (only once)."""
    if not hasattr(terminal_notifier_installed, '_checked'):
        terminal_notifier_installed._checked = True
        try:
            subprocess.run(["which", "terminal-notifier"],
                         capture_output=True, text=True, check=True)
            return True
        except subprocess.CalledProcessError:
            print("Error: terminal-notifier not found. Install with: brew install terminal-notifier")
            return False
    return True

def os_notify():
    """Sends a native macOS notification using terminal-notifier."""
    if not terminal_notifier_installed():
        return
    
    message = f"AI response complete in project: {PROJECT_DIR_DISPLAY}"
    title = "AI Response Complete"
    
    # Use terminal-notifier for reliable notifications on macOS 15+; Claude Opus reports that 
    # "macOS 15+ requires explicit notification permissions for command-line tools,
    # and osascript's basic display notification doesn't always trigger the permission request properly."
    process = subprocess.run(
        ["terminal-notifier", "-message", message, "-title", title],
        capture_output=True, text=True
    )
    if process.returncode != 0:
        print(f"Error sending notification: {process.stderr.strip()}")

# --- Main Execution ---
if __name__ == "__main__":
    flash()
    os_notify()
    speak()
 
  

📝 Note: After downloading, rename to sample_notifier.py and ensure it’s executable with chmod +x sample_notifier.py.

To use this script instead of just the FlashScreen:

./scripts/file_watcher.py --file .cursor_response_complete -- ./scripts/sample_notifier.py

Watch Convenience Script

This is a simple shell script to run the file watcher with the notifier, for convenience:

#!/bin/bash
scripts/file_watcher.py -f .cursor_response_complete -v scripts/sample_notifier.py

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

  1. Open Cursor AI
  2. Navigate to: Cursor → Settings → Cursor Settings → Rules
  3. 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.
    
  4. Important: You must use Agent mode for this to work (other modes don’t write to filesystem)
  5. Important: Enable the AI to complete responses without asking for confirmation

Step 2: Download and Setup Scripts

  1. Download the scripts using the download buttons in the Implementation Code section above
  2. Create a scripts directory in your project root:
    mkdir scripts
    
  3. Place the downloaded files in the scripts directory:
    • Rename file_watcher.py.txt to file_watcher.py
    • Rename FlashScreen.txt to FlashScreen
  4. Make them executable:
    chmod +x scripts/file_watcher.py
    chmod +x scripts/FlashScreen
    # or
    chmod +x scripts/*  # Make all files in the 'scripts' directory executable
    

Step 3: Setup Your Flash Image

Choose one of these options:

Option A: Use the default image path (recommended)

# Copy your image to the default location where he FlashScreen script will automatically use it
cp ~/Pictures/my-flash-image.jpg ~/system-flash-image.jpg

Option B: Pass a custom image path

# Pass any image file to FlashScreen directly  
./scripts/file_watcher.py --file .cursor_response_complete -- ./scripts/FlashScreen ~/Pictures/my-flash-image.jpg

Step 4: Start the Monitoring Script

Once your scripts are in place, you can start monitoring for the signal file. Open a terminal, navigate to your project’s root directory, and choose one of the following methods.

Option A: Basic Usage (Flash Only)

This command watches for the signal file and triggers only the screen flash.

cd /path/to/your/project
./scripts/file_watcher.py --file .cursor_response_complete -- ./scripts/FlashScreen

Option B: Full Notifications (Recommended)

This command uses the sample_notifier.py script to trigger all three notifications (flash, sound, and system notification). The scripts/watch convenience script does the same thing with verbose logging enabled.

# Using the notifier script directly
cd /path/to/your/project
./scripts/file_watcher.py --file .cursor_response_complete -- ./scripts/sample_notifier.py

# Or using the convenience script
cd /path/to/your/project
./scripts/watch

You can add the --verbose flag to any file_watcher.py command to see more detailed logging.

Step 5: Test the Setup

  1. Test the flash script directly:
    # Test with default image or white flash
    ./scripts/FlashScreen
       
    # Test with a specific image
    ./scripts/FlashScreen ~/Pictures/test-image.jpg
       
    # Test with a colored flash
    ./scripts/FlashScreen -c red
    
  2. 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

  • Symptom: Nothing happens when a response completes.
    • Cause: The signal file (.cursor_response_complete) is likely not being created.
    • Solution: Ensure you are using Agent Mode in Cursor AI and that your file-creation rule is correctly configured and enabled.
  • Symptom: Terminal shows a “Permission denied” error.
    • Cause: A script you’re trying to run lacks execute permissions.
    • Solution: Make the script executable. For example: chmod +x scripts/file_watcher.py.
  • Symptom: Terminal shows a “command not found” error.
    • Cause: A program needed by the scripts is either not installed or not in your system’s PATH.
    • Solution: Identify which command is missing (e.g., python3, say, terminal-notifier) and ensure it is installed and accessible from your terminal.
  • Symptom: The screen flash is a solid color instead of your image.
    • Cause: The FlashScreen script cannot find the image file.
    • Solution: If using the default, verify that ~/system-flash-image.jpg exists. If passing a path directly, ensure the path is correct.
  • Symptom: The say command and system notification appear, but the screen does not flash.
    • Cause: This points to an issue specifically with the FlashScreen script.
    • Solution:
      1. Confirm that scripts/FlashScreen exists and is executable.
      2. Run ./scripts/FlashScreen directly from your terminal to see if it produces any errors on its own.

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/FlashScreen, say, and terminal-notifier
  • Multiple projects: Place scripts in a system-wide location and use absolute paths
  • Customize scripts: View and download the source code in the Implementation Code section above

About the Author

This solution was developed by Keith Bennett of Bennett Business Solutions, Inc. Keith is an experienced software engineer and consultant specializing in automation, development tooling, and creative technical solutions. He is currently open to work and available for employment and consulting engagements. He can be reached at [email protected] or other methods listed in the contact section at the website mentioned above.