diff --git a/lib/core/app_protection.sh b/lib/core/app_protection.sh index bcd1d97..1b754b8 100755 --- a/lib/core/app_protection.sh +++ b/lib/core/app_protection.sh @@ -493,6 +493,9 @@ should_protect_data() { # Check if a path is protected from deletion # Centralized logic to protect system settings, control center, and critical apps # +# In uninstall mode (MOLE_UNINSTALL_MODE=1), only system-critical components are protected. +# Data-protected apps (VPNs, dev tools, etc.) can be uninstalled when user explicitly chooses to. +# # Args: $1 - path to check # Returns: 0 if protected, 1 if safe to delete should_protect_path() { @@ -577,17 +580,31 @@ should_protect_path() { # 6. Match full path against protected patterns # This catches things like /Users/tw93/Library/Caches/Claude when pattern is *Claude* - for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do - if bundle_matches_pattern "$path" "$pattern"; then - return 0 - fi - done + # In uninstall mode, only check system-critical bundles (user explicitly chose to uninstall) + if [[ "${MOLE_UNINSTALL_MODE:-0}" == "1" ]]; then + # Uninstall mode: only protect system-critical components + for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do + if bundle_matches_pattern "$path" "$pattern"; then + return 0 + fi + done + else + # Normal mode (cleanup): protect both system-critical and data-protected bundles + for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do + if bundle_matches_pattern "$path" "$pattern"; then + return 0 + fi + done + fi # 7. Check if the filename itself matches any protected patterns - local filename - filename=$(basename "$path") - if should_protect_data "$filename"; then - return 0 + # Skip in uninstall mode - user explicitly chose to remove this app + if [[ "${MOLE_UNINSTALL_MODE:-0}" != "1" ]]; then + local filename + filename=$(basename "$path") + if should_protect_data "$filename"; then + return 0 + fi fi return 1 diff --git a/lib/core/file_ops.sh b/lib/core/file_ops.sh index 9044dd6..24075ec 100644 --- a/lib/core/file_ops.sh +++ b/lib/core/file_ops.sh @@ -102,7 +102,8 @@ validate_path_for_deletion() { /private/var/db/diagnostics | /private/var/db/diagnostics/* | \ /private/var/db/DiagnosticPipeline | /private/var/db/DiagnosticPipeline/* | \ /private/var/db/powerlog | /private/var/db/powerlog/* | \ - /private/var/db/reportmemoryexception | /private/var/db/reportmemoryexception/*) + /private/var/db/reportmemoryexception | /private/var/db/reportmemoryexception/* | \ + /private/var/db/receipts/*.bom | /private/var/db/receipts/*.plist) return 0 ;; esac diff --git a/lib/uninstall/batch.sh b/lib/uninstall/batch.sh index b808367..cf8ba56 100755 --- a/lib/uninstall/batch.sh +++ b/lib/uninstall/batch.sh @@ -169,8 +169,8 @@ remove_file_list() { batch_uninstall_applications() { local total_size_freed=0 - # Trap to clean up spinner on interrupt - trap 'stop_inline_spinner 2>/dev/null; echo ""; return 130' INT TERM + # Trap to clean up spinner and uninstall mode on interrupt + trap 'stop_inline_spinner 2>/dev/null; unset MOLE_UNINSTALL_MODE; echo ""; return 130' INT TERM # shellcheck disable=SC2154 if [[ ${#selected_apps[@]} -eq 0 ]]; then @@ -360,6 +360,10 @@ batch_uninstall_applications() { ;; esac + # Enable uninstall mode - allows deletion of data-protected apps (VPNs, dev tools, etc.) + # that user explicitly chose to uninstall. System-critical components remain protected. + export MOLE_UNINSTALL_MODE=1 + # Request sudo if needed. if [[ ${#sudo_apps[@]} -gt 0 ]]; then if ! sudo -n true 2> /dev/null; then @@ -635,6 +639,9 @@ batch_uninstall_applications() { sudo_keepalive_pid="" fi + # Disable uninstall mode + unset MOLE_UNINSTALL_MODE + # Invalidate cache if any apps were successfully uninstalled. if [[ $success_count -gt 0 ]]; then local cache_file="$HOME/.cache/mole/app_scan_cache"