Skip to content

Update Fleet-maintained apps - #50138

Merged
allenhouchins merged 2 commits into
mainfrom
fma-2607291235
Jul 29, 2026
Merged

Update Fleet-maintained apps#50138
allenhouchins merged 2 commits into
mainfrom
fma-2607291235

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 versions of AnyBurn, BetterZip, Canva, Eclipse Temurin, ExifCleaner, Gemini, LibreOffice, NordVPN, Postman, TablePlus, Tailscale, and other maintained applications across Windows and macOS.
  • Bug Fixes
    • Improved macOS uninstall cleanup for numerous applications by removing additional caches, preferences, recent-document entries, containers, support files, and related data.
    • Updated installer downloads and verification checks to match the latest releases.

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

Copy link
Copy Markdown
Contributor

Script Diff Results

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 31c0d61d -> ab5ff0cc ===

--- /tmp/old.y9uFrh	2026-07-29 12:40:50.430718669 +0000
+++ /tmp/new.QMczLj	2026-07-29 12:40:50.430718669 +0000
@@ -169,6 +169,13 @@
 sudo rm -rf '/Library/ScriptingAdditions/BartenderHelper.osax'
 sudo rm -rf '/System/Library/ScriptingAdditions/BartenderSystemHelper.osax'
 sudo rm -rf "$APPDIR/Bartender 6.app"
+trash $LOGGED_IN_USER '~/Library/Application Scripts/24J875RH8J.com.surteesstudios.Bartender'
+trash $LOGGED_IN_USER '~/Library/Application Support/Bartender 6'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.surteesstudios.bartender.sfl*'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.surteesstudios.Bartender.revenuecat'
 trash $LOGGED_IN_USER '~/Library/Caches/com.surteesstudios.Bartender'
+trash $LOGGED_IN_USER '~/Library/Caches/com.surteesstudios.Bartender.revenuecat'
 trash $LOGGED_IN_USER '~/Library/Cookies/com.surteesstudios.Bartender.binarycookies'
+trash $LOGGED_IN_USER '~/Library/Group Containers/24J875RH8J.com.surteesstudios.Bartender'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.surteesstudios.Bartender'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.surteesstudios.Bartender.plist'

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

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

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 87ce57a8 -> ff358276 ===

--- /tmp/old.QzZtBL	2026-07-29 12:40:50.593722054 +0000
+++ /tmp/new.UxD7bM	2026-07-29 12:40:50.593722054 +0000
@@ -53,5 +53,6 @@
 }
 
 sudo rm -rf "$APPDIR/CMake.app"
+trash $LOGGED_IN_USER '~/Library/Preferences/com.kitware.CMakeSetup.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/org.cmake.cmake.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/org.cmake.cmake.savedState'

ee/maintained-apps/outputs/eclipse-temurin-jdk-11/windows.json

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

ee/maintained-apps/outputs/eclipse-temurin-jdk-17/windows.json

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

ee/maintained-apps/outputs/eclipse-temurin-jre-11/windows.json

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

ee/maintained-apps/outputs/eclipse-temurin-jre-21/windows.json

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

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

=== Install Script (no changes) ===
=== Uninstall // e680fa19 -> 3939b006 ===

--- /tmp/old.hJmkQw	2026-07-29 12:40:50.808726520 +0000
+++ /tmp/new.StvrLU	2026-07-29 12:40:50.808726520 +0000
@@ -55,5 +55,6 @@
 sudo rm -rf "$APPDIR/EtreCheckPro.app"
 trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.etresoft.etrecheck*.sfl*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.etresoft.EtreCheck*'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.etresoft.EtreCheck*'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.etresoft.EtreCheck*.plist'
 trash $LOGGED_IN_USER '~/Library/WebKit/com.etresoft.EtreCheck*'

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // cbcf926e -> c1bae326 ===

--- /tmp/old.S5VVM2	2026-07-29 12:40:50.948729427 +0000
+++ /tmp/new.uYUoJg	2026-07-29 12:40:50.948729427 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/GDevelop 5.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.gdevelop-app.ide.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/GDevelop 5'
 trash $LOGGED_IN_USER '~/Library/Logs/GDevelop 5'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.gdevelop-app.ide.plist'

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 7b9b9d06 -> 8135b983 ===

--- /tmp/old.eULLBh	2026-07-29 12:40:51.037731276 +0000
+++ /tmp/new.1MVm6y	2026-07-29 12:40:51.037731276 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/Granola.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.granola.app.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/Granola'
 trash $LOGGED_IN_USER '~/Library/Caches/com.granola.app'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.granola.app'

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

=== Install Script (no changes) ===
=== Uninstall // f35082d0 -> 85ab2d46 ===

--- /tmp/old.bgMAKA	2026-07-29 12:40:51.087732314 +0000
+++ /tmp/new.OA8oaN	2026-07-29 12:40:51.087732314 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/jamovi.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/org.jamovi.jamovi.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/jamovi'
 trash $LOGGED_IN_USER '~/Library/Logs/jamovi'
 trash $LOGGED_IN_USER '~/Library/Preferences/org.jamovi.jamovi.plist'

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

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

--- /tmp/old.UtVGV0	2026-07-29 12:40:51.139733394 +0000
+++ /tmp/new.79ZBse	2026-07-29 12:40:51.139733394 +0000
@@ -163,12 +163,17 @@
 remove_pkg_files 'app.kaleidoscope.uninstall_ksdiff'
 forget_pkg 'app.kaleidoscope.uninstall_ksdiff'
 sudo rm -rf "$APPDIR/Kaleidoscope.app"
+trash $LOGGED_IN_USER '~/Library/Application Scripts/app.kaleidoscope.v*.KaleidoscopePrism'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/app.kaleidoscope.v*.KSShareExtension'
 trash $LOGGED_IN_USER '~/Library/Application Support/app.kaleidoscope.v*'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.blackpixel.kaleidoscope'
 trash $LOGGED_IN_USER '~/Library/Application Support/Kaleidoscope'
 trash $LOGGED_IN_USER '~/Library/Caches/app.kaleidoscope.v*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.blackpixel.kaleidoscope'
 trash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.blackpixel.kaleidoscope'
+trash $LOGGED_IN_USER '~/Library/Containers/app.kaleidoscope.v*.KaleidoscopePrism'
+trash $LOGGED_IN_USER '~/Library/Containers/app.kaleidoscope.v*.KSShareExtension'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/app.kaleidoscope.v*'
 trash $LOGGED_IN_USER '~/Library/Preferences/app.kaleidoscope.v*.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.blackpixel.kaleidoscope.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/app.kaleidoscope.v*.savedState'

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

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

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

=== Install Script (no changes) ===
=== Uninstall // ba6e6e70 -> 3b479c55 ===

--- /tmp/old.aH1EXV	2026-07-29 12:40:51.225735180 +0000
+++ /tmp/new.IJoOZL	2026-07-29 12:40:51.225735180 +0000
@@ -236,6 +236,7 @@
 forget_pkg 'com.malwarebytes.mbam.*'
 sudo rm -rf '/Library/Application Support/Malwarebytes/MBAM'
 sudo rmdir '/Library/Application Support/Malwarebytes'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.malwarebytes.mbam'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.malwarebytes.mbam.frontend.*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.crashlytics.data/com.malwarebytes.mbam.frontend.*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.malwarebytes.mbam.frontend.*'

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

=== Install Script (no changes) ===
=== Uninstall // 3ec00885 -> 6420e7ee ===

--- /tmp/old.d0SwEm	2026-07-29 12:40:51.278736281 +0000
+++ /tmp/new.OBtB8B	2026-07-29 12:40:51.278736281 +0000
@@ -233,10 +233,14 @@
 remove_pkg_files 'com.nextcloud.desktopclient'
 forget_pkg 'com.nextcloud.desktopclient'
 sudo rm -rf '/Applications/Nextcloud.app'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/com.nextcloud.desktopclient.FileProviderExt'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.nextcloud.desktopclient.FinderSyncExt'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/NKUJUXUJ3B.com.nextcloud.desktopclient'
 trash $LOGGED_IN_USER '~/Library/Application Support/Nextcloud'
 trash $LOGGED_IN_USER '~/Library/Caches/Nextcloud'
+trash $LOGGED_IN_USER '~/Library/Containers/com.nextcloud.desktopclient.FileProviderExt'
 trash $LOGGED_IN_USER '~/Library/Containers/com.nextcloud.desktopclient.FinderSyncExt'
 trash $LOGGED_IN_USER '~/Library/Group Containers/com.nextcloud.desktopclient'
+trash $LOGGED_IN_USER '~/Library/Group Containers/NKUJUXUJ3B.com.nextcloud.desktopclient'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.nextcloud.desktopclient.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/Nextcloud'

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 6c8b1ba5 -> ddfd228c ===

--- /tmp/old.coosEA	2026-07-29 12:40:51.367738130 +0000
+++ /tmp/new.aMkI3B	2026-07-29 12:40:51.367738130 +0000
@@ -55,5 +55,6 @@
 sudo rm -rf "$APPDIR/OmniOutliner.app"
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.omnigroup.OmniOutliner6'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.omnigroup.OmniOutliner6.Thumbnails'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.omnigroup.omnioutliner*.sfl*'
 trash $LOGGED_IN_USER '~/Library/Containers/com.omnigroup.OmniOutliner6'
 trash $LOGGED_IN_USER '~/Library/Containers/com.omnigroup.OmniOutliner6.Thumbnails'

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

=== Install Script (no changes) ===
=== Uninstall // 966b2fa5 -> 20883323 ===

--- /tmp/old.HoawTR	2026-07-29 12:40:51.417739168 +0000
+++ /tmp/new.oFgmJa	2026-07-29 12:40:51.417739168 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/Postman.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.postmanlabs.mac.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.postmanlabs.mac.ShipIt'
 trash $LOGGED_IN_USER '~/Library/Application Support/Postman'
 trash $LOGGED_IN_USER '~/Library/Caches/com.postmanlabs.mac'

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

=== Install Script (no changes) ===
=== Uninstall // 86416480 -> 6a559637 ===

--- /tmp/old.pLNH4A	2026-07-29 12:40:51.467740207 +0000
+++ /tmp/new.yJRUxI	2026-07-29 12:40:51.467740207 +0000
@@ -53,10 +53,12 @@
 }
 
 sudo rm -rf "$APPDIR/Raindrop.io.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/io.raindrop.macapp.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/Raindrop.io'
 trash $LOGGED_IN_USER '~/Library/Caches/com.apple.Safari/Extensions/Raindrop.io.safariextension'
 trash $LOGGED_IN_USER '~/Library/Cookies/io.raindrop.mac.binarycookies'
 trash $LOGGED_IN_USER '~/Library/Preferences/io.raindrop.mac.helper.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/io.raindrop.mac.plist'
+trash $LOGGED_IN_USER '~/Library/Preferences/io.raindrop.macapp.plist'
 trash $LOGGED_IN_USER '~/Library/Safari/Extensions/Raindrop.io.safariextz'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/io.raindrop.mac.savedState'

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

=== Install Script (no changes) ===
=== Uninstall // 4fd2b3fa -> 07fb9072 ===

--- /tmp/old.XmNYBY	2026-07-29 12:40:51.517741245 +0000
+++ /tmp/new.KnSSir	2026-07-29 12:40:51.517741245 +0000
@@ -58,3 +58,4 @@
 trash $LOGGED_IN_USER '~/Library/Caches/com.devolutions.remotedesktopmanager'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.devolutions.remotedesktopmanager.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.devolutions.remotedesktopmanager.savedState'
+trash $LOGGED_IN_USER '~/Library/WebKit/com.devolutions.remotedesktopmanager'

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

=== Install Script (no changes) ===
=== Uninstall // 1b0bcecf -> 9714135f ===

--- /tmp/old.596oy0	2026-07-29 12:40:51.565742242 +0000
+++ /tmp/new.5sYe0W	2026-07-29 12:40:51.566742263 +0000
@@ -58,6 +58,7 @@
 trash $LOGGED_IN_USER '~/Library/Application Support/com.rightfontapp.RightFont*'
 trash $LOGGED_IN_USER '~/Library/Application Support/RightFont'
 trash $LOGGED_IN_USER '~/Library/Caches/com.rightfontapp.RightFont*'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.rightfontapp.RightFont5'
 trash $LOGGED_IN_USER '~/Library/Logs/RightFont*'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.rightfontapp.RightFont*.plist'
 trash $LOGGED_IN_USER '~/Library/WebKit/com.rightfontapp.RightFont*'

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

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

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

=== Install // 92413a45 -> 06b9111a ===

--- /tmp/old.DxicFj	2026-07-29 12:40:51.671744444 +0000
+++ /tmp/new.kbCAhz	2026-07-29 12:40:51.671744444 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'io.tailscale.ipn.macsys'
-sudo installer -pkg "$TMPDIR/Tailscale-1.98.9-macos.pkg" -target /
+sudo installer -pkg "$TMPDIR/Tailscale-1.98.10-macos.pkg" -target /
 relaunch_application 'io.tailscale.ipn.macsys'

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

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

=== Install Script (no changes) ===
=== Uninstall // d0cdc2d9 -> f00ebab2 ===

--- /tmp/old.sCutcX	2026-07-29 12:40:51.729745648 +0000
+++ /tmp/new.0IAFHw	2026-07-29 12:40:51.729745648 +0000
@@ -250,6 +250,7 @@
 forget_pkg 'TeamViewerUninstaller'
 sudo rm -rf '/Applications/TeamViewer.app'
 sudo rm -rf '/Library/Preferences/com.teamviewer*'
+trash $LOGGED_IN_USER '/Library/Application Support/TeamViewer'
 trash $LOGGED_IN_USER '~/Library/Application Support/TeamViewer'
 trash $LOGGED_IN_USER '~/Library/Caches/com.teamviewer.TeamViewer'
 trash $LOGGED_IN_USER '~/Library/Caches/TeamViewer'

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

=== Install Script (no changes) ===
=== Uninstall // 88749f85 -> 907ef18f ===

--- /tmp/old.PXTJzJ	2026-07-29 12:40:51.781746728 +0000
+++ /tmp/new.w93DqQ	2026-07-29 12:40:51.781746728 +0000
@@ -55,6 +55,7 @@
 sudo rm -rf "$APPDIR/Thunderbird.app"
 sudo rmdir '~/Library/Caches/Mozilla'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/org.mozilla.thunderbird*.sfl*'
+trash $LOGGED_IN_USER '~/Library/Application Support/Thunderbird'
 trash $LOGGED_IN_USER '~/Library/Caches/Mozilla/updates/Applications/Thunderbird*'
 trash $LOGGED_IN_USER '~/Library/Caches/Thunderbird'
 trash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.thunderbird*.plist'

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

=== Install Script (no changes) ===
=== Uninstall // d196a4c8 -> 6ca374e8 ===

--- /tmp/old.6zrLhY	2026-07-29 12:40:51.829747725 +0000
+++ /tmp/new.he6f8m	2026-07-29 12:40:51.830747746 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/WebCatalog.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.webcatalog.jordan.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/WebCatalog'
 trash $LOGGED_IN_USER '~/Library/Caches/com.webcatalog.jordan'
 trash $LOGGED_IN_USER '~/Library/Caches/com.webcatalog.jordan.ShipIt'

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

=== Install Script (no changes) ===
=== Uninstall // 18d6be1e -> a5e852b0 ===

--- /tmp/old.G3imdv	2026-07-29 12:40:51.879748764 +0000
+++ /tmp/new.ToRgIx	2026-07-29 12:40:51.879748764 +0000
@@ -96,6 +96,7 @@
 sudo rm -rf "$APPDIR/WeChat.app"
 trash $LOGGED_IN_USER '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat.IPCHelper'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/5A4RE8SF68.com.tencent.xinWeChat'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat.MiniProgram'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat.WeChatMacShare'
@@ -106,5 +107,6 @@
 trash $LOGGED_IN_USER '~/Library/Containers/com.tencent.xinWeChat.WeChatMacShare'
 trash $LOGGED_IN_USER '~/Library/Cookies/com.tencent.xinWeChat.binarycookies'
 trash $LOGGED_IN_USER '~/Library/Group Containers/$(TeamIdentifierPrefix)com.tencent.xinWeChat'
+trash $LOGGED_IN_USER '~/Library/Group Containers/5A4RE8SF68.com.tencent.xinWeChat'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.tencent.xinWeChat.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.tencent.xinWeChat.savedState'

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

=== Install Script (no changes) ===
=== Uninstall // effe5345 -> 71fe81c3 ===

--- /tmp/old.B2tYMe	2026-07-29 12:40:51.931749844 +0000
+++ /tmp/new.Oob8AW	2026-07-29 12:40:51.931749844 +0000
@@ -53,6 +53,8 @@
 }
 
 sudo rm -rf "$APPDIR/WorkFlowy.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.workflowy.desktop.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/WorkFlowy'
+trash $LOGGED_IN_USER '~/Library/Logs/WorkFlowy'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.workflowy.desktop.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.workflowy.desktop.savedState'

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Updated maintained-app output definitions across Windows and macOS. Windows entries received newer versions, installer URLs, patch-detection thresholds, and checksums. macOS entries received release metadata updates, installer reference changes, and revised uninstall scripts with expanded user-library cleanup targets, including shared recent-document data and application-specific support directories.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is too brief and omits the required template sections, checklist items, and testing details. Fill in the required template sections, including Related issue, checklist items, Testing, and any applicable migration or configuration details.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: updating Fleet-maintained app data.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fma-2607291235

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

🤖 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/cmake-app/darwin.json`:
- Line 20: Invoke the existing quit_application helper before removing each
application bundle, then preserve the existing cleanup order. Apply this in
ee/maintained-apps/outputs/cmake-app/darwin.json:20-20 for org.cmake.cmake;
etrecheckpro/darwin.json:20-20 for com.etresoft.EtreCheck4;
gdevelop/darwin.json:20-21 for com.gdevelop-app.ide; rightfont/darwin.json:20-20
for com.rightfontapp.RightFont5; thunderbird/darwin.json:20-20 for
org.mozilla.thunderbird; webcatalog/darwin.json:18-19 for com.webcatalog.jordan;
and workflowy/darwin.json:20-20 for com.workflowy.desktop.

In `@ee/maintained-apps/outputs/granola/darwin.json`:
- Line 19: Make every destructive operation in the embedded uninstall scripts
propagate failures instead of allowing later commands to mask them. Update the
scripts’ top-level command flow and the trash() helper so app removal and trash
operations fail the script; also cover package, receipt, launchctl, and
extraction cleanup where present. Apply this to
ee/maintained-apps/outputs/granola/darwin.json (anchor),
ee/maintained-apps/outputs/jamovi/darwin.json,
ee/maintained-apps/outputs/kaleidoscope/darwin.json,
ee/maintained-apps/outputs/malwarebytes/darwin.json,
ee/maintained-apps/outputs/nextcloud/darwin.json,
ee/maintained-apps/outputs/omnioutliner/darwin.json,
ee/maintained-apps/outputs/postman/darwin.json,
ee/maintained-apps/outputs/raindropio/darwin.json, and
ee/maintained-apps/outputs/remote-desktop-manager/darwin.json, preserving each
script’s existing cleanup behavior while returning a nonzero status whenever a
required operation fails.</code>

In `@ee/maintained-apps/outputs/kaleidoscope/darwin.json`:
- Line 19: Align the quit targets with the installed application identifiers: in
ee/maintained-apps/outputs/kaleidoscope/darwin.json at lines 19-19, update
quit_application to target app.kaleidoscope.v6; in
ee/maintained-apps/outputs/malwarebytes/darwin.json at lines 19-19, target
com.malwarebytes.mbam.frontend.application and remove the agent separately.

In `@ee/maintained-apps/outputs/malwarebytes/darwin.json`:
- Line 19: Update remove_launchctl_service in both
ee/maintained-apps/outputs/malwarebytes/darwin.json (line 19) and
ee/maintained-apps/outputs/nextcloud/darwin.json (line 19) so user LaunchAgents
are removed with launchctl bootout in the logged-in user’s gui domain via
LOGGED_IN_USER, while system LaunchDaemons continue using the system domain;
preserve the existing wildcard expansion and plist cleanup behavior.

In `@ee/maintained-apps/outputs/rightfont/darwin.json`:
- Line 20: Update the sudo rmdir calls in
ee/maintained-apps/outputs/rightfont/darwin.json:20-20 and
ee/maintained-apps/outputs/thunderbird/darwin.json:20-20 to use
/Users/$LOGGED_IN_USER/... paths instead of quoted ~ paths, including the
RightFont and Mozilla cache directories, so the expanded home paths are passed
to rmdir.

In `@ee/maintained-apps/outputs/teamviewer/darwin.json`:
- Line 20: Update the system preference removal command near the TeamViewer
cleanup calls so the com.teamviewer* wildcard is expanded by the shell before rm
runs; remove the quotes around the glob or explicitly enumerate the matching
preference paths while preserving sudo rm -rf behavior.

In `@ee/maintained-apps/outputs/wechat/darwin.json`:
- Line 20: Replace each single-quoted trash argument containing the literal
$(TeamIdentifierPrefix) in the script’s WeChat cleanup calls with the
corresponding concrete path or a narrowly scoped wildcard that trash() can
expand. Preserve the existing cleanup targets and avoid relying on placeholder
substitution, since trash() does not perform it.
🪄 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: 099fd2a0-ae1d-4809-88d1-c76601dc4a83

📥 Commits

Reviewing files that changed from the base of the PR and between 9c2ef14 and 7b85bc7.

📒 Files selected for processing (34)
  • ee/maintained-apps/outputs/anyburn/windows.json
  • ee/maintained-apps/outputs/bartender/darwin.json
  • ee/maintained-apps/outputs/betterzip/darwin.json
  • ee/maintained-apps/outputs/canva/darwin.json
  • ee/maintained-apps/outputs/canva/windows.json
  • ee/maintained-apps/outputs/cmake-app/darwin.json
  • ee/maintained-apps/outputs/eclipse-temurin-jdk-11/windows.json
  • ee/maintained-apps/outputs/eclipse-temurin-jdk-17/windows.json
  • ee/maintained-apps/outputs/eclipse-temurin-jre-11/windows.json
  • ee/maintained-apps/outputs/eclipse-temurin-jre-21/windows.json
  • ee/maintained-apps/outputs/etrecheckpro/darwin.json
  • ee/maintained-apps/outputs/exifcleaner/darwin.json
  • ee/maintained-apps/outputs/firefox@nightly/darwin.json
  • ee/maintained-apps/outputs/gdevelop/darwin.json
  • ee/maintained-apps/outputs/google-gemini/darwin.json
  • ee/maintained-apps/outputs/granola/darwin.json
  • ee/maintained-apps/outputs/jamovi/darwin.json
  • ee/maintained-apps/outputs/kaleidoscope/darwin.json
  • ee/maintained-apps/outputs/libreoffice/windows.json
  • ee/maintained-apps/outputs/malwarebytes/darwin.json
  • ee/maintained-apps/outputs/nextcloud/darwin.json
  • ee/maintained-apps/outputs/nordvpn/windows.json
  • ee/maintained-apps/outputs/omnioutliner/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/tableplus/windows.json
  • ee/maintained-apps/outputs/tailscale-app/darwin.json
  • ee/maintained-apps/outputs/teamviewer/darwin.json
  • ee/maintained-apps/outputs/thunderbird/darwin.json
  • ee/maintained-apps/outputs/webcatalog/darwin.json
  • ee/maintained-apps/outputs/wechat/darwin.json
  • ee/maintained-apps/outputs/workflowy/darwin.json

"87ce57a8": "#!/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/CMake.app\"\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.cmake.cmake.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.cmake.cmake.savedState'\n",
"e8baa8ae": "#!/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.cmake.cmake'\nif [ -d \"$APPDIR/CMake.app\" ]; then\n\tsudo mv \"$APPDIR/CMake.app\" \"$TMPDIR/CMake.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/CMake.app\" \"$APPDIR\"\nrelaunch_application 'org.cmake.cmake'\n"
"e8baa8ae": "#!/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.cmake.cmake'\nif [ -d \"$APPDIR/CMake.app\" ]; then\n\tsudo mv \"$APPDIR/CMake.app\" \"$TMPDIR/CMake.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/CMake.app\" \"$APPDIR\"\nrelaunch_application 'org.cmake.cmake'\n",
"ff358276": "#!/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/CMake.app\"\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.kitware.CMakeSetup.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.cmake.cmake.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.cmake.cmake.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 "Repository files matching darwin.json:"
git ls-files 'ee/maintained-apps/outputs/*/darwin.json' | sort

echo
echo "Occurrences of QuitAll/kill/killall/pkill in managed darwin uninstall scripts:"
rg -n "QuitAll|killall|pkill|kill|quitting|quit|running|Process\\(" ee/maintained-apps outputs -g 'darwin.json' || true

echo
echo "Show uninstall scripts around target removal for each relevant file:"
for f in \
  ee/maintained-apps/outputs/cmake-app/darwin.json \
  ee/maintained-apps/outputs/etrecheckpro/darwin.json \
  ee/maintained-apps/outputs/gdevelop/darwin.json \
  ee/maintained-apps/outputs/rightfont/darwin.json \
  ee/maintained-apps/outputs/thunderbird/darwin.json \
  ee/maintained-apps/outputs/webcatalog/darwin.json \
  ee/maintained-apps/outputs/workflowy/darwin.json
do
  echo "===== $f ====="
  python3 - <<'PY' "$f"
import json, sys
from pathlib import Path
path=sys.argv[1]
text=pathlib.Path(path).read_text() if (pathlib:=__import__('pathlib')).Path else Path(path).read_text()
data=json.loads(text)
refs=data.get("refs")
if not refs:
    print("no refs")
    raise SystemExit
for k,v in refs.items():
    if not isinstance(v,str) or "$APPDIR/" not in v and "`rm" not in v:
        continue
    lines=v.strip().splitlines()
    for i,l in enumerate(lines,1):
        if "$APPDIR/" in l or "`rm" in l or "sudo rm" in l or "sudo rmdir" in l:
            start=max(1,i-3); end=min(len(lines),i+25)
            print(f"key={k} removal line={i}: {l}")
            for n in range(start,end+1):
                print(f"{n:4}: {lines[n-1]}")
            print()
            break
PY
done

echo
echo "Search for package schema/generated code that may add QuitAll/termination:"
rg -n "QuitAll|termination|running|killing|pkill|killall|keepProcesses|apps" -S ee/maintained-apps --glob '*.go' --glob '*.json' --glob '*.ts' --glob '*.js' || true

Repository: fleetdm/fleet

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

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

files = [
    Path("ee/maintained-apps/outputs/cmake-app/darwin.json"),
    Path("ee/maintained-apps/outputs/etrecheckpro/darwin.json"),
    Path("ee/maintained-apps/outputs/gdevelop/darwin.json"),
    Path("ee/maintained-apps/outputs/rightfont/darwin.json"),
    Path("ee/maintained-apps/outputs/thunderbird/darwin.json"),
    Path("ee/maintained-apps/outputs/webcatalog/darwin.json"),
    Path("ee/maintained-apps/outputs/workflowy/darwin.json"),
]

bundle_ids = {
    "ee/maintained-apps/outputs/cmake-app/darwin.json": "org.cmake.cmake",
    "ee/maintained-apps/outputs/etrecheckpro/darwin.json": "com.etresoft.EtreCheck4",
    "ee/maintained-apps/outputs/gdevelop/darwin.json": "com.gdevelop-app.ide",
    "ee/maintained-apps/outputs/rightfont/darwin.json": "com.rightfontapp.RightFont5",
    "ee/maintained-apps/outputs/thunderbird/darwin.json": "org.mozilla.thunderbird",
    "ee/maintained-apps/outputs/webcatalog/darwin.json": "com.webcatalog.jordan",
    "ee/maintained-apps/outputs/workflowy/darwin.json": "com.workflowy.desktop",
}

for path in files:
    data = json.loads(path.read_text())
    for ref, script in data.get("refs", {}).items():
        if not (re.search(r'--only-dirs|--pkg|pkgutil|installer -pkg', script) or "$APPDIR/" in script or re.search(r'/Applications/', script)):
            continue
        print(f"===== {path} key={ref} =====")
        # split by script boundaries based json key values containing functions; print around bundle app removals
        lines = script.rstrip().splitlines()
        for i,l in enumerate(lines,1):
            if "$APPDIR/" in l or re.search(r'/Applications/', l) or re.search(r'CMake|EtreCheck|GDevelop|RightFont|Thunderbird|WebCatalog|WorkFlowy', l, re.I):
                start=max(1,i-25); end=min(len(lines),i+20)
                has_quit=bool(re.search(r'quit_application\s*\(|quit_and_track_application\s*\(', script[sum(len(x)+1 for x in lines[:max(1,i-1)]:):script.find(script.split("'"+lines[i-1].split()[0],1)[0] if " " in l else l,0):]))
                print("around removal:", l)
                print("has_app_quit_helper_before:", "quit_application " in script[:len("\n".join(lines[:i]))] or "quit_and_")
                for n in range(start,end+1):
                    print(f"{n:4}: {lines[n-1]}")
        break

# Determine if each script explicitly contains a terminal-app quit invocation before bundle removal,
# by a simple read-only script-content check.
for path in files:
    path=Path(path)
    data=json.loads(path.read_text())
    for script in data.get("refs", {}).values():
        if "$APPDIR/" in script or re.search(r'/Applications/', script):
            # Find first bundle deletion line if present.
            lines=script.splitlines()
            first_removal=None
            for idx,l in enumerate(lines):
                if re.search(r'\$APPDIR/|/Applications/', l):
                    first_removal=(idx,l)
                    break
            # Does the script contain one of the known quit helpers before first removal?
            before=re.split(r'\n', script[:len("\n".join(lines[:first_removal[0]]) if first_removal else -1])
            print(path, first_removal, "has_app_quit_before_removal=",
                  any(line.strip().startswith("quit_application '") or line.strip().startswith("quit_and_track_application '") for line in before))
            break
PY

Repository: fleetdm/fleet

Length of output: 559


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, re, sys
from pathlib import Path

files = [
    Path("ee/maintained-apps/outputs/cmake-app/darwin.json"),
    Path("ee/maintained-apps/outputs/etrecheckpro/darwin.json"),
    Path("ee/maintained-apps/outputs/gdevelop/darwin.json"),
    Path("ee/maintained-apps/outputs/rightfont/darwin.json"),
    Path("ee/maintained-apps/outputs/thunderbird/darwin.json"),
    Path("ee/maintained-apps/outputs/webcatalog/darwin.json"),
    Path("ee/maintained-apps/outputs/workflowy/darwin.json"),
]

for path in files:
    try:
        data = json.loads(path.read_text())
    except Exception as e:
        print(f"ERROR {path}: {type(e).__name__}: {e}")
        continue

    for ref, script in data.get("refs", {}).items():
        lines = script.splitlines()
        removal_lines = []
        for idx, l in enumerate(lines):
            if "$APPDIR/" in l or re.search(r'/Applications/', l):
                removal_lines.append((idx, l))

        if not removal_lines:
            continue

        first_removal_idx, first_removal = removal_lines[0]
        first_removal_indexed = "\n".join(lines[:first_removal_idx])
        has_quit_before = bool(re.search(r'quit_application\s+[\'"]|[\'"]\s+quit_and_track_application\s+[\'"]|[\'"]\s+quit_application\s+[\'"]|[\'"]\s+quit_and_track_application\s+[\'"]', first_removal_indexed))
        has_force_kill_before = bool("killall -9" in first_removal_indexed or "pkill -9" in first_removal_indexed or re.search(r'\b\((?:/bin|/usr/bin)/)?(?:sudo\s+)?(?:killall|pkill) -(?:9|KILL|K|n|kill)\b', first_removal_indexed))

        # Print concise summary for each matching removal line.
        print(f"===== {path} key={ref} removals={len(removal_lines)} =====")
        for idx, l in removal_lines:
            print(f"  line={idx+1}: {l}")
            # Simple prefix check for explicit app quit helper or force-kill before this line.
            before = "\n".join(lines[:idx])
            print(f"    explicit_app_quit_before={bool(re.search(r'quit_application\s+(?:\"[^\"]+\"|\\'[^\\']+\\'|/bin/echo)', before) or re.search(r'quit_and_tracker?' not in before else bool(re.search(r'quit_application|quit_and', before))} force_kill_before={has_force_kill_before}")

Repository: fleetdm/fleet

Length of output: 887


🏁 Script executed:

#!/bin/bash
set -euo pipefail

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

files = [
    "ee/maintained-apps/outputs/cmake-app/darwin.json",
    "ee/maintained-apps/outputs/etrecheckpro/darwin.json",
    "ee/maintained-apps/outputs/gdevelop/darwin.json",
    "ee/maintained-apps/outputs/rightfont/darwin.json",
    "ee/maintained-apps/outputs/thunderbird/darwin.json",
    "ee/maintained-apps/outputs/webcatalog/darwin.json",
    "ee/maintained-apps/outputs/workflowy/darwin.json",
]

for path_str in files:
    path = Path(path_str)
    try:
        data = json.loads(path.read_text())
    except Exception as e:
        print(f"ERROR {path_str}: {type(e).__name__}: {e}")
        continue

    for ref, script in data.get("refs", {}).items():
        lines = script.splitlines()
        all_quit_helpers_called = list(re.finditer(r'quit_application\b|quit_and_track_application\b', script))
        removals = []
        for idx, l in enumerate(lines):
            if "$APPDIR/" in l or re.search(r"/Applications/", l):
                removals.append((idx, l))
                break

        if not removals or not all_quit_helpers_called:
            print(f"NO-QUIT-PRE-BUNDLE {path_str} key={ref}")
            for idx, l in removals:
                print(f"  line={idx+1}: {l}")
        else:
            first_rem_idx, first_rem = removals[0]
            pre = "\n".join(lines[:first_rem_idx])
            last_helper_before = None
            for match in all_quit_helpers_called:
                if match.start() < first_rem_idx:
                    last_helper_before = match
                else:
                    break
            print(f"QUIT-PRE-BUNDLE {path_str} key={ref}")
            print(f"  app_quit_helper={last_helper_before.group() if last_helper_before else None}")
            print(f"  first_bundle_removal={first_rem_idx+1}: {first_rem}")
PY

Repository: fleetdm/fleet

Length of output: 2023


Quit the running macOS apps before deleting their bundles.

Each of these uninstall scripts removes the /Applications/ bundle before terminating the application, so a running instance can leave data behind after cleanup. Add the existing quit_application helper invocation before sudo rm -rf "$APPDIR/..."/rm -rf /Applications/... for:

  • cmake-app: org.cmake.cmake
  • etrecheckpro: com.etresoft.EtreCheck4
  • gdevelop: com.gdevelop-app.ide
  • rightfont: com.rightfontapp.RightFont5
  • thunderbird: org.mozilla.thunderbird
  • webcatalog: com.webcatalog.jordan
  • workflowy: com.workflowy.desktop
📍 Affects 7 files
  • ee/maintained-apps/outputs/cmake-app/darwin.json#L20-L20 (this comment)
  • ee/maintained-apps/outputs/etrecheckpro/darwin.json#L20-L20
  • ee/maintained-apps/outputs/gdevelop/darwin.json#L20-L21
  • ee/maintained-apps/outputs/rightfont/darwin.json#L20-L20
  • ee/maintained-apps/outputs/thunderbird/darwin.json#L20-L20
  • ee/maintained-apps/outputs/webcatalog/darwin.json#L18-L19
  • ee/maintained-apps/outputs/workflowy/darwin.json#L20-L20
🤖 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/cmake-app/darwin.json` at line 20, Invoke the
existing quit_application helper before removing each application bundle, then
preserve the existing cleanup order. Apply this in
ee/maintained-apps/outputs/cmake-app/darwin.json:20-20 for org.cmake.cmake;
etrecheckpro/darwin.json:20-20 for com.etresoft.EtreCheck4;
gdevelop/darwin.json:20-21 for com.gdevelop-app.ide; rightfont/darwin.json:20-20
for com.rightfontapp.RightFont5; thunderbird/darwin.json:20-20 for
org.mozilla.thunderbird; webcatalog/darwin.json:18-19 for com.webcatalog.jordan;
and workflowy/darwin.json:20-20 for com.workflowy.desktop.

],
"refs": {
"7b9b9d06": "#!/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/Granola.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/Granola'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.granola.app'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.granola.app'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.granola.app.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.getgranola.app.savedState'\n",
"8135b983": "#!/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/Granola.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.granola.app.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Granola'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.granola.app'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.granola.app'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.granola.app.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.getgranola.app.savedState'\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make uninstall failures observable across all generated scripts.

The embedded scripts do not consistently propagate failures from destructive operations, so a later successful command can make an incomplete uninstall exit zero.

  • ee/maintained-apps/outputs/granola/darwin.json#L19-L19: propagate failures from app removal and trash().
  • ee/maintained-apps/outputs/jamovi/darwin.json#L20-L20: propagate failures from app removal and trash().
  • ee/maintained-apps/outputs/kaleidoscope/darwin.json#L19-L19: propagate failures from package, receipt, app, and trash cleanup.
  • ee/maintained-apps/outputs/malwarebytes/darwin.json#L19-L19: propagate failures from launchctl, package, app, and trash cleanup.
  • ee/maintained-apps/outputs/nextcloud/darwin.json#L19-L19: propagate failures from launchctl, package, app, and trash cleanup.
  • ee/maintained-apps/outputs/omnioutliner/darwin.json#L20-L20: propagate failures from app removal and trash().
  • ee/maintained-apps/outputs/postman/darwin.json#L19-L19: propagate failures from extraction, app removal, and trash().
  • ee/maintained-apps/outputs/raindropio/darwin.json#L19-L19: propagate failures from app removal and trash().
  • ee/maintained-apps/outputs/remote-desktop-manager/darwin.json#L19-L19: propagate failures from app removal and trash().
📍 Affects 9 files
  • ee/maintained-apps/outputs/granola/darwin.json#L19-L19 (this comment)
  • ee/maintained-apps/outputs/jamovi/darwin.json#L20-L20
  • ee/maintained-apps/outputs/kaleidoscope/darwin.json#L19-L19
  • ee/maintained-apps/outputs/malwarebytes/darwin.json#L19-L19
  • ee/maintained-apps/outputs/nextcloud/darwin.json#L19-L19
  • ee/maintained-apps/outputs/omnioutliner/darwin.json#L20-L20
  • ee/maintained-apps/outputs/postman/darwin.json#L19-L19
  • ee/maintained-apps/outputs/raindropio/darwin.json#L19-L19
  • ee/maintained-apps/outputs/remote-desktop-manager/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/granola/darwin.json` at line 19, Make every
destructive operation in the embedded uninstall scripts propagate failures
instead of allowing later commands to mask them. Update the scripts’ top-level
command flow and the trash() helper so app removal and trash operations fail the
script; also cover package, receipt, launchctl, and extraction cleanup where
present. Apply this to ee/maintained-apps/outputs/granola/darwin.json (anchor),
ee/maintained-apps/outputs/jamovi/darwin.json,
ee/maintained-apps/outputs/kaleidoscope/darwin.json,
ee/maintained-apps/outputs/malwarebytes/darwin.json,
ee/maintained-apps/outputs/nextcloud/darwin.json,
ee/maintained-apps/outputs/omnioutliner/darwin.json,
ee/maintained-apps/outputs/postman/darwin.json,
ee/maintained-apps/outputs/raindropio/darwin.json, and
ee/maintained-apps/outputs/remote-desktop-manager/darwin.json, preserving each
script’s existing cleanup behavior while returning a nonzero status whenever a
required operation fails.</code>

],
"refs": {
"1e1dcbc9": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\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_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\nquit_application 'app.kaleidoscope.v7'\nremove_pkg_files 'app.kaleidoscope.uninstall_ksdiff'\nforget_pkg 'app.kaleidoscope.uninstall_ksdiff'\nsudo rm -rf \"$APPDIR/Kaleidoscope.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/app.kaleidoscope.v*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.blackpixel.kaleidoscope'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Kaleidoscope'\ntrash $LOGGED_IN_USER '~/Library/Caches/app.kaleidoscope.v*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.blackpixel.kaleidoscope'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.blackpixel.kaleidoscope'\ntrash $LOGGED_IN_USER '~/Library/Preferences/app.kaleidoscope.v*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.blackpixel.kaleidoscope.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/app.kaleidoscope.v*.savedState'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.blackpixel.kaleidoscope.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/app.kaleidoscope.v*'\n",
"983b071c": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\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_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\nquit_application 'app.kaleidoscope.v7'\nremove_pkg_files 'app.kaleidoscope.uninstall_ksdiff'\nforget_pkg 'app.kaleidoscope.uninstall_ksdiff'\nsudo rm -rf \"$APPDIR/Kaleidoscope.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/app.kaleidoscope.v*.KaleidoscopePrism'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/app.kaleidoscope.v*.KSShareExtension'\ntrash $LOGGED_IN_USER '~/Library/Application Support/app.kaleidoscope.v*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.blackpixel.kaleidoscope'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Kaleidoscope'\ntrash $LOGGED_IN_USER '~/Library/Caches/app.kaleidoscope.v*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.blackpixel.kaleidoscope'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.blackpixel.kaleidoscope'\ntrash $LOGGED_IN_USER '~/Library/Containers/app.kaleidoscope.v*.KaleidoscopePrism'\ntrash $LOGGED_IN_USER '~/Library/Containers/app.kaleidoscope.v*.KSShareExtension'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/app.kaleidoscope.v*'\ntrash $LOGGED_IN_USER '~/Library/Preferences/app.kaleidoscope.v*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.blackpixel.kaleidoscope.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/app.kaleidoscope.v*.savedState'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.blackpixel.kaleidoscope.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/app.kaleidoscope.v*'\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

Keep uninstall bundle identifiers aligned with installation metadata.

  • ee/maintained-apps/outputs/kaleidoscope/darwin.json#L19-L19: change quit_application 'app.kaleidoscope.v7' to the identifier used by the metadata and installer, app.kaleidoscope.v6, or update all references consistently.
  • ee/maintained-apps/outputs/malwarebytes/darwin.json#L19-L19: change the quit target from com.malwarebytes.mbam.frontend.agent to the installed GUI application identifier com.malwarebytes.mbam.frontend.application; remove the agent separately.
📍 Affects 2 files
  • ee/maintained-apps/outputs/kaleidoscope/darwin.json#L19-L19 (this comment)
  • ee/maintained-apps/outputs/malwarebytes/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/kaleidoscope/darwin.json` at line 19, Align the
quit targets with the installed application identifiers: in
ee/maintained-apps/outputs/kaleidoscope/darwin.json at lines 19-19, update
quit_application to target app.kaleidoscope.v6; in
ee/maintained-apps/outputs/malwarebytes/darwin.json at lines 19-19, target
com.malwarebytes.mbam.frontend.application and remove the agent separately.

],
"refs": {
"ba6e6e70": "#!/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.malwarebytes.mbam.frontend.agent'\nremove_launchctl_service 'com.malwarebytes.mbam.rtprotection.daemon'\nremove_launchctl_service 'com.malwarebytes.mbam.settings.daemon'\nquit_application 'com.malwarebytes.mbam.frontend.agent'\nremove_pkg_files 'com.malwarebytes.mbam.*'\nforget_pkg 'com.malwarebytes.mbam.*'\nsudo rm -rf '/Library/Application Support/Malwarebytes/MBAM'\nsudo rmdir '/Library/Application Support/Malwarebytes'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.crashlytics.data/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Caches/io.fabric.sdk.mac.data/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.malwarebytes.mbam.frontend.application.savedState'\n",
"3b479c55": "#!/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.malwarebytes.mbam.frontend.agent'\nremove_launchctl_service 'com.malwarebytes.mbam.rtprotection.daemon'\nremove_launchctl_service 'com.malwarebytes.mbam.settings.daemon'\nquit_application 'com.malwarebytes.mbam.frontend.agent'\nremove_pkg_files 'com.malwarebytes.mbam.*'\nforget_pkg 'com.malwarebytes.mbam.*'\nsudo rm -rf '/Library/Application Support/Malwarebytes/MBAM'\nsudo rmdir '/Library/Application Support/Malwarebytes'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.malwarebytes.mbam'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.crashlytics.data/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Caches/io.fabric.sdk.mac.data/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.malwarebytes.mbam.frontend.*'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.malwarebytes.mbam.frontend.application.savedState'\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files =="
git ls-files | rg 'ee/maintained-apps/outputs/(malwarebytes|nextcloud)/darwin\.json$' || true

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

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

echo "== launchctl removal lines =="
rg -n "launchctl|LaunchAgents|LaunchDaemons|sudo (rm|launchctl|runuser|su)" ee/maintained-apps/outputs/malwarebytes/darwin.json ee/maintained-apps/outputs/nextcloud/darwin.json

Repository: fleetdm/fleet

Length of output: 50369


🌐 Web query:

Apple launchctl user domain sudo launchctl remove LaunchAgents /Library/LaunchAgents

💡 Result:

The command structure you are inquiring about, sudo launchctl remove LaunchAgents /Library/LaunchAgents, is not a valid way to manage services on macOS. The launchctl command requires specific targets (domains and service labels) rather than directory paths to function correctly [1][2]. Using the legacy launchctl remove command or targeting a directory path will not work as expected [2]. Modern macOS versions use the bootstrap/bootout syntax, which explicitly requires a domain target and the specific service label (defined in the plist file) [2][3]. For managing LaunchAgents located in /Library/LaunchAgents, follow these guidelines: 1. Identify the Service Label: Open the.plist file in /Library/LaunchAgents and find the string associated with the Label key (e.g., com.example.service). 2. Determine the Correct Domain: Services in /Library/LaunchAgents are typically loaded into the system domain or a specific user's GUI domain depending on how they were configured [1][4]. 3. Use the Modern Command Syntax: To stop and remove a service, use the bootout command: launchctl bootout / Example: If the label is com.example.service and it is running in the user's GUI domain: launchctl bootout gui/$(id -u)/com.example.service Key Differences: - Legacy vs. Modern: Legacy commands like load, unload, and remove have been superseded by bootstrap and bootout [2]. - Targets: You must provide a domain target (e.g., system/, gui//) followed by the service label [1][2]. Providing a file path (like /Library/LaunchAgents/...) to bootout or remove is incorrect; bootout takes the label, not the path [2]. - Permissions: Modifying the system domain (system/) requires root privileges (sudo), whereas modifying your own user or GUI domain generally does not [1][5]. If you simply want to prevent an agent from loading in the future, you should disable it: launchctl disable / This change persists across reboots [6]. If you are trying to delete the file entirely, you must manually remove the.plist file from the /Library/LaunchAgents directory using the rm command after the service has been successfully booted out [7][2].

Citations:


🌐 Web query:

Apple launchctl user domain sudo launchctl remove LaunchAgents /Library/LaunchDaemons

💡 Result:

It is highly recommended that you do not attempt to globally remove or delete files from /Library/LaunchAgents or /Library/LaunchDaemons unless you are performing specific troubleshooting for a known service, as doing so can cause system instability, render applications inoperable, or prevent the system from booting correctly [1][2][3]. The launchctl remove command is considered a legacy command and is often ineffective in modern versions of macOS [4][5][6]. Furthermore, indiscriminately applying sudo launchctl to these directories is dangerous [1][3]. To safely manage services, follow these guidelines: 1. Understand the difference between domains: - LaunchAgents (~/Library/LaunchAgents or /Library/LaunchAgents) run on behalf of a specific user [1][3]. - LaunchDaemons (/Library/LaunchDaemons) run with system-level privileges [1][3]. 2. Use modern, supported subcommands: Modern macOS (10.11+) favors bootstrap and bootout over load and unload, and launchctl remove should generally be avoided in favor of bootout [6]. - To stop a system-wide service (LaunchDaemon): sudo launchctl bootout system/<label> [7][6]. - To stop a user-level service (LaunchAgent): launchctl bootout gui/<UID>/<label> [8][6]. 3. Manage persistence: If your goal is to permanently disable a service, do not just delete the file. Instead: - Use launchctl bootout to stop the currently running instance [7][8]. - Disable the service's automatic loading (if supported) using launchctl disable [5]. - Move the configuration file (.plist) out of the LaunchAgents or LaunchDaemons directory to a backup location if you no longer wish for it to load on reboot [9][2]. 4. Troubleshooting errors: If you receive "Not privileged" or "No such process" errors, ensure you are operating in the correct domain (e.g., using sudo for system daemons, but not for user-level agents) and that you are using the correct service label rather than a file path when using bootout [10][11][6]. Use launchctl print to inspect services and find the correct labels [4][8][6].

Citations:


Run user launchctl cleanup in the logged-in user’s domain.

remove_launchctl_service tries plain sudo launchctl remove for labels from both /Library/LaunchAgents and /Library/LaunchDaemons. Use a user-specific target such as /bin/launchctl asuser "$(id -u "$LOGGED_IN_USER")" launchctl bootout gui/$(id -u "$LOGGED_IN_USER")/... for user LaunchAgents, and keep system daemon removal in the system domain. Apply the same change for both the Malwarebytes and Nextcloud uninstall scripts.

📍 Affects 2 files
  • ee/maintained-apps/outputs/malwarebytes/darwin.json#L19-L19 (this comment)
  • ee/maintained-apps/outputs/nextcloud/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/malwarebytes/darwin.json` at line 19, Update
remove_launchctl_service in both
ee/maintained-apps/outputs/malwarebytes/darwin.json (line 19) and
ee/maintained-apps/outputs/nextcloud/darwin.json (line 19) so user LaunchAgents
are removed with launchctl bootout in the logged-in user’s gui domain via
LOGGED_IN_USER, while system LaunchDaemons continue using the system domain;
preserve the existing wildcard expansion and plist cleanup behavior.

"1b0bcecf": "#!/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/RightFont.app\"\nsudo rmdir '~/RightFont'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.rightfontapp.rightfont*.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.rightfontapp.RightFont*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/RightFont'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.rightfontapp.RightFont*'\ntrash $LOGGED_IN_USER '~/Library/Logs/RightFont*'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.rightfontapp.RightFont*.plist'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.rightfontapp.RightFont*'\n",
"40969d32": "#!/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.rightfontapp.RightFont5'\nif [ -d \"$APPDIR/RightFont.app\" ]; then\n\tsudo mv \"$APPDIR/RightFont.app\" \"$TMPDIR/RightFont.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/RightFont.app\" \"$APPDIR\"\nrelaunch_application 'com.rightfontapp.RightFont5'\n"
"40969d32": "#!/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.rightfontapp.RightFont5'\nif [ -d \"$APPDIR/RightFont.app\" ]; then\n\tsudo mv \"$APPDIR/RightFont.app\" \"$TMPDIR/RightFont.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/RightFont.app\" \"$APPDIR\"\nrelaunch_application 'com.rightfontapp.RightFont5'\n",
"9714135f": "#!/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/RightFont.app\"\nsudo rmdir '~/RightFont'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.rightfontapp.rightfont*.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.rightfontapp.RightFont*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/RightFont'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.rightfontapp.RightFont*'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.rightfontapp.RightFont5'\ntrash $LOGGED_IN_USER '~/Library/Logs/RightFont*'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.rightfontapp.RightFont*.plist'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.rightfontapp.RightFont*'\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

printf 'Files with rmdir in maintained-apps outputs:\n'
rg -n 'sudo rmdir|trash\(' ee/maintained-apps/outputs -g 'darwin.json' || true

printf '\nRelevant snippets:\n'
for f in ee/maintained-apps/outputs/rightfont/darwin.json ee/maintained-apps/outputs/thunderbird/darwin.json; do
  echo "---- $f ----"
  wc -l "$f"
  sed -n '1,120p' "$f"
done

printf '\nBehavioral probe for bash word splitting/globbing with quoted vs unquoted paths:\n'
tmp="$(mktemp -d)"
mkdir -p "$tmp/Users/user/Library/Caches/Mozilla"
mkdir -p "$tmp/Users/user/RightFont"
printf 'Mock rmdir at ~/Library/Caches/Mozilla:\n'
( cd "$tmp" && bash -c 'sudo rmdir ~/Library/Caches/Mozilla' ) || true
printf 'Mock rmdir with explicit user path:\n'
( cd "$tmp" && LOGGED_IN_USER=user && rm -rf "/Users/$LOGGED_IN_USER/Library/Caches/Mozilla" 2>/dev/null; test -d "$tmp/Users/user/Library/Caches/Mozilla" && echo present; test ! -d "$tmp/Users/user/Library/Caches/Mozilla" && echo removed )
printf 'Mock rmdir unquoted tilde (for direct comparison):\n'
( cd "$tmp" && rm -rf ~/Library/Caches/Mozilla 2>/dev/null; test -d "$tmp/Users/user/Library/Caches/Mozilla" && echo present; test ! -d "$tmp/Users/user/Library/Caches/Mozilla" && echo removed )
rm -rf "$tmp"

Repository: fleetdm/fleet

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files with rmdir in maintained-apps outputs:\n'
rg -n 'sudo rmdir|trash\(' ee/maintained-apps/outputs -g 'darwin.json' || true

printf '\nRelevant snippets:\n'
for f in ee/maintained-apps/outputs/rightfont/darwin.json ee/maintained-apps/outputs/thunderbird/darwin.json; do
  echo "---- $f ----"
  wc -l "$f"
  sed -n '1,120p' "$f"
done

printf '\nBehavioral probe for bash word splitting/globbing with quoted vs unquoted paths:\n'
tmp="$(mktemp -d)"
mkdir -p "$tmp/Users/user/Library/Caches/Mozilla"
mkdir -p "$tmp/Users/user/RightFont"
printf 'Mock rmdir at ~/Library/Caches/Mozilla:\n'
( cd "$ tmp" && bash -c 'sudo rmdir ~/Library/Caches/Mozilla' ) || true
printf 'Mock rmdir with explicit user path:\n'
( cd "$tmp" && LOGGED_IN_USER=user && rm -rf "/Users/$LOGGED_IN_USER/Library/Caches/Mozilla" 2>/dev/null; test -d "$tmp/Users/user/Library/Caches/Mozilla" && echo present; test ! -d "$tmp/Users/user/Library/Caches/Mozilla" && echo removed )
printf 'Mock rmdir unquoted tilde (for direct comparison):\n'
( cd "$tmp" && rm -rf ~/Library/Caches/Mozilla 2>/dev/null; test -d "$tmp/Users/user/Library/Caches/Mozilla" && echo present; test ! -d "$tmp/Users/user/Library/Caches/Mozilla" && echo removed )
rm -rf "$tmp"

Repository: fleetdm/fleet

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

mkdir -p "$tmp/Users/user/RightFont"
mkdir -p "$tmp/Users/user/Library/Caches/Mozilla"
mkdir -p "$tmp/sudo"
touch "$tmp/sudo/rmdir"

printf 'Quoted path: sudo rmdir ~/RightFont with HOME=$HOME\n'
(cd "$tmp" && HOME="$HOME" PATH="$tmp" bash -c 'echo "~/RightFont"; sudo rmdir ~/RightFont' 2>&1 | sed -n '1,10p') || true

printf 'Traced quoted path with HOME overrode to /Users/user:\n'
(cd "$tmp" && HOME="$tmp/Users/user" PATH="$tmp" bash -c 'echo "~/RightFont"; sudo rmdir ~/RightFont' 2>&1 | sed -n '1,10p') || true

printf 'Explicit path: sudo rmdir /Users/$LOGGED_IN_USER/RightFont with LOGGED_IN_USER=user:\n'
(cd "$tmp" && LOGGED_IN_USER=user PATH="$tmp" bash -c 'sudo rmdir "/Users/$LOGGED_IN_USER/RightFont"' 2>&1 | sed -n '1,10p') || true

printf 'Unquoted tilde: rm -rf ~/RightFont from script HOME, then explicit path from script HOME, then quoted string:\n'
(cd "$tmp" && LOGGED_IN_USER=user PATH="$tmp" bash -c '
  mkdir -p "$HOME/RightFont"
  echo "--- init ---"
  find "$HOME/RightFont" -maxdepth 1 -type d
  rm -rf ~/RightFont
  echo "--- after unquoted tilde rm ---"
  find "$HOME/Right_font" -maxdepth 1 -type d 2>/dev/null || true
  find "$HOME/RightFont" -maxdepth 1 -type d 2>/dev/null || true
  mkdir -p "$HOME/RightFont"
  sudo rmdir "$HOME/RightFont"
  echo "--- after explicit rm ---"
  find "$HOME/RightFont" -maxdepth 1 -type d 2>/dev/null || true
  mkdir -p "$HOME/RightFont"
  PATH="$tmp sudo" sudo rmdir "~/RightFont"
  echo "--- after quoted rm ---"
  find "$HOME/RightFont" -maxdepth 1 -type d 2>/dev/null || true
') || true

Repository: fleetdm/fleet

Length of output: 608


🌐 Web query:

bash manual tilde expansion quoted string rmdir quotes

💡 Result:

In Bash, tilde expansion (~) is not performed when it is enclosed in quotes (either single or double quotes) [1][2]. The shell interprets the tilde as a literal character when quoted, meaning it will not expand to the user's home directory [2][3]. To use tilde expansion alongside quoted paths (for example, to protect against spaces in a filename), you must leave the tilde unquoted while quoting the rest of the path [3][4]. For example: rmdir ~/"Directory With Spaces" Regarding the use of quotes with the rmdir command: 1. Quoting prevents the shell from interpreting special characters within a filename, such as spaces, parentheses, or dashes [5][6]. 2. If you need to remove a directory that starts with a dash (which might be misinterpreted as a command option), you should use the -- separator before the directory name, or use a relative path like./-directory-name [7]. 3. Note that rmdir is specifically designed to remove empty directories [5]. If you intend to remove a directory and its contents, you should use rm -rf [5]. When working with commands like rmdir or rm, remember that Bash processes the quoting and expansion before the command itself is executed [5]. If you quote the tilde, the command will receive the literal ~ character as part of the path, which will likely result in a "No such file or directory" error unless a directory named ~ exists in the current location [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXPORT

PATH="$PATH:/bin:/usr/bin"

mkdir -p "$tmp/Users/user/RightFont"
mkdir -p "$tmp/Users/user/Library/Caches/Mozilla"

mkdir -p "$tmp/mock-bin"
cat > "$tmp/mock-bin/sudo" <<'BSH'
#!/bin/sh
exec "$@"
BSH
chmod +x "$tmp/mock-bin/sudo"

mkdir -p "$tmp/mock-rmdir"
cat > "$tmp/mock-rmdir/rmdir" <<'BSH'
#!/bin/sh
for p do
  case "$p" in
    */Users/user/*/RightFont|*/Users/user/Library/Caches/Mozilla) echo "would rmdir $p" ;;
    "~/RightFont"|"~/Library/Caches/Mozilla") echo "received literal $p, no ~/Users/user" ;;
    *) echo "unhandled $p" ;;
  esac
done
exit 0
BSH
chmod +x "$tmp/mock-rmdir/rmdir"

mkdir -p "$tmp/mock-rmdir/RightFont"
chmod +x "$tmp/mock-rmdir/RightFont"

printf 'sudo PATH has mocked rmdir at /Users/user/RightFont:\n'
(cd "$tmp" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir ~/RightFont) || true

printf 'sudo PATH has mocked rmdir at current dir:\n'
(cd "$tmp/mock-rmdir/RightFont" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir ~/RightFont) || true

printf 'current dir has literal ~/RightFont directory:\n'
mkdir -p "$tmp/Users/user/RightFont"
(cd "$tmp/Users/user/RightFont" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir ~/RightFont) || true

printf 'explicit path succeeds:\n'
LOGGED_IN_USER=user
(cd "$tmp" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir "/Users/$LOGGED_IN_USER/RightFont") || true

printf 'current dir with quoted shell path receives literal ~:\n'
mkdir -p "$tmp/Users/user/RightFont"
(cd "$tmp/Users/user/RightFont" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir '~/RightFont') || true

Repository: fleetdm/fleet

Length of output: 208


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

PATH="$PATH:/bin:/usr/bin"

mkdir -p "$tmp/Users/user/RightFont"
mkdir -p "$tmp/Users/user/Library/Caches/Mozilla"

mkdir -p "$tmp/mock-rmdir"
cat > "$tmp/mock-rmdir/rmdir" <<'BSH'
#!/bin/sh
for p do
  case "$p" in
    */Users/user/*/RightFont|*/Users/user/Library/Caches/Mozilla) echo "expanded path: $p" ;;
    "~/RightFont"|"~/Library/Caches/Mozilla") echo "literal quoted path: $p" ;;
    *) echo "unhandled: $p" ;;
  esac
done
exit ${#}
BSH
chmod +x "$tmp/mock-rmdir/rmdir"

printf 'sudo PATH has mocked rmdir in target home (unquoted):\n'
(cd "$tmp" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir ~/RightFont) || true

printf 'current dir has literal ~/RightFont directory:\n'
mkdir -p "$tmp/Users/user/RightFont"
(cd "$tmp/Users/user/RightFont" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir ~/RightFont) || true

printf 'current dir quoted shell path receives literal ~:\n'
mkdir -p "$tmp/Users/user/RightFont"
(cd "$tmp/Users/user/RightFont" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir '~/RightFont') || true

printf 'explicit path expands LOGGED_IN_USER before sudo receives it:\n'
(LOGGED_IN_USER=user && cd "$tmp" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir "/Users/$LOGGED_IN_USER/RightFont") || true

printf 'explicit path expands LOGGED_IN_USER before sudo, quoted around the whole argument:\n'
(LOGGED_IN_USER=user && cd "$tmp" && PATH="$tmp/mock-rmdir:$PATH" HOME="$HOME" sudo rmdir "/Users/$LOGGED_IN_USER/Library/Caches/Mozilla") || true

Repository: fleetdm/fleet

Length of output: 643


Use the expanded home path in the sudo rmdir calls.

Quoting keeps ~ literal, so these commands try /Users/<logged-in-user>/~/RightFont and /Users/<logged-in-user>/~/Library/Caches/Mozilla instead of the intended paths. Replace both with /Users/$LOGGED_IN_USER/... before passing them to rmdir.

  • ee/maintained-apps/outputs/rightfont/darwin.json#L20-L20
  • ee/maintained-apps/outputs/thunderbird/darwin.json#L20-L20
📍 Affects 2 files
  • ee/maintained-apps/outputs/rightfont/darwin.json#L20-L20 (this comment)
  • ee/maintained-apps/outputs/thunderbird/darwin.json#L20-L20
🤖 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/rightfont/darwin.json` at line 20, Update the sudo
rmdir calls in ee/maintained-apps/outputs/rightfont/darwin.json:20-20 and
ee/maintained-apps/outputs/thunderbird/darwin.json:20-20 to use
/Users/$LOGGED_IN_USER/... paths instead of quoted ~ paths, including the
RightFont and Mozilla cache directories, so the expanded home paths are passed
to rmdir.

"refs": {
"71f9d405": "#!/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.teamviewer.TeamViewer'\nsudo installer -pkg \"$TMPDIR/TeamViewer.pkg\" -target /\nrelaunch_application 'com.teamviewer.TeamViewer'\n",
"d0cdc2d9": "#!/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.teamviewer.desktop'\nremove_launchctl_service 'com.teamviewer.Helper'\nremove_launchctl_service 'com.teamviewer.service'\nremove_launchctl_service 'com.teamviewer.teamviewer'\nremove_launchctl_service 'com.teamviewer.teamviewer_desktop'\nremove_launchctl_service 'com.teamviewer.teamviewer_service'\nremove_launchctl_service 'com.teamviewer.UninstallerHelper'\nremove_launchctl_service 'com.teamviewer.UninstallerWatcher'\nquit_application 'com.teamviewer.TeamViewer'\nquit_application 'com.teamviewer.TeamViewerUninstaller'\nremove_pkg_files 'com.teamviewer.AuthorizationPlugin'\nforget_pkg 'com.teamviewer.AuthorizationPlugin'\nremove_pkg_files 'com.teamviewer.AuthorizationResources'\nforget_pkg 'com.teamviewer.AuthorizationResources'\nremove_pkg_files 'com.teamviewer.remoteaudiodriver'\nforget_pkg 'com.teamviewer.remoteaudiodriver'\nremove_pkg_files 'com.teamviewer.teamviewer.*'\nforget_pkg 'com.teamviewer.teamviewer.*'\nremove_pkg_files 'TeamViewerUninstaller'\nforget_pkg 'TeamViewerUninstaller'\nsudo rm -rf '/Applications/TeamViewer.app'\nsudo rm -rf '/Library/Preferences/com.teamviewer*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.teamviewer.TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Caches/TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.teamviewer.TeamViewer.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.teamviewer.TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.teamviewer.TeamViewer.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Logs/TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.teamviewer*'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.teamviewer.TeamViewer.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.teamviewer.TeamViewer'\n"
"f00ebab2": "#!/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.teamviewer.desktop'\nremove_launchctl_service 'com.teamviewer.Helper'\nremove_launchctl_service 'com.teamviewer.service'\nremove_launchctl_service 'com.teamviewer.teamviewer'\nremove_launchctl_service 'com.teamviewer.teamviewer_desktop'\nremove_launchctl_service 'com.teamviewer.teamviewer_service'\nremove_launchctl_service 'com.teamviewer.UninstallerHelper'\nremove_launchctl_service 'com.teamviewer.UninstallerWatcher'\nquit_application 'com.teamviewer.TeamViewer'\nquit_application 'com.teamviewer.TeamViewerUninstaller'\nremove_pkg_files 'com.teamviewer.AuthorizationPlugin'\nforget_pkg 'com.teamviewer.AuthorizationPlugin'\nremove_pkg_files 'com.teamviewer.AuthorizationResources'\nforget_pkg 'com.teamviewer.AuthorizationResources'\nremove_pkg_files 'com.teamviewer.remoteaudiodriver'\nforget_pkg 'com.teamviewer.remoteaudiodriver'\nremove_pkg_files 'com.teamviewer.teamviewer.*'\nforget_pkg 'com.teamviewer.teamviewer.*'\nremove_pkg_files 'TeamViewerUninstaller'\nforget_pkg 'TeamViewerUninstaller'\nsudo rm -rf '/Applications/TeamViewer.app'\nsudo rm -rf '/Library/Preferences/com.teamviewer*'\ntrash $LOGGED_IN_USER '/Library/Application Support/TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Application Support/TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.teamviewer.TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Caches/TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.teamviewer.TeamViewer.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.teamviewer.TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.teamviewer.TeamViewer.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Logs/TeamViewer'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.teamviewer*'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.teamviewer.TeamViewer.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.teamviewer.TeamViewer'\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:

#!/usr/bin/env bash
set -euo pipefail

file='ee/maintained-apps/outputs/teamviewer/darwin.json'
echo 'file exists:'
[ -f "$file" ] && echo yes || echo no

echo 'matching lines:'
rg -n "rm -rf '/Library/Preferences/com.teamviewer" "$file" || true

echo 'shell behavior check for quoted glob argument:'
mkdir -p /tmp/teamviewer_glob_test
echo content >/tmp/teamviewer_glob_test/com.teamviewer.foo.plist
echo content >/tmp/teamviewer_glob_test/com.teamviewer.bar.plist

tmpdir="$(mktemp -d)"
export HOME="$tmpdir"

python3 - <<'PY'
import os
p=os.system('sudo rm -rf \'/Library/Preferences/com.teamviewer*\' >/tmp/teamviewer_glob_test/mount_error 2>/tmp/teamviewer_glob_test/rm_stderr')
print("rm_status_q_glob", p)
PY

# Simulate the shell behavior of the quoted pattern with echo without running rm:
bash -c 'set -v; for arg in "'\\\"""/Library/Preferences/com.teamviewer*\""\"\"" '"\\\'';/Library/Preferences/com.teamviewer*'"\\\'"\""; do printf "raw="; printf "%s\n" "$arg"; done | sed -n "1,10p"'

bash -c '
for arg in "'\" /Library/Preferences/com.teamviewer*\"" \"'"; do
  printf "raw="
  printf "%s\n" "$arg"
done
rm -rf /tmp/teamviewer_glob_test/com.teamviewer.*
'

echo 'remaining files:'
find /tmp/teamviewer_glob_test -maxdepth 1 -type f -o -type d | sort

Repository: fleetdm/fleet

Length of output: 9991


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
tmpdir="$(mktemp -d)"
rm -rf /tmp/teamviewer_glob_test 2>/dev/null || true
mkdir -p /tmp/teamviewer_glob_test
echo content >/tmp/teamviewer_glob_test/com.teamviewer.foo.plist
echo content >/tmp/teamviewer_glob_test/com.teamviewer.bar.plist
echo "before:"
find /tmp/teamviewer_glob_test -maxdepth 1 -type f | sort

echo "quoted glob attempt:"
if sudo rm -rf '/tmp/teamviewer_glob_test/com.teamviewer*'; then
  echo "success"
else
  echo "failed with status $?"
fi

echo "after:"
find /tmp/teamviewer_glob_test -maxdepth 1 -type f | sort

echo "unquoted glob on same files:"
sudo rm -rf "/tmp/teamviewer_glob_test"/com.teamviewer* || true
find /tmp/teamviewer_glob_test -maxdepth 1 -type f | sort
find /tmp/teamviewer_glob_test -maxdepth 1 -type d -empty -print -delete | sort || true

Repository: fleetdm/fleet

Length of output: 623


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
tmpdir="$(mktemp -d)"
rm -rf /tmp/teamviewer_glob_test 2>/dev/null || true
mkdir -p /tmp/teamviewer_glob_test
echo content >"/tmp/teamviewer_glob_test/com.teamviewer.foo.plist"
echo content >"/tmp/teamviewer_glob_test/com.teamviewer.bar.plist"
echo "before:"
find /tmp/teamviewer_glob_test -maxdepth 1 -type f | sort

echo "quoted glob using /bin/rm directly:"
if /bin/rm -rf '/tmp/teamviewer_glob_test/com.teamviewer*'; then
  echo "quoted_glob_rm_status=0"
else
  echo "quoted_glob_rm_status=$?"
fi

echo "after quoted glob:"
find /tmp/teamviewer_glob_test -maxdepth 1 -type f | sort

echo "unquoted glob directly:"
/bin/rm -rf /tmp/teamviewer_glob_test/com.teamviewer* || true
echo "after unquoted glob:"
find /tmp/teamviewer_glob_test -maxdepth 1 -type f | sort
find /tmp/teamviewer_glob_test -maxdepth 1 -type d -empty -print -delete | sort || true

Repository: fleetdm/fleet

Length of output: 513


Do not quote the system preference glob.

sudo rm -rf '/Library/Preferences/com.teamviewer*' passes * to rm as part of the filename rather than matching preferences, so TeamViewer system pref files are left behind. Use an unquoted glob or enumerate the intended 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/teamviewer/darwin.json` at line 20, Update the
system preference removal command near the TeamViewer cleanup calls so the
com.teamviewer* wildcard is expanded by the shell before rm runs; remove the
quotes around the glob or explicitly enumerate the matching preference paths
while preserving sudo rm -rf behavior.

"18d6be1e": "#!/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.tencent.xinWeChat'\nsudo rm -rf \"$APPDIR/WeChat.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat.IPCHelper'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat.MiniProgram'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat.WeChatMacShare'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Containers/$(TeamIdentifierPrefix)com.tencent.xinWeChat.IPCHelper'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.tencent.xinWeChat.MiniProgram'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.tencent.xinWeChat.WeChatMacShare'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.tencent.xinWeChat.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/$(TeamIdentifierPrefix)com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.tencent.xinWeChat.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.tencent.xinWeChat.savedState'\n",
"57f30b74": "#!/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 'com.tencent.xinWeChat'\nif [ -d \"$APPDIR/WeChat.app\" ]; then\n\tsudo mv \"$APPDIR/WeChat.app\" \"$TMPDIR/WeChat.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/WeChat.app\" \"$APPDIR\"\nrelaunch_application 'com.tencent.xinWeChat'\n"
"57f30b74": "#!/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 'com.tencent.xinWeChat'\nif [ -d \"$APPDIR/WeChat.app\" ]; then\n\tsudo mv \"$APPDIR/WeChat.app\" \"$TMPDIR/WeChat.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/WeChat.app\" \"$APPDIR\"\nrelaunch_application 'com.tencent.xinWeChat'\n",
"a5e852b0": "#!/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.tencent.xinWeChat'\nsudo rm -rf \"$APPDIR/WeChat.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat.IPCHelper'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/5A4RE8SF68.com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat.MiniProgram'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat.WeChatMacShare'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Containers/$(TeamIdentifierPrefix)com.tencent.xinWeChat.IPCHelper'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.tencent.xinWeChat.MiniProgram'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.tencent.xinWeChat.WeChatMacShare'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.tencent.xinWeChat.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/$(TeamIdentifierPrefix)com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/5A4RE8SF68.com.tencent.xinWeChat'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.tencent.xinWeChat.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.tencent.xinWeChat.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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
if [ -f ee/maintained-apps/outputs/wechat/darwin.json ]; then
  sed -n '1,80p' ee/maintained-apps/outputs/wechat/darwin.json
else
  echo "file not found"
fi

echo
echo "== occurrences of TeamIdentifierPrefix in repo =="
rg -n "TeamIdentifierPrefix|Application Scripts|Containers|Group Containers" ee/maintained-apps/outputs/wechat/darwin.json . --glob 'ee/maintained-apps/outputs/wechat/darwin.json' --glob '*.sh' --glob '*.json' | head -200

echo
echo "== package files around wechat =="
fd -t f . ee/maintained-apps/outputs/wechat | sort

Repository: fleetdm/fleet

Length of output: 50370


🌐 Web query:

Apple TeamIdentifierPrefix path placeholder Application Scripts Containers Group Containers

💡 Result:

In macOS development, TeamIdentifierPrefix, Application Scripts, Containers, and Group Containers refer to distinct parts of the App Sandbox architecture used for file system isolation and data sharing [1][2][3]. TeamIdentifierPrefix $(TeamIdentifierPrefix) is a build-time variable in Xcode that resolves to your Apple Development Team ID followed by a period (e.g., T123ABC456.) [4]. When configuring App Groups for macOS, it is a best practice to prefix your group identifiers with this Team ID to ensure uniqueness and security [5][6]. While some documentation or online discussions may use $(TeamIdentifierPrefix) directly in entitlement files, Apple recommends using the explicit string in your code or configuration to ensure consistent resolution during archival and distribution [6][7][4]. Group Containers Group Containers are shared directories located at /Library/Group Containers/ that allow multiple apps or extensions from the same development team to share data [8][2][3]. - Accessing these containers must be done via the FileManager method containerURL(forSecurityApplicationGroupIdentifier:) [8][3]. - You should never construct these paths manually, as their specific location in the file system may change in future macOS releases [8]. - The directory is automatically created the first time your app accesses it [8][3]. Application Scripts The Application Scripts directory (/Library/Application Scripts/[bundle ID]) is a specialized location used primarily by NSUserScriptTask to run user-provided scripts [9][3]. - Unlike Group Containers, there is no direct, equivalent "App Group" version of the Application Scripts directory [9]. - Sandboxed apps can read scripts from this directory but generally cannot write to it [3]. - Because NSUserScriptTask is designed to target specific application bundles, it does not have built-in support for sharing scripts via an App Group container [9]. Containers The term "Containers" typically refers to an app's primary sandbox directory, located at ~/Library/Containers/[bundle ID]/ [9][3]. This is where an app's private data, including its own Library/Application Support, Library/Caches, and Library/Preferences folders, is stored [9][3]. Every sandboxed app has its own container, which is isolated from other apps unless explicitly shared via App Groups [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== static check for $(TeamIdentifierPrefix) in quoted trash calls =="
python3 - <<'PY'
import json, re
path = "ee/maintained-apps/outputs/wechat/darwin.json"
data = json.load(open(path))
script = data["refs"]["a5e852b0"]
pattern = re.compile(r"trash\s+\S+\s+['\"](.+?)['\"]")
for m in pattern.finditer(script):
    q = m.group(1)
    if "$(TeamIdentifierPrefix)" in q or q.startswith("~/Library/Application Scripts/") or q.startswith("~/Library/Containers/") or q.startswith("~/Library/Group Containers/"):
        print(f"quote={q!r}")
        print(f"   contains placeholder: {bool(re.search(r'\\$\\(TeamIdentifierPrefix\\)', q))}")
        print(f"   quoted literal prefix: {q.strip('/').split('/',1)[0]}/{q.split('/',1)[1].split('/',1)[0]*('/'+q.split('/',1)[1].split('/',1)[0]).split('/',2)[0] if '/' in q else ''}")
PY

echo
echo "== concrete WeChat prefixed paths used in same file =="
python3 - <<'PY'
import json, re
script = json.load(open("ee/maintained-apps/outputs/wechat/darwin.json"))["refs"]["a5e852b0"]
for line in script.splitlines():
    if "5A4RE8SF68.com.tencent.xinWeChat" in line or 'com.tencent.xinWeChat' in line:
        print(line)
PY

Repository: fleetdm/fleet

Length of output: 530


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== inspect generated JSON and referenced script =="
wc -l ee/maintained-apps/outputs/wechat/darwin.json
python3 -m json.tool ee/maintained-apps/outputs/wechat/darwin.json >/tmp/wechat-json-pretty.json 2>&1 || true

echo "== lines in referenced script containing WeChat paths =="
python3 <<'PY'
import json, re
script = json.load(open("ee/maintained-apps/outputs/wechat/darwin.json", encoding="utf-8"))["refs"]["a5e852b0"]
pattern = re.compile(r"trash\s+(\S+)\s+['\"](.+?)['\"]")
for line in script.splitlines():
    if "WeChat" in line:
        print("L", line)

print("\nquoted trash paths containing TeamIdentifierPrefix:")
for m in pattern.finditer(script):
    args = (m.group(1), m.group(2))
    if "TeamIdentifierPrefix" in args[1]:
        print(args)

print("\nexisting concrete team-prefixed WeChat paths in script:")
for line in script.splitlines():
    if "5A4RE8SF68.com.tencent.xinWeChat" in line:
        print(line)
PY

echo
echo "== compare literal bash expansion in equivalent quoted argument =="
bash -c "x='~/${TeamIdentifierPrefix}com.tencent.xinWeChat'; echo \"val=$x\"; bash -c 'x=~/\${TeamIdentifierPrefix}com.tencent.xinWeChat; echo val=$x' <<< /tmp"

Repository: fleetdm/fleet

Length of output: 2585


Replace the literal $(TeamIdentifierPrefix) paths.

These single-quoted trash arguments pass the expansion syntax literally into trash(), and trash() has no placeholder substitution. That skips WeChat sandbox paths whose IDs are already spelled out with 5A4RE8SF68.com.tencent.xinWeChat; use those concrete paths or a narrowly scoped wildcard.

🤖 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/wechat/darwin.json` at line 20, Replace each
single-quoted trash argument containing the literal $(TeamIdentifierPrefix) in
the script’s WeChat cleanup calls with the corresponding concrete path or a
narrowly scoped wildcard that trash() can expand. Preserve the existing cleanup
targets and avoid relying on placeholder substitution, since trash() does not
perform it.

@allenhouchins
allenhouchins merged commit f67d9a5 into main Jul 29, 2026
12 checks passed
@allenhouchins
allenhouchins deleted the fma-2607291235 branch July 29, 2026 14:35
@github-actions

Copy link
Copy Markdown
Contributor

Script Diff Results

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

=== Install Script (no changes) ===
=== Uninstall // 31c0d61d -> ab5ff0cc ===

--- /tmp/old.rKSXKM	2026-07-29 14:36:14.799688220 +0000
+++ /tmp/new.EqKHvk	2026-07-29 14:36:14.800688241 +0000
@@ -169,6 +169,13 @@
 sudo rm -rf '/Library/ScriptingAdditions/BartenderHelper.osax'
 sudo rm -rf '/System/Library/ScriptingAdditions/BartenderSystemHelper.osax'
 sudo rm -rf "$APPDIR/Bartender 6.app"
+trash $LOGGED_IN_USER '~/Library/Application Scripts/24J875RH8J.com.surteesstudios.Bartender'
+trash $LOGGED_IN_USER '~/Library/Application Support/Bartender 6'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.surteesstudios.bartender.sfl*'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.surteesstudios.Bartender.revenuecat'
 trash $LOGGED_IN_USER '~/Library/Caches/com.surteesstudios.Bartender'
+trash $LOGGED_IN_USER '~/Library/Caches/com.surteesstudios.Bartender.revenuecat'
 trash $LOGGED_IN_USER '~/Library/Cookies/com.surteesstudios.Bartender.binarycookies'
+trash $LOGGED_IN_USER '~/Library/Group Containers/24J875RH8J.com.surteesstudios.Bartender'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.surteesstudios.Bartender'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.surteesstudios.Bartender.plist'

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

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

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 87ce57a8 -> ff358276 ===

--- /tmp/old.L5uExv	2026-07-29 14:36:14.972691892 +0000
+++ /tmp/new.hWwIKs	2026-07-29 14:36:14.972691892 +0000
@@ -53,5 +53,6 @@
 }
 
 sudo rm -rf "$APPDIR/CMake.app"
+trash $LOGGED_IN_USER '~/Library/Preferences/com.kitware.CMakeSetup.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/org.cmake.cmake.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/org.cmake.cmake.savedState'

ee/maintained-apps/outputs/eclipse-temurin-jdk-11/windows.json

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

ee/maintained-apps/outputs/eclipse-temurin-jdk-17/windows.json

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

ee/maintained-apps/outputs/eclipse-temurin-jre-11/windows.json

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

ee/maintained-apps/outputs/eclipse-temurin-jre-21/windows.json

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

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

=== Install Script (no changes) ===
=== Uninstall // e680fa19 -> 3939b006 ===

--- /tmp/old.0ImalO	2026-07-29 14:36:15.187696451 +0000
+++ /tmp/new.zb9Gb2	2026-07-29 14:36:15.187696451 +0000
@@ -55,5 +55,6 @@
 sudo rm -rf "$APPDIR/EtreCheckPro.app"
 trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.etresoft.etrecheck*.sfl*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.etresoft.EtreCheck*'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.etresoft.EtreCheck*'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.etresoft.EtreCheck*.plist'
 trash $LOGGED_IN_USER '~/Library/WebKit/com.etresoft.EtreCheck*'

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

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

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

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

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

=== Install Script (no changes) ===
=== Uninstall // cbcf926e -> c1bae326 ===

--- /tmp/old.9ccGSz	2026-07-29 14:36:15.332699520 +0000
+++ /tmp/new.hnaPx4	2026-07-29 14:36:15.332699520 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/GDevelop 5.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.gdevelop-app.ide.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/GDevelop 5'
 trash $LOGGED_IN_USER '~/Library/Logs/GDevelop 5'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.gdevelop-app.ide.plist'

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 7b9b9d06 -> 8135b983 ===

--- /tmp/old.UUE1rh	2026-07-29 14:36:15.428701552 +0000
+++ /tmp/new.ZedxiJ	2026-07-29 14:36:15.428701552 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/Granola.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.granola.app.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/Granola'
 trash $LOGGED_IN_USER '~/Library/Caches/com.granola.app'
 trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.granola.app'

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

=== Install Script (no changes) ===
=== Uninstall // f35082d0 -> 85ab2d46 ===

--- /tmp/old.8m0Kby	2026-07-29 14:36:15.485702759 +0000
+++ /tmp/new.WIixSY	2026-07-29 14:36:15.485702759 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/jamovi.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/org.jamovi.jamovi.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/jamovi'
 trash $LOGGED_IN_USER '~/Library/Logs/jamovi'
 trash $LOGGED_IN_USER '~/Library/Preferences/org.jamovi.jamovi.plist'

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

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

--- /tmp/old.MhWGcy	2026-07-29 14:36:15.544704008 +0000
+++ /tmp/new.V4MIRd	2026-07-29 14:36:15.544704008 +0000
@@ -163,12 +163,17 @@
 remove_pkg_files 'app.kaleidoscope.uninstall_ksdiff'
 forget_pkg 'app.kaleidoscope.uninstall_ksdiff'
 sudo rm -rf "$APPDIR/Kaleidoscope.app"
+trash $LOGGED_IN_USER '~/Library/Application Scripts/app.kaleidoscope.v*.KaleidoscopePrism'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/app.kaleidoscope.v*.KSShareExtension'
 trash $LOGGED_IN_USER '~/Library/Application Support/app.kaleidoscope.v*'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.blackpixel.kaleidoscope'
 trash $LOGGED_IN_USER '~/Library/Application Support/Kaleidoscope'
 trash $LOGGED_IN_USER '~/Library/Caches/app.kaleidoscope.v*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.blackpixel.kaleidoscope'
 trash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.blackpixel.kaleidoscope'
+trash $LOGGED_IN_USER '~/Library/Containers/app.kaleidoscope.v*.KaleidoscopePrism'
+trash $LOGGED_IN_USER '~/Library/Containers/app.kaleidoscope.v*.KSShareExtension'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/app.kaleidoscope.v*'
 trash $LOGGED_IN_USER '~/Library/Preferences/app.kaleidoscope.v*.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.blackpixel.kaleidoscope.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/app.kaleidoscope.v*.savedState'

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

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

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

=== Install Script (no changes) ===
=== Uninstall // ba6e6e70 -> 3b479c55 ===

--- /tmp/old.lx3cAk	2026-07-29 14:36:15.633705892 +0000
+++ /tmp/new.ewfKLh	2026-07-29 14:36:15.633705892 +0000
@@ -236,6 +236,7 @@
 forget_pkg 'com.malwarebytes.mbam.*'
 sudo rm -rf '/Library/Application Support/Malwarebytes/MBAM'
 sudo rmdir '/Library/Application Support/Malwarebytes'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.malwarebytes.mbam'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.malwarebytes.mbam.frontend.*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.crashlytics.data/com.malwarebytes.mbam.frontend.*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.malwarebytes.mbam.frontend.*'

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

=== Install Script (no changes) ===
=== Uninstall // 3ec00885 -> 6420e7ee ===

--- /tmp/old.02Y1ck	2026-07-29 14:36:15.685706993 +0000
+++ /tmp/new.lYzboh	2026-07-29 14:36:15.686707014 +0000
@@ -233,10 +233,14 @@
 remove_pkg_files 'com.nextcloud.desktopclient'
 forget_pkg 'com.nextcloud.desktopclient'
 sudo rm -rf '/Applications/Nextcloud.app'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/com.nextcloud.desktopclient.FileProviderExt'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.nextcloud.desktopclient.FinderSyncExt'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/NKUJUXUJ3B.com.nextcloud.desktopclient'
 trash $LOGGED_IN_USER '~/Library/Application Support/Nextcloud'
 trash $LOGGED_IN_USER '~/Library/Caches/Nextcloud'
+trash $LOGGED_IN_USER '~/Library/Containers/com.nextcloud.desktopclient.FileProviderExt'
 trash $LOGGED_IN_USER '~/Library/Containers/com.nextcloud.desktopclient.FinderSyncExt'
 trash $LOGGED_IN_USER '~/Library/Group Containers/com.nextcloud.desktopclient'
+trash $LOGGED_IN_USER '~/Library/Group Containers/NKUJUXUJ3B.com.nextcloud.desktopclient'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.nextcloud.desktopclient.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/Nextcloud'

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

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

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

=== Install Script (no changes) ===
=== Uninstall // 6c8b1ba5 -> ddfd228c ===

--- /tmp/old.cL1TlE	2026-07-29 14:36:15.774708877 +0000
+++ /tmp/new.CRh0NZ	2026-07-29 14:36:15.774708877 +0000
@@ -55,5 +55,6 @@
 sudo rm -rf "$APPDIR/OmniOutliner.app"
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.omnigroup.OmniOutliner6'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.omnigroup.OmniOutliner6.Thumbnails'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.omnigroup.omnioutliner*.sfl*'
 trash $LOGGED_IN_USER '~/Library/Containers/com.omnigroup.OmniOutliner6'
 trash $LOGGED_IN_USER '~/Library/Containers/com.omnigroup.OmniOutliner6.Thumbnails'

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

=== Install Script (no changes) ===
=== Uninstall // 966b2fa5 -> 20883323 ===

--- /tmp/old.eJq2jx	2026-07-29 14:36:15.823709914 +0000
+++ /tmp/new.urvRqf	2026-07-29 14:36:15.823709914 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/Postman.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.postmanlabs.mac.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.postmanlabs.mac.ShipIt'
 trash $LOGGED_IN_USER '~/Library/Application Support/Postman'
 trash $LOGGED_IN_USER '~/Library/Caches/com.postmanlabs.mac'

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

=== Install Script (no changes) ===
=== Uninstall // 86416480 -> 6a559637 ===

--- /tmp/old.kcuRoO	2026-07-29 14:36:15.871710930 +0000
+++ /tmp/new.Fygt5P	2026-07-29 14:36:15.872710951 +0000
@@ -53,10 +53,12 @@
 }
 
 sudo rm -rf "$APPDIR/Raindrop.io.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/io.raindrop.macapp.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/Raindrop.io'
 trash $LOGGED_IN_USER '~/Library/Caches/com.apple.Safari/Extensions/Raindrop.io.safariextension'
 trash $LOGGED_IN_USER '~/Library/Cookies/io.raindrop.mac.binarycookies'
 trash $LOGGED_IN_USER '~/Library/Preferences/io.raindrop.mac.helper.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/io.raindrop.mac.plist'
+trash $LOGGED_IN_USER '~/Library/Preferences/io.raindrop.macapp.plist'
 trash $LOGGED_IN_USER '~/Library/Safari/Extensions/Raindrop.io.safariextz'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/io.raindrop.mac.savedState'

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

=== Install Script (no changes) ===
=== Uninstall // 4fd2b3fa -> 07fb9072 ===

--- /tmp/old.Gc5sAE	2026-07-29 14:36:15.921711988 +0000
+++ /tmp/new.jF1xC1	2026-07-29 14:36:15.921711988 +0000
@@ -58,3 +58,4 @@
 trash $LOGGED_IN_USER '~/Library/Caches/com.devolutions.remotedesktopmanager'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.devolutions.remotedesktopmanager.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.devolutions.remotedesktopmanager.savedState'
+trash $LOGGED_IN_USER '~/Library/WebKit/com.devolutions.remotedesktopmanager'

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

=== Install Script (no changes) ===
=== Uninstall // 1b0bcecf -> 9714135f ===

--- /tmp/old.41Ssta	2026-07-29 14:36:15.969713004 +0000
+++ /tmp/new.oNspvL	2026-07-29 14:36:15.969713004 +0000
@@ -58,6 +58,7 @@
 trash $LOGGED_IN_USER '~/Library/Application Support/com.rightfontapp.RightFont*'
 trash $LOGGED_IN_USER '~/Library/Application Support/RightFont'
 trash $LOGGED_IN_USER '~/Library/Caches/com.rightfontapp.RightFont*'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.rightfontapp.RightFont5'
 trash $LOGGED_IN_USER '~/Library/Logs/RightFont*'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.rightfontapp.RightFont*.plist'
 trash $LOGGED_IN_USER '~/Library/WebKit/com.rightfontapp.RightFont*'

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

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

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

=== Install // 92413a45 -> 06b9111a ===

--- /tmp/old.OHofWd	2026-07-29 14:36:16.071715164 +0000
+++ /tmp/new.GgViXb	2026-07-29 14:36:16.071715164 +0000
@@ -96,5 +96,5 @@
 
 # install pkg files
 quit_and_track_application 'io.tailscale.ipn.macsys'
-sudo installer -pkg "$TMPDIR/Tailscale-1.98.9-macos.pkg" -target /
+sudo installer -pkg "$TMPDIR/Tailscale-1.98.10-macos.pkg" -target /
 relaunch_application 'io.tailscale.ipn.macsys'

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

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

=== Install Script (no changes) ===
=== Uninstall // d0cdc2d9 -> f00ebab2 ===

--- /tmp/old.yoziCI	2026-07-29 14:36:16.125716307 +0000
+++ /tmp/new.FvhWsG	2026-07-29 14:36:16.125716307 +0000
@@ -250,6 +250,7 @@
 forget_pkg 'TeamViewerUninstaller'
 sudo rm -rf '/Applications/TeamViewer.app'
 sudo rm -rf '/Library/Preferences/com.teamviewer*'
+trash $LOGGED_IN_USER '/Library/Application Support/TeamViewer'
 trash $LOGGED_IN_USER '~/Library/Application Support/TeamViewer'
 trash $LOGGED_IN_USER '~/Library/Caches/com.teamviewer.TeamViewer'
 trash $LOGGED_IN_USER '~/Library/Caches/TeamViewer'

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

=== Install Script (no changes) ===
=== Uninstall // 88749f85 -> 907ef18f ===

--- /tmp/old.giRw9f	2026-07-29 14:36:16.174717344 +0000
+++ /tmp/new.FTCVU1	2026-07-29 14:36:16.174717344 +0000
@@ -55,6 +55,7 @@
 sudo rm -rf "$APPDIR/Thunderbird.app"
 sudo rmdir '~/Library/Caches/Mozilla'
 trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/org.mozilla.thunderbird*.sfl*'
+trash $LOGGED_IN_USER '~/Library/Application Support/Thunderbird'
 trash $LOGGED_IN_USER '~/Library/Caches/Mozilla/updates/Applications/Thunderbird*'
 trash $LOGGED_IN_USER '~/Library/Caches/Thunderbird'
 trash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.thunderbird*.plist'

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

=== Install Script (no changes) ===
=== Uninstall // d196a4c8 -> 6ca374e8 ===

--- /tmp/old.SHJgoM	2026-07-29 14:36:16.223718381 +0000
+++ /tmp/new.nankpJ	2026-07-29 14:36:16.223718381 +0000
@@ -53,6 +53,7 @@
 }
 
 sudo rm -rf "$APPDIR/WebCatalog.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.webcatalog.jordan.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/WebCatalog'
 trash $LOGGED_IN_USER '~/Library/Caches/com.webcatalog.jordan'
 trash $LOGGED_IN_USER '~/Library/Caches/com.webcatalog.jordan.ShipIt'

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

=== Install Script (no changes) ===
=== Uninstall // 18d6be1e -> a5e852b0 ===

--- /tmp/old.BkPdyP	2026-07-29 14:36:16.274719461 +0000
+++ /tmp/new.LKHULb	2026-07-29 14:36:16.274719461 +0000
@@ -96,6 +96,7 @@
 sudo rm -rf "$APPDIR/WeChat.app"
 trash $LOGGED_IN_USER '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat.IPCHelper'
+trash $LOGGED_IN_USER '~/Library/Application Scripts/5A4RE8SF68.com.tencent.xinWeChat'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat.MiniProgram'
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.tencent.xinWeChat.WeChatMacShare'
@@ -106,5 +107,6 @@
 trash $LOGGED_IN_USER '~/Library/Containers/com.tencent.xinWeChat.WeChatMacShare'
 trash $LOGGED_IN_USER '~/Library/Cookies/com.tencent.xinWeChat.binarycookies'
 trash $LOGGED_IN_USER '~/Library/Group Containers/$(TeamIdentifierPrefix)com.tencent.xinWeChat'
+trash $LOGGED_IN_USER '~/Library/Group Containers/5A4RE8SF68.com.tencent.xinWeChat'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.tencent.xinWeChat.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.tencent.xinWeChat.savedState'

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

=== Install Script (no changes) ===
=== Uninstall // effe5345 -> 71fe81c3 ===

--- /tmp/old.1q5q3K	2026-07-29 14:36:16.322720477 +0000
+++ /tmp/new.pwm11Y	2026-07-29 14:36:16.323720498 +0000
@@ -53,6 +53,8 @@
 }
 
 sudo rm -rf "$APPDIR/WorkFlowy.app"
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.workflowy.desktop.sfl*'
 trash $LOGGED_IN_USER '~/Library/Application Support/WorkFlowy'
+trash $LOGGED_IN_USER '~/Library/Logs/WorkFlowy'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.workflowy.desktop.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.workflowy.desktop.savedState'

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