Skip to content

Update Fleet-maintained apps - #50112

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

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

Conversation

@fleet-release

@fleet-release fleet-release commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Automated ingestion of latest Fleet-maintained app data.

Summary by CodeRabbit

  • New Features

    • Added support for the latest releases of numerous maintained applications across macOS and Windows, including Firefox, Nextcloud, Docker Desktop, Microsoft Office apps, Kiro, GDevelop, and others.
    • Updated installer downloads and verification data for newly published versions.
  • Bug Fixes

    • Improved uninstall cleanup for several macOS applications by removing additional background services, recent-document entries, caches, and application data.
    • Improved Windows uninstall handling when no matching uninstall entry is found.

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

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/1password/darwin.json

=== Install Script (no changes) ===
=== Uninstall // c927cf5a -> a642a51c ===

--- /tmp/old.1H4r2r	2026-07-29 02:52:06.984595649 +0000
+++ /tmp/new.UG7F4o	2026-07-29 02:52:06.984595649 +0000
@@ -45,6 +45,76 @@
 }
 
 
+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"
@@ -92,6 +162,8 @@
   fi
 }
 
+remove_launchctl_service '2BUA8C4S2C.com.1password.browser-helper'
+remove_launchctl_service 'com.1password.1password-launcher'
 quit_application 'com.1password.1password'
 sudo rm -rf "$APPDIR/1Password.app"
 trash $LOGGED_IN_USER '~/Library/Application Scripts/2BUA8C4S2C.com.1password*'

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

=== Install Script (no changes) ===
=== Uninstall // 9850444d -> 62b94363 ===

--- /tmp/old.1D3MfY	2026-07-29 02:52:07.018594249 +0000
+++ /tmp/new.vExmKp	2026-07-29 02:52:07.018594249 +0000
@@ -54,6 +54,7 @@
 
 sudo rm -rf "$APPDIR/ActiveDock 2.app"
 trash $LOGGED_IN_USER '~/Library/Application Support/ActiveDock 2'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.sergey-gerasimenko.activedock-2.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.sergey-gerasimenko.ActiveDock-2'
 trash $LOGGED_IN_USER '~/Library/Caches/com.sergey-gerasimenko.ActiveDock-2'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.sergey-gerasimenko.ActiveDock-2'

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

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

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

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

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

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

ee/maintained-apps/outputs/appium-inspector/darwin.json

=== Install Script (no changes) ===
=== Uninstall // 84d10064 -> 123508e6 ===

--- /tmp/old.wZyO56	2026-07-29 02:52:07.126589803 +0000
+++ /tmp/new.Dznj0p	2026-07-29 02:52:07.126589803 +0000
@@ -54,6 +54,7 @@
 
 sudo rm -rf "$APPDIR/Appium Inspector.app"
 trash $LOGGED_IN_USER '~/Library/Application Support/appium-inspector'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/io.appium.inspector.sfl*'
 trash $LOGGED_IN_USER '~/Library/Logs/Appium Inspector'
 trash $LOGGED_IN_USER '~/Library/Preferences/io.appium.inspector.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/io.appium.inspector.savedState'

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

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

ee/maintained-apps/outputs/aws-cli/windows.json

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

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

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

ee/maintained-apps/outputs/azul-zulu-25-jre/windows.json

=== Install Script (no changes) ===
=== Uninstall // ef6bb3e7 -> 6129996f ===

--- /tmp/old.lR6nm2	2026-07-29 02:52:07.463575931 +0000
+++ /tmp/new.9U4mov	2026-07-29 02:52:07.463575931 +0000
@@ -1,4 +1,4 @@
-$product_code = '{28ED6065-4CBD-47B6-9010-582DD8465EFC}'
+$product_code = '{9BA5257B-93C3-4E9B-A90D-39DFA157A7AA}'
 $timeoutSeconds = 300  # 5 minute timeout
 
 # Fleet uninstalls app using product code that's extracted on upload

ee/maintained-apps/outputs/bartender/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-atlas/darwin.json

=== Install Script (no changes) ===
=== Uninstall // e792c12a -> 179b26c0 ===

--- /tmp/old.pkfhTb	2026-07-29 02:52:07.562571855 +0000
+++ /tmp/new.FrUhYw	2026-07-29 02:52:07.562571855 +0000
@@ -171,6 +171,7 @@
 trash $LOGGED_IN_USER '~/Library/Caches/com.openai.atlas'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.atlas'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.atlas.binarycookies'
+trash $LOGGED_IN_USER '~/Library/LaunchAgents/com.openai.atlas.update-helper.plist'
 trash $LOGGED_IN_USER '~/Library/Logs/DiagnosticReports/ChatGPT Atlas (Service)*.ips'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.openai.atlas.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.openai.atlas.web.plist'

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 9d80ae1c -> 03b9aece ===

--- /tmp/old.YrFF0N	2026-07-29 02:52:07.665567615 +0000
+++ /tmp/new.8HOQdr	2026-07-29 02:52:07.665567615 +0000
@@ -57,6 +57,7 @@
 trash $LOGGED_IN_USER '~/.cursor'
 trash $LOGGED_IN_USER '~/.cursor-tutor'
 trash $LOGGED_IN_USER '~/Library/Application Support/Caches/cursor-updater'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.todesktop.230313mzl4w4u92.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/Cursor'
 trash $LOGGED_IN_USER '~/Library/Caches/com.todesktop.*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.todesktop.*.ShipIt'

ee/maintained-apps/outputs/cycling74-max/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/delinea-connection-manager/windows.json

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

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

=== Install Script (no changes) ===
=== Uninstall // 46a242eb -> 450a6520 ===

--- /tmp/old.yLzHoC	2026-07-29 02:52:07.821561194 +0000
+++ /tmp/new.SDFtq4	2026-07-29 02:52:07.821561194 +0000
@@ -45,6 +45,76 @@
 }
 
 
+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"
@@ -92,6 +162,7 @@
   fi
 }
 
+remove_launchctl_service 'com.discord.discord.ShipIt'
 quit_application 'com.hnc.Discord'
 quit_application 'com.hnc.Discord.helper.Plugin'
 quit_application 'com.hnc.Discord.helper.Renderer'
@@ -104,6 +175,7 @@
 trash $LOGGED_IN_USER '~/Library/Cookies/com.hnc.Discord.binarycookies'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.hnc.Discord'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.hnc.Discord.binarycookies'
+trash $LOGGED_IN_USER '~/Library/Preferences/ByHost/com.discord.discord.ShipIt.*.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.hnc.Discord.helper.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.hnc.Discord.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.hnc.Discord.savedState'

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

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

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

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

ee/maintained-apps/outputs/elgato-stream-deck/darwin.json

=== Install // 214a13cd -> 81223e72 ===

--- /tmp/old.s84V70	2026-07-29 02:52:07.915557324 +0000
+++ /tmp/new.CqLSdZ	2026-07-29 02:52:07.916557283 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'com.elgato.StreamDeck'
-sudo installer -pkg "$TMPDIR/Stream_Deck_7.5.0.22885.pkg" -target /
+sudo installer -pkg "$TMPDIR/Stream_Deck_7.5.1.22901.pkg" -target /
 relaunch_application 'com.elgato.StreamDeck'

=== Uninstall // b842b140 -> aff3ed62 ===

--- /tmp/old.UfAhlm	2026-07-29 02:52:07.928556789 +0000
+++ /tmp/new.0Bl0BO	2026-07-29 02:52:07.928556789 +0000
@@ -229,6 +229,7 @@
 }
 
 remove_launchctl_service 'com.elgato.StreamDeck*'
+remove_launchctl_service 'com.elgato.StreamDeck.trampoline'
 quit_application 'com.elgato.StreamDeck'
 remove_pkg_files 'com.elgato.StreamDeck'
 forget_pkg 'com.elgato.StreamDeck'

ee/maintained-apps/outputs/elgato-stream-deck/windows.json

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

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 5fe54a7b -> ffdf9bc3 ===

--- /tmp/old.oFD6bY	2026-07-29 02:52:08.052551685 +0000
+++ /tmp/new.wg77tQ	2026-07-29 02:52:08.052551685 +0000
@@ -94,9 +94,11 @@
 
 quit_application 'com.figma.agent'
 sudo rm -rf "$APPDIR/Figma.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.figma.desktop.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/Figma'
 trash $LOGGED_IN_USER '~/Library/Application Support/figma-desktop'
 trash $LOGGED_IN_USER '~/Library/Caches/com.figma.agent'
 trash $LOGGED_IN_USER '~/Library/Caches/com.figma.Desktop'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.figma.agent'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.figma.Desktop.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.figma.Desktop.savedState'

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

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

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

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

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

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

ee/maintained-apps/outputs/firefox@developer-edition/darwin.json

=== Install Script (no changes) ===
=== Uninstall // b108b0ff -> 292c5733 ===

--- /tmp/old.gyzOaQ	2026-07-29 02:52:08.158547321 +0000
+++ /tmp/new.VjNBms	2026-07-29 02:52:08.158547321 +0000
@@ -65,5 +65,6 @@
 trash $LOGGED_IN_USER '~/Library/Caches/Mozilla/updates/Applications/Firefox'
 trash $LOGGED_IN_USER '~/Library/Caches/org.mozilla.firefox'
 trash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.firefox.plist'
+trash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.firefoxdeveloperedition.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/org.mozilla.firefox.savedState'
 trash $LOGGED_IN_USER '~/Library/WebKit/org.mozilla.firefox'

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

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

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 4775195c -> 12495d2a ===

--- /tmp/old.AkpH21	2026-07-29 02:52:08.283542175 +0000
+++ /tmp/new.2Twj6p	2026-07-29 02:52:08.283542175 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/Framer.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.framer.electron.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/Framer'
 trash $LOGGED_IN_USER '~/Library/Caches/com.framer.electron'
 trash $LOGGED_IN_USER '~/Library/Caches/com.framer.electron.ShipIt'

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 65bf9482 -> 33618979 ===

--- /tmp/old.mLFuob	2026-07-29 02:52:08.373538471 +0000
+++ /tmp/new.F8Y68v	2026-07-29 02:52:08.373538471 +0000
@@ -100,5 +100,6 @@
 trash $LOGGED_IN_USER '~/Library/Caches/com.electron.gitify*'
 trash $LOGGED_IN_USER '~/Library/Caches/gitify-updater'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.electron.gitify'
+trash $LOGGED_IN_USER '~/Library/Logs/gitify'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.electron.gitify*.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.electron.gitify.savedState'

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

=== Install Script (no changes) ===
=== Uninstall // 8ac93526 -> 7dff0961 ===

--- /tmp/old.Sex9VQ	2026-07-29 02:52:08.408537030 +0000
+++ /tmp/new.ZcK2IW	2026-07-29 02:52:08.409536989 +0000
@@ -126,12 +126,14 @@
 remove_launchctl_service 'com.grammarly.ProjectLlama.Shepherd'
 remove_launchctl_service 'com.grammarly.ProjectLlama.Uninstaller'
 sudo rm -rf "$APPDIR/Grammarly Desktop.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.grammarly.projectllama.loginhelper.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.grammarly.ProjectLlama'
 trash $LOGGED_IN_USER '~/Library/Caches/com.grammarly.ProjectLlama'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.grammarly.GRLlamaOnboarding.binarycookies'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.grammarly.ProjectLlama'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.grammarly.ProjectLlama.binarycookies'
 trash $LOGGED_IN_USER '~/Library/LaunchAgents/com.grammarly.ProjectLlama.Shepherd.plist'
+trash $LOGGED_IN_USER '~/Library/LaunchAgents/com.grammarly.ProjectLlama.Uninstaller.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.grammarly.ProjectLlama.plist'
 trash $LOGGED_IN_USER '~/Library/WebKit/com.grammarly.GRLlamaOnboarding'
 trash $LOGGED_IN_USER '~/Library/WebKit/com.grammarly.ProjectLlama'

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/hive-app/darwin.json

=== Install Script (no changes) ===
=== Uninstall // 78bb4e43 -> 951b5587 ===

--- /tmp/old.ry8CQp	2026-07-29 02:52:08.487533778 +0000
+++ /tmp/new.X8lUlI	2026-07-29 02:52:08.487533778 +0000
@@ -54,6 +54,7 @@
 
 sudo rm -rf "$APPDIR/Hive.app"
 trash $LOGGED_IN_USER '~/.hive'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.hive.app.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/hive'
 trash $LOGGED_IN_USER '~/Library/Logs/hive'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.hive.app.plist'

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

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

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

=== Install Script (no changes) ===
=== Uninstall // c6bb8769 -> 1e1dcbc9 ===

--- /tmp/old.LkfFAI	2026-07-29 02:52:08.567530485 +0000
+++ /tmp/new.odhxRF	2026-07-29 02:52:08.567530485 +0000
@@ -159,7 +159,7 @@
   fi
 }
 
-quit_application 'app.kaleidoscope.v6'
+quit_application 'app.kaleidoscope.v7'
 remove_pkg_files 'app.kaleidoscope.uninstall_ksdiff'
 forget_pkg 'app.kaleidoscope.uninstall_ksdiff'
 sudo rm -rf "$APPDIR/Kaleidoscope.app"

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/loom/darwin.json

=== Install Script (no changes) ===
=== Uninstall // f408ec47 -> 1a901a1f ===

--- /tmp/old.Fg0WVY	2026-07-29 02:52:08.668526327 +0000
+++ /tmp/new.jq4VyM	2026-07-29 02:52:08.668526327 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/Loom.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.loom.desktop.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/Loom'
 trash $LOGGED_IN_USER '~/Library/Logs/Loom'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.loom.desktop.plist'

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

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

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

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

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

=== Install // eb844ac0 -> d6b1d4ec ===

--- /tmp/old.nUHAbI	2026-07-29 02:52:08.782521634 +0000
+++ /tmp/new.yjQqv2	2026-07-29 02:52:08.782521634 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'com.malwarebytes.mbam.frontend.application'
-sudo installer -pkg "$TMPDIR/Malwarebytes-Mac-5.24.0.4031.pkg" -target /
+sudo installer -pkg "$TMPDIR/Malwarebytes-Mac-5.25.2.4106.pkg" -target /
 relaunch_application 'com.malwarebytes.mbam.frontend.application'

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

ee/maintained-apps/outputs/microsoft-auto-update/darwin.json

=== Install // 64b51d51 -> 758dae4d ===

--- /tmp/old.A6O04j	2026-07-29 02:52:08.829519699 +0000
+++ /tmp/new.ZRWUzm	2026-07-29 02:52:08.829519699 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'com.microsoft.autoupdate2'
-sudo installer -pkg "$TMPDIR/Microsoft_AutoUpdate_4.83.26040910_Updater.pkg" -target /
+sudo installer -pkg "$TMPDIR/Microsoft_AutoUpdate_4.84.26071119_Updater.pkg" -target /
 relaunch_application 'com.microsoft.autoupdate2'

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

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

=== Install // a058ee4b -> 98c4e79f ===

--- /tmp/old.5pYPY2	2026-07-29 02:52:08.868518094 +0000
+++ /tmp/new.baL78R	2026-07-29 02:52:08.868518094 +0000
@@ -117,6 +117,6 @@
 
 EOF
 
-sudo installer -pkg "$TMPDIR"/Microsoft_Excel_16.111.26071913_Installer.pkg -target / -applyChoiceChangesXML "$CHOICE_XML"
+sudo installer -pkg "$TMPDIR"/Microsoft_Excel_16.111.26072617_Installer.pkg -target / -applyChoiceChangesXML "$CHOICE_XML"
 
 relaunch_application 'com.microsoft.Excel'

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

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

=== Install // fe16d50a -> 0ac5b395 ===

--- /tmp/old.aiPRvj	2026-07-29 02:52:08.906516530 +0000
+++ /tmp/new.dTbpKc	2026-07-29 02:52:08.906516530 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'com.microsoft.onenote.mac'
-sudo installer -pkg "$TMPDIR/Microsoft_OneNote_16.111.26071913_Updater.pkg" -target /
+sudo installer -pkg "$TMPDIR/Microsoft_OneNote_16.111.26072617_Updater.pkg" -target /
 relaunch_application 'com.microsoft.onenote.mac'

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

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

=== Install // daf855a0 -> 36641a79 ===

--- /tmp/old.Ae4kHy	2026-07-29 02:52:08.944514965 +0000
+++ /tmp/new.y2Yqi4	2026-07-29 02:52:08.944514965 +0000
@@ -117,6 +117,6 @@
 
 EOF
 
-sudo installer -pkg "$TMPDIR"/Microsoft_PowerPoint_16.111.26071913_Installer.pkg -target / -applyChoiceChangesXML "$CHOICE_XML"
+sudo installer -pkg "$TMPDIR"/Microsoft_PowerPoint_16.111.26072617_Installer.pkg -target / -applyChoiceChangesXML "$CHOICE_XML"
 
 relaunch_application 'com.microsoft.Powerpoint'

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

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

=== Install // e06030dd -> b470c108 ===

--- /tmp/old.Wl4S3h	2026-07-29 02:52:08.983513360 +0000
+++ /tmp/new.zZ4uMG	2026-07-29 02:52:08.983513360 +0000
@@ -117,6 +117,6 @@
 
 EOF
 
-sudo installer -pkg "$TMPDIR"/Microsoft_Word_16.111.26071913_Installer.pkg -target / -applyChoiceChangesXML "$CHOICE_XML"
+sudo installer -pkg "$TMPDIR"/Microsoft_Word_16.111.26072617_Installer.pkg -target / -applyChoiceChangesXML "$CHOICE_XML"
 
 relaunch_application 'com.microsoft.Word'

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

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

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

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

=== Install // c18d4d3b -> d9838806 ===

--- /tmp/old.IbqZtg	2026-07-29 02:52:09.055510396 +0000
+++ /tmp/new.W7KNPP	2026-07-29 02:52:09.055510396 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'com.nextcloud.desktopclient'
-sudo installer -pkg "$TMPDIR/Nextcloud-33.0.7.pkg" -target /
+sudo installer -pkg "$TMPDIR/Nextcloud-34.0.0.pkg" -target /
 relaunch_application 'com.nextcloud.desktopclient'

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 97f61740 -> 4ab665bb ===

--- /tmp/old.gkAMwD	2026-07-29 02:52:09.127507432 +0000
+++ /tmp/new.qERsye	2026-07-29 02:52:09.127507432 +0000
@@ -235,9 +235,11 @@
 remove_pkg_files 'com.nordvpn.macos.teams'
 forget_pkg 'com.nordvpn.macos.teams'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.nordvpn.macos.teams'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/group.com.nordlayer.macos'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.nordvpn.macos.teams.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.nordvpn.macos.teams'
 trash $LOGGED_IN_USER '~/Library/Caches/com.nordvpn.macos.teams'
 trash $LOGGED_IN_USER '~/Library/Containers/com.nordvpn.macos.teams'
+trash $LOGGED_IN_USER '~/Library/Group Containers/group.com.nordlayer.macos'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.nordvpn.macos.teams'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.nordvpn.macos.teams.plist'

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

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

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // ad62b190 -> 99fcb3f1 ===

--- /tmp/old.kCKFbU	2026-07-29 02:52:09.274501381 +0000
+++ /tmp/new.QRDivB	2026-07-29 02:52:09.274501381 +0000
@@ -54,6 +54,7 @@
 
 sudo rm -rf "$APPDIR/OpenCode.app"
 trash $LOGGED_IN_USER '~/Library/Application Support/ai.opencode.desktop'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/ai.opencode.desktop.sfl*'
 trash $LOGGED_IN_USER '~/Library/Caches/ai.opencode.desktop'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/ai.opencode.desktop'
 trash $LOGGED_IN_USER '~/Library/Logs/ai.opencode.desktop'

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

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

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

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

ee/maintained-apps/outputs/remote-desktop-manager/darwin.json

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

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // a5d6883d -> 99a71452 ===

--- /tmp/old.WAaNdG	2026-07-29 02:52:09.486492654 +0000
+++ /tmp/new.D6m9CE	2026-07-29 02:52:09.486492654 +0000
@@ -53,6 +53,8 @@
 }
 
 sudo rm -rf "$APPDIR/Spokenly.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/app.spokenly.revenuecat'
 trash $LOGGED_IN_USER '~/Library/Application Support/Spokenly'
 trash $LOGGED_IN_USER '~/Library/Caches/app.spokenly'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/app.spokenly'
 trash $LOGGED_IN_USER '~/Library/Preferences/app.spokenly.plist'

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

=== Install Script (no changes) ===
=== Uninstall // 6335aa0a -> 1fb72c38 ===

--- /tmp/old.Mt8iFk	2026-07-29 02:52:09.519491296 +0000
+++ /tmp/new.96MwA6	2026-07-29 02:52:09.520491254 +0000
@@ -30,7 +30,7 @@
             $exitCode = $process.ExitCode; Write-Host "Uninstall exit code: $exitCode"; break
         }
     }
-    if (-not $foundUninstaller) { Write-Host "Uninstaller for '$softwareName' not found."; Exit 1 }
+    if (-not $foundUninstaller) { Write-Host "Uninstaller for '$softwareName' not found."; Exit 0 }
 } catch { Write-Host "Error: $_"; Exit 1 }
 
 Exit $exitCode

ee/maintained-apps/outputs/sql-server-management-studio/windows.json

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

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

=== Install Script (no changes) ===
=== Uninstall // 20e49d94 -> 19abdcd7 ===

--- /tmp/old.BZ7pMJ	2026-07-29 02:52:09.583488661 +0000
+++ /tmp/new.9b1MvI	2026-07-29 02:52:09.583488661 +0000
@@ -53,8 +53,10 @@
 }
 
 sudo rm -rf "$APPDIR/TablePlus.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.tinyapp.tableplus.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.tinyapp.TablePlus'
 trash $LOGGED_IN_USER '~/Library/Caches/com.tinyapp.TablePlus'
 trash $LOGGED_IN_USER '~/Library/Cookies/com.tinyapp.TablePlus.binarycookies'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.tinyapp.TablePlus'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.tinyapp.TablePlus.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.tinyapp.TablePlus.savedState'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 0b25ddc3 -> cd8944ef ===

--- /tmp/old.gesYbK	2026-07-29 02:52:09.881476394 +0000
+++ /tmp/new.6VUxin	2026-07-29 02:52:09.881476394 +0000
@@ -94,6 +94,10 @@
 
 quit_application 'net.whatsapp.WhatsApp'
 sudo rm -rf "$APPDIR/WhatsApp.app"
+trash $LOGGED_IN_USER '~/Library/Application Scripts/group.net.whatsapp.family'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/group.net.whatsapp.WhatsApp.private'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/group.net.whatsapp.WhatsApp.shared'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/group.net.whatsapp.WhatsAppSMB.shared'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/net.whatsapp.WhatsApp*'
 trash $LOGGED_IN_USER '~/Library/Caches/net.whatsapp.WhatsApp'
 trash $LOGGED_IN_USER '~/Library/Containers/net.whatsapp.WhatsApp*'

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

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

@github-actions

Copy link
Copy Markdown
Contributor

Closing in favor of #50113.

@github-actions github-actions Bot closed this Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Updated numerous Fleet-maintained app manifests with newer versions, patch-detection thresholds, installer URLs, and SHA-256 checksums across macOS and Windows outputs. Several entries now reference revised uninstall scripts with expanded launchctl, plist, shared-file-list, Library, and application-data cleanup. Installer scripts for selected Microsoft, Malwarebytes, Nextcloud, and Elgato packages now reference updated package artifacts. One Thaw installer URL changed repository ownership, and Spyder’s no-uninstaller result is now successful.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: automated updates to Fleet-maintained app metadata.
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-2607290246

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: 9

🤖 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/1password/darwin.json`:
- Line 20: Update remove_launchctl_service to check and remove services through
explicit system and logged-in-user GUI launchd domains rather than relying on
the caller’s default domain. Derive the user domain from LOGGED_IN_USER and its
UID, and construct user LaunchAgent paths from /Users/$LOGGED_IN_USER instead of
$HOME, while preserving the existing sudo handling for system operations.

In `@ee/maintained-apps/outputs/cursor/darwin.json`:
- Around line 19-20: Update the uninstall script’s `sudo rm -rf 'cursor'`
command to target the same absolute CLI-link path created by the installer’s
`/bin/ln` command, and remove it with `rm --` rather than recursive deletion.
Ensure the installer and uninstaller consistently reference that absolute
expected symlink path.

In `@ee/maintained-apps/outputs/discord/darwin.json`:
- Line 19: The launchAgent cleanup in remove_launchctl_service currently uses
the root launchd domain and /root when run as root. In
ee/maintained-apps/outputs/discord/darwin.json:19-19, update cleanup for
com.discord.discord.ShipIt to resolve the console user’s UID and home directory,
run launchctl list/remove against gui/<uid>, and remove the plist from that
user’s home LaunchAgents directory; apply the same change to
com.openai.atlas.update-helper in
ee/maintained-apps/outputs/chatgpt-atlas/darwin.json:19-19.

In `@ee/maintained-apps/outputs/elgato-stream-deck/darwin.json`:
- Line 20: Update remove_launchctl_service so wildcard patterns still remove
matching plist files when no matching launchctl service is loaded; do not return
before the path-cleanup loops. Preserve the explicit service handling and ensure
the stale com.elgato.StreamDeck.trampoline plist is cleaned up, since the quoted
~/Library fallback is ineffective.

In `@ee/maintained-apps/outputs/figma/darwin.json`:
- Line 20: Update the uninstall sequence around quit_application to also quit
the managed Figma desktop bundle ID, com.figma.Desktop, before removing
Figma.app; retain the existing com.figma.agent shutdown and cleanup behavior.

In `@ee/maintained-apps/outputs/firefox`@developer-edition/darwin.json:
- Around line 11-19: Update uninstall script ref 292c5733 to remove only Firefox
Developer Edition data, replacing regular Firefox paths and identifiers such as
~/Library/Application Support/Firefox, org.mozilla.firefox, and firefox_* with
their Developer Edition-specific equivalents matching
org.mozilla.firefoxdeveloperedition. Preserve cleanup of shared Mozilla
directories only where it cannot affect regular Firefox data.

In `@ee/maintained-apps/outputs/kaleidoscope/darwin.json`:
- Around line 4-7: Update the Kaleidoscope package’s detection queries and all
related installer, quit, relaunch, and uninstallant references from
app.kaleidoscope.v6 to app.kaleidoscope.v7, while preserving the existing
version checks and operation flow.

In `@ee/maintained-apps/outputs/nordlayer/darwin.json`:
- Line 20: Validate LOGGED_IN_USER after it is populated, and only execute the
subsequent user-specific trash calls when it is non-empty and not root or
loginwindow. Quote the LOGGED_IN_USER argument at each trash invocation,
preserving the existing cleanup behavior for valid console users.

In `@ee/maintained-apps/outputs/tableplus/darwin.json`:
- Line 19: Update the shared-file-list cleanup argument in the script to use the
exact case-sensitive filename prefix com.tinyapp.TablePlus.sfl*. Leave the trash
function and other cleanup paths unchanged.
🪄 Autofix (Beta)

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: 3240b6e1-0dff-4800-9e52-360551c3093c

📥 Commits

Reviewing files that changed from the base of the PR and between a3cdb7e and 3afc758.

📒 Files selected for processing (84)
  • ee/maintained-apps/outputs/1password/darwin.json
  • ee/maintained-apps/outputs/activedock/darwin.json
  • ee/maintained-apps/outputs/antigravity/darwin.json
  • ee/maintained-apps/outputs/anyburn/windows.json
  • ee/maintained-apps/outputs/anytype/darwin.json
  • ee/maintained-apps/outputs/appium-inspector/darwin.json
  • ee/maintained-apps/outputs/aptakube/windows.json
  • ee/maintained-apps/outputs/aws-cli/windows.json
  • ee/maintained-apps/outputs/aws-vpn-client/darwin.json
  • ee/maintained-apps/outputs/azul-zulu-25-jre/windows.json
  • ee/maintained-apps/outputs/bartender/darwin.json
  • ee/maintained-apps/outputs/chatbox/windows.json
  • ee/maintained-apps/outputs/chatgpt-atlas/darwin.json
  • ee/maintained-apps/outputs/cmake-app/darwin.json
  • ee/maintained-apps/outputs/crashplan/windows.json
  • ee/maintained-apps/outputs/cursor/darwin.json
  • ee/maintained-apps/outputs/cycling74-max/darwin.json
  • ee/maintained-apps/outputs/dataflare/darwin.json
  • ee/maintained-apps/outputs/dataflare/windows.json
  • ee/maintained-apps/outputs/delinea-connection-manager/windows.json
  • ee/maintained-apps/outputs/discord/darwin.json
  • ee/maintained-apps/outputs/docker/windows.json
  • ee/maintained-apps/outputs/drawio/windows.json
  • ee/maintained-apps/outputs/elgato-stream-deck/darwin.json
  • ee/maintained-apps/outputs/elgato-stream-deck/windows.json
  • ee/maintained-apps/outputs/etrecheckpro/darwin.json
  • ee/maintained-apps/outputs/extradock/darwin.json
  • ee/maintained-apps/outputs/figma/darwin.json
  • ee/maintained-apps/outputs/figma/windows.json
  • ee/maintained-apps/outputs/firefox/darwin.json
  • ee/maintained-apps/outputs/firefox/windows.json
  • ee/maintained-apps/outputs/firefox@developer-edition/darwin.json
  • ee/maintained-apps/outputs/firefox@nightly/darwin.json
  • ee/maintained-apps/outputs/firefox@nightly/windows.json
  • ee/maintained-apps/outputs/forklift/darwin.json
  • ee/maintained-apps/outputs/framer/darwin.json
  • ee/maintained-apps/outputs/gdevelop/darwin.json
  • ee/maintained-apps/outputs/gdevelop/windows.json
  • ee/maintained-apps/outputs/gitify/darwin.json
  • ee/maintained-apps/outputs/grammarly-desktop/darwin.json
  • ee/maintained-apps/outputs/granola/darwin.json
  • ee/maintained-apps/outputs/granola/windows.json
  • ee/maintained-apps/outputs/hive-app/darwin.json
  • ee/maintained-apps/outputs/jamovi/darwin.json
  • ee/maintained-apps/outputs/kaleidoscope/darwin.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/loom/darwin.json
  • ee/maintained-apps/outputs/lulu/darwin.json
  • ee/maintained-apps/outputs/maccy/darwin.json
  • ee/maintained-apps/outputs/malwarebytes/darwin.json
  • ee/maintained-apps/outputs/microsoft-auto-update/darwin.json
  • ee/maintained-apps/outputs/microsoft-excel/darwin.json
  • ee/maintained-apps/outputs/microsoft-onenote/darwin.json
  • ee/maintained-apps/outputs/microsoft-powerpoint/darwin.json
  • ee/maintained-apps/outputs/microsoft-word/darwin.json
  • ee/maintained-apps/outputs/milanote/darwin.json
  • ee/maintained-apps/outputs/nextcloud/darwin.json
  • ee/maintained-apps/outputs/nextcloud/windows.json
  • ee/maintained-apps/outputs/nordlayer/darwin.json
  • ee/maintained-apps/outputs/notion/darwin.json
  • ee/maintained-apps/outputs/omnioutliner/darwin.json
  • ee/maintained-apps/outputs/onionshare/darwin.json
  • ee/maintained-apps/outputs/opencode-desktop/darwin.json
  • ee/maintained-apps/outputs/postman/darwin.json
  • ee/maintained-apps/outputs/raindropio/darwin.json
  • ee/maintained-apps/outputs/remote-desktop-manager/darwin.json
  • ee/maintained-apps/outputs/rightfont/darwin.json
  • ee/maintained-apps/outputs/roboform/darwin.json
  • ee/maintained-apps/outputs/spokenly/darwin.json
  • ee/maintained-apps/outputs/spyder/windows.json
  • ee/maintained-apps/outputs/sql-server-management-studio/windows.json
  • ee/maintained-apps/outputs/tableplus/darwin.json
  • ee/maintained-apps/outputs/tabtab/darwin.json
  • ee/maintained-apps/outputs/tailscale/windows.json
  • ee/maintained-apps/outputs/teamviewer/darwin.json
  • ee/maintained-apps/outputs/thaw/darwin.json
  • ee/maintained-apps/outputs/thunderbird/darwin.json
  • ee/maintained-apps/outputs/thunderbird/windows.json
  • ee/maintained-apps/outputs/webcatalog/darwin.json
  • ee/maintained-apps/outputs/wechat/darwin.json
  • ee/maintained-apps/outputs/whatsapp/darwin.json
  • ee/maintained-apps/outputs/workflowy/darwin.json

"refs": {
"1d854c01": "#!/bin/bash\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 [[ $EUID -eq 0 && \"$console_user\" == \"root\" ]]; 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\nquit_application 'com.1password.1password'\ninstaller -pkg \"$INSTALLER_PATH\" -target /\n\n",
"c927cf5a": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\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\nquit_application 'com.1password.1password'\nsudo rm -rf \"$APPDIR/1Password.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/2BUA8C4S2C.com.1password*'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/2BUA8C4S2C.com.agilebits'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.1password.1password-launcher'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.1password.browser-support'\ntrash $LOGGED_IN_USER '~/Library/Application Support/1Password'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Arc/User Data/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.1password.1password.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/1Password*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Beta/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Canary/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Dev/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Beta/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Canary/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Dev/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Mozilla/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Vivaldi/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Containers/2BUA8C4S2C.com.1password.browser-helper'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.1password.1password*'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.1password.browser-support'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/2BUA8C4S2C.com.1password'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/2BUA8C4S2C.com.agilebits'\ntrash $LOGGED_IN_USER '~/Library/Logs/1Password'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.1password.1password.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/group.com.1password.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.1password.1password.savedState'\n"
"a642a51c": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\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 '2BUA8C4S2C.com.1password.browser-helper'\nremove_launchctl_service 'com.1password.1password-launcher'\nquit_application 'com.1password.1password'\nsudo rm -rf \"$APPDIR/1Password.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/2BUA8C4S2C.com.1password*'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/2BUA8C4S2C.com.agilebits'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.1password.1password-launcher'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.1password.browser-support'\ntrash $LOGGED_IN_USER '~/Library/Application Support/1Password'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Arc/User Data/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.1password.1password.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/1Password*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Beta/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Canary/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Dev/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Beta/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Canary/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Dev/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Mozilla/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Vivaldi/NativeMessagingHosts/com.1password.1password.json'\ntrash $LOGGED_IN_USER '~/Library/Containers/2BUA8C4S2C.com.1password.browser-helper'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.1password.1password*'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.1password.browser-support'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/2BUA8C4S2C.com.1password'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/2BUA8C4S2C.com.agilebits'\ntrash $LOGGED_IN_USER '~/Library/Logs/1Password'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.1password.1password.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/group.com.1password.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.1password.1password.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

Use the correct launchd domain for status checks and user paths.

should_sudo only changes the removal command; plist_status=$(launchctl list ...) always checks the caller’s domain. Consequently, system services may never be detected, while user services can be checked in one domain and removed in another. Also, $HOME resolves to /var/root when the uninstall runs as root, so user LaunchAgent plists are not removed from the logged-in user’s home.

Use explicit system/GUI domains and build user paths from LOGGED_IN_USER.

Proposed correction
-      plist_status=$(launchctl list "${service_label}" 2>/dev/null)
+      if [[ "$should_sudo" == "true" ]]; then
+        plist_status=$(sudo launchctl list "${service_label}" 2>/dev/null)
+      else
+        plist_status=$(launchctl list "${service_label}" 2>/dev/null)
+      fi
...
-          paths[i]="${HOME}${paths[i]}"
+          paths[i]="/Users/${LOGGED_IN_USER}${paths[i]}"

Verify both the system and logged-in-user domains on a test Mac:

#!/bin/bash
user=$(stat -f "%Su" /dev/console)
uid=$(id -u "$user")

for label in \
  "2BUA8C4S2C.com.1password.browser-helper" \
  "com.1password.1password-launcher"; do
  sudo launchctl print "system/$label" >/dev/null 2>&1 && echo "system: $label"
  launchctl print "gui/$uid/$label" >/dev/null 2>&1 && echo "user: $label"
done
🤖 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/1password/darwin.json` at line 20, Update
remove_launchctl_service to check and remove services through explicit system
and logged-in-user GUI launchd domains rather than relying on the caller’s
default domain. Derive the user domain from LOGGED_IN_USER and its UID, and
construct user LaunchAgent paths from /Users/$LOGGED_IN_USER instead of $HOME,
while preserving the existing sudo handling for system operations.

Comment on lines +19 to +20
"03b9aece": "#!/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/Cursor.app\"\nsudo rm -rf 'cursor'\ntrash $LOGGED_IN_USER '~/.cursor'\ntrash $LOGGED_IN_USER '~/.cursor-tutor'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Caches/cursor-updater'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.todesktop.230313mzl4w4u92.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Cursor'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.todesktop.*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.todesktop.*.ShipIt'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.todesktop.*'\ntrash $LOGGED_IN_USER '~/Library/Logs/Cursor'\ntrash $LOGGED_IN_USER '~/Library/Preferences/ByHost/com.todesktop.*.ShipIt.*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.todesktop.*.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.todesktop.*.savedState'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/todesktop.com.ToDesktop-Installer.savedState'\n",
"5147ff0b": "#!/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# extract contents\nunzip \"$INSTALLER_PATH\" -d \"$TMPDIR\"\n# copy to the applications folder\nquit_and_track_application 'com.todesktop.230313mzl4w4u92'\nif [ -d \"$APPDIR/Cursor.app\" ]; then\n\tsudo mv \"$APPDIR/Cursor.app\" \"$TMPDIR/Cursor.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Cursor.app\" \"$APPDIR\"\nrelaunch_application 'com.todesktop.230313mzl4w4u92'\nmkdir -p .\n/bin/ln -h -f -s -- \"$APPDIR/Cursor.app/Contents/Resources/app/bin/code\" \"cursor\"\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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not recursively delete a relative path as root.

Line 19 runs sudo rm -rf 'cursor'. If uninstall runs from a directory containing an unrelated cursor directory, it can delete that directory recursively; from another working directory, it will not remove the link created by the installer on Line 20. Use one absolute CLI-link path in both scripts and remove only the expected symlink with rm --.

🤖 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/cursor/darwin.json` around lines 19 - 20, Update
the uninstall script’s `sudo rm -rf 'cursor'` command to target the same
absolute CLI-link path created by the installer’s `/bin/ln` command, and remove
it with `rm --` rather than recursive deletion. Ensure the installer and
uninstaller consistently reference that absolute expected symlink path.

],
"refs": {
"46a242eb": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\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\nquit_application 'com.hnc.Discord'\nquit_application 'com.hnc.Discord.helper.Plugin'\nquit_application 'com.hnc.Discord.helper.Renderer'\nsudo rm -rf \"$APPDIR/Discord.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.hnc.discord.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/discord'\ntrash $LOGGED_IN_USER '~/Library/Application%20Support/discord'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.hnc.Discord'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.hnc.Discord.ShipIt'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.hnc.Discord.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.hnc.Discord'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.hnc.Discord.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.hnc.Discord.helper.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.hnc.Discord.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.hnc.Discord.savedState'\n",
"450a6520": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\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.discord.discord.ShipIt'\nquit_application 'com.hnc.Discord'\nquit_application 'com.hnc.Discord.helper.Plugin'\nquit_application 'com.hnc.Discord.helper.Renderer'\nsudo rm -rf \"$APPDIR/Discord.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.hnc.discord.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/discord'\ntrash $LOGGED_IN_USER '~/Library/Application%20Support/discord'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.hnc.Discord'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.hnc.Discord.ShipIt'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.hnc.Discord.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.hnc.Discord'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.hnc.Discord.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/ByHost/com.discord.discord.ShipIt.*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.hnc.Discord.helper.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.hnc.Discord.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.hnc.Discord.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

printf 'Locate target files:\n'
git ls-files 'ee/maintained-apps/outputs/*/*.json' | grep -E '/(disc ord|chatgpt-atlas)/darwin\.json$' || true

printf '\nRelevant snippets:\n'
for f in ee/maintained-apps/outputs/discord/darwin.json ee/maintained-apps/outputs/chatgpt-atlas/darwin.json; do
  if [ -f "$f" ]; then
    echo "--- $f"
    sed -n '1,260p' "$f"
  fi
done

printf '\nSearch for generated darwin uninstall targets and helper patterns:\n'
rg -n "remove_launchctl_service|launch_agent|LaunchAgent|shipit|update-helper|dar win|darwin\.json" ee/maintained-apps ee -g '*.ts' -g '*.js' -g '*.go' -g '*.json' | head -n 200

Repository: fleetdm/fleet

Length of output: 1915


🌐 Web query:

Apple launchctl user launchd domain sudo /bin/launchctl remove user -l <UID> default Domain.plist

💡 Result:

The command format you mentioned, "sudo /bin/launchctl remove user default Domain.plist," is not a valid or standard command in modern macOS (macOS 10.10 and later) [1][2]. The "remove" subcommand is legacy syntax, and the management of services now relies on the "bootstrap" and "bootout" subcommands [1][3][4]. In modern macOS, you manage services by targeting specific "domains" rather than manipulating plist files directly by path [5][6]. The correct modern approach to unload a service is to use the bootout command [4]. Common and Correct Modern Syntax: 1. Identify the Domain and Service: - GUI-based services (most user-level agents) reside in the gui/<UID> domain [5][6]. - Non-GUI user services reside in the user/<UID> domain [5][6]. - System-wide services reside in the system/ domain (requires root/sudo) [5][6]. - You can find your current UID by running id -u [2][7][4]. 2. Unload (Stop) a Service: To unload a service, use the following syntax [4]: launchctl bootout <domain-target>/<service-identifier> Example (for a GUI user with UID 501): launchctl bootout gui/501/com.example.service [8][4] 3. Disable a Service Persistently: If you want to prevent a service from starting entirely (across reboots), use the disable command [7][6]: launchctl disable <domain-target>/<service-identifier> [8] Example: launchctl disable gui/501/com.example.service [8] Key Notes: - Bootout vs. Unload: bootout replaces the legacy unload command [1][4]. It requires the service identifier (the name inside the plist), not the file path [4]. - Privileges: You typically do not need sudo for gui/ or user/ domain services if you are acting on your own domain [1]. If you are managing system/ services, sudo is required [1][3][4]. - Error Handling: Unlike legacy commands, bootout and bootstrap provide clear error messages if they fail [4]. If you receive an "Input/output error," it often indicates the service is in a domain context you are not currently part of (e.g., trying to unload a GUI service from an SSH session) [9][10].

Citations:


🌐 Web query:

macOS LaunchAgents path user Home Library LaunchAgents default plist launchctl remove sudo launchctl bootout user UID

💡 Result:

On macOS, LaunchAgents are service configuration files (property lists, or plists) that tell launchd to run tasks on behalf of a specific user [1][2]. The default path for user-specific LaunchAgents is: /Library/LaunchAgents For these agents, the user does not need to use sudo to manage them, as they run with the privileges of the logged-in user [3][4]. Managing LaunchAgents via launchctl The modern, preferred way to manage these services is using the bootstrap and bootout subcommands [5][6]. 1. Identify your UID You can find your current user ID (UID) by running: id -u 2. Use bootout to remove a service To stop and remove a service, use the bootout subcommand. Note that while bootstrap accepts a path, bootout requires the domain and the service label (not the file path) [5]. launchctl bootout gui/UID/label For example, if your UID is 501 and your label is com.example.myagent: launchctl bootout gui/501/com.example.myagent Key Distinctions - bootstrap vs. bootout: bootstrap targets a file path to load a service, while bootout targets a service label to unload it [7][5]. - gui/UID: This domain represents the user's graphical session. Using this domain ensures the agent runs in the correct session context [5][6]. - sudo: Generally, you should not use sudo for user LaunchAgents (/Library/LaunchAgents). sudo is typically reserved for system-wide services located in /Library/LaunchDaemons or /Library/LaunchAgents, which run under the system domain [5][4]. If you are acting on your own LaunchAgent, running command-line tools without sudo is sufficient and safer [4]. If a service is stubborn, you can use launchctl print gui/UID/label to inspect its status or use kickstart -k to force-restart it without fully unloading/loading it [5].

Citations:


🌐 Web query:

macOS launchctl remove legacy remove service sudo bootout domain

💡 Result:

In modern macOS versions, the launchctl remove and unload commands are considered legacy [1][2][3]. Apple recommends using bootout for managing the removal or stopping of services [1][2]. Key differences and usage guidance: 1. Modern Syntax (Recommended): Use launchctl bootout to stop and remove a service [1][2][3]. Unlike the legacy unload command, bootout requires specifying the target domain (e.g., gui/UID, system/, or user/UID) along with the service identifier [3][4]. - Example: launchctl bootout system/com.example.service [4] - Example (for a user agent): launchctl bootout gui/$(id -u) /path/to/service.plist [3] 2. Legacy Commands: launchctl remove and unload are older methods that may still work but are generally discouraged in favor of bootout and bootstrap [1][2][5]. remove by label is particularly noted in legacy documentation as a way to remove a job, but it is not the preferred modern practice [6][7]. 3. Sudo Usage: Modern launchctl commands generally do not require sudo if you are targeting your own user session (e.g., gui/UID) [2][3]. However, sudo is required when managing system-wide services located in /Library/LaunchDaemons (targeting the system/ domain) [3][4]. In summary, for current macOS environments, prefer launchctl bootout <domain>/<service_label_or_path> over the older remove or unload commands to ensure better error reporting and adherence to current system architecture [1][3].

Citations:


Run launchAgent cleanup in the console user’s launchd domain.

When the uninstaller runs as root, launchctl list/remove operates in the system/root domain and $HOME points to /root, so user LaunchAgents saved at ~/Library/LaunchAgents can remain installed. Find the console user’s UID and home directory, target that user’s gui/<uid> launchd domain for the jobs, and delete the user plist from that home directory instead.

  • ee/maintained-apps/outputs/discord/darwin.json#L19-<tail>: apply this to com.discord.discord.ShipIt.
  • ee/maintained-apps/outputs/chatgpt-atlas/darwin.json#L19-<tail>: apply this to com.openai.atlas.update-helper.
📍 Affects 2 files
  • ee/maintained-apps/outputs/discord/darwin.json#L19-L19 (this comment)
  • ee/maintained-apps/outputs/chatgpt-atlas/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/discord/darwin.json` at line 19, The launchAgent
cleanup in remove_launchctl_service currently uses the root launchd domain and
/root when run as root. In ee/maintained-apps/outputs/discord/darwin.json:19-19,
update cleanup for com.discord.discord.ShipIt to resolve the console user’s UID
and home directory, run launchctl list/remove against gui/<uid>, and remove the
plist from that user’s home LaunchAgents directory; apply the same change to
com.openai.atlas.update-helper in
ee/maintained-apps/outputs/chatgpt-atlas/darwin.json:19-19.

"214a13cd": "#!/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.elgato.StreamDeck'\nsudo installer -pkg \"$TMPDIR/Stream_Deck_7.5.0.22885.pkg\" -target /\nrelaunch_application 'com.elgato.StreamDeck'\n",
"b842b140": "#!/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.elgato.StreamDeck*'\nquit_application 'com.elgato.StreamDeck'\nremove_pkg_files 'com.elgato.StreamDeck'\nforget_pkg 'com.elgato.StreamDeck'\nsudo rm -rf '/Applications/Elgato Stream Deck.app'\nsudo rm -rf '/Library/LaunchAgents/com.elgato.StreamDeck.plist'\nsudo rm -rf '~/Library/LaunchAgents/com.elgato.StreamDeck.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.elgato.StreamDeck'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.elgato.StreamDeck'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.elgato.StreamDeck'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.elgato.StreamDeck.plist'\n"
"81223e72": "#!/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.elgato.StreamDeck'\nsudo installer -pkg \"$TMPDIR/Stream_Deck_7.5.1.22901.pkg\" -target /\nrelaunch_application 'com.elgato.StreamDeck'\n",
"aff3ed62": "#!/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.elgato.StreamDeck*'\nremove_launchctl_service 'com.elgato.StreamDeck.trampoline'\nquit_application 'com.elgato.StreamDeck'\nremove_pkg_files 'com.elgato.StreamDeck'\nforget_pkg 'com.elgato.StreamDeck'\nsudo rm -rf '/Applications/Elgato Stream Deck.app'\nsudo rm -rf '/Library/LaunchAgents/com.elgato.StreamDeck.plist'\nsudo rm -rf '~/Library/LaunchAgents/com.elgato.StreamDeck.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.elgato.StreamDeck'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.elgato.StreamDeck'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.elgato.StreamDeck'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.elgato.StreamDeck.plist'\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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve cleanup for unloaded launch services.

remove_launchctl_service 'com.elgato.StreamDeck*' returns before deleting plist paths when no matching service is loaded. After removing the explicit com.elgato.StreamDeck.trampoline call, a stale trampoline plist can remain; the quoted ~/Library/... command does not provide a working fallback. Restore the explicit cleanup or change the helper to remove matching plist files even when services are unloaded.

🤖 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/elgato-stream-deck/darwin.json` at line 20, Update
remove_launchctl_service so wildcard patterns still remove matching plist files
when no matching launchctl service is loaded; do not return before the
path-cleanup loops. Preserve the explicit service handling and ensure the stale
com.elgato.StreamDeck.trampoline plist is cleaned up, since the quoted ~/Library
fallback is ineffective.

"5fe54a7b": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\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\nquit_application 'com.figma.agent'\nsudo rm -rf \"$APPDIR/Figma.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/Figma'\ntrash $LOGGED_IN_USER '~/Library/Application Support/figma-desktop'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.figma.agent'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.figma.Desktop'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.figma.Desktop.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.figma.Desktop.savedState'\n",
"f0952230": "#!/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# extract contents\nunzip \"$INSTALLER_PATH\" -d \"$TMPDIR\"\n# copy to the applications folder\nquit_and_track_application 'com.figma.Desktop'\nif [ -d \"$APPDIR/Figma.app\" ]; then\n\tsudo mv \"$APPDIR/Figma.app\" \"$TMPDIR/Figma.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Figma.app\" \"$APPDIR\"\nrelaunch_application 'com.figma.Desktop'\n"
"f0952230": "#!/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# extract contents\nunzip \"$INSTALLER_PATH\" -d \"$TMPDIR\"\n# copy to the applications folder\nquit_and_track_application 'com.figma.Desktop'\nif [ -d \"$APPDIR/Figma.app\" ]; then\n\tsudo mv \"$APPDIR/Figma.app\" \"$TMPDIR/Figma.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Figma.app\" \"$APPDIR\"\nrelaunch_application 'com.figma.Desktop'\n",
"ffdf9bc3": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\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\nquit_application 'com.figma.agent'\nsudo rm -rf \"$APPDIR/Figma.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.figma.desktop.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Figma'\ntrash $LOGGED_IN_USER '~/Library/Application Support/figma-desktop'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.figma.agent'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.figma.Desktop'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.figma.agent'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.figma.Desktop.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.figma.Desktop.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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate figma files =="
git ls-files | rg '(^|/)figma/|manifest|darwin\.json|package|apps|maintained-apps' | sed -n '1,200p'

echo
echo "== relevant figma files =="
fd -d 3 -a '.*' . | rg 'figma' || true

echo
echo "== search Figma references =="
rg -n "Figma|figma|application id|quit_application|com\.figma|manifest|bundle_id|bundleIdentifier" -S . --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' | sed -n '1,240p'

Repository: fleetdm/fleet

Length of output: 1915


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files present? =="
pwd
git rev-parse --show-toplevel || true
git status --short || true
git ls-files ee/maintained-apps 'figma*' -z 2>/dev/null | tr '\0' '\n' | sed -n '1,200p' || true

echo
echo "== targeted location =="
if [ -f ee/maintained-apps/outputs/figma/darwin.json ]; then
  wc -l ee/maintained-apps/outputs/figma/darwin.json
  sed -n '1,40p' ee/maintained-apps/outputs/figma/darwin.json
else
  fd -a 'darwin\.json|manifest|README' . | sed -n '1,120p' || true
fi

echo
echo "== focused references with bounded output =="
rg -n "com\.figma|Figma|figma|quit_application|application id|bundle_id|bundleIdentifier|manifest" ee/maintained-apps outputs packages apps 2>/dev/null | sed -n '1,240p' || true

Repository: fleetdm/fleet

Length of output: 1915


🌐 Web query:

Figma macOS application bundle identifier com.figma.Desktop com.figma.agent

💡 Result:

The macOS bundle identifiers for Figma are as follows: - com.figma.Desktop: This is the bundle identifier for the main Figma desktop application [1][2]. It is used for system configurations, such as managing property list settings (e.g., via the defaults command) [1], and is the standard identifier used by device management and security tools [3][4]. - com.figma.agent: This is the bundle identifier for the Figma Agent (also referred to as FigmaAgent) [5][6]. The Figma Agent is a background utility that automatically detects local fonts and makes them available within the Figma application [7][8]. It operates as a background service and is often associated with Login Items and Launch Agents on macOS [9][6][8]. While the main application identifier (com.figma.Desktop) is public and stable [1][10], the Figma Agent (com.figma.agent) is a supplementary process [7]. Users or administrators managing these applications may need to interact with both identifiers when configuring permissions, startup items, or cleanup scripts [5][11][4][6].

Citations:


Quit the managed Figma application, not only its agent.

The uninstall cleans up Figma.app and com.figma.Desktop data, but only quits com.figma.agent. Quit com.figma.Desktop too so rm -rf "$APPDIR/Figma.app" does not try to remove the app while the main Figma process is still running.

🤖 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/figma/darwin.json` at line 20, Update the
uninstall sequence around quit_application to also quit the managed Figma
desktop bundle ID, com.figma.Desktop, before removing Figma.app; retain the
existing com.figma.agent shutdown and cleanup behavior.

Comment on lines +11 to +19
"uninstall_script_ref": "292c5733",
"sha256": "a90d1dd5c5cad5fdae375968a39149fd337574834bdd9cdaef9ef7be9bc203b0",
"default_categories": [
"Browsers"
]
}
],
"refs": {
"a313f903": "#!/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# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\" || exit 1\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\n# copy to the applications folder\nquit_and_track_application 'org.mozilla.firefoxdeveloperedition'\nif [ -d \"$APPDIR/Firefox Developer Edition.app\" ]; then\n\tsudo mv \"$APPDIR/Firefox Developer Edition.app\" \"$TMPDIR/Firefox Developer Edition.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Firefox Developer Edition.app\" \"$APPDIR\"\nrelaunch_application 'org.mozilla.firefoxdeveloperedition'\n",
"b108b0ff": "#!/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/Firefox Developer Edition.app\"\nsudo rmdir '~/Library/Application Support/Mozilla'\nsudo rmdir '~/Library/Caches/Mozilla'\nsudo rmdir '~/Library/Caches/Mozilla/updates'\nsudo rmdir '~/Library/Caches/Mozilla/updates/Applications'\ntrash $LOGGED_IN_USER '/Library/Logs/DiagnosticReports/firefox_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/org.mozilla.firefox.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/firefox_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Firefox'\ntrash $LOGGED_IN_USER '~/Library/Caches/Firefox'\ntrash $LOGGED_IN_USER '~/Library/Caches/Mozilla/updates/Applications/Firefox'\ntrash $LOGGED_IN_USER '~/Library/Caches/org.mozilla.firefox'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.firefox.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.mozilla.firefox.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/org.mozilla.firefox'\n"
"292c5733": "#!/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/Firefox Developer Edition.app\"\nsudo rmdir '~/Library/Application Support/Mozilla'\nsudo rmdir '~/Library/Caches/Mozilla'\nsudo rmdir '~/Library/Caches/Mozilla/updates'\nsudo rmdir '~/Library/Caches/Mozilla/updates/Applications'\ntrash $LOGGED_IN_USER '/Library/Logs/DiagnosticReports/firefox_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/org.mozilla.firefox.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/firefox_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Firefox'\ntrash $LOGGED_IN_USER '~/Library/Caches/Firefox'\ntrash $LOGGED_IN_USER '~/Library/Caches/Mozilla/updates/Applications/Firefox'\ntrash $LOGGED_IN_USER '~/Library/Caches/org.mozilla.firefox'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.firefox.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.firefoxdeveloperedition.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.mozilla.firefox.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/org.mozilla.firefox'\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

Do not remove regular Firefox data when uninstalling Developer Edition.

The manifest identifies this app as org.mozilla.firefoxdeveloperedition, but the new script trashes regular Firefox paths and identifiers, including ~/Library/Application Support/Firefox and org.mozilla.firefox state. On systems with both products installed, this can remove the regular Firefox profile during Developer Edition uninstall. Scope cleanup to Developer Edition-specific paths.

🤖 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/firefox`@developer-edition/darwin.json around
lines 11 - 19, Update uninstall script ref 292c5733 to remove only Firefox
Developer Edition data, replacing regular Firefox paths and identifiers such as
~/Library/Application Support/Firefox, org.mozilla.firefox, and firefox_* with
their Developer Edition-specific equivalents matching
org.mozilla.firefoxdeveloperedition. Preserve cleanup of shared Mozilla
directories only where it cannot affect regular Firefox data.

Comment on lines +4 to +7
"version": "7.0",
"queries": {
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'app.kaleidoscope.v6';",
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'app.kaleidoscope.v6' AND version_compare(bundle_short_version, '6.7') < 0);"
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'app.kaleidoscope.v6' AND version_compare(bundle_short_version, '7.0') < 0);"

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 "== locate kaleidoscope files =="
fd -i 'kaleidoscope|kaleidoscope' . || true

echo
echo "== target darwin.json relevant lines =="
if [ -f ee/maintained-apps/outputs/kaleidoscope/darwin.json ]; then
  cat -n ee/maintained-apps/outputs/kaleidoscope/darwin.json
fi

echo
echo "== inspect installed app identifiers/scripts =="
for f in $(fd -i 'kaleidoscope.*\.(sh|json|yaml|yml)$' ee/maintained-apps/outputs 2>/dev/null || true); do
  echo "--- $f"
  rg -n "app\.kaleidoscope\.(v6|v7)|kaleidoscope|bundle_identifier|identify|quitall|quit" "$f" || true
done

echo
echo "== broader bundle id search =="
rg -n "app\.kaleidoscope\.(v6|v7)|kaleidoscope\.v6|kaleidoscope\.v7" .

Repository: fleetdm/fleet

Length of output: 1915


🌐 Web query:

Kaleidoscope.app CFBundleIdentifier app.kaleidoscope.v6 app.kaleidoscope.v7

💡 Result:

The Kaleidoscope app uses version-specific CFBundleIdentifiers for its major releases to allow multiple versions to coexist on the same system. The identifiers app.kaleidoscope.v6 and app.kaleidoscope.v7 are the official CFBundleIdentifiers for Kaleidoscope version 6 and version 7, respectively [1][2]. Earlier versions of the application utilized different identifier schemes, such as app.kaleidoscope.v4 for version 4 [3] and com.blackpixel.kaleidoscope for older releases managed by the previous developer, Black Pixel [4]. These bundle identifiers are essential for macOS management, as they are used by system tools for configuration, installation, and uninstallation, as well as for defining application-specific paths in the Library folder (e.g., Application Support, Caches, and Saved Application State) [1][2].

Citations:


Align the Kaleidoscope operations with the 7.0 bundle identifier.

Kaleidoscope 7 uses app.kaleidoscope.v7, while this package still queries and quits/installer-relaunches app.kaleidoscope.v6. Update the detection queries, installer, and uninstallant scripts to target app.kaleidoscope.v7 consistently.

🤖 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/kaleidoscope/darwin.json` around lines 4 - 7,
Update the Kaleidoscope package’s detection queries and all related installer,
quit, relaunch, and uninstallant references from app.kaleidoscope.v6 to
app.kaleidoscope.v7, while preserving the existing version checks and operation
flow.

"refs": {
"3a2f038c": "#!/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.nordvpn.macos.teams'\nsudo installer -pkg \"$TMPDIR/NordLayer_v3.11.0.pkg\" -target /\nrelaunch_application 'com.nordvpn.macos.teams'\n",
"97f61740": "#!/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.nordvpn.macos.teams'\nremove_launchctl_service 'com.nordvpn.macos.teams.assistant'\nremove_launchctl_service 'com.nordvpn.macos.teams.helper'\nquit_application 'com.nordvpn.macos.teams'\nremove_pkg_files 'com.nordvpn.macos.teams'\nforget_pkg 'com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.nordvpn.macos.teams.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.nordvpn.macos.teams.plist'\n"
"4ab665bb": "#!/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.nordvpn.macos.teams'\nremove_launchctl_service 'com.nordvpn.macos.teams.assistant'\nremove_launchctl_service 'com.nordvpn.macos.teams.helper'\nquit_application 'com.nordvpn.macos.teams'\nremove_pkg_files 'com.nordvpn.macos.teams'\nforget_pkg 'com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/group.com.nordlayer.macos'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.nordvpn.macos.teams.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/group.com.nordlayer.macos'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.nordvpn.macos.teams'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.nordvpn.macos.teams.plist'\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

echo "Files:"
git ls-files | rg '(^|/)darwi.*\.json$|nordlayer' || true

echo
echo "Target file snippets:"
if [ -f ee/maintained-apps/outputs/nordlayer/darwin.json ]; then
  nl -ba ee/maintained-apps/outputs/nordlayer/darwin.json | sed -n '1,80p'
fi

echo
echo "Occurrences of console user and LOGGED_IN_USER in context:"
rg -n "ConsoleUser|console_user|LOGGED_IN_USER|quitting application|remove_launchctl_service|quit_application|remove_pkg_files|forget_pkg|trash" .

Repository: fleetdm/fleet

Length of output: 1915


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD:"
pwd

echo "Top-level entries:"
ls -la | sed -n '1,80p'

echo "Candidate files:"
git ls-files | rg '(^|/)darwi.*\.json$|nordlayer' || true

echo
echo "Target file snippets:"
if [ -f ee/maintained-apps/outputs/nordlayer/darwin.json ]; then
  nl -ba ee/maintained-apps/outputs/nordlayer/darwin.json | sed -n '1,90p'
fi

echo
echo "Occurrences of console user and LOGGED_IN_USER in context:"
rg -n "ConsoleUser|console_user|LOGGED_IN_USER|quitting application|remove_launchctl_service|quit_application|remove_pkg_files|forget_pkg|trash" . || true

Repository: fleetdm/fleet

Length of output: 1915


Guard user-specific cleanup when no console user is available.

LOGGED_IN_USER is populated from State:/Users/ConsoleUser; during unattended installation it can be empty, root, or loginwindow. Skipping the subsequent trash calls when it is invalid, and quoting $LOGGED_IN_USER, prevents malformed cleanup commands and avoids leaving user-scoped data behind.

🤖 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/nordlayer/darwin.json` at line 20, Validate
LOGGED_IN_USER after it is populated, and only execute the subsequent
user-specific trash calls when it is non-empty and not root or loginwindow.
Quote the LOGGED_IN_USER argument at each trash invocation, preserving the
existing cleanup behavior for valid console users.

],
"refs": {
"20e49d94": "#!/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/TablePlus.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.tinyapp.TablePlus'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.tinyapp.TablePlus'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.tinyapp.TablePlus.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.tinyapp.TablePlus.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.tinyapp.TablePlus.savedState'\n",
"19abdcd7": "#!/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/TablePlus.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.tinyapp.tableplus.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.tinyapp.TablePlus'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.tinyapp.TablePlus'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.tinyapp.TablePlus.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.tinyapp.TablePlus'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.tinyapp.TablePlus.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.tinyapp.TablePlus.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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Candidate files"
fd -a 'darwin\.json$' . | sed 's#^\./##' | grep 'tableplus/darwin.json' || true

echo "## Relevant tableplus darwin.json"
if [ -f ee/maintained-apps/outputs/tableplus/darwin.json ]; then
  wc -l ee/maintained-apps/outputs/tableplus/darwin.json
  cat -n ee/maintained-apps/outputs/tableplus/darwin.json
fi

echo "## Occurrences of tableplus identifiers in maintained-apps outputs"
rg -n "microapp|TablePlus|tableplus|sfl|SFL|sharedfilelist" ee/maintained-apps/outputs/tableplus ee/maintained-apps 2>/dev/null | head -200 || true

Repository: fleetdm/fleet

Length of output: 1915


🌐 Web query:

TablePlus macOS bundle identifier com.tinyapp.TablePlus ApplicationRecentDocuments sfl file

💡 Result:

The macOS application bundle identifier for TablePlus is com.tinyapp.TablePlus [1][2][3]. Regarding the.sfl files you mentioned: these are Shared File List files managed by the macOS system service sharedfilelistd [4]. In macOS, applications that utilize standard AppKit document management (via NSDocumentController) delegate the tracking of "Recent Documents" to the operating system [5]. Specifically: 1. Storage Location: Recent documents for applications are stored in a system-managed directory at ~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/ [4][6][5]. 2. File Format: For TablePlus, macOS maintains a file named com.tinyapp.TablePlus.sfl (or a variant like.sfl2) within that directory [4][7]. 3. Purpose: These.sfl files are binary property lists that store bookmarks to recently opened documents, which the system uses to populate the Open Recent menu in the application's File menu [4][7][5]. Because these files are maintained by the system service sharedfilelistd, they are not intended to be managed or edited directly by users or the application itself [5]. Changes to the Recent Items list are handled automatically by macOS when the application opens or interacts with files [4]. Do not attempt to manually modify these files, as they are system-protected and may cause issues with the application's ability to track recent files correctly [4][5].

Citations:


Keep the shared-file-list cleanup case-sensitive.

macOS records TablePlus recent documents as com.tinyapp.TablePlus.sfl*; com.tinyapp.tableplus.sfl* will not match on a case-sensitive volume. Use the exact bundle-identifier filename.

🤖 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/tableplus/darwin.json` at line 19, Update the
shared-file-list cleanup argument in the script to use the exact case-sensitive
filename prefix com.tinyapp.TablePlus.sfl*. Leave the trash function and other
cleanup paths unchanged.

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