Update Fleet-maintained apps - #50138
Conversation
Generated automatically with cmd/maintained-apps.
Script Diff Resultsee/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' |
WalkthroughUpdated 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)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (34)
ee/maintained-apps/outputs/anyburn/windows.jsonee/maintained-apps/outputs/bartender/darwin.jsonee/maintained-apps/outputs/betterzip/darwin.jsonee/maintained-apps/outputs/canva/darwin.jsonee/maintained-apps/outputs/canva/windows.jsonee/maintained-apps/outputs/cmake-app/darwin.jsonee/maintained-apps/outputs/eclipse-temurin-jdk-11/windows.jsonee/maintained-apps/outputs/eclipse-temurin-jdk-17/windows.jsonee/maintained-apps/outputs/eclipse-temurin-jre-11/windows.jsonee/maintained-apps/outputs/eclipse-temurin-jre-21/windows.jsonee/maintained-apps/outputs/etrecheckpro/darwin.jsonee/maintained-apps/outputs/exifcleaner/darwin.jsonee/maintained-apps/outputs/firefox@nightly/darwin.jsonee/maintained-apps/outputs/gdevelop/darwin.jsonee/maintained-apps/outputs/google-gemini/darwin.jsonee/maintained-apps/outputs/granola/darwin.jsonee/maintained-apps/outputs/jamovi/darwin.jsonee/maintained-apps/outputs/kaleidoscope/darwin.jsonee/maintained-apps/outputs/libreoffice/windows.jsonee/maintained-apps/outputs/malwarebytes/darwin.jsonee/maintained-apps/outputs/nextcloud/darwin.jsonee/maintained-apps/outputs/nordvpn/windows.jsonee/maintained-apps/outputs/omnioutliner/darwin.jsonee/maintained-apps/outputs/postman/darwin.jsonee/maintained-apps/outputs/raindropio/darwin.jsonee/maintained-apps/outputs/remote-desktop-manager/darwin.jsonee/maintained-apps/outputs/rightfont/darwin.jsonee/maintained-apps/outputs/tableplus/windows.jsonee/maintained-apps/outputs/tailscale-app/darwin.jsonee/maintained-apps/outputs/teamviewer/darwin.jsonee/maintained-apps/outputs/thunderbird/darwin.jsonee/maintained-apps/outputs/webcatalog/darwin.jsonee/maintained-apps/outputs/wechat/darwin.jsonee/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" |
There was a problem hiding this comment.
🩺 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' || trueRepository: 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
PYRepository: 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}")
PYRepository: 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.cmakeetrecheckpro:com.etresoft.EtreCheck4gdevelop:com.gdevelop-app.iderightfont:com.rightfontapp.RightFont5thunderbird:org.mozilla.thunderbirdwebcatalog:com.webcatalog.jordanworkflowy: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-L20ee/maintained-apps/outputs/gdevelop/darwin.json#L20-L21ee/maintained-apps/outputs/rightfont/darwin.json#L20-L20ee/maintained-apps/outputs/thunderbird/darwin.json#L20-L20ee/maintained-apps/outputs/webcatalog/darwin.json#L18-L19ee/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", |
There was a problem hiding this comment.
🎯 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 andtrash().ee/maintained-apps/outputs/jamovi/darwin.json#L20-L20: propagate failures from app removal andtrash().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 andtrash().ee/maintained-apps/outputs/postman/darwin.json#L19-L19: propagate failures from extraction, app removal, andtrash().ee/maintained-apps/outputs/raindropio/darwin.json#L19-L19: propagate failures from app removal andtrash().ee/maintained-apps/outputs/remote-desktop-manager/darwin.json#L19-L19: propagate failures from app removal andtrash().
📍 Affects 9 files
ee/maintained-apps/outputs/granola/darwin.json#L19-L19(this comment)ee/maintained-apps/outputs/jamovi/darwin.json#L20-L20ee/maintained-apps/outputs/kaleidoscope/darwin.json#L19-L19ee/maintained-apps/outputs/malwarebytes/darwin.json#L19-L19ee/maintained-apps/outputs/nextcloud/darwin.json#L19-L19ee/maintained-apps/outputs/omnioutliner/darwin.json#L20-L20ee/maintained-apps/outputs/postman/darwin.json#L19-L19ee/maintained-apps/outputs/raindropio/darwin.json#L19-L19ee/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", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Keep uninstall bundle identifiers aligned with installation metadata.
ee/maintained-apps/outputs/kaleidoscope/darwin.json#L19-L19: changequit_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 fromcom.malwarebytes.mbam.frontend.agentto the installed GUI application identifiercom.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", |
There was a problem hiding this comment.
🎯 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.jsonRepository: 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:
- 1: https://keith.github.io/xcode-man-pages/launchctl.1.html
- 2: https://osxhub.com/macos-launchctl-manage-services/
- 3: https://joelsenders.wordpress.com/2019/03/14/dear-launchctl-were-all-using-you-wrong/
- 4: https://stackoverflow.com/questions/70127661/running-launchd-services-with-non-root-user-on-macos
- 5: https://babodee.wordpress.com/2016/04/09/launchctl-2-0-syntax/
- 6: https://ss64.com/mac/launchctl.html
- 7: https://stackoverflow.com/questions/31206756/launchctl-remove-enabled-disabled-override
🌐 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:
- 1: https://ss64.com/mac/launchctl.html
- 2: https://apple.stackexchange.com/questions/300826/will-deleting-removing-some-system-processes-plist-files-screw-up-my-mac
- 3: https://manp.gs/mac/1/launchctl
- 4: https://mybyways.com/blog/how-to-remove-a-background-service-in-macos
- 5: https://forums.macrumors.com/threads/cleanup-launchctl.2326040/
- 6: https://osxhub.com/macos-launchctl-manage-services/
- 7: https://apple.stackexchange.com/questions/366281/launchd-confusion-on-semantics-of-bootstrap-and-bootout-etc-after-reading-manu
- 8: https://kip.land/references/launchd
- 9: https://apple.stackexchange.com/questions/37188/stopping-launchagents-and-daemons
- 10: https://stackoverflow.com/questions/39742555/running-job-on-bamboo-not-privileged-to-remove-service-on-osx
- 11: https://serverfault.com/questions/287290/removing-a-process-from-launchd-without-restarting
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" |
There was a problem hiding this comment.
🎯 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
') || trueRepository: 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:
- 1: https://unix.stackexchange.com/questions/151850/why-doesnt-the-tilde-expand-inside-double-quotes
- 2: https://askubuntu.com/questions/1032370/why-cant-i-cd-to-a-quoted-tilde
- 3: https://stackoverflow.com/questions/67117575/how-to-use-inside-a-double-quote-path-reference
- 4: https://superuser.com/questions/872896/expanding-tilde-convenience-paths-when-using-quoted-paths-in-a-bash-script
- 5: https://unix.stackexchange.com/questions/494561/how-to-escape-parenthesis-in-bash-rmdir
- 6: https://man.archlinux.org/man/bash.1
- 7: https://unix.stackexchange.com/questions/681456/how-can-i-remove-a-directory-with-the-name-of-single-quotes-are-part-of-th
🏁 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') || trueRepository: 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") || trueRepository: 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-L20ee/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" |
There was a problem hiding this comment.
🎯 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 | sortRepository: 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 || trueRepository: 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 || trueRepository: 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" |
There was a problem hiding this comment.
🔒 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 | sortRepository: 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 /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:
- 1: https://apple-docs.everest.mt/docs/bundleresources/entitlements/com.apple.security.application-groups/
- 2: https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html
- 3: https://paperzz.com/doc/1215731/app-sandbox-design-guide---apple-developer
- 4: How to use shared AppGroup UserDefaults in macOS and Xcode extension onmyway133/blog#201
- 5: https://developer.apple.com/documentation/xcode/accessing-app-group-containers
- 6: https://stackoverflow.com/questions/38037745/should-macos-app-group-name-start-with-group-or-development-team-id
- 7: https://developer.apple.com/forums/thread/758358
- 8: https://developer.apple.com/documentation/foundation/filemanager/containerurl(forsecurityapplicationgroupidentifier:)
- 9: https://developer.apple.com/forums/thread/746143
🏁 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)
PYRepository: 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.
Script Diff Resultsee/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' |
Automated ingestion of latest Fleet-maintained app data.
Summary by CodeRabbit