Created
July 20, 2025 10:16
-
-
Save April-June-August/7a1ef5d190d385ae7005c10d150a82cc to your computer and use it in GitHub Desktop.
Check for invalid Alfred Workflows and remove them
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 | |
""" | |
Script to find and move Alfred workflow folders that are missing info.plist files. | |
This helps clean up "dirty" workflow folders that cause alfred-workflow-packager to fail. | |
""" | |
import glob | |
import os | |
import os.path | |
import plistlib | |
import shutil | |
from datetime import datetime | |
def read_plist_from_path(plist_path): | |
"""Read a .plist file and return its contents as a dictionary.""" | |
with open(plist_path, "rb") as plist_file: | |
return plistlib.load(plist_file) | |
def get_alfred_prefs_dir(): | |
"""Get the Alfred preferences directory, accounting for sync folder settings.""" | |
library_dir = os.path.join(os.path.expanduser("~"), "Library") | |
# Try Alfred 3 preferences first, then fallback to current version | |
try: | |
core_prefs = read_plist_from_path( | |
os.path.join( | |
library_dir, | |
"Preferences", | |
"com.runningwithcrayons.Alfred-Preferences-3.plist" | |
) | |
) | |
except IOError: | |
core_prefs = read_plist_from_path( | |
os.path.join( | |
library_dir, | |
"Preferences", | |
"com.runningwithcrayons.Alfred-Preferences.plist" | |
) | |
) | |
# Check if user is syncing preferences | |
if core_prefs.get("syncfolder"): | |
return os.path.expanduser(core_prefs["syncfolder"]) | |
else: | |
return os.path.join(library_dir, "Application Support", "Alfred") | |
def move_invalid_workflows(): | |
"""Find and move all workflow directories that are missing/corrupt info.plist files.""" | |
try: | |
prefs_dir = get_alfred_prefs_dir() | |
workflows_dir = os.path.join(prefs_dir, "Alfred.alfredpreferences", "workflows") | |
# Create invalid workflows subfolder with timestamp | |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
invalid_folder = os.path.join(workflows_dir, f"_invalid_workflows_{timestamp}") | |
print(f"Checking workflows in: {workflows_dir}") | |
print("-" * 60) | |
# Get all workflow directories | |
workflow_dirs = glob.glob(os.path.join(workflows_dir, "*")) | |
workflow_dirs = [d for d in workflow_dirs if os.path.isdir(d) and not os.path.basename(d).startswith("_invalid_workflows")] | |
invalid_workflows = [] | |
total_workflows = len(workflow_dirs) | |
for workflow_dir in workflow_dirs: | |
workflow_name = os.path.basename(workflow_dir) | |
info_plist_path = os.path.join(workflow_dir, "info.plist") | |
is_invalid = False | |
reason = "" | |
if not os.path.exists(info_plist_path): | |
is_invalid = True | |
reason = "MISSING info.plist" | |
print(f"❌ MISSING: {workflow_name}") | |
else: | |
# Try to read the plist to check if it's valid | |
try: | |
read_plist_from_path(info_plist_path) | |
print(f"✅ OK: {workflow_name}") | |
except Exception as e: | |
is_invalid = True | |
reason = f"CORRUPT info.plist: {str(e)}" | |
print(f"⚠️ CORRUPT: {workflow_name} (Error: {str(e)})") | |
if is_invalid: | |
invalid_workflows.append((workflow_dir, workflow_name, reason)) | |
# Move invalid workflows if any found | |
moved_count = 0 | |
if invalid_workflows: | |
# Create the invalid folder | |
os.makedirs(invalid_folder, exist_ok=True) | |
print(f"\nCreated folder for invalid workflows: {invalid_folder}") | |
print(f"\nMoving {len(invalid_workflows)} invalid workflow(s):") | |
for workflow_dir, workflow_name, reason in invalid_workflows: | |
try: | |
dest_path = os.path.join(invalid_folder, workflow_name) | |
shutil.move(workflow_dir, dest_path) | |
print(f" ✅ Moved: {workflow_name} ({reason})") | |
moved_count += 1 | |
except Exception as e: | |
print(f" ❌ Failed to move {workflow_name}: {str(e)}") | |
print("-" * 60) | |
print(f"Summary:") | |
print(f" Total workflow folders checked: {total_workflows}") | |
print(f" Invalid workflows found: {len(invalid_workflows)}") | |
print(f" Invalid workflows moved: {moved_count}") | |
print(f" Valid workflows remaining: {total_workflows - len(invalid_workflows)}") | |
if moved_count > 0: | |
print(f"\nInvalid workflows moved to: {invalid_folder}") | |
return invalid_workflows | |
except Exception as e: | |
print(f"Error: {str(e)}") | |
return [] | |
if __name__ == "__main__": | |
import sys | |
if len(sys.argv) > 1 and sys.argv[1] == "--move": | |
print("🚨 MOVING INVALID WORKFLOWS TO SUBFOLDER") | |
print("=" * 60) | |
move_invalid_workflows() | |
else: | |
print("📋 CHECKING WORKFLOWS (READ-ONLY MODE)") | |
print("To move invalid workflows, run: python3 check_workflows.py --move") | |
print("=" * 60) | |
# Read-only version of the function | |
try: | |
prefs_dir = get_alfred_prefs_dir() | |
workflows_dir = os.path.join(prefs_dir, "Alfred.alfredpreferences", "workflows") | |
print(f"Checking workflows in: {workflows_dir}") | |
print("-" * 60) | |
# Get all workflow directories | |
workflow_dirs = glob.glob(os.path.join(workflows_dir, "*")) | |
workflow_dirs = [d for d in workflow_dirs if os.path.isdir(d) and not os.path.basename(d).startswith("_invalid_workflows")] | |
invalid_workflows = [] | |
total_workflows = len(workflow_dirs) | |
for workflow_dir in workflow_dirs: | |
workflow_name = os.path.basename(workflow_dir) | |
info_plist_path = os.path.join(workflow_dir, "info.plist") | |
if not os.path.exists(info_plist_path): | |
invalid_workflows.append(workflow_dir) | |
print(f"❌ MISSING: {workflow_name}") | |
else: | |
# Try to read the plist to check if it's valid | |
try: | |
read_plist_from_path(info_plist_path) | |
print(f"✅ OK: {workflow_name}") | |
except Exception as e: | |
invalid_workflows.append(workflow_dir) | |
print(f"⚠️ CORRUPT: {workflow_name} (Error: {str(e)})") | |
print("-" * 60) | |
print(f"Summary:") | |
print(f" Total workflow folders: {total_workflows}") | |
print(f" Missing/corrupt info.plist: {len(invalid_workflows)}") | |
print(f" Valid workflows: {total_workflows - len(invalid_workflows)}") | |
if invalid_workflows: | |
print(f"\nWorkflow folders with missing/corrupt info.plist:") | |
for workflow_dir in invalid_workflows: | |
print(f" {workflow_dir}") | |
print(f"\nTo move these to a subfolder, run: python3 check_workflows.py --move") | |
except Exception as e: | |
print(f"Error: {str(e)}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment