Created
November 4, 2024 06:38
-
-
Save thepushkarp/806109fc11e972227b2da7354a05be71 to your computer and use it in GitHub Desktop.
Cursor and Copilot Instruction File Synchronization Script.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Cursor and Copilot Instruction File Synchronization Script. | |
Synchronizes instruction files between Cursor and GitHub Copilot by copying contents | |
between .cursorrules and .github/copilot-instructions.md files. | |
Requirements: | |
- Python 3.6+ (uses f-strings) | |
- No external dependencies required (uses only standard library) | |
Usage: | |
python sync_ai_instructions.py # Interactive usage | |
python sync_ai_instructions.py --dry-run # Preview changes | |
python sync_ai_instructions.py -y # Auto-confirm simple operations | |
python sync_ai_instructions.py --prefer-cursor # Always prefer .cursorrules | |
python sync_ai_instructions.py --prefer-copilot # Always prefer copilot-instructions.md | |
""" | |
import os | |
import shutil | |
import sys | |
import argparse | |
from enum import Enum | |
from typing import Optional, Tuple | |
class FileType(Enum): | |
CURSOR = ".cursorrules" | |
COPILOT = ".github/copilot-instructions.md" | |
def __str__(self): | |
return self.value | |
def check_files() -> Tuple[bool, bool]: | |
""" | |
Check for the existence of instruction files. | |
Returns: | |
Tuple of (cursor_exists: bool, copilot_exists: bool) | |
""" | |
cursor_exists = os.path.isfile(str(FileType.CURSOR)) | |
copilot_exists = os.path.isfile(str(FileType.COPILOT)) | |
return cursor_exists, copilot_exists | |
def ensure_github_dir() -> None: | |
"""Create .github directory if it doesn't exist.""" | |
if not os.path.exists('.github'): | |
os.makedirs('.github') | |
def get_user_confirmation(message: str, default_yes: bool = False) -> bool: | |
""" | |
Ask for user confirmation. | |
Args: | |
message: Prompt message to display | |
default_yes: Whether to use yes as the default option | |
Returns: | |
bool: True if user confirms, False otherwise | |
""" | |
if default_yes: | |
prompt = f"{message} [Y/n]: " | |
response = input(prompt).strip().lower() | |
return response not in ('n', 'no') | |
else: | |
prompt = f"{message} [y/N]: " | |
response = input(prompt).strip().lower() | |
return response in ('y', 'yes') | |
def copy_file(source: FileType, target: FileType, dry_run: bool) -> None: | |
""" | |
Copy contents from source file to target file. | |
Args: | |
source: Source file type | |
target: Target file type | |
dry_run: If True, only print what would be done | |
""" | |
if dry_run: | |
print(f"[DRY RUN] Would copy {source} to {target}") | |
return | |
if target == FileType.COPILOT: | |
ensure_github_dir() | |
shutil.copy2(str(source), str(target)) | |
print(f"Copied {source} to {target}") | |
def handle_both_files(args: argparse.Namespace) -> bool: | |
""" | |
Handle the case when both files exist. | |
Args: | |
args: Command line arguments | |
Returns: | |
bool: True if operation was handled, False otherwise | |
""" | |
if args.prefer_cursor: | |
copy_file(FileType.CURSOR, FileType.COPILOT, args.dry_run) | |
return True | |
if args.prefer_copilot: | |
copy_file(FileType.COPILOT, FileType.CURSOR, args.dry_run) | |
return True | |
while True: | |
choice = input("""Both files exist. Choose an action: | |
1. Copy .cursorrules to .github/copilot-instructions.md | |
2. Copy .github/copilot-instructions.md to .cursorrules | |
3. Leave files as they are | |
Enter choice (1-3): """) | |
if choice == '1': | |
copy_file(FileType.CURSOR, FileType.COPILOT, args.dry_run) | |
break | |
elif choice == '2': | |
copy_file(FileType.COPILOT, FileType.CURSOR, args.dry_run) | |
break | |
elif choice == '3': | |
print("Files left unchanged") | |
break | |
else: | |
print("Invalid choice. Please enter 1, 2, or 3.") | |
return True | |
def sync_instructions(args: argparse.Namespace) -> None: | |
""" | |
Synchronize instruction files based on their existence and user preferences. | |
Args: | |
args: Command line arguments containing dry_run, yes, and preference flags | |
""" | |
cursor_exists, copilot_exists = check_files() | |
# Neither file exists | |
if not cursor_exists and not copilot_exists: | |
print("Neither .cursorrules nor .github/copilot-instructions.md exists.") | |
return | |
# Both files exist | |
if cursor_exists and copilot_exists: | |
handle_both_files(args) | |
return | |
# Only .cursorrules exists | |
if cursor_exists: | |
print("Found .cursorrules but no .github/copilot-instructions.md") | |
if args.yes or get_user_confirmation( | |
"Copy .cursorrules to .github/copilot-instructions.md?", | |
args.yes | |
): | |
copy_file(FileType.CURSOR, FileType.COPILOT, args.dry_run) | |
return | |
# Only copilot-instructions.md exists | |
if copilot_exists: | |
print("Found .github/copilot-instructions.md but no .cursorrules") | |
if args.yes or get_user_confirmation( | |
"Copy .github/copilot-instructions.md to .cursorrules?", | |
args.yes | |
): | |
copy_file(FileType.COPILOT, FileType.CURSOR, args.dry_run) | |
return | |
def main() -> None: | |
parser = argparse.ArgumentParser( | |
description='Synchronize .cursorrules and .github/copilot-instructions.md files', | |
formatter_class=argparse.RawDescriptionHelpFormatter, | |
epilog=""" | |
Examples: | |
%(prog)s --dry-run # Show what would be done without making changes | |
%(prog)s -y # Auto-confirm simple operations | |
%(prog)s --prefer-cursor # Always prefer .cursorrules when both exist | |
%(prog)s --prefer-copilot # Always prefer copilot-instructions.md when both exist | |
""" | |
) | |
parser.add_argument( | |
'--dry-run', | |
action='store_true', | |
help='Show what would be done without making changes' | |
) | |
parser.add_argument( | |
'-y', '--yes', | |
action='store_true', | |
help='Automatically answer yes to prompts (except when both files exist)' | |
) | |
preference_group = parser.add_mutually_exclusive_group() | |
preference_group.add_argument( | |
'--prefer-cursor', | |
action='store_true', | |
help='Prefer .cursorrules when both files exist' | |
) | |
preference_group.add_argument( | |
'--prefer-copilot', | |
action='store_true', | |
help='Prefer .github/copilot-instructions.md when both files exist' | |
) | |
args = parser.parse_args() | |
try: | |
sync_instructions(args) | |
except PermissionError: | |
print("Error: Permission denied. Please check file permissions.") | |
sys.exit(1) | |
except KeyboardInterrupt: | |
print("\nOperation cancelled by user") | |
sys.exit(1) | |
except Exception as e: | |
print(f"An error occurred: {str(e)}") | |
sys.exit(1) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment