Update Fleet-maintained apps - #50127
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.f4PY4t 2026-07-29 08:59:29.071819091 +0000
+++ /tmp/new.NrDVSk 2026-07-29 08:59:29.071819091 +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/cmake-app/darwin.json=== Install Script (no changes) ===
=== Uninstall // 87ce57a8 -> ff358276 ===
--- /tmp/old.FeygjG 2026-07-29 08:59:29.115819190 +0000
+++ /tmp/new.wRrjdI 2026-07-29 08:59:29.115819190 +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/etrecheckpro/darwin.json=== Install Script (no changes) ===
=== Uninstall // e680fa19 -> 3939b006 ===
--- /tmp/old.3zldRJ 2026-07-29 08:59:29.159819290 +0000
+++ /tmp/new.DVITMb 2026-07-29 08:59:29.159819290 +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.ukzB7e 2026-07-29 08:59:29.294819595 +0000
+++ /tmp/new.3xKeRq 2026-07-29 08:59:29.295819597 +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/granola/darwin.json=== Install Script (no changes) ===
=== Uninstall // 7b9b9d06 -> 8135b983 ===
--- /tmp/old.soVHfN 2026-07-29 08:59:29.346819712 +0000
+++ /tmp/new.uT9F3T 2026-07-29 08:59:29.347819714 +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.i2tb6p 2026-07-29 08:59:29.401819836 +0000
+++ /tmp/new.tb7Ot3 2026-07-29 08:59:29.401819836 +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.bA2iGD 2026-07-29 08:59:29.448819943 +0000
+++ /tmp/new.5Dhpgz 2026-07-29 08:59:29.449819945 +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/malwarebytes/darwin.json=== Install Script (no changes) ===
=== Uninstall // ba6e6e70 -> 3b479c55 ===
--- /tmp/old.xlrgMZ 2026-07-29 08:59:29.497820053 +0000
+++ /tmp/new.Qx9wfy 2026-07-29 08:59:29.498820055 +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.xmAZLU 2026-07-29 08:59:29.545820162 +0000
+++ /tmp/new.9wvcxA 2026-07-29 08:59:29.545820162 +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/omnioutliner/darwin.json=== Install Script (no changes) ===
=== Uninstall // 6c8b1ba5 -> ddfd228c ===
--- /tmp/old.hJCpyE 2026-07-29 08:59:29.590820263 +0000
+++ /tmp/new.R7u58R 2026-07-29 08:59:29.590820263 +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.bInhc9 2026-07-29 08:59:29.634820363 +0000
+++ /tmp/new.yx2GKb 2026-07-29 08:59:29.634820363 +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.AgMaYA 2026-07-29 08:59:29.679820464 +0000
+++ /tmp/new.5vCMkw 2026-07-29 08:59:29.679820464 +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.8uV3eJ 2026-07-29 08:59:29.723820564 +0000
+++ /tmp/new.Wami9A 2026-07-29 08:59:29.723820564 +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.vCSmnq 2026-07-29 08:59:29.769820667 +0000
+++ /tmp/new.JdySIC 2026-07-29 08:59:29.769820667 +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/tailscale-app/darwin.json=== Install // 92413a45 -> 06b9111a ===
--- /tmp/old.3O0BXn 2026-07-29 08:59:29.823820790 +0000
+++ /tmp/new.sZKeaQ 2026-07-29 08:59:29.824820792 +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.Q6a1TI 2026-07-29 08:59:29.874820905 +0000
+++ /tmp/new.71h8k7 2026-07-29 08:59:29.874820905 +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.LC1bM7 2026-07-29 08:59:29.919821006 +0000
+++ /tmp/new.g1PVMv 2026-07-29 08:59:29.919821006 +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.OFPFzy 2026-07-29 08:59:29.967821115 +0000
+++ /tmp/new.n31dsz 2026-07-29 08:59:29.967821115 +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.aSHXOB 2026-07-29 08:59:30.022821239 +0000
+++ /tmp/new.tfJ7xl 2026-07-29 08:59:30.022821239 +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.aPID25 2026-07-29 08:59:30.075821359 +0000
+++ /tmp/new.2GTnzL 2026-07-29 08:59:30.075821359 +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 metadata for AnyBurn, ExifCleaner, Firefox Nightly, Postman, and Tailscale. Revised macOS uninstall script references and embedded scripts for multiple applications, adding or changing cleanup of application data, shared recent-document lists, containers, preferences, caches, HTTP storage, logs, services, receipts, and app bundles. 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: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ee/maintained-apps/outputs/omnioutliner/darwin.json`:
- Line 20: Update the recent-document glob passed to trash in the OmniOutliner
cleanup script to use the correctly cased bundle identifier,
com.omnigroup.OmniOutliner6*.sfl*. Leave the surrounding cleanup paths and trash
function unchanged.
In `@ee/maintained-apps/outputs/rightfont/darwin.json`:
- Line 20: Update the RightFont cleanup command in the uninstall script to
target /Users/$LOGGED_IN_USER/RightFont instead of the single-quoted
'~/RightFont' literal. Preserve the existing rmdir behavior while using the
already-captured LOGGED_IN_USER value.
In `@ee/maintained-apps/outputs/teamviewer/darwin.json`:
- Line 20: Update the TeamViewer system-preference removal command near the
cleanup calls so the com.teamviewer* wildcard is expanded by a shell running
with sudo rather than passed as a quoted literal to rm. Preserve removal of all
matching entries under /Library/Preferences, and keep the existing cleanup scope
unchanged.
In `@ee/maintained-apps/outputs/wechat/darwin.json`:
- Line 20: Resolve the TeamIdentifierPrefix value before invoking trash for the
affected Application Scripts, Containers, and Group Containers paths. Update the
call sites around the trash function so the paths contain the concrete prefix
rather than the literal $(TeamIdentifierPrefix) text, while preserving the
existing cleanup behavior.
🪄 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: c1999e17-39ca-42c6-8cac-583a29b9d7e2
📒 Files selected for processing (23)
ee/maintained-apps/outputs/anyburn/windows.jsonee/maintained-apps/outputs/bartender/darwin.jsonee/maintained-apps/outputs/cmake-app/darwin.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/granola/darwin.jsonee/maintained-apps/outputs/jamovi/darwin.jsonee/maintained-apps/outputs/kaleidoscope/darwin.jsonee/maintained-apps/outputs/malwarebytes/darwin.jsonee/maintained-apps/outputs/nextcloud/darwin.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/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
| "refs": { | ||
| "535cbe5c": "#!/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.omnigroup.OmniOutliner6'\nif [ -d \"$APPDIR/OmniOutliner.app\" ]; then\n\tsudo mv \"$APPDIR/OmniOutliner.app\" \"$TMPDIR/OmniOutliner.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/OmniOutliner.app\" \"$APPDIR\"\nrelaunch_application 'com.omnigroup.OmniOutliner6'\n", | ||
| "6c8b1ba5": "#!/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/OmniOutliner.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.omnigroup.OmniOutliner6'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.omnigroup.OmniOutliner6.Thumbnails'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.omnigroup.OmniOutliner6'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.omnigroup.OmniOutliner6.Thumbnails'\n" | ||
| "ddfd228c": "#!/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/OmniOutliner.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.omnigroup.OmniOutliner6'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.omnigroup.OmniOutliner6.Thumbnails'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.omnigroup.omnioutliner*.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.omnigroup.OmniOutliner6'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.omnigroup.OmniOutliner6.Thumbnails'\n" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file location =="
git ls-files | grep 'ee/maintained-apps/outputs/omnioutliner/darwin.json' || true
echo "== relevant lines =="
if [ -f ee/maintained-apps/outputs/omnioutliner/darwin.json ]; then
cat -n ee/maintained-apps/outputs/omnioutliner/darwin.json | sed -n '15,35p'
fi
echo "== all OmniOutliner references =="
rg -n "omnioutliner|OmniOutliner|com\.omnigroup|sfl" ee/maintained-apps/outputs/omnioutliner Darwin || trueRepository: fleetdm/fleet
Length of output: 13196
🌐 Web query:
macOS com.apple.sharedfilelist ApplicationRecentDocuments bundle identifier recent documents filename casing sfl
💡 Result:
On macOS, the directory ~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/ stores recent document lists for individual applications [1][2][3]. These lists are maintained by the sharedfilelistd daemon [4]. Technical details regarding these files include: 1. Filename Casing and Identity: The files within this directory are typically named using the application's bundle identifier followed by an extension (e.g., com.example.appname.sfl2) [3][5]. While bundle identifiers are technically case-sensitive, they are conventionally written in lowercase [5]. When programmatically interacting with these files, it is standard practice to treat the bundle identifier as the primary key [3][5]. 2. File Format: These files are binary property lists, specifically formatted as NSKeyedArchiver archives [6][7]. While historically referred to as.sfl files (introduced in OS X 10.11), they have evolved into.sfl2 and.sfl3 formats in later versions of macOS [1][8][9]. Despite the extension change, they remain encoded property lists that contain serialized objects, such as SFLListItems, which store metadata and Bookmarks (binary BLOBs) pointing to the actual files [1][8][6]. 3. Content: The files do not store human-readable file paths directly; instead, they use persistent Bookmarks that the system resolves to locate files [1][8]. Because they are binary archives, they cannot be read directly as text files and require specialized tools or unarchiving via the NSKeyedUnarchiver class to extract their contents [10][6][7]. If you are encountering issues such as a failure to populate the "Recent Documents" menu, it is often due to the corruption of these specific files [11]. Removing the associated.sfl/.sfl2/.sfl3 file for the affected application and restarting the sharedfilelistd daemon (via the command killall sharedfilelistd) is a common troubleshooting step [3][11].
Citations:
- 1: https://eclecticlight.co/2023/08/04/recent-items-property-lists-bookmarks-and-resolvers/
- 2: https://eclecticlight.co/2019/03/18/how-launchservices-builds-the-open-recent-menu-and-outsmarts-aliases/
- 3: https://developer.apple.com/forums/thread/710278
- 4: https://keith.github.io/xcode-man-pages/sharedfilelistd.8.html
- 5: https://forum.latenightsw.com/t/how-do-i-get-recent-files-of-all-apps/1299
- 6: http://www.mac4n6.com/blog/2016/1/1/manual-analysis-of-nskeyedarchiver-formatted-plist-files-a-review-of-the-new-os-x-1011-recent-items
- 7: https://gist.github.com/pudquick/4776b4b2075bf9b7e512
- 8: https://eclecticlight.co/2024/05/18/how-to-decode-a-bookmark-in-a-property-list/
- 9: https://jsac.jpcert.or.jp/archive/2022/pdf/JSAC2022_workshop_macOS-forensic_en.pdf
- 10: https://www.synalysis.blog/blog/how-to-decode-apple-binary-property-list-files
- 11: https://forum.literatureandlatte.com/t/recent-documents-menu-unpopulated/33517
🌐 Web query:
site:eclecticlight.co macOS recent documents bundle identifier casing Omniscript .sfl
💡 Result:
On macOS, the "Recent Items" and "Open Recent" lists are managed by the Launch Services framework, which utilizes Shared File List (.sfl,.sfl2,.sfl3) property list files [1][2][3][4]. These files are stored in the user's Library directory at: ~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/ In this directory, individual.sfl files are named according to the application's bundle identifier [1]. For example, an application with the bundle ID com.example.app would correspond to a file named com.example.app.sfl (or.sfl2/3 in newer versions) [1][5]. Regarding your query about "Omniscript" and casing: 1. Bundle Identifier Casing: macOS bundle identifiers are conventionally written in reverse domain name notation using lowercase letters (e.g., com.apple.textedit) [6]. While the file system on macOS is typically case-insensitive (but case-preserving), bundle identifiers themselves are treated as case-sensitive in many system contexts. It is standard practice to maintain them in lowercase to ensure compatibility and avoid resolution issues. 2. Omniscript: There is no recognized macOS system component, file type, or standard technology known as "Omniscript" associated with.sfl files or the Launch Services framework [1][2][3][4]. It is possible this term is a misremembered name for an application, a third-party scripting tool, or perhaps a confusion with the term "OpenScript" or a similar utility. The.sfl files themselves are property lists (plists) that contain Base-64 encoded "Bookmarks"—a specialized macOS data structure used to track file locations even if they are moved or renamed on the same volume [2][3][7][4]. They do not contain plain-text file paths [2][3]. If you are encountering a file with this name or suspect it is affecting your recent documents, it is likely a third-party tool or an incorrectly named file, as it does not align with native macOS naming conventions for Launch Services data [1][5].
Citations:
- 1: https://eclecticlight.co/2017/08/10/recent-items-launch-services-and-sharedfilelists/
- 2: https://eclecticlight.co/2023/08/04/recent-items-property-lists-bookmarks-and-resolvers/
- 3: https://eclecticlight.co/2024/05/18/how-to-decode-a-bookmark-in-a-property-list/
- 4: https://eclecticlight.co/2018/03/05/open-recent-inodes-and-bookmarks-how-macos-remembers-files/
- 5: https://eclecticlight.co/2019/03/18/how-launchservices-builds-the-open-recent-menu-and-outsmarts-aliases/
- 6: https://eclecticlight.co/2023/07/06/how-to-get-the-right-app-to-open-a-document-and-fix-launchservices-problems/
- 7: https://eclecticlight.co/2018/03/08/using-macos-bookmarks-as-enduring-references-precize-and-preciziun/
🌐 Web query:
"~/.Trash" "ApplicationRecentDocuments" ".sfl2" "OmniOutliner"
💡 Result:
The file path you mentioned, ~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/, is the standard directory where macOS stores Shared File List (SFL) files that track recently opened documents for individual applications [1][2][3]. The files within this folder typically have an.sfl2 extension (or occasionally.sfl in older macOS versions) [1][3][4]. Regarding OmniOutliner, a file named with the application's bundle identifier followed by.sfl2 in that directory contains the list of documents recently opened by OmniOutliner [2][3]. Key characteristics of these files include: 1. Function: They are property list files managed by the macOS system service sharedfilelistd [1][2]. They enable the "Open Recent" functionality found in most macOS applications [1][5]. 2. Content: These files do not contain plain-text file paths [1][6]. Instead, they store macOS "Bookmark" data, which are opaque, binary-encoded objects that resolve to file system paths [1][6][4]. 3. Management: The operating system updates these files automatically when an application is opened or when a document is accessed [2]. Manually editing or deleting them is generally unnecessary and can be handled by the system [2][5]. 4. Accessing Information: Because they contain binary-encoded Bookmarks, they cannot be read directly with a standard text editor [1][6]. To view the contents, you must use specialized tools or scripts (such as those using Swift's NSKeyedUnarchiver) capable of decoding the stored Bookmark data into usable file paths [1][7][6]. If you were looking at these files in the ~/.Trash folder, it suggests they were recently deleted from the Application Support directory [1][2]. You can restore them to their original location if you need to recover OmniOutliner's "Recent Documents" history, or simply leave them in the Trash to be permanently removed [2].
Citations:
- 1: https://eclecticlight.co/2023/08/04/recent-items-property-lists-bookmarks-and-resolvers/
- 2: https://eclecticlight.co/2017/08/10/recent-items-launch-services-and-sharedfilelists/
- 3: https://www.cyberengage.org/post/understanding-macos-app-preference-files-mru-files-shared-file-lists-and-account-artifacts-for-di
- 4: https://eclecticlight.co/2020/05/21/bookmarks-a-type-of-alias-their-access-and-use/
- 5: https://filext.com/file-extension/SFL2
- 6: https://eclecticlight.co/2024/05/18/how-to-decode-a-bookmark-in-a-property-list/
- 7: https://stackoverflow.com/questions/77211553/how-can-i-decode-an-nscoder-with-a-terminal-command
🌐 Web query:
Google "OmniOutliner recent documents shared file list case-sensitive"
💡 Result:
OmniOutliner documentation and user resources do not indicate that the "Recent Documents" or shared file list functionality is case-sensitive [1][2][3]. The application's interface for managing files, such as the Resource Browser on macOS or the Document Browser on iOS, generally handles file indexing and searching based on system-level file system behaviors [1][2][4]. On Apple platforms (macOS, iOS, iPadOS), the underlying file systems (APFS/HFS+) are typically case-insensitive but case-preserving, which informs how applications interact with file paths and names [5]. While there have been historical reports from users regarding case-sensitive behavior in specific, older in-app filtering contexts (such as certain column-specific filters) [6], there is no evidence to suggest that the Recent Documents list itself employs case-sensitive matching for identifying or displaying files. If you are encountering issues where files are not appearing as expected, it is likely related to file path indexing or synchronization across locations (such as iCloud Drive or local storage) rather than case-sensitivity in the file list UI itself [2][3]. You can typically use the search field provided within the Resource Browser or Document Browser to filter through your recent documents, and this search is intended to assist in finding files regardless of exact case matching [1][2][7].
Citations:
- 1: https://support.omnigroup.com/documentation/omnioutliner/mac/5.7/en/using-the-resource-browser/
- 2: https://support.omnigroup.com/documentation/omnioutliner/ios/3.10/en/the-document-browser/
- 3: https://support.omnigroup.com/documentation/omnioutliner/ios/3.1.1/en/the-document-browser/
- 4: https://support.omnigroup.com/documentation/omnioutliner/universal/6.2/en/documents/
- 5: https://support.omnigroup.com/omnioutliner-4-applescript-changes/
- 6: https://discourse.omnigroup.com/t/case-insensitive-filtering/28196
- 7: https://support.omnigroup.com/documentation/omnioutliner/ios/3.10/en/essentials/the-document-browser/
Preserve the bundle identifier’s casing in the recent-document glob.
The shared recent-document file is named from the bundle ID, so use com.omnigroup.OmniOutliner6*.sfl* instead of the lowercase pattern to avoid failing on case-sensitive macOS volumes.
🤖 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/omnioutliner/darwin.json` at line 20, Update the
recent-document glob passed to trash in the OmniOutliner cleanup script to use
the correctly cased bundle identifier, com.omnigroup.OmniOutliner6*.sfl*. Leave
the surrounding cleanup paths and trash function unchanged.
| "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
file="ee/maintained-apps/outputs/rightfont/darwin.json"
echo "file exists: $(test -f "$file" && echo yes || echo no)"
echo "relevant diff/file snippets:"
if [ -f "$file" ]; then
rg -n "sudo rmdir|APPDIR|LOGGED_IN_USER|trash\\(" "$file" -C 2 || true
python3 - <<'PY'
import json
path="ee/maintained-apps/outputs/rightfont/darwin.json"
with open(path) as f: obj=json.load(f)
print("json type:", type(obj).__name__)
print("keys:", obj.keys() if isinstance(obj, dict) else list(obj)[:5])
text=str(obj) if isinstance(obj, (dict,list)) else obj
idx=text.index("sudo rmdir") if isinstance(text,str) else -1
print("text snippet around sudo rmdir:")
print(text[max(0,idx-200):idx+400] if idx!=-1 else "sudo rmdir not found in string form")
PY
fi
echo "shell-tilde-expansion snippets modeled via set -x simulation:"
sh -c 'APPDIR="/Applications/"; LOGGED_IN_USER="alice"; set -- "$(sudo rm -f /tmp/rightfont-audit 2>/dev/null; sudo rmdir '\''~/RightFont'\'' 2>/dev/null || true; sudo rmdir /Users/$LOGGED_IN_USER/RightFont 2>/dev/null || true; echo ok)" >&2'Repository: fleetdm/fleet
Length of output: 7023
🌐 Web query:
Shell tilde expansion single quotes documentation
💡 Result:
In POSIX-compliant shells, including Bash, tilde expansion does not occur when the tilde () character is enclosed within single quotes [1][2][3]. Tilde expansion is strictly defined as an operation performed on an unquoted tilde-prefix [4][5]. According to the POSIX Shell Command Language specification, a tilde-prefix must be unquoted to be recognized and expanded by the shell [4][6]. When you wrap a string in single quotes, the shell preserves the literal value of every character inside those quotes, which explicitly prevents any expansion, including tilde expansion [1][2]. If you need to include the user's home directory in a path while also protecting the path from word splitting (for example, if the path contains spaces), you can quote only the portion of the path that contains spaces, leaving the tilde unquoted [7]. For example: - Incorrect (tilde is quoted): "/Documents/My File.txt" (The shell looks for a literal file named ~) - Correct (tilde is unquoted): ~/"Documents/My File.txt" - Correct (tilde is unquoted): ~/'Documents/My File.txt' Alternatively, you can use the $HOME environment variable, which can be expanded inside double quotes [3]. Using "$HOME/Documents/My File.txt" will correctly expand to the full path while preserving the space-containing filename [7][3].
Citations:
- 1: https://man7.org/linux/man-pages/man1/bash.1.html
- 2: https://man.archlinux.org/man/bash.1.en.html
- 3: https://unix.stackexchange.com/questions/151850/why-doesnt-the-tilde-expand-inside-double-quotes
- 4: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html
- 5: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
- 6: https://pubs.opengroup.org/onlinepubs/9699969799/utilities/xcu_chap02.html
- 7: https://stackoverflow.com/questions/67117575/how-to-use-inside-a-double-quote-path-reference
Use the logged-in user’s path for RightFont.
'~/RightFont' is passed literally because the tilde is inside single quotes, so /Users/<user>/RightFont can be missed during uninstall. Use the value already captured in LOGGED_IN_USER for cleanup.
Proposed fix
- sudo rmdir '~/RightFont'
+ sudo rmdir "/Users/$LOGGED_IN_USER/RightFont"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "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" | |
| "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 \"/Users/$LOGGED_IN_USER/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" |
🤖 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
RightFont cleanup command in the uninstall script to target
/Users/$LOGGED_IN_USER/RightFont instead of the single-quoted '~/RightFont'
literal. Preserve the existing rmdir behavior while using the already-captured
LOGGED_IN_USER value.
| "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.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target file =="
if [ -f ee/maintained-apps/outputs/teamviewer/darwin.json ]; then
wc -l ee/maintained-apps/outputs/teamviewer/darwin.json
sed -n '80,120p' ee/maintained-apps/outputs/teamviewer/darwin.json | cat -n
else
fd -a 'darwin\.json$' ee/maintained-apps/outputs/teamviewer || true
fi
echo
echo "== matching removal lines in all maintained teamviewer outputs =="
rg -n "sudo rm -rf '.*/Preferences/com\.teamviewer|rm -rf '/Library/Preferences/com\.teamviewer|/Library/Preferences/com\.teamviewer" ee/maintained-apps/outputs/teamviewer || true
echo
echo "== shell expansion/argument behavior probe =="
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
mkdir -p "$tmpdir/dir with spaces"
touch "$tmpdir/dir with spaces/com.teamviewer.pref"
(cd "$tmpdir" && bash -c 'echo "quoted:"; for x in "/tmp/thisdirwithspaces/com.teamviewer*"; do echo "$x"; done' bash)
(cd "$tmpdir" && sh -c 'echo "unquoted:"; for x in /tmp/thisdirwithspaces/com.teamviewer*; do echo "$x"; done')
(cd "$tmpdir" && sh -c 'echo "single-quoted:"; for x in '\''/tmp/thisdirwithspaces/com.teamviewer*'\''; do echo "$x"; done')Repository: fleetdm/fleet
Length of output: 10212
🌐 Web query:
Apple rm -rf remove preference files /Library/Preferences command no glob documentation
💡 Result:
To remove or manage preference files in /Library/Preferences on macOS, the recommended method is the built-in defaults command rather than direct file deletion with rm [1][2][3]. 1. Using the defaults command: The defaults command safely handles macOS preference domains and integrates with the cfprefsd caching service [1][2][4]. To delete a preference domain, use: sudo defaults delete For example, to delete a domain in /Library/Preferences: sudo defaults delete /Library/Preferences/com.apple.example.plist This approach is safer than using rm because it notifies the system of the change, which helps prevent cached preferences from persisting [2][5][4]. 2. Limitations of direct deletion with rm: The rm command is a standard Unix utility for removing files, not a specialized preference management tool [6]. It does not have a "no glob" mode; globbing (such as ) is handled by the shell (e.g., zsh or bash), not the command itself [7][8]. To prevent the shell from expanding wildcards, you must quote the path, but this prevents rm from identifying the files, as it will look for a literal filename containing the asterisk [7][9]. 3. Recommended alternative for mass deletion: If you must delete files using wildcards (e.g., to clear multiple.plist files), use the find command instead of rm directly [10][9]. The find command allows you to locate files based on patterns and execute rm safely [7]. Example: sudo find /Library/Preferences -name "com.apple.example..plist" -exec rm -v {} ; Always ensure you have properly quit the associated application and consider flushing the preference cache by running sudo killall cfprefsd after manual deletions to ensure the system recognizes the removal [11][5][4]. Direct manipulation of system library files carries risks; if you are not certain of the target, use backups [2][5].
Citations:
- 1: https://manp.gs/mac/1/defaults
- 2: https://eclecticlight.co/2019/08/22/working-safely-and-effectively-with-preferences-in-mojave/
- 3: https://pawelgrzybek.com/change-macos-user-preferences-via-command-line/
- 4: https://stackoverflow.com/questions/19303958/deleting-plist-file-does-not-reset-app-on-macos-10-9
- 5: https://forums.macrumors.com/threads/are-plist-files-generally-safe-to-delete.2010913/
- 6: https://www.unix.com/man_page/osx/1/rm/
- 7: https://superuser.com/questions/1884878/why-am-i-unable-to-apply-r-with-rm-on-macos-sequoia-15-3-1-to-delete-files-on
- 8: https://apple.stackexchange.com/questions/366600/specific-effect-of-rm-r
- 9: https://superuser.com/questions/832933/remove-rm-command-not-working-with-wildcards-on-mac-os-x
- 10: https://techoverflow.net/2021/09/04/how-to-remove-every-plist-file-on-the-command-line/
- 11: https://apple.stackexchange.com/questions/400916/delete-a-specific-key-value-using-the-defaults-delete-command-line-tool
🌐 Web query:
site:apple.stackexchange.com sudo rm -rf /Library/Preferences/com.example* glob shell expansion
💡 Result:
When you run a command like sudo rm /Library/Preferences/com.example*, the shell performs path expansion (globbing) before the command is ever executed [1][2]. Here is how the process works: 1. Shell Expansion: The shell (such as zsh or bash) scans the directory /Library/Preferences/ for any files or directories that begin with com.example [1]. 2. Argument Substitution: The shell replaces the pattern /Library/Preferences/com.example* with a list of all matching file paths [1][2]. 3. Execution: The shell then executes the sudo rm command, passing that expanded list of files as individual arguments to rm [1]. Because the expansion happens before the command runs, the rm command itself never sees the wildcard character (); it only receives the specific list of files that the shell found [2]. Important Considerations: - Zsh Behavior: By default, if the zsh shell (the default on modern macOS) cannot find any files matching the pattern, it will throw an error (no matches found) and refuse to execute the command at all [3][4]. - Bash Behavior: In older shells or configurations (like bash), if no match is found, the shell may pass the literal string (e.g., /Library/Preferences/com.example) to the command, which will cause rm to report an error because the file literally named com.example* does not exist [4]. - Safety Tip: To safely verify what will be deleted, always run the command with ls first to see the list of files that will be passed to rm [1][5][6]: ls /Library/Preferences/com.example* - Risk: Using sudo rm with wildcards is inherently risky, as a typo or unexpected expansion can lead to the accidental deletion of unintended files [6]. Always ensure you are targeting the exact directory and files you intend to remove [7].
Citations:
- 1: https://apple.stackexchange.com/questions/366600/specific-effect-of-rm-r
- 2: https://apple.stackexchange.com/questions/462358/how-to-make-normal-command-ls-and-the-wildcard-in-zsh
- 3: https://apple.stackexchange.com/questions/362039/why-does-rm-in-zsh-abort
- 4: https://apple.stackexchange.com/questions/449233/does-macos-change-how-redirection-works
- 5: https://apple.stackexchange.com/questions/15880/os-x-terminal-remove-files-older-than-x-days
- 6: https://apple.stackexchange.com/questions/448507/delete-all-duplicate-files
- 7: https://apple.stackexchange.com/questions/329415/terminal-command-for-removing-all-named-files-except-for-one
Do not quote the TeamViewer system preference glob.
sudo sh -c 'rm -rf /Library/Preferences/com.teamviewer*' expands the wildcard in the shell before rm receives the matching files, so these preference removals actually run instead of rm waiting for a literal file named com.teamviewer*.
Proposed fix
- sudo rm -rf '/Library/Preferences/com.teamviewer*'
+ sudo sh -c 'rm -rf /Library/Preferences/com.teamviewer*'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "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" | |
| sudo sh -c 'rm -rf /Library/Preferences/com.teamviewer*' |
🤖 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
TeamViewer system-preference removal command near the cleanup calls so the
com.teamviewer* wildcard is expanded by a shell running with sudo rather than
passed as a quoted literal to rm. Preserve removal of all matching entries under
/Library/Preferences, and keep the existing cleanup scope unchanged.
| "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 =="
cat -n ee/maintained-apps/outputs/wechat/darwin.json | sed -n '1,80p'
echo
echo "== parse JSON output key =="
python3 - <<'PY'
import json
from pathlib import Path
p = Path("ee/maintained-apps/outputs/wechat/darwin.json")
data = json.loads(p.read_text())
key, val = next(iter(data.items()))
print("key:", key)
print("lines:", len(val.splitlines()))
for i, line in enumerate(val.splitlines(), 1):
if "TeamIdentifierPrefix" in line or "trash" in line or "Application Scripts" in line or "Group Containers" in line:
print(f"{i}: {line}")
PY
echo
echo "== bash substitution behavior for quoted paths in trash function =="
set +e
bash --noprofile --norc -x <<'BASH'
trash() {
local logged_in_user="$1"
local target_file="$2"
echo "trash called with target_file=$target_file"
}
LOGGED_IN_USER=user
trash user '~/Library/Application Scripts/$(TeamIdentifierPrefix)com.tencent.xinWeChat'
trap - EXIT
BASHRepository: fleetdm/fleet
Length of output: 9358
🌐 Web query:
WeChat macOS TeamIdentifierPrefix application scripts containers group containers uninstall script
💡 Result:
WeChat for macOS utilizes the Apple App Sandbox architecture, which separates the application bundle from its user data containers [1]. Because these data containers are persistent and not removed when the application itself is deleted, a standard drag-and-drop uninstallation leaves significant data on the system [1][2]. The TeamIdentifierPrefix variable is a macOS placeholder used in paths for sandboxed apps and app groups [3][4]. When specific to WeChat (Bundle ID: com.tencent.xinWeChat), paths often include this prefix to ensure the sandbox container is uniquely tied to the developer's Team ID [3][5]. Below are the key locations where WeChat data resides and what should be targeted for a complete manual uninstallation [3]. Core Data and Sandbox Locations: 1. Application Sandbox: ~/Library/Containers/com.tencent.xinWeChat/ 2. Application Scripts: ~/Library/Application Scripts/com.tencent.xinWeChat/ 3. Group Containers: ~/Library/Group Containers/$(TeamIdentifierPrefix)com.tencent.xinWeChat/ 4. Caches: ~/Library/Caches/com.tencent.xinWeChat/ 5. Preferences: ~/Library/Preferences/com.tencent.xinWeChat.plist 6. Saved State: ~/Library/Saved Application State/com.tencent.xinWeChat.savedState/ Comprehensive Uninstall Procedure: To perform a complete removal of WeChat and all its associated data, follow these steps [3][6][2]: 1. Quit WeChat: Ensure the application is completely closed (check the Menu Bar and Dock) [6][7]. 2. Move Application to Trash: Navigate to the Applications folder in Finder and move WeChat.app to the Trash [6][8]. 3. Remove Sandbox and Support Files: Open Finder, press Command+Shift+G, and navigate to the ~/Library folder. Locate and delete the following directories and files if they exist [3][2]: - ~/Library/Containers/com.tencent.xinWeChat/ - ~/Library/Application Scripts/com.tencent.xinWeChat/ - ~/Library/Group Containers/$(TeamIdentifierPrefix)com.tencent.xinWeChat/ (Note: Replace
Citations:
- 1: https://github.com/LiJunYi2/WeChat-Split
- 2: https://uninstallmacapp.com/wechat-removal.html
- 3: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/w/wechat.rb
- 4: https://stackoverflow.com/questions/27915537/should-app-group-containers-have-the-team-identifier-or-not
- 5: https://github.com/jackwener/wx-cli/blob/main/docs/macos-permission-guide.md
- 6: https://www.imymac.com/powermymac/wechat-for-mac.html
- 7: https://www.wsxdn.com/Troubleshooting/mac/129902.html
- 8: https://howtoremoveapp.com/remove-wechat.html
- 9: https://wener.me/notes/platform/wechat/inside
- 10: https://github.com/wujianchi/wechat-cli
- 11: https://liguobao.github.io/p/wechat-backup/
🌐 Web query:
macOS TeamIdentifierPrefix command $TeamIdentifierPrefix team identifier application group containers
💡 Result:
Citations:
- 1: https://stackoverflow.com/questions/38037745/should-macos-app-group-name-start-with-group-or-development-team-id
- 2: https://stackoverflow.com/questions/12294083/difference-between-teamidentifierprefix-and-appidentifierprefix
- 3: https://stackoverflow.com/questions/29799834/itms-90289-using-app-groups-in-mac-app-store
- 4: https://developer.apple.com/forums/thread/758358
- 5: https://stackoverflow.com/questions/29705789/ios-osx-app-group-ids-start-them-with-group-or-team-id
- 6: https://stackoverflow.com/questions/27915537/should-app-group-containers-have-the-team-identifier-or-not
- 7: https://stackoverflow.com/questions/22574990/containerurlforsecurityapplicationgroupidentifier-getting-nil-value
🌐 Web query:
site:github.com/Homebrew/homebrew-cask "TeamIdentifierPrefix" "Application Scripts" "Group Containers"
💡 Result:
In the context of macOS sandboxing and Homebrew Cask configurations, TeamIdentifierPrefix refers to the Apple-assigned Team ID prefix required for paths in the ~/Library/Containers and ~/Library/Group Containers directories [1][2]. When a macOS application is sandboxed, it is often assigned a container directory whose name includes its Team ID followed by its bundle identifier (e.g., TEAMID.com.example.app) [1]. Application Scripts for sandboxed apps are similarly located in ~/Library/Application Scripts/TEAMID.com.example.app [1][2]. The $(TeamIdentifierPrefix) notation used in Homebrew Cask files is a placeholder variable [1][2]. It allows the Cask's zap stanza—which handles the removal of leftover application files—to remain generic or adaptable, as the actual Team ID prefix is specific to the developer's signing certificate and can change or be complex to resolve programmatically during uninstallation [1][3]. Because Homebrew Cask does not automatically resolve this variable in all contexts, users or maintainers may sometimes encounter issues where these paths are not correctly cleaned up if the prefix is not manually replaced with the actual 10-character Team ID associated with the application's code-signing identity [3].
Citations:
- 1: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/w/wechat.rb
- 2: https://github.com/Homebrew/homebrew-cask/blob/56e660ce69e78e1dd89f9cdd74f8d31374d4c957/Casks/w/wechat.rb
- 3: brew upgrade --cask wechat error Homebrew/homebrew-cask#147383
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Homebrew Cask zap stanzas containing TeamIdentifierPrefix =="
python3 - <<'PY'
import urllib.request
urls = [
"https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/w/wechat.rb",
"https://raw.githubusercontent.com/Homebrew/homebrew-cask/a8bc8f7f56cbb62963d472c9d4793be0656e2c/Casks/w/wechat.rb",
]
for url in urls:
print(f"\n-- {url} --")
data = urllib.request.urlopen(url, timeout=20).read().decode()
for i, line in enumerate(data.splitlines(), 1):
if "TeamIdentifierPrefix" in line or "Application Scripts" in line or "Group Containers" in line or "def .zap" in line:
# include surrounding zap block context
lines = data.splitlines()
start=max(1,i-8); end=min(len(lines), i+8)
for j in range(start,end+1):
print(f"{j:4}: {lines[j-1]}")
PY
echo
echo "== exact grep in codebase for TeamIdentifierPrefix =="
rg -n "TeamIdentifierPrefix|Application Scripts/\\\$|Group Containers/\\\$" . --glob '!vendor/**' --glob '!node_modules/**' || trueRepository: fleetdm/fleet
Length of output: 2588
🌐 Web query:
github Homebrew homebrew-cask Casks w wechat.rb Group Containers $TeamIdentifierPrefix
💡 Result:
In Homebrew Cask formulas, brew uninstall --zap—to dynamically match and remove these container folders regardless of the specific Team ID associated with the installed version of the application [1][3]. The inclusion of this variable in the wechat.rb cask ensures that, upon uninstallation, Homebrew can correctly identify and clean up the various application-specific support folders, caches, and group containers associated with WeChat, even if the underlying folder names include the developer's mandatory Team ID prefix [1][2][4].
Citations:
- 1: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/w/wechat.rb
- 2: https://github.com/Homebrew/homebrew-cask/blob/5b9712bea26961007cc1dd32211cc022abfed6d1/Casks/w/wechat.rb
- 3: https://github.com/Homebrew/homebrew-cask/blob/712b3b77724c7ee99615745418f4692a7df36164/Casks/w/wechat.rb
- 4: https://daringfireball.net/linked/2020/02/24/ugly-prefixes
- 5: https://stackoverflow.com/questions/27915537/should-app-group-containers-have-the-team-identifier-or-not
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files matching wechat cask names =="
git ls-files | rg -i '(^|/)(wechat|maintained-apps|outputs/.*/.*\.json)$' || true
echo
echo "== repository-wide TeamIdentifierPrefix occurrences =="
git ls-files -z | xargs -0 rg -n "TeamIdentifierPrefix|Application Scripts/\\$\(|Group Containers/\\$\(|CONTAINERIDENTIFIER|SANDSTROM|APPID" 2>/dev/null || true
echo
echo "== nearby json output sections around wechat ref =="
python3 - <<'PY'
import json
from pathlib import Path
p=Path("ee/maintained-apps/outputs/wechat/darwin.json")
d=json.loads(p.read_text())
for section in d:
print(f"{section}: type={type(d[section]).__name__}")
refs=d["refs"].get("a5e852b0","")
for i,line in enumerate(refs.splitlines(),1):
if "trash" in line or "TeamIdentifierPrefix" in line or "Application Scripts" in line or "Group Containers" in line or "Containers" in line:
print(f"{i}: {line}")
PYRepository: fleetdm/fleet
Length of output: 50369
Resolve $(TeamIdentifierPrefix) before passing these paths to trash.
trash receives a literal argument when this value is single-quoted, so the prefixed Application Scripts, Containers, and Group Containers paths do not match the actual Team ID-based WeChat sandbox folders. Use a concrete prefix before removing these paths to avoid leaving WeChat data behind.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ee/maintained-apps/outputs/wechat/darwin.json` at line 20, Resolve the
TeamIdentifierPrefix value before invoking trash for the affected Application
Scripts, Containers, and Group Containers paths. Update the call sites around
the trash function so the paths contain the concrete prefix rather than the
literal $(TeamIdentifierPrefix) text, while preserving the existing cleanup
behavior.
|
Closing in favor of #50138. |
Automated ingestion of latest Fleet-maintained app data.
Summary by CodeRabbit