#!/bin/bash # Mole - Deeper system cleanup # Complete cleanup with smart password handling set -euo pipefail # Get script directory and source common functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../lib/common.sh" # Configuration SYSTEM_CLEAN=false IS_M_SERIES=$([ "$(uname -m)" = "arm64" ] && echo "true" || echo "false") total_items=0 # Tracking variables TRACK_SECTION=0 SECTION_ACTIVITY=0 LAST_CLEAN_RESULT=0 files_cleaned=0 total_size_cleaned=0 SUDO_KEEPALIVE_PID="" note_activity() { if [[ $TRACK_SECTION -eq 1 ]]; then SECTION_ACTIVITY=1 fi } # Cleanup background processes cleanup() { if [[ -n "$SUDO_KEEPALIVE_PID" ]]; then kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true SUDO_KEEPALIVE_PID="" fi if [[ -n "$SPINNER_PID" ]]; then kill "$SPINNER_PID" 2>/dev/null || true SPINNER_PID="" fi } trap cleanup EXIT INT TERM # Loading animation functions SPINNER_PID="" start_spinner() { local message="$1" # Check if we're in an interactive terminal if [[ ! -t 1 ]]; then # Non-interactive, just show static message echo -n " ${BLUE}🔍${NC} $message" return fi # Display message without newline echo -n " ${BLUE}🔍${NC} $message" # Start simple dots animation for interactive terminals ( local delay=0.5 while true; do printf "\r ${BLUE}🔍${NC} $message. " sleep $delay printf "\r ${BLUE}🔍${NC} $message.. " sleep $delay printf "\r ${BLUE}🔍${NC} $message..." sleep $delay printf "\r ${BLUE}🔍${NC} $message " sleep $delay done ) & SPINNER_PID=$! } stop_spinner() { local result_message="${1:-Done}" if [[ ! -t 1 ]]; then # Non-interactive, just show result echo " ✓ $result_message" return fi if [[ -n "$SPINNER_PID" ]]; then kill "$SPINNER_PID" 2>/dev/null wait "$SPINNER_PID" 2>/dev/null SPINNER_PID="" # Clear the line and show result printf "\r ${GREEN}✓${NC} %s\n" "$result_message" else # No spinner was running, just show result echo " ${GREEN}✓${NC} $result_message" fi } # Cleanup background processes on exit start_section() { TRACK_SECTION=1 SECTION_ACTIVITY=0 echo "" echo -e "${PURPLE}▶ $1${NC}" } end_section() { if [[ $TRACK_SECTION -eq 1 && $SECTION_ACTIVITY -eq 0 ]]; then echo -e " ${BLUE}✨${NC} Nothing to tidy" fi TRACK_SECTION=0 } safe_clean() { if [[ $# -eq 0 ]]; then return 0 fi local description local -a targets if [[ $# -eq 1 ]]; then description="$1" targets=("$1") else description="${@: -1}" targets=("${@:1:$#-1}") fi local removed_any=0 local total_size_bytes=0 local total_count=0 # Optimized: skip size calculation for empty checks, just try to delete # Size calculation is the slowest part - do it in parallel local -a existing_paths=() for path in "${targets[@]}"; do [[ -e "$path" ]] && existing_paths+=("$path") done if [[ ${#existing_paths[@]} -eq 0 ]]; then LAST_CLEAN_RESULT=0 return 0 fi # Fast parallel processing for multiple targets if [[ ${#existing_paths[@]} -gt 3 ]]; then local temp_dir=$(mktemp -d) # Launch parallel du jobs (bash 3.2 compatible) local -a pids=() for path in "${existing_paths[@]}"; do ( local size=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0") local count=$(find "$path" -type f 2>/dev/null | wc -l | tr -d ' ') echo "$size $count" > "$temp_dir/$(echo -n "$path" | shasum -a 256 | cut -d' ' -f1)" ) & pids+=($!) # Limit to 15 parallel jobs (bash 3.2 compatible) if (( ${#pids[@]} >= 15 )); then wait "${pids[0]}" 2>/dev/null || true pids=("${pids[@]:1}") fi done # Wait for remaining jobs for pid in "${pids[@]}"; do wait "$pid" 2>/dev/null || true done # Collect results and delete for path in "${existing_paths[@]}"; do local hash=$(echo -n "$path" | shasum -a 256 | cut -d' ' -f1) if [[ -f "$temp_dir/$hash" ]]; then read -r size count < "$temp_dir/$hash" if [[ "$count" -gt 0 && "$size" -gt 0 ]]; then rm -rf "$path" 2>/dev/null || true ((total_size_bytes += size)) ((total_count += count)) removed_any=1 fi fi done rm -rf "$temp_dir" else # Serial processing for few targets (faster than parallel overhead) for path in "${existing_paths[@]}"; do local size_bytes=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0") local count=$(find "$path" -type f 2>/dev/null | wc -l | tr -d ' ') if [[ "$count" -gt 0 && "$size_bytes" -gt 0 ]]; then rm -rf "$path" 2>/dev/null || true ((total_size_bytes += size_bytes)) ((total_count += count)) removed_any=1 fi done fi # Only show output if something was actually cleaned if [[ $removed_any -eq 1 ]]; then local size_human if [[ $total_size_bytes -gt 1048576 ]]; then # > 1GB size_human=$(echo "$total_size_bytes" | awk '{printf "%.1fGB", $1/1024/1024}') elif [[ $total_size_bytes -gt 1024 ]]; then # > 1MB size_human=$(echo "$total_size_bytes" | awk '{printf "%.1fMB", $1/1024}') else size_human="${total_size_bytes}KB" fi local label="$description" if [[ ${#targets[@]} -gt 1 ]]; then label+=" (${#targets[@]} items)" fi echo -e " ${GREEN}✓${NC} $label ${GREEN}($size_human)${NC}" ((files_cleaned+=total_count)) ((total_size_cleaned+=total_size_bytes)) ((total_items++)) note_activity fi LAST_CLEAN_RESULT=$removed_any return 0 } start_cleanup() { echo "Mole will remove app caches, browser data, developer tools, and temporary files." echo "" # Check if we're in an interactive terminal if [[ -t 0 ]]; then # Interactive mode - ask for password echo "Enter admin password for system-level cleanup (or press Enter to skip):" echo -n "> " read -s password echo "" else # Non-interactive mode - skip password prompt password="" echo "" echo -e "${BLUE}ℹ${NC} Running in non-interactive mode" echo " • System-level cleanup will be skipped (requires password)" echo " • User-level cleanup will proceed automatically" echo "" fi if [[ -n "$password" ]] && echo "$password" | sudo -S true 2>/dev/null; then SYSTEM_CLEAN=true # Start sudo keepalive with error handling and shorter intervals ( local retry_count=0 while true; do if ! sudo -n true 2>/dev/null; then ((retry_count++)) if [[ $retry_count -ge 3 ]]; then log_warning "Sudo keepalive failed, system-level cleanup may be interrupted" >&2 exit 1 fi sleep 5 continue fi retry_count=0 sleep 30 kill -0 "$$" 2>/dev/null || exit done ) 2>/dev/null & SUDO_KEEPALIVE_PID=$! log_info "Starting comprehensive cleanup with admin privileges..." else SYSTEM_CLEAN=false log_info "Starting user-level cleanup..." if [[ -n "$password" ]]; then echo -e "${YELLOW}⚠️ Invalid password, continuing with user-level cleanup${NC}" fi fi } perform_cleanup() { echo "" echo "🍎 $(detect_architecture) | 💾 Free space: $(get_free_space)" # Get initial space space_before=$(df / | tail -1 | awk '{print $4}') # Initialize counters total_items=0 files_cleaned=0 total_size_cleaned=0 # ===== 1. System cleanup (if admin) - Do this first while sudo is fresh ===== if [[ "$SYSTEM_CLEAN" == "true" ]]; then start_section "System-level cleanup" # Clean system caches more safely - avoid input method and system service caches sudo find /Library/Caches -name "*.cache" -delete 2>/dev/null || true sudo find /Library/Caches -name "*.tmp" -delete 2>/dev/null || true sudo find /Library/Caches -type f -name "*.log" -delete 2>/dev/null || true sudo rm -rf /tmp/* 2>/dev/null && log_success "System temp files" || true sudo rm -rf /var/tmp/* 2>/dev/null && log_success "System var temp" || true log_success "System library caches (safely cleaned)" end_section fi # ===== 2. User essentials ===== start_section "System essentials" safe_clean ~/Library/Caches/* "User app cache" safe_clean ~/Library/Logs/* "User app logs" safe_clean ~/.Trash/* "Trash" # Empty the trash on all mounted volumes if [[ -d "/Volumes" ]]; then for volume in /Volumes/*; do if [[ -d "$volume" && -d "$volume/.Trashes" ]]; then find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -exec rm -rf {} \; 2>/dev/null || true fi done fi safe_clean ~/Library/Application\ Support/CrashReporter/* "Crash reports" safe_clean ~/Library/DiagnosticReports/* "Diagnostic reports" safe_clean ~/Library/Caches/com.apple.QuickLook.thumbnailcache "QuickLook thumbnails" safe_clean ~/Library/Caches/Quick\ Look/* "QuickLook cache" safe_clean ~/Library/Caches/com.apple.LaunchServices* "Launch services cache" safe_clean ~/Library/Caches/com.apple.iconservices* "Icon services cache" safe_clean ~/Library/Caches/CloudKit/* "CloudKit cache" safe_clean ~/Library/Caches/com.apple.bird* "iCloud cache" end_section # ===== 2. Browsers ===== start_section "Browser cleanup" # Safari safe_clean ~/Library/Caches/com.apple.Safari/* "Safari cache" safe_clean ~/Library/Safari/LocalStorage/* "Safari local storage" safe_clean ~/Library/Safari/Databases/* "Safari databases" # Chrome/Chromium family safe_clean ~/Library/Caches/Google/Chrome/* "Chrome cache" safe_clean ~/Library/Application\ Support/Google/Chrome/*/Application\ Cache/* "Chrome app cache" safe_clean ~/Library/Application\ Support/Google/Chrome/*/GPUCache/* "Chrome GPU cache" safe_clean ~/Library/Caches/Chromium/* "Chromium cache" # Other browsers safe_clean ~/Library/Caches/com.microsoft.edgemac/* "Edge cache" safe_clean ~/Library/Caches/company.thebrowser.Browser/* "Arc cache" safe_clean ~/Library/Caches/BraveSoftware/Brave-Browser/* "Brave cache" safe_clean ~/Library/Caches/Firefox/* "Firefox cache" safe_clean ~/Library/Caches/com.operasoftware.Opera/* "Opera cache" safe_clean ~/Library/Caches/com.vivaldi.Vivaldi/* "Vivaldi cache" # Browser support files safe_clean ~/Library/Application\ Support/Firefox/Profiles/*/cache2/* "Firefox profile cache" end_section # ===== 3. Developer tools ===== start_section "Developer tools" # Node.js ecosystem if command -v npm >/dev/null 2>&1; then npm cache clean --force >/dev/null 2>&1 || true echo -e " ${GREEN}✓${NC} npm cache cleaned" note_activity fi safe_clean ~/.npm/_cacache/* "npm cache directory" safe_clean ~/.yarn/cache/* "Yarn cache" safe_clean ~/.bun/install/cache/* "Bun cache" # Python ecosystem if command -v pip3 >/dev/null 2>&1; then pip3 cache purge >/dev/null 2>&1 || true echo -e " ${GREEN}✓${NC} pip cache cleaned" note_activity fi safe_clean ~/.cache/pip/* "pip cache directory" safe_clean ~/Library/Caches/pip/* "pip cache (macOS)" safe_clean ~/.pyenv/cache/* "pyenv cache" # Go ecosystem if command -v go >/dev/null 2>&1; then go clean -modcache >/dev/null 2>&1 || true go clean -cache >/dev/null 2>&1 || true echo -e " ${GREEN}✓${NC} Go cache cleaned" note_activity fi safe_clean ~/Library/Caches/go-build/* "Go build cache" safe_clean ~/go/pkg/mod/cache/* "Go module cache" # Rust safe_clean ~/.cargo/registry/cache/* "Rust cargo cache" # Docker if command -v docker >/dev/null 2>&1; then docker system prune -af --volumes >/dev/null 2>&1 || true echo -e " ${GREEN}✓${NC} Docker resources cleaned" note_activity fi # Container tools safe_clean ~/.kube/cache/* "Kubernetes cache" if command -v podman >/dev/null 2>&1; then podman system prune -af --volumes >/dev/null 2>&1 || true echo -e " ${GREEN}✓${NC} Podman resources cleaned" note_activity fi safe_clean ~/.local/share/containers/storage/tmp/* "Container storage temp" # Cloud CLI tools safe_clean ~/.aws/cli/cache/* "AWS CLI cache" safe_clean ~/.config/gcloud/logs/* "Google Cloud logs" safe_clean ~/.azure/logs/* "Azure CLI logs" # Homebrew cleanup safe_clean ~/Library/Caches/Homebrew/* "Homebrew cache" safe_clean /opt/homebrew/var/homebrew/locks/* "Homebrew lock files (M series)" safe_clean /usr/local/var/homebrew/locks/* "Homebrew lock files (Intel)" if command -v brew >/dev/null 2>&1; then brew cleanup >/dev/null 2>&1 || true echo -e " ${GREEN}✓${NC} Homebrew cache cleaned" note_activity fi # Git safe_clean ~/.gitconfig.lock "Git config lock" end_section # ===== Extended developer caches ===== start_section "Extended developer caches" # Additional Node.js and frontend tools safe_clean ~/.pnpm-store/* "pnpm store cache" safe_clean ~/.cache/typescript/* "TypeScript cache" safe_clean ~/.cache/electron/* "Electron cache" safe_clean ~/.turbo/* "Turbo cache" safe_clean ~/.next/* "Next.js cache" safe_clean ~/.vite/* "Vite cache" safe_clean ~/.cache/vite/* "Vite global cache" safe_clean ~/.cache/webpack/* "Webpack cache" safe_clean ~/.parcel-cache/* "Parcel cache" # Design and development tools safe_clean ~/Library/Caches/Google/AndroidStudio*/* "Android Studio cache" safe_clean ~/Library/Caches/com.unity3d.*/* "Unity cache" safe_clean ~/Library/Caches/com.postmanlabs.mac/* "Postman cache" safe_clean ~/Library/Caches/com.konghq.insomnia/* "Insomnia cache" safe_clean ~/Library/Caches/com.tinyapp.TablePlus/* "TablePlus cache" safe_clean ~/Library/Caches/com.mongodb.compass/* "MongoDB Compass cache" safe_clean ~/Library/Caches/com.figma.Desktop/* "Figma cache" safe_clean ~/Library/Caches/com.github.GitHubDesktop/* "GitHub Desktop cache" safe_clean ~/Library/Caches/com.microsoft.VSCode/* "VS Code cache" safe_clean ~/Library/Caches/com.sublimetext.*/* "Sublime Text cache" # Python tooling safe_clean ~/.cache/poetry/* "Poetry cache" safe_clean ~/.cache/uv/* "uv cache" safe_clean ~/.cache/ruff/* "Ruff cache" safe_clean ~/.cache/mypy/* "MyPy cache" safe_clean ~/.pytest_cache/* "Pytest cache" # AI/ML and Data Science tools safe_clean ~/.jupyter/runtime/* "Jupyter runtime cache" safe_clean ~/.cache/huggingface/* "Hugging Face cache" safe_clean ~/.cache/torch/* "PyTorch cache" safe_clean ~/.cache/tensorflow/* "TensorFlow cache" safe_clean ~/.conda/pkgs/* "Conda packages cache" safe_clean ~/anaconda3/pkgs/* "Anaconda packages cache" safe_clean ~/.cache/wandb/* "Weights & Biases cache" # Rust tooling safe_clean ~/.cargo/git/* "Cargo git cache" # Java tooling safe_clean ~/.gradle/caches/* "Gradle caches" safe_clean ~/.m2/repository/* "Maven repository cache" safe_clean ~/.sbt/* "SBT cache" # Cloud and container tools safe_clean ~/.docker/buildx/cache/* "Docker BuildX cache" safe_clean ~/.cache/terraform/* "Terraform cache" # API and network development tools safe_clean ~/Library/Caches/com.getpaw.Paw/* "Paw API cache" safe_clean ~/Library/Caches/com.charlesproxy.charles/* "Charles Proxy cache" safe_clean ~/Library/Caches/com.proxyman.NSProxy/* "Proxyman cache" # CI/CD tools safe_clean ~/.grafana/cache/* "Grafana cache" safe_clean ~/.prometheus/data/wal/* "Prometheus WAL cache" safe_clean ~/.jenkins/workspace/*/target/* "Jenkins workspace cache" safe_clean ~/.cache/gitlab-runner/* "GitLab Runner cache" safe_clean ~/.github/cache/* "GitHub Actions cache" safe_clean ~/.circleci/cache/* "CircleCI cache" # Additional development tools safe_clean ~/.oh-my-zsh/cache/* "Oh My Zsh cache" safe_clean ~/.config/fish/fish_history.bak* "Fish shell backup" safe_clean ~/.bash_history.bak* "Bash history backup" safe_clean ~/.zsh_history.bak* "Zsh history backup" safe_clean ~/.sonar/* "SonarQube cache" safe_clean ~/.cache/eslint/* "ESLint cache" safe_clean ~/.cache/prettier/* "Prettier cache" # Mobile development safe_clean ~/Library/Caches/CocoaPods/* "CocoaPods cache" safe_clean ~/.bundle/cache/* "Ruby Bundler cache" safe_clean ~/.composer/cache/* "PHP Composer cache" safe_clean ~/.nuget/packages/* "NuGet packages cache" safe_clean ~/.ivy2/cache/* "Ivy cache" safe_clean ~/.pub-cache/* "Dart Pub cache" # Network tools cache safe_clean ~/.cache/curl/* "curl cache" safe_clean ~/.cache/wget/* "wget cache" safe_clean ~/Library/Caches/curl/* "curl cache (macOS)" safe_clean ~/Library/Caches/wget/* "wget cache (macOS)" # Git and version control safe_clean ~/.cache/pre-commit/* "pre-commit cache" safe_clean ~/.gitconfig.bak* "Git config backup" # Mobile development additional safe_clean ~/.cache/flutter/* "Flutter cache" safe_clean ~/.gradle/daemon/* "Gradle daemon logs" safe_clean ~/Library/Developer/Xcode/iOS\ DeviceSupport/*/Symbols/System/Library/Caches/* "iOS device cache" safe_clean ~/.android/cache/* "Android SDK cache" # Other language tool caches safe_clean ~/.cache/swift-package-manager/* "Swift package manager cache" safe_clean ~/.cache/bazel/* "Bazel cache" safe_clean ~/.cache/zig/* "Zig cache" safe_clean ~/.cache/deno/* "Deno cache" # Database tools safe_clean ~/Library/Caches/com.sequel-ace.sequel-ace/* "Sequel Ace cache" safe_clean ~/Library/Caches/com.eggerapps.Sequel-Pro/* "Sequel Pro cache" safe_clean ~/Library/Caches/redis-desktop-manager/* "Redis Desktop Manager cache" # Crash reports and debugging safe_clean ~/Library/Caches/SentryCrash/* "Sentry crash reports" safe_clean ~/Library/Caches/KSCrash/* "KSCrash reports" safe_clean ~/Library/Caches/com.crashlytics.data/* "Crashlytics data" safe_clean ~/Library/HTTPStorages/* "HTTP storage cache" end_section # ===== 4. Applications ===== start_section "Applications" # Xcode & iOS development safe_clean ~/Library/Developer/Xcode/DerivedData/* "Xcode derived data" safe_clean ~/Library/Developer/Xcode/Archives/* "Xcode archives" safe_clean ~/Library/Developer/CoreSimulator/Caches/* "Simulator cache" safe_clean ~/Library/Developer/CoreSimulator/Devices/*/data/tmp/* "Simulator temp files" safe_clean ~/Library/Caches/com.apple.dt.Xcode/* "Xcode cache" # VS Code family safe_clean ~/Library/Application\ Support/Code/logs/* "VS Code logs" safe_clean ~/Library/Application\ Support/Code/CachedExtensions/* "VS Code extension cache" safe_clean ~/Library/Application\ Support/Code/CachedData/* "VS Code data cache" # JetBrains IDEs safe_clean ~/Library/Logs/IntelliJIdea*/* "IntelliJ IDEA logs" safe_clean ~/Library/Logs/PhpStorm*/* "PhpStorm logs" safe_clean ~/Library/Logs/PyCharm*/* "PyCharm logs" safe_clean ~/Library/Logs/WebStorm*/* "WebStorm logs" safe_clean ~/Library/Logs/GoLand*/* "GoLand logs" safe_clean ~/Library/Logs/CLion*/* "CLion logs" safe_clean ~/Library/Logs/DataGrip*/* "DataGrip logs" safe_clean ~/Library/Caches/JetBrains/* "JetBrains cache" # Communication and social apps safe_clean ~/Library/Application\ Support/discord/Cache/* "Discord cache" safe_clean ~/Library/Application\ Support/Slack/Cache/* "Slack cache" safe_clean ~/Library/Caches/us.zoom.xos/* "Zoom cache" safe_clean ~/Library/Caches/com.tencent.xinWeChat/* "WeChat cache" safe_clean ~/Library/Caches/ru.keepcoder.Telegram/* "Telegram cache" safe_clean ~/Library/Caches/com.openai.chat/* "ChatGPT cache" safe_clean ~/Library/Caches/com.anthropic.claudefordesktop/* "Claude desktop cache" safe_clean ~/Library/Logs/Claude/* "Claude logs" safe_clean ~/Library/Caches/com.microsoft.teams2/* "Microsoft Teams cache" safe_clean ~/Library/Caches/net.whatsapp.WhatsApp/* "WhatsApp cache" safe_clean ~/Library/Caches/com.skype.skype/* "Skype cache" # Design and creative software safe_clean ~/Library/Caches/com.bohemiancoding.sketch3/* "Sketch cache" safe_clean ~/Library/Application\ Support/com.bohemiancoding.sketch3/cache/* "Sketch app cache" safe_clean ~/Library/Caches/net.telestream.screenflow10/* "ScreenFlow cache" # Productivity and dev utilities safe_clean ~/Library/Caches/com.raycast.macos/* "Raycast cache" safe_clean ~/Library/Caches/com.tw93.MiaoYan/* "MiaoYan cache" safe_clean ~/Library/Caches/com.filo.client/* "Filo cache" safe_clean ~/Library/Caches/com.flomoapp.mac/* "Flomo cache" # Music and entertainment safe_clean ~/Library/Caches/com.spotify.client/* "Spotify cache" # Gaming and entertainment safe_clean ~/Library/Caches/com.valvesoftware.steam/* "Steam cache" safe_clean ~/Library/Caches/com.epicgames.EpicGamesLauncher/* "Epic Games cache" # Utilities and productivity (only cache, avoid license/settings data) safe_clean ~/Library/Caches/com.runjuu.Input-Source-Pro/* "Input Source Pro cache" safe_clean ~/Library/Caches/macos-wakatime.WakaTime/* "WakaTime cache" safe_clean ~/Library/Caches/notion.id/* "Notion cache" safe_clean ~/Library/Caches/md.obsidian/* "Obsidian cache" safe_clean ~/Library/Caches/com.runningwithcrayons.Alfred/* "Alfred cache" safe_clean ~/Library/Caches/cx.c3.theunarchiver/* "The Unarchiver cache" # Note: Skipping App Cleaner, 1Password and similar apps to preserve licenses end_section # ===== Orphaned leftovers ===== start_section "Orphaned app files" # Build a list of installed application bundle identifiers echo -n " ${BLUE}🔍${NC} Scanning installed applications..." local installed_bundles=$(mktemp) # More robust approach that won't hang for app in /Applications/*.app; do if [[ -d "$app" && -f "$app/Contents/Info.plist" ]]; then bundle_id=$(defaults read "$app/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "") [[ -n "$bundle_id" ]] && echo "$bundle_id" >> "$installed_bundles" fi done local app_count=$(wc -l < "$installed_bundles" | tr -d ' ') echo " ${GREEN}✓${NC} Found $app_count apps" local found_orphaned=false local cache_count=0 local data_count=0 local pref_count=0 # Check for orphaned caches (with protection for critical system settings) echo -n " ${BLUE}🔍${NC} Scanning cache directories..." if ls ~/Library/Caches/com.* >/dev/null 2>&1; then for cache_dir in ~/Library/Caches/com.*; do [[ -d "$cache_dir" ]] || continue local bundle_id=$(basename "$cache_dir") # CRITICAL: Skip system-essential caches if should_preserve_bundle "$bundle_id"; then continue fi if ! grep -q "$bundle_id" "$installed_bundles" 2>/dev/null; then safe_clean "$cache_dir" "Orphaned cache: $bundle_id" found_orphaned=true ((cache_count++)) fi done fi echo " ${GREEN}✓${NC} Complete ($cache_count removed)" # Check for orphaned application support data (with protection for critical system settings) echo -n " ${BLUE}🔍${NC} Scanning application data..." if ls ~/Library/Application\ Support/com.* >/dev/null 2>&1; then for support_dir in ~/Library/Application\ Support/com.*; do [[ -d "$support_dir" ]] || continue local bundle_id=$(basename "$support_dir") # CRITICAL: Skip system-essential data if should_preserve_bundle "$bundle_id"; then continue fi # Extra safety for Application Support data (preserve licenses and critical settings) case "$bundle_id" in # System components *dock*|*Dock*|*controlcenter*|*ControlCenter*|*systempreferences*|*SystemPreferences*) continue ;; # Paid software and license-critical apps *nektony*|*macpaw*|*jetbrains*|*sublimetext*|*adobe*|*1password*|*agilebits*|*omnigroup*|*culturedcode*) continue ;; # Security and password managers *lastpass*|*dashlane*|*bitwarden*|*keepass*) continue ;; *trackpad*|*Trackpad*|*mouse*|*Mouse*|*keyboard*|*Keyboard*) continue ;; esac if ! grep -q "$bundle_id" "$installed_bundles" 2>/dev/null; then safe_clean "$support_dir" "Orphaned data: $bundle_id" found_orphaned=true ((data_count++)) fi done fi echo " ${GREEN}✓${NC} Complete ($data_count removed)" # Check for orphaned preferences (with protection for critical system settings) echo -n " ${BLUE}🔍${NC} Scanning preference files..." if ls ~/Library/Preferences/com.*.plist >/dev/null 2>&1; then for pref_file in ~/Library/Preferences/com.*.plist; do [[ -f "$pref_file" ]] || continue local bundle_id=$(basename "$pref_file" .plist) # CRITICAL: Skip system-essential preferences if should_preserve_bundle "$bundle_id"; then continue fi # Extra safety: Never delete preference files that might affect system behavior or paid app licenses case "$bundle_id" in # System components *dock*|*Dock*|*trackpad*|*Trackpad*|*mouse*|*Mouse*|*keyboard*|*Keyboard*) continue ;; *systempreferences*|*SystemPreferences*|*controlcenter*|*ControlCenter*) continue ;; *menubar*|*MenuBar*|*hotkeys*|*HotKeys*|*shortcuts*|*Shortcuts*) continue ;; # Licensed software and critical apps (preserve activation data) *nektony*|*macpaw*|*jetbrains*|*sublimetext*|*adobe*|*1password*|*agilebits*) continue ;; *omnigroup*|*culturedcode*|*lastpass*|*dashlane*|*bitwarden*|*keepass*) continue ;; *bohemiancoding*|*figma*|*framer*|*panic*|*sequelpro*|*tinyapp*|*pixelmator*) continue ;; esac if ! grep -q "$bundle_id" "$installed_bundles" 2>/dev/null; then safe_clean "$pref_file" "Orphaned preference: $bundle_id" found_orphaned=true ((pref_count++)) fi done fi echo " ${GREEN}✓${NC} Complete ($pref_count removed)" # Clean up temp file rm -f "$installed_bundles" # Clean test data safe_clean ~/Library/Application\ Support/TestApp* "Test app data" safe_clean ~/Library/Application\ Support/MyApp/* "Test app data" safe_clean ~/Library/Application\ Support/GitHub*/* "GitHub test data" safe_clean ~/Library/Application\ Support/Twitter*/* "Twitter test data" safe_clean ~/Library/Application\ Support/TestNoValue/* "Test data" safe_clean ~/Library/Application\ Support/Wk*/* "Test data" end_section # ===== Apple Silicon optimizations ===== if [[ "$IS_M_SERIES" == "true" ]]; then start_section "Apple Silicon optimizations" safe_clean /Library/Apple/usr/share/rosetta/rosetta_update_bundle "Rosetta 2 cache" safe_clean ~/Library/Caches/com.apple.rosetta.update "Rosetta 2 user cache" safe_clean ~/Library/Caches/com.apple.amp.mediasevicesd "Apple Silicon media service cache" safe_clean ~/Library/Caches/com.apple.bird.lsuseractivity "User activity cache" end_section fi # System cleanup was moved to the beginning (right after password verification) # ===== iOS device backups ===== start_section "iOS device backups" backup_dir="$HOME/Library/Application Support/MobileSync/Backup" if [[ -d "$backup_dir" ]] && find "$backup_dir" -mindepth 1 -maxdepth 1 | read -r _; then backup_kb=$(du -sk "$backup_dir" 2>/dev/null | awk '{print $1}') if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then # >100MB backup_human=$(du -sh "$backup_dir" 2>/dev/null | awk '{print $1}') note_activity echo -e " ${BLUE}💾${NC} Found ${GREEN}${backup_human}${NC} iOS backups" echo -e " ${YELLOW}💡${NC} You can delete them manually: ${backup_dir}" fi fi end_section # ===== Final summary ===== space_after=$(df / | tail -1 | awk '{print $4}') space_freed_kb=$((space_after - space_before)) echo "" echo "====================================================================" echo "🎉 CLEANUP COMPLETE!" if [[ $total_size_cleaned -gt 0 ]]; then local freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}') echo "💾 Space freed: ${GREEN}${freed_gb}GB${NC} | Free space now: $(get_free_space)" # Add some context to make it more impressive if [[ $(echo "$freed_gb" | awk '{print ($1 >= 1) ? 1 : 0}') -eq 1 ]]; then local movies=$(echo "$freed_gb" | awk '{printf "%.0f", $1/4.5}') if [[ $movies -gt 0 ]]; then echo "🎬 That's like ~$movies 4K movies worth of space!" fi fi else echo "💾 No significant space was freed (system was already clean) | Free space: $(get_free_space)" fi if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then echo "📊 Files cleaned: $files_cleaned | Categories processed: $total_items" elif [[ $files_cleaned -gt 0 ]]; then echo "📊 Files cleaned: $files_cleaned" elif [[ $total_items -gt 0 ]]; then echo "🗂️ Categories processed: $total_items" fi if [[ "$SYSTEM_CLEAN" != "true" ]]; then echo "" echo -e "${BLUE}💡 For deeper cleanup, run with admin password next time${NC}" fi echo "====================================================================" } # Cleanup function - restore cursor on exit cleanup() { # Restore cursor show_cursor # Kill any background processes if [[ -n "${SUDO_KEEPALIVE_PID:-}" ]]; then kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true fi if [[ -n "${SPINNER_PID:-}" ]]; then kill "$SPINNER_PID" 2>/dev/null || true fi exit "${1:-0}" } # Set trap for cleanup on exit trap cleanup EXIT INT TERM main() { case "${1:-""}" in "--help"|"-h") echo "Mole - Deeper system cleanup" echo "Usage: clean.sh [options]" echo "" echo "Options:" echo " --help, -h Show this help" echo "" echo "Interactive cleanup with smart password handling" echo "" exit 0 ;; *) hide_cursor start_cleanup perform_cleanup show_cursor ;; esac } main "$@"