Skip to content

Update Fleet-maintained apps - #50781

Closed
fleet-release wants to merge 1 commit into
mainfrom
fma-2608071615
Closed

Update Fleet-maintained apps#50781
fleet-release wants to merge 1 commit into
mainfrom
fma-2608071615

Conversation

@fleet-release

@fleet-release fleet-release commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Automated ingestion of latest Fleet-maintained app data.

Summary by CodeRabbit

  • New Features

    • Added the latest available releases for dozens of macOS and Windows applications, including browsers, productivity tools, developer utilities, communication apps, and media software.
    • Updated download details and verification information so installations use the correct current packages.
  • Bug Fixes

    • Improved removal of residual files and background services for Aviatrix VPN Client, Citrix Workspace, Krita, MongoDB Compass, and Proton Drive.
    • Corrected package metadata and version detection for several applications.

Generated automatically with cmd/maintained-apps.
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/alt-tab/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/amadine/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/applite/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/arc/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json

=== Install Script (no changes) ===
=== Uninstall // 7ef6154e -> abc2830e ===

--- /tmp/old.AUzVBK	2026-08-07 16:21:48.832132654 +0000
+++ /tmp/new.kZJqtl	2026-08-07 16:21:48.832132654 +0000
@@ -188,10 +188,11 @@
   fi
 }
 
-remove_launchctl_service 'aviatrix.vpn.client.rp.plist'
+remove_launchctl_service 'aviatrix.vpn.client.rp'
 remove_pkg_files 'com.Aviatrix.VPNClient'
 forget_pkg 'com.Aviatrix.VPNClient'
 sudo rm -rf '/Applications/Aviatrix VPN Client.app'
+trash $LOGGED_IN_USER '/Library/Application Support/Aviatrix VPN Client'
 trash $LOGGED_IN_USER '~/Library/Aviatrix'
 trash $LOGGED_IN_USER '~/Library/Logs/AviatrixVPNC'
 trash $LOGGED_IN_USER '~/Library/Preferences/org.pythonmac.unspecified.AviatrixVPNClient.plist'

ee/maintained-apps/outputs/badgeify/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/beekeeper-studio/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/brave-browser/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/burp-suite/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/calibre/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/calibre/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/capcut/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/chatbox/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/chatgpt/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/cherry-studio/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/citrix-workspace/darwin.json

=== Install // f06e00fd -> a78b38b6 ===

--- /tmp/old.OPrq5q	2026-08-07 16:21:49.289128110 +0000
+++ /tmp/new.i5Glsj	2026-08-07 16:21:49.289128110 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'com.citrix.receiver.nomas'
-sudo installer -pkg "$TMPDIR/CitrixWorkspaceAppUniversal26.03.11.41.pkg" -target / || exit $?
+sudo installer -pkg "$TMPDIR/CitrixWorkspaceAppUniversal26.07.0.71.pkg" -target / || exit $?
 relaunch_application 'com.citrix.receiver.nomas'

=== Uninstall // edbf2a34 -> c847120e ===

--- /tmp/old.fRix24	2026-08-07 16:21:49.309127911 +0000
+++ /tmp/new.iKjZTM	2026-08-07 16:21:49.309127911 +0000
@@ -233,6 +233,7 @@
 remove_launchctl_service 'com.citrix.CtxWorkspaceHelperDaemon'
 remove_launchctl_service 'com.citrix.ctxworkspaceupdater'
 remove_launchctl_service 'com.citrix.devicetrust.launchagent'
+remove_launchctl_service 'com.citrix.PluginBroker'
 remove_launchctl_service 'com.citrix.ReceiverHelper'
 remove_launchctl_service 'com.citrix.ReceiverUninstallHelper'
 remove_launchctl_service 'com.citrix.ReceiverUpdaterHelper'
@@ -259,6 +260,9 @@
 forget_pkg 'com.citrix.ICAClienthdx'
 remove_pkg_files 'com.citrix.receiver.bcr'
 forget_pkg 'com.citrix.receiver.bcr'
+sudo rm -rf '/Applications/Citrix Workspace.app'
+sudo rm -rf '/Library/Citrix Workspace'
+trash $LOGGED_IN_USER '/Library/Logs/Citrix Workspace'
 trash $LOGGED_IN_USER '~/Library/Application Support/Citrix Receiver'
 trash $LOGGED_IN_USER '~/Library/Application Support/Citrix Workspace'
 trash $LOGGED_IN_USER '~/Library/Application Support/Citrix'

ee/maintained-apps/outputs/claude/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/clion/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/cmake-app/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/codexbar/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/coteditor/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/cursor/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/dataflare/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/dataflare/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/datagrip/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/deezer/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/downie/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/drawio/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/drofus/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/elevate-uc/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/evernote/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/fellow/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/firefox@nightly/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/framer/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/gitkraken/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/gog-galaxy/darwin.json

=== Install // 9f170824 -> 89b9a1b7 ===

--- /tmp/old.JyCdpY	2026-08-07 16:21:50.054120503 +0000
+++ /tmp/new.d0aclp	2026-08-07 16:21:50.054120503 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'com.gog.galaxy.cef.renderer'
-sudo installer -pkg "$TMPDIR/galaxy_client_2.1.7.22.pkg" -target / || exit $?
+sudo installer -pkg "$TMPDIR/galaxy_client_2.1.8.32.pkg" -target / || exit $?
 relaunch_application 'com.gog.galaxy.cef.renderer'

=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/google-chrome/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/granola/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/granola/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/ibm-semeru-jre-11/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/imageglass/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/kiro-cli/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/kiro/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/kiro/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/krita/darwin.json

=== Install Script (no changes) ===
=== Uninstall // 7b9f27dc -> 3947b0f0 ===

--- /tmp/old.YN1e5Z	2026-08-07 16:21:50.394117123 +0000
+++ /tmp/new.pcW7sM	2026-08-07 16:21:50.394117123 +0000
@@ -52,8 +52,11 @@
   fi
 }
 
-sudo rm -rf "$APPDIR/krita.app"
-trash $LOGGED_IN_USER '~/Library/Application Support/krita'
+sudo rm -rf "$APPDIR/Krita.app"
+trash $LOGGED_IN_USER '~/Library/Application Scripts/org.krita.*'
+trash $LOGGED_IN_USER '~/Library/Application Support/krita*'
+trash $LOGGED_IN_USER '~/Library/Caches/krita'
+trash $LOGGED_IN_USER '~/Library/Containers/org.krita.*'
 trash $LOGGED_IN_USER '~/Library/Preferences/kritadisplayrc'
 trash $LOGGED_IN_USER '~/Library/Preferences/kritarc'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/org.krita.savedState'

ee/maintained-apps/outputs/loom/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/loom/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/marked-app/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/masscode/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/melodics/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/microsoft-edge/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/mongodb-compass/darwin.json

=== Install Script (no changes) ===
=== Uninstall // 9db6b9b2 -> 022cab65 ===

--- /tmp/old.YS9Z1y	2026-08-07 16:21:50.667114408 +0000
+++ /tmp/new.1iKctN	2026-08-07 16:21:50.668114398 +0000
@@ -5,6 +5,46 @@
 LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
 # functions
 
+quit_application() {
+  local bundle_id="$1"
+  local timeout_duration=10
+
+  # check if the application is running
+  local app_running
+  app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+  if [[ "$app_running" != "true" ]]; then
+    return
+  fi
+
+  local console_user
+  console_user=$(stat -f "%Su" /dev/console)
+  if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
+    echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
+    return
+  fi
+
+  echo "Quitting application '$bundle_id'..."
+
+  # try to quit the application within the timeout period
+  local quit_success=false
+  SECONDS=0
+  while (( SECONDS < timeout_duration )); do
+    if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then
+      if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then
+        echo "Application '$bundle_id' quit successfully."
+        quit_success=true
+        break
+      fi
+    fi
+    sleep 1
+  done
+
+  if [[ "$quit_success" = false ]]; then
+    echo "Application '$bundle_id' did not quit."
+  fi
+}
+
+
 trash() {
   local logged_in_user="$1"
   local target_file="$2"
@@ -52,6 +92,7 @@
   fi
 }
 
+quit_application 'com.mongodb.compass'
 sudo rm -rf "$APPDIR/MongoDB Compass.app"
 trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.mongodb.compass.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/MongoDB Compass'

ee/maintained-apps/outputs/mongodb-compass/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/nordpass/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/nordpass/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/notesnook/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/nova/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/onedrive/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/opencode-desktop/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/opera/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/orbstack/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/p4v/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/p4v/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/pale-moon/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/parallels/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/postman/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/postman/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/powerphotos/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/prisma-browser/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/prisma-browser/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/proton-drive/darwin.json

=== Install Script (no changes) ===
=== Uninstall // fc6f5099 -> ac89ef74 ===

--- /tmp/old.W2cFck	2026-08-07 16:21:51.478106344 +0000
+++ /tmp/new.uV1UgO	2026-08-07 16:21:51.479106334 +0000
@@ -5,6 +5,76 @@
 LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
 # functions
 
+remove_launchctl_service() {
+  local service="$1"
+  local booleans=("true" "false")
+  local plist_status
+  local paths
+  local should_sudo
+
+  echo "Removing launchctl service ${service}"
+
+  # A wildcard label can't be used with launchctl or as a plist name, so expand
+  # it to the labels of currently loaded services that match the pattern.
+  local services=("$service")
+  if [[ "$service" == *"*"* ]]; then
+    local regex
+    # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so
+    # it matches a full label rather than a substring.
+    regex=$(printf '%s' "$service" | sed -e 's/[][(){}.^$+?|\\]/\\&/g' -e 's/\*/.*/g')
+    regex="^${regex}$"
+    services=()
+    local id
+    # Match every loaded job by label regardless of PID; launchctl list reports
+    # loaded-but-not-running jobs with a "-" in the PID column.
+    while read -r _ _ id; do
+      [[ "$id" =~ $regex ]] && services+=("$id")
+    done < <(launchctl list 2>/dev/null | tail -n +2)
+    if [[ ${#services[@]} -eq 0 ]]; then
+      echo "No loaded launchctl service matches ${service}"
+      return
+    fi
+  fi
+
+  local service_label
+  for service_label in "${services[@]}"; do
+    for should_sudo in "${booleans[@]}"; do
+      plist_status=$(launchctl list "${service_label}" 2>/dev/null)
+
+      if [[ $plist_status == \{* ]]; then
+        if [[ $should_sudo == "true" ]]; then
+          sudo launchctl remove "${service_label}"
+        else
+          launchctl remove "${service_label}"
+        fi
+        sleep 1
+      fi
+
+      paths=(
+        "/Library/LaunchAgents/${service_label}.plist"
+        "/Library/LaunchDaemons/${service_label}.plist"
+      )
+
+      # if not using sudo, prepend the home directory to the paths
+      if [[ $should_sudo == "false" ]]; then
+        for i in "${!paths[@]}"; do
+          paths[i]="${HOME}${paths[i]}"
+        done
+      fi
+
+      for path in "${paths[@]}"; do
+        if [[ -e "$path" ]]; then
+          if [[ $should_sudo == "true" ]]; then
+            sudo rm -f -- "$path"
+          else
+            rm -f -- "$path"
+          fi
+        fi
+      done
+    done
+  done
+}
+
 trash() {
   local logged_in_user="$1"
   local target_file="$2"
@@ -52,6 +122,7 @@
   fi
 }
 
+remove_launchctl_service 'ch.protonmail.drive.agent'
 sudo rm -rf "$APPDIR/Proton Drive.app"
 trash $LOGGED_IN_USER '~/Library/Application Scripts/ch.protonmail.drive*'
 trash $LOGGED_IN_USER '~/Library/Application Support/FileProvider/ch.protonmail.drive.fileprovider'

ee/maintained-apps/outputs/rider/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/rive/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/rustrover/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/rustrover/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/shapr3d/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/spokenly/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/spotify/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/spyder/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/superwhisper/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/sync/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/telegram/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/viscosity/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/viscosity/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/vivaldi/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/webex/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/whatsapp/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/windows-app/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/wispr-flow/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/workflowy/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/xnconvert/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/zen/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Updated maintained-app JSON definitions for many Darwin and Windows applications. Changes include release versions, patch detection thresholds, installer URLs, artifact identifiers, and SHA-256 checksums. Selected entries also update install or uninstall script references and cleanup commands for launch services, applications, logs, caches, and support files.

Possibly related PRs

  • fleetdm/fleet#50779 — Updates the same maintained-app manifests with corresponding release metadata.
  • fleetdm/fleet#50778 — Updates overlapping maintained-app versions, URLs, queries, and checksums.
  • fleetdm/fleet#50656 — Directly overlaps with the Krita Darwin uninstall-script changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description identifies the automated app-data update but omits the required issue reference, checklist, and testing information. Complete the template sections, or remove non-applicable items, and document testing and QA results.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: updating Fleet-maintained application data.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fma-2608071615

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json`:
- Line 20: Update remove_launchctl_service in
ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json (lines 20-20),
ee/maintained-apps/outputs/citrix-workspace/darwin.json (lines 20-20), and
ee/maintained-apps/outputs/proton-drive/darwin.json (lines 19-19) to resolve the
console user’s UID and remove user-scoped services through that user’s launchctl
bootstrap context, such as launchctl asuser or the equivalent gui/user bootout
flow; preserve the existing system/root removal behavior and apply it to
aviatrix.vpn.client.rp, com.citrix.* and ch.protonmail.drive.agent.

In `@ee/maintained-apps/outputs/brave-browser/windows.json`:
- Around line 4-9: Update the Brave entry’s “version” value and the version
threshold in the “patched” query to 1.93.132 so they match the installer release
at “installer_url”; preserve the existing query structure and other metadata.

In `@ee/maintained-apps/outputs/citrix-workspace/darwin.json`:
- Line 20: The quit_application helper currently logs timeout failures but
allows destructive uninstall steps to continue. In
ee/maintained-apps/outputs/citrix-workspace/darwin.json:20-20, make
quit_application return failure when the application remains running and abort
the script after the last required Citrix quit_application call if any required
process cannot quit; in
ee/maintained-apps/outputs/mongodb-compass/darwin.json:19-19, apply the same
behavior for com.mongodb.compass before removing the application or user data.
Add a force-quit path only if required.

In `@ee/maintained-apps/outputs/krita/darwin.json`:
- Line 19: Update the uninstall script’s application removal command to use the
same case-sensitive bundle path as the installer, `/Applications/krita.app`,
instead of `/Applications/Krita.app`. Keep the existing removal flow unchanged
so it targets only the installed Krita bundle.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0be683bb-5a7f-4d44-97ec-dc24a49920ba

📥 Commits

Reviewing files that changed from the base of the PR and between fc35085 and e56681b.

📒 Files selected for processing (92)
  • ee/maintained-apps/outputs/alt-tab/darwin.json
  • ee/maintained-apps/outputs/amadine/darwin.json
  • ee/maintained-apps/outputs/applite/darwin.json
  • ee/maintained-apps/outputs/arc/darwin.json
  • ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json
  • ee/maintained-apps/outputs/badgeify/darwin.json
  • ee/maintained-apps/outputs/beekeeper-studio/windows.json
  • ee/maintained-apps/outputs/brave-browser/windows.json
  • ee/maintained-apps/outputs/burp-suite/darwin.json
  • ee/maintained-apps/outputs/calibre/darwin.json
  • ee/maintained-apps/outputs/calibre/windows.json
  • ee/maintained-apps/outputs/capcut/darwin.json
  • ee/maintained-apps/outputs/chatbox/windows.json
  • ee/maintained-apps/outputs/chatgpt/darwin.json
  • ee/maintained-apps/outputs/cherry-studio/darwin.json
  • ee/maintained-apps/outputs/citrix-workspace/darwin.json
  • ee/maintained-apps/outputs/claude/darwin.json
  • ee/maintained-apps/outputs/clion/windows.json
  • ee/maintained-apps/outputs/cmake-app/windows.json
  • ee/maintained-apps/outputs/codexbar/darwin.json
  • ee/maintained-apps/outputs/coteditor/darwin.json
  • ee/maintained-apps/outputs/cursor/darwin.json
  • ee/maintained-apps/outputs/dataflare/darwin.json
  • ee/maintained-apps/outputs/dataflare/windows.json
  • ee/maintained-apps/outputs/datagrip/darwin.json
  • ee/maintained-apps/outputs/deezer/darwin.json
  • ee/maintained-apps/outputs/downie/darwin.json
  • ee/maintained-apps/outputs/drawio/darwin.json
  • ee/maintained-apps/outputs/drofus/windows.json
  • ee/maintained-apps/outputs/elevate-uc/windows.json
  • ee/maintained-apps/outputs/evernote/windows.json
  • ee/maintained-apps/outputs/fellow/windows.json
  • ee/maintained-apps/outputs/firefox@nightly/darwin.json
  • ee/maintained-apps/outputs/framer/darwin.json
  • ee/maintained-apps/outputs/gitkraken/windows.json
  • ee/maintained-apps/outputs/gog-galaxy/darwin.json
  • ee/maintained-apps/outputs/google-chrome/darwin.json
  • ee/maintained-apps/outputs/granola/darwin.json
  • ee/maintained-apps/outputs/granola/windows.json
  • ee/maintained-apps/outputs/ibm-semeru-jre-11/windows.json
  • ee/maintained-apps/outputs/imageglass/windows.json
  • ee/maintained-apps/outputs/kiro-cli/darwin.json
  • ee/maintained-apps/outputs/kiro/darwin.json
  • ee/maintained-apps/outputs/kiro/windows.json
  • ee/maintained-apps/outputs/krita/darwin.json
  • ee/maintained-apps/outputs/loom/darwin.json
  • ee/maintained-apps/outputs/loom/windows.json
  • ee/maintained-apps/outputs/marked-app/darwin.json
  • ee/maintained-apps/outputs/masscode/darwin.json
  • ee/maintained-apps/outputs/melodics/darwin.json
  • ee/maintained-apps/outputs/microsoft-edge/darwin.json
  • ee/maintained-apps/outputs/mongodb-compass/darwin.json
  • ee/maintained-apps/outputs/mongodb-compass/windows.json
  • ee/maintained-apps/outputs/nordpass/darwin.json
  • ee/maintained-apps/outputs/nordpass/windows.json
  • ee/maintained-apps/outputs/notesnook/windows.json
  • ee/maintained-apps/outputs/nova/darwin.json
  • ee/maintained-apps/outputs/onedrive/windows.json
  • ee/maintained-apps/outputs/opencode-desktop/darwin.json
  • ee/maintained-apps/outputs/opera/darwin.json
  • ee/maintained-apps/outputs/orbstack/darwin.json
  • ee/maintained-apps/outputs/p4v/darwin.json
  • ee/maintained-apps/outputs/p4v/windows.json
  • ee/maintained-apps/outputs/pale-moon/windows.json
  • ee/maintained-apps/outputs/parallels/darwin.json
  • ee/maintained-apps/outputs/postman/darwin.json
  • ee/maintained-apps/outputs/postman/windows.json
  • ee/maintained-apps/outputs/powerphotos/darwin.json
  • ee/maintained-apps/outputs/prisma-browser/darwin.json
  • ee/maintained-apps/outputs/prisma-browser/windows.json
  • ee/maintained-apps/outputs/proton-drive/darwin.json
  • ee/maintained-apps/outputs/rider/windows.json
  • ee/maintained-apps/outputs/rive/darwin.json
  • ee/maintained-apps/outputs/rustrover/darwin.json
  • ee/maintained-apps/outputs/rustrover/windows.json
  • ee/maintained-apps/outputs/shapr3d/darwin.json
  • ee/maintained-apps/outputs/spokenly/darwin.json
  • ee/maintained-apps/outputs/spotify/darwin.json
  • ee/maintained-apps/outputs/spyder/windows.json
  • ee/maintained-apps/outputs/superwhisper/darwin.json
  • ee/maintained-apps/outputs/sync/darwin.json
  • ee/maintained-apps/outputs/telegram/windows.json
  • ee/maintained-apps/outputs/viscosity/darwin.json
  • ee/maintained-apps/outputs/viscosity/windows.json
  • ee/maintained-apps/outputs/vivaldi/darwin.json
  • ee/maintained-apps/outputs/webex/darwin.json
  • ee/maintained-apps/outputs/whatsapp/darwin.json
  • ee/maintained-apps/outputs/windows-app/windows.json
  • ee/maintained-apps/outputs/wispr-flow/darwin.json
  • ee/maintained-apps/outputs/workflowy/darwin.json
  • ee/maintained-apps/outputs/xnconvert/windows.json
  • ee/maintained-apps/outputs/zen/darwin.json

"refs": {
"692de513": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'org.pythonmac.unspecified.AviatrixVPNClient'\nsudo installer -pkg \"$TMPDIR/AVPNC_mac.pkg\" -target / || exit $?\nrelaunch_application 'org.pythonmac.unspecified.AviatrixVPNClient'\n",
"7ef6154e": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'aviatrix.vpn.client.rp.plist'\nremove_pkg_files 'com.Aviatrix.VPNClient'\nforget_pkg 'com.Aviatrix.VPNClient'\nsudo rm -rf '/Applications/Aviatrix VPN Client.app'\ntrash $LOGGED_IN_USER '~/Library/Aviatrix'\ntrash $LOGGED_IN_USER '~/Library/Logs/AviatrixVPNC'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.pythonmac.unspecified.AviatrixVPNClient.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.pythonmac.unspecified.AviatrixVPNClient.savedState'\n"
"abc2830e": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'aviatrix.vpn.client.rp'\nremove_pkg_files 'com.Aviatrix.VPNClient'\nforget_pkg 'com.Aviatrix.VPNClient'\nsudo rm -rf '/Applications/Aviatrix VPN Client.app'\ntrash $LOGGED_IN_USER '/Library/Application Support/Aviatrix VPN Client'\ntrash $LOGGED_IN_USER '~/Library/Aviatrix'\ntrash $LOGGED_IN_USER '~/Library/Logs/AviatrixVPNC'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.pythonmac.unspecified.AviatrixVPNClient.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.pythonmac.unspecified.AviatrixVPNClient.savedState'\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for file in \
  ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json \
  ee/maintained-apps/outputs/citrix-workspace/darwin.json \
  ee/maintained-apps/outputs/proton-drive/darwin.json
do
  jq -r '.refs[.versions[0].uninstall_script_ref]' "$file" |
    rg -n -C 3 'launchctl list|launchctl remove|\$HOME|LaunchAgents'
done

rg -n -C 3 'uninstall_script_ref|INSTALLER_PATH|run.*script|script.*run' .

Repository: fleetdm/fleet

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for file in \
  ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json \
  ee/maintained-apps/outputs/citrix-workspace/darwin.json \
  ee/maintained-apps/outputs/proton-drive/darwin.json
do
  echo "== $file refs =="
  jq -r '.refs[.versions[0].uninstall_script_ref]' "$file" > "/tmp/uninstall.$(basename "$file" .json).sh"
  echo "== launchctl and home paths in script =="
  rg -n -C 2 'launchctl (list|remove|bootout|asuser)|\$HOME|/Library/LaunchAgents|~/Library/LaunchAgents|rm -f|Applications/Aviatrix VPN Client.app|com.citrix|ch.protonmail.drive.agent' "/tmp/uninstall.$(basename "$file" .json).sh" || true
done

echo "== focused launchctl occurrences in the three scripts =="
rg -n 'launchctl (list|remove|asuser|bootout)' /tmp/uninstall.*.sh

Repository: fleetdm/fleet

Length of output: 8079


🌐 Web query:

launchctl asuser man page bootstrap domains list remove user agent domain macOS

💡 Result:

The launchctl tool in macOS provides specific commands for managing services within different bootstrap domains [1][2]. Asuser The asuser subcommand executes a given command within the context of a specified user's bootstrap, including the Mach bootstrap namespace, exception server, and security audit session [3][4]. It does not modify credentials (UID, GID) or inherit user-specific environment variables [5][6]. It is primarily used to run commands as if they were launched by a specific user account [7][8]. The syntax is: launchctl asuser [args] Bootstrap Domains launchctl uses domain targets to manage services [1][2]. Key domains include: - gui/: The graphical user session (Aqua). This domain is created when a user logs in via the GUI [1][9]. It is the appropriate target for LaunchAgents that require UI interaction or keychain access [9]. - user/: The background session for a user. This exists independently of a GUI login and is used for non-UI tasks (e.g., SSH sessions) [1][9]. - system: The system domain, which manages root-level services [1][2]. Managing Services (Bootstrap/Bootout) To add or remove services from a domain, use the bootstrap and bootout subcommands [1][2]. The legacy load and unload commands have been largely superseded by these in modern macOS versions [2][6]. - To load a service into a specific domain: launchctl bootstrap Example: launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.plist [9] - To remove (unload) a service from a domain: launchctl bootout [1][10] Notes - Always use the UID (not the username) for asuser and domain-target commands [7][6]. You can retrieve a UID via id -u <username> [6]. - When working with services, ensure you are targeting the correct domain (e.g., gui/ vs. user/) to avoid permission or session-related issues, such as keychain or TCC errors [9]. [9]

Citations:


Remove console-user launch services in the console-user domain.

When Fleet runs as root, launchctl list targets root/system domains and launchctl remove cannot see user-scoped launch agents. Resolve the console-user UID and run the removal in that user’s bootstrap context, for example launchctl asuser "$uid" launchctl remove "$service_label" or launchctl bootout gui/user:${uid} "$service_label". Apply this to the launched remove_launchctl_service calls in aviatrix-vpn-client, citrix-workspace, and proton-drive so com.citrix.*, ch.protonmail.drive.agent, and aviatrix.vpn.client.rp user LaunchAgents are removed.

📍 Affects 3 files
  • ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json#L20-L20 (this comment)
  • ee/maintained-apps/outputs/citrix-workspace/darwin.json#L20-L20
  • ee/maintained-apps/outputs/proton-drive/darwin.json#L19-L19
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json` at line 20,
Update remove_launchctl_service in
ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json (lines 20-20),
ee/maintained-apps/outputs/citrix-workspace/darwin.json (lines 20-20), and
ee/maintained-apps/outputs/proton-drive/darwin.json (lines 19-19) to resolve the
console user’s UID and remove user-scoped services through that user’s launchctl
bootstrap context, such as launchctl asuser or the equivalent gui/user bootout
flow; preserve the existing system/root removal behavior and apply it to
aviatrix.vpn.client.rp, com.citrix.* and ch.protonmail.drive.agent.

Comment on lines +4 to +9
"version": "151.1.93.132",
"queries": {
"exists": "SELECT 1 FROM programs WHERE name = 'Brave' AND publisher = 'Brave Software Inc';",
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Brave' AND publisher = 'Brave Software Inc' AND version_compare(version, '151.1.93.129') < 0);"
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Brave' AND publisher = 'Brave Software Inc' AND version_compare(version, '151.1.93.132') < 0);"
},
"installer_url": "https://github.com/brave/brave-browser/releases/download/v1.93.129/BraveBrowserStandaloneSilentSetup.exe",
"installer_url": "https://github.com/brave/brave-browser/releases/download/v1.93.132/BraveBrowserStandaloneSilentSetup.exe",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== target json ==="
cat -n ee/maintained-apps/outputs/brave-browser/windows.json

echo
echo "=== similar maintained app samples ==="
fd -a . ee/maintained-apps/outputs | head -50

echo
echo "=== version fields and installer_url pattern for .json ==="
python3 - <<'PY'
import json, pathlib
root=pathlib.Path("ee/maintained-apps/outputs")
for p in sorted(root.glob("*.json")):
    try:
        data=json.loads(p.read_text())
    except Exception as e:
        print(f"{p}: parse error {e}")
        continue
    for section in data.get("platforms", {}).values():
        if isinstance(section, dict):
            version=section.get("version")
            installer=section.get("installer_url")
            queries=section.get("queries", {})
            print(f"{p} {section.get('name') or ''} version={version} installer={installer} patched_contains_version={queries.get('patched','')}")
PY

echo
echo "=== release URL head (installers may not return JSON) ==="
python3 - <<'PY'
import urllib.request, socket
urls=[
  "https://github.com/brave/brave-browser/releases/download/v1.93.132/BraveBrowserStandaloneSilentSetup.exe",
  "https://github.com/brave/brave-browser/releases/download/v151.1.93.132/BraveBrowserStandaloneSilentSetup.exe",
]
for url in urls:
    req=urllib.request.Request(url, method="HEAD", headers={"User-Agent":"CodeRabbit verifier"})
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            print(url, r.status, dict(r.headers), r.headers.get("Location") or "")
    except Exception as e:
        print(url, type(e).__name__, str(e)[:500])
PY

Repository: fleetdm/fleet

Length of output: 12749


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== target json ==="
cat -n ee/maintained-apps/outputs/brave-browser/windows.json

echo
echo "=== similar maintained app samples ==="
fd -a . ee/maintained-apps/outputs | head -50

echo
echo "=== version fields and installer_url pattern for .json ==="
python3 - <<'PY'
import json, pathlib
root=pathlib.Path("ee/maintained-apps/outputs")
for p in sorted(root.glob("*.json")):
    try:
        data=json.loads(p.read_text())
    except Exception as e:
        print(f"{p}: parse error {e}")
        continue
    for section in data.get("platforms", {}).values():
        if isinstance(section, dict):
            version=section.get("version")
            installer=section.get("installer_url")
            queries=section.get("queries", {})
            print(f"{p} {section.get('name') or ''} version={version} installer={installer} patched_contains_version={queries.get('patched','')}")
PY

echo
echo "=== release URL head (installers may not return JSON) ==="
python3 - <<'PY'
import urllib.request, socket
urls=[
  "https://github.com/brave/brave-browser/releases/download/v1.93.132/BraveBrowserStandaloneSilentSetup.exe",
  "https://github.com/brave/brave-browser/releases/download/v151.1.93.132/BraveBrowserStandaloneSilentSetup.exe",
]
for url in urls:
    req=urllib.request.Request(url, method="HEAD", headers={"User-Agent":"CodeRabbit verifier"})
    try:
        with urllib.request.urlopen(req[:20) as r:
            print(url, r.status, dict(r.headers), r.headers.get("Location") or "")
    except Exception as e:
        print(url, type(e).__name__, str(e)[:500])
PY

Repository: fleetdm/fleet

Length of output: 12737


🌐 Web query:

Brave Browser Windows installer reports version 1.93.132 version_compare comparison

💡 Result:

Brave Browser version 1.93.132 is a legitimate release, officially launched on August 5, 2026 [1][2]. The term "version_compare" is not a specific error message associated with the Brave Browser Windows installer [1][3]. Instead, version_compare is a standard PHP function used in web development (such as in CMS platforms like WordPress or Nextcloud) to compare version number strings [4][5][6]. If you are encountering an error involving this term, it is likely occurring within an unrelated software environment, a custom script, or a web-based management tool that is attempting to compare software versions, rather than a native alert generated by the Brave Browser installer itself [4][5][7]. If you are seeing such an error, it typically indicates a programming or compatibility issue where a system is failing to correctly parse or compare the version string "1.93.132" [6]. If you are trying to install or update the browser and are encountering this, ensure that you are downloading the official installer from the Brave website [8][3] and that no local security software or scripts are interfering with the process.

Citations:


🌐 Web query:

Brave Browser for Windows install path uninstall registry entry displayed version 1.93.132

💡 Result:

The installation path and registry details for Brave Browser on Windows are determined by the installation method (system-wide versus per-user) [1]. Installation Path Brave Browser typically installs to one of the following locations: - System-wide install: C:\Program Files\BraveSoftware\Brave-Browser\Application\ [2][3][4] - Per-user install: %LOCALAPPDATA%\BraveSoftware\Brave-Browser\Application\ (which resolves to C:\Users<Username>\AppData\Local\BraveSoftware\Brave-Browser\Application) [2][4] Uninstall Registry Entries The registry key used for uninstallation depends on whether the browser was installed for all users (system-level) or for a single user [1]. For system-wide installations, the registry key is generally located at: HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\BraveSoftware Brave-Browser [1][5][6] For per-user installations, the registry key is generally located at: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\BraveSoftware Brave-Browser [7] The "Displayed Version" (e.g., 1.93.132) is stored within these registry keys under the DisplayVersion value [5]. You can view or verify this by navigating to the appropriate Uninstall key in the Registry Editor (regedit) and checking the data field for DisplayVersion [5]. Notes on Uninstallation - If you need to uninstall via command line, the setup.exe located in the Application<Version>\Installer directory is typically used [1][6]. - Brave maintains a support article on uninstalling the browser, which emphasizes using the "Add or Remove programs" window in Windows for a standard removal [8]. - Be aware that some registry keys and folders may persist after uninstallation, depending on whether you choose to remove browsing data during the uninstall process [9][8].

Citations:


🌐 Web query:

site:silentinstallhq.com "BraveSoftware Brave-Browser" "DisplayVersion"

💡 Result:

In Windows systems, the DisplayVersion of Brave Browser is typically stored within the Windows Registry under the uninstall keys used for application management [1]. You can retrieve this information by querying the following registry paths [1]: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\BraveSoftware Brave-Browser HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\BraveSoftware Brave-Browser The DisplayVersion value is stored as a string within these keys, representing the installed version of the browser [1]. Administrators and system managers often use this registry entry to detect if Brave is installed or to verify its current version using scripting tools like PowerShell [1]. For example, using PowerShell, you can access this information with the following command: Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\BraveSoftware Brave-Browser' -Name DisplayVersion -ea SilentlyContinue This command retrieves the version number directly from the registry, allowing for automated detection and verification processes [1].

Citations:


Align Brave’s tracked version with the installed release.

The installer URL points to release v1.93.132, so the installed DisplayVersion will be 1.93.132. With the current patched query comparing against 151.1.93.132, any installed Brave matches version_compare(version, '151.1.93.132') < 0 and the software stays marked as needing an update. Use 1.93.132 in both "version" and the patched query, or point at an artifact that actually installs 151.1.93.132.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/brave-browser/windows.json` around lines 4 - 9,
Update the Brave entry’s “version” value and the version threshold in the
“patched” query to 1.93.132 so they match the installer release at
“installer_url”; preserve the existing query structure and other metadata.

"edbf2a34": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.citrix.AuthManager_Mac'\nremove_launchctl_service 'com.citrix.ctxusbd'\nremove_launchctl_service 'com.citrix.CtxWorkspaceHelperDaemon'\nremove_launchctl_service 'com.citrix.ctxworkspaceupdater'\nremove_launchctl_service 'com.citrix.devicetrust.launchagent'\nremove_launchctl_service 'com.citrix.ReceiverHelper'\nremove_launchctl_service 'com.citrix.ReceiverUninstallHelper'\nremove_launchctl_service 'com.citrix.ReceiverUpdaterHelper'\nremove_launchctl_service 'com.citrix.safariadapter'\nremove_launchctl_service 'com.citrix.ServiceRecords'\nremove_launchctl_service 'com.citrix.UninstallMonitor'\nquit_application 'Citrix.ServiceRecords'\nquit_application 'com.citrix.CitrixReceiverLauncher'\nquit_application 'com.citrix.receiver.nomas'\nquit_application 'com.citrix.ReceiverHelper'\nremove_pkg_files 'com.citrix.common'\nforget_pkg 'com.citrix.common'\nremove_pkg_files 'com.citrix.devicetrust.client'\nforget_pkg 'com.citrix.devicetrust.client'\nremove_pkg_files 'com.citrix.devicetrust.client.ica'\nforget_pkg 'com.citrix.devicetrust.client.ica'\nremove_pkg_files 'com.citrix.enterprisebrowserinstaller'\nforget_pkg 'com.citrix.enterprisebrowserinstaller'\nremove_pkg_files 'com.citrix.ICAClient'\nforget_pkg 'com.citrix.ICAClient'\nremove_pkg_files 'com.citrix.ICAClientcwa'\nforget_pkg 'com.citrix.ICAClientcwa'\nremove_pkg_files 'com.citrix.ICAClienthdx'\nforget_pkg 'com.citrix.ICAClienthdx'\nremove_pkg_files 'com.citrix.receiver.bcr'\nforget_pkg 'com.citrix.receiver.bcr'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Citrix Receiver'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Citrix Workspace'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Citrix'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.citrix.CitrixReceiverLauncher'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.citrix.HdxRtcEngine'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.citrix.receiver*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.citrix.ReceiverUpdater'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.citrix.receiver*'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.citrix.CitrixReceiverLauncher'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.citrix.receiver*'\ntrash $LOGGED_IN_USER '~/Library/Logs/Citrix Workspace'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.AuthManager.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.CitrixReceiverLauncher.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.HdxRtcEngine.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.receiver*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.Receiver*.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.citrix.receiver.nomas.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.citrix.receiver.nomas'\n",
"f06e00fd": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'com.citrix.receiver.nomas'\nsudo installer -pkg \"$TMPDIR/CitrixWorkspaceAppUniversal26.03.11.41.pkg\" -target / || exit $?\nrelaunch_application 'com.citrix.receiver.nomas'\n"
"a78b38b6": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'com.citrix.receiver.nomas'\nsudo installer -pkg \"$TMPDIR/CitrixWorkspaceAppUniversal26.07.0.71.pkg\" -target / || exit $?\nrelaunch_application 'com.citrix.receiver.nomas'\n",
"c847120e": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.citrix.AuthManager_Mac'\nremove_launchctl_service 'com.citrix.ctxusbd'\nremove_launchctl_service 'com.citrix.CtxWorkspaceHelperDaemon'\nremove_launchctl_service 'com.citrix.ctxworkspaceupdater'\nremove_launchctl_service 'com.citrix.devicetrust.launchagent'\nremove_launchctl_service 'com.citrix.PluginBroker'\nremove_launchctl_service 'com.citrix.ReceiverHelper'\nremove_launchctl_service 'com.citrix.ReceiverUninstallHelper'\nremove_launchctl_service 'com.citrix.ReceiverUpdaterHelper'\nremove_launchctl_service 'com.citrix.safariadapter'\nremove_launchctl_service 'com.citrix.ServiceRecords'\nremove_launchctl_service 'com.citrix.UninstallMonitor'\nquit_application 'Citrix.ServiceRecords'\nquit_application 'com.citrix.CitrixReceiverLauncher'\nquit_application 'com.citrix.receiver.nomas'\nquit_application 'com.citrix.ReceiverHelper'\nremove_pkg_files 'com.citrix.common'\nforget_pkg 'com.citrix.common'\nremove_pkg_files 'com.citrix.devicetrust.client'\nforget_pkg 'com.citrix.devicetrust.client'\nremove_pkg_files 'com.citrix.devicetrust.client.ica'\nforget_pkg 'com.citrix.devicetrust.client.ica'\nremove_pkg_files 'com.citrix.enterprisebrowserinstaller'\nforget_pkg 'com.citrix.enterprisebrowserinstaller'\nremove_pkg_files 'com.citrix.ICAClient'\nforget_pkg 'com.citrix.ICAClient'\nremove_pkg_files 'com.citrix.ICAClientcwa'\nforget_pkg 'com.citrix.ICAClientcwa'\nremove_pkg_files 'com.citrix.ICAClienthdx'\nforget_pkg 'com.citrix.ICAClienthdx'\nremove_pkg_files 'com.citrix.receiver.bcr'\nforget_pkg 'com.citrix.receiver.bcr'\nsudo rm -rf '/Applications/Citrix Workspace.app'\nsudo rm -rf '/Library/Citrix Workspace'\ntrash $LOGGED_IN_USER '/Library/Logs/Citrix Workspace'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Citrix Receiver'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Citrix Workspace'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Citrix'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.citrix.CitrixReceiverLauncher'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.citrix.HdxRtcEngine'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.citrix.receiver*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.citrix.ReceiverUpdater'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.citrix.receiver*'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.citrix.CitrixReceiverLauncher'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.citrix.receiver*'\ntrash $LOGGED_IN_USER '~/Library/Logs/Citrix Workspace'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.AuthManager.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.CitrixReceiverLauncher.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.HdxRtcEngine.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.receiver*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.citrix.Receiver*.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.citrix.receiver.nomas.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.citrix.receiver.nomas'\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)citrix-workspace/darwin\.json$|(^|/)mongodb-compass/darwin\.json$'

echo "== citrix relevant lines =="
sed -n '1,220p' ee/maintained-apps/outputs/citrix-workspace/darwin.json | cat -n

echo "== mongodb relevant lines =="
sed -n '1,120p' ee/maintained-apps/outputs/mongodb-compass/darwin.json | cat -n

echo "== function call occurrences in citrix darwin =="
python3 - <<'PY'
import json
p='ee/maintained-apps/outputs/citrix-workspace/darwin.json'
data=json.load(open(p))
script=data[list(data.keys())[0]]
lines=script.splitlines()
for i,l in enumerate(lines,1):
    if "quit_application" in l or "remove_pkg_files" in l or "forget_pkg" in l or "sudo rm -rf" in l or "trash" in l:
        print(f"{i}: {l}")
PY

echo "== mongodb function call occurrences =="
python3 - <<'PY'
import json
p='ee/maintained-apps/outputs/mongodb-compass/darwi n.json'
PY
python3 - <<'PY'
import json
p='ee/maintained-apps/outputs/mongodb-compass/darwin.json'
data=json.load(open(p))
script=data[list(data.keys())[0]]
lines=script.splitlines()
for i,l in enumerate(lines,1):
    if "quit_application" in l or "rm -rf" in l or "trash" in l:
        print(f"{i}: {l}")
PY

Repository: fleetdm/fleet

Length of output: 24141


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, pathlib

for path in [
    "ee/maintained-apps/outputs/citrix-workspace/darwin.json",
    "ee/maintained-apps/outputs/mongodb-compass/darwin.json",
]:
    p=pathlib.Path(path)
    data=json.loads(p.read_text())
    ref=data["refs"][next(k for k,v in data["refs"].items() if "uninstall_script_ref" in data["versions"][0] and v == next(kv["uninstall_script_ref"] for kv in data["versions"]))]
    lines=ref.splitlines()
    print(f"== {path} ==")
    for i,l in enumerate(lines,1):
        if any(tok in l for tok in ("quit_application", "remove_pkg_files", "forget_pkg", "rm -rf", "trash", "}"), exact_word=False):
            pass
        for tok in ["quit_application", "remove_pkg_files", "forget_pkg", "rm -rf", "trash"]:
            if tok in l:
                print(f"{i}: {l}")
    print()

# Behavioral static verifier for the concern: does any uninstall script call quit_application and later remove files unconditionally?
for path in [
    "ee/maintained-apps/outputs/citrix-workspace/darwin.json",
    "ee/maintained-apps/outputs/mongodb-compass/darwin.json",
]:
    data=json.load(open(path))
    # pick the unique refs of the uninstall script, no dependency on schema details.
    refs=[v for v in data["refs"].values()]
    # each file has one uninstall ref in both outputs.
    script=refs[0]
    lines=script.splitlines()
    quit_calls=[]
    removes=[]
    for i,l in enumerate(lines,1):
        if "quit_application " in l or "quit_application(" in l:
            quit_calls.append((i,l.strip()))
        if any(rem in l for rem in ["sudo rm -rf", "rm -rf", "trash "]):
            removes.append((i,l.strip(), "remove"))
    print(f"== unconditional removal check for {path} ==")
    if quit_calls and removes and quit_calls[-1][0] < removes[0][0]:
        print("CONCERN_TRUE: exit 2")
    else:
        print("CONCERN_FALSE: exit 0")
PY

Repository: fleetdm/fleet

Length of output: 376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, pathlib, re

for path in [
    "ee/maintained-apps/outputs/citrix-workspace/darwin.json",
    "ee/maintained-apps/outputs/mongodb-compass/darwin.json",
]:
    data=json.load(open(path))
    refs=data["refs"]
    version=data["versions"][0]
    target=version["uninstall_script_ref"]
    script=refs[target]
    print(f"== {path} target ref {target} ==")
    for i,l in enumerate(script.splitlines(), 1):
        if any(tok in l for tok in ("quit_application", "remove_pkg_files", "forget_pkg", "rm -rf", "trash")) and not l.strip().startswith("#"):
            print(f"{i}: {l}")

for path in ["ee/maintained-apps/outputs/citrix-workspace/darwin.json", "ee/maintained-apps/outputs/mongodb-compass/darwin.json"]:
    data=json.load(open(path))
    script=data["refs"][data["versions"][0]["uninstall_script_ref"]]
    quit_calls=[]
    removal_calls=[]
    for i,l in enumerate(script.splitlines(),1):
        if re.search(r"\bquit_application\b", l) and not l.strip().startswith("#"):
            quit_calls.append((i,l.strip()))
        if re.search(r"\b(sudo\s+)?rm\s+-rf\b|\btrash\b", l) and not l.strip().startswith("#"):
            removal_calls.append((i,l.strip()))
    concern=False
    if quit_calls and removal_calls and quit_calls[-1][0] < removal_calls[0][0]:
        concern=True
    print(f"== conditional removal check for {path} ==")
    print("quit_calls:", quit_calls)
    print("removal_calls:", removal_calls)
    print("CONCERN_TRUE" if concern else "CONCERN_FALSE")
PY

Repository: fleetdm/fleet

Length of output: 8727


Stop uninstall when the application cannot quit.

quit_application logs failure but keeps going, then both uninstall scripts remove the app bundle, receipts/packages, and user data unconditionally. Make the helper return a failure and return from the script after the last requiredCitrix MongoDB Compass quit call when the timeout expires. Add an explicit force-quit path only if required.

  • ee/maintained-apps/outputs/citrix-workspace/darwin.json#L20-L20: abort package, application, and data removal when any required Citrix process remains running.
  • ee/maintained-apps/outputs/mongodb-compass/darwin.json#L19-L19: abort application and data removal when com.mongodb.compass remains running.
📍 Affects 2 files
  • ee/maintained-apps/outputs/citrix-workspace/darwin.json#L20-L20 (this comment)
  • ee/maintained-apps/outputs/mongodb-compass/darwin.json#L19-L19
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/citrix-workspace/darwin.json` at line 20, The
quit_application helper currently logs timeout failures but allows destructive
uninstall steps to continue. In
ee/maintained-apps/outputs/citrix-workspace/darwin.json:20-20, make
quit_application return failure when the application remains running and abort
the script after the last required Citrix quit_application call if any required
process cannot quit; in
ee/maintained-apps/outputs/mongodb-compass/darwin.json:19-19, apply the same
behavior for com.mongodb.compass before removing the application or user data.
Add a force-quit path only if required.

],
"refs": {
"7b9f27dc": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nsudo rm -rf \"$APPDIR/krita.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/krita'\ntrash $LOGGED_IN_USER '~/Library/Preferences/kritadisplayrc'\ntrash $LOGGED_IN_USER '~/Library/Preferences/kritarc'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.krita.savedState'\n",
"3947b0f0": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nsudo rm -rf \"$APPDIR/Krita.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/org.krita.*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/krita*'\ntrash $LOGGED_IN_USER '~/Library/Caches/krita'\ntrash $LOGGED_IN_USER '~/Library/Containers/org.krita.*'\ntrash $LOGGED_IN_USER '~/Library/Preferences/kritadisplayrc'\ntrash $LOGGED_IN_USER '~/Library/Preferences/kritarc'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.krita.savedState'\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='ee/maintained-apps/outputs/krita/darwin.json'
jq -r '
  .refs[.versions[0].install_script_ref],
  .refs[.versions[0].uninstall_script_ref]
' "$file" | rg -n '\$APPDIR/.+krita\.app|\$APPDIR/.+Krita\.app'

Repository: fleetdm/fleet

Length of output: 219


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='ee/maintained-apps/outputs/krita/darwin.json'

printf 'JSON refs:\n'
jq -r '.refs[.versions[0].install_script_ref], .refs[.versions[0].uninstall_script_ref]' "$file"

install_ref=$(jq -r '.refs[.versions[0].install_script_ref]' "$file")
uninstall_ref=$(jq -r '.refs[.versions[0].uninstall_script_ref]' "$file")

printf '\nInstall script references:\n'
python3 - <<'PY' "$install_ref"
import json, re, sys
ref=sys.argv[1]
matches=re.findall(r'(?<!["\'])sha256-[0-9a-fA-F]{64}', ref)
print('\n'.join(matches))
PY
uninstall_line=$(jq -r '.refs[.versions[0].uninstall_script_ref]' "$file" | python3 - <<'PY'
import re, sys
s=sys.stdin.read()
print('\n'.join(re.findall(r'(?<!["\'])sha256-[0-9a-fA-F]{64}', s)))
PY
)

printf '\nInstall line matching AppleBundlePath:\n'
grep -n 'AppleBundlePath\|rita\.app\|Krita\.app' ee/maintained-apps/outputs/krita/darwin.json || true

printf '\nUninstall line matching rm paths:\n'
grep -n 'rm -rf\|rita\.app\|Krita\.app' ee/maintained-apps/outputs/krita/darwin.json || true

printf '\nPython case-sensitivity probe for filesystem string comparison:\n'
python3 - <<'PY'
for a,b in [("/Applications/krita.app","/Applications/Krita.app"), ("/Applications/Krita.app","/Applications/Krita.app")]:
    print(f"{a!r} != {b!r}: {a != b}")
PY

Repository: fleetdm/fleet

Length of output: 6039


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='ee/maintained-apps/outputs/krita/darwin.json'
install_ref="$(jq -r '.refs[.versions[0].install_script_ref]' "$file" | sha256sum | cut -d' ' -f1)"
uninstall_ref="$(jq -r '.refs[.versions[0].uninstall_script_ref]' "$file" | sha256sum | cut -d' ' -f1)"

jq -r '
  "install_hash: \(.refs[.versions[0].install_script_ref] | `@text` | sha256sum),
   uninstall_hash: \(.refs[.versions[0].uninstall_script_ref] | `@text` | sha256sum),
   install_shell_refs: \(.refs[.versions[0].install_script_ref | explode[].ascii_downcase? // .] | if test("sha256-") then "refs_found" else "no_refs" end),
   uninstall_shell_refs: \(.refs[.versions[0].uninstall_script_ref | explode[].ascii_downcase? // .] | if test("sha256-") then "refs_found" else "no_refs" end)"
' "$file"

printf '\nCase-sensitive path comparison behavior:\n'
python3 - <<'PY'
for a,b in [("/Applications/krita.app","/Applications/Krita.app"), ("/Applications/Krita.app","/Applications/Krita.app")]:
    print(f"{a!r} != {b!r}: {a != b}")
PY

Repository: fleetdm/fleet

Length of output: 658


Use the installed bundle path in the uninstall script.

The install script copies /Applications/krita.app, but this script removes /Applications/Krita.app. On case-sensitive volumes, the uninstall leaves the installed application behind and reports success for unrelated matching names, such as a different app named Krita.app.

Proposed fix
-sudo rm -rf "$APPDIR/Krita.app"
+sudo rm -rf "$APPDIR/krita.app"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"3947b0f0": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nsudo rm -rf \"$APPDIR/Krita.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/org.krita.*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/krita*'\ntrash $LOGGED_IN_USER '~/Library/Caches/krita'\ntrash $LOGGED_IN_USER '~/Library/Containers/org.krita.*'\ntrash $LOGGED_IN_USER '~/Library/Preferences/kritadisplayrc'\ntrash $LOGGED_IN_USER '~/Library/Preferences/kritarc'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.krita.savedState'\n",
"3947b0f0": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nsudo rm -rf \"$APPDIR/krita.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/org.krita.*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/krita*'\ntrash $LOGGED_IN_USER '~/Library/Caches/krita'\ntrash $LOGGED_IN_USER '~/Library/Containers/org.krita.*'\ntrash $LOGGED_IN_USER '~/Library/Preferences/kritadisplayrc'\ntrash $LOGGED_IN_USER '~/Library/Preferences/kritarc'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.krita.savedState'\n",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/krita/darwin.json` at line 19, Update the
uninstall script’s application removal command to use the same case-sensitive
bundle path as the installer, `/Applications/krita.app`, instead of
`/Applications/Krita.app`. Keep the existing removal flow unchanged so it
targets only the installed Krita bundle.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Closing in favor of #50794.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants