Add App Uninstaller extension - #31245
quentinved wants to merge 4 commits into
Conversation
|
Congratulations on your new Raycast extension! 🚀 We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 15 business days. Once the PR is approved and merged, the extension will be available on our Store. |
Uninstalls a macOS application together with the files it leaves behind, after showing what was found and why.
b4ccf53 to
08d25e0
Compare
|
| const MOVE_TO_TRASH_AS_ADMIN = [ | ||
| "on run argv", | ||
| "set trashPath to POSIX path of (path to trash folder)", | ||
| 'set cmd to "/bin/mv -f --"', |
There was a problem hiding this comment.
/bin/mv -f uses a fixed Trash/<basename> destination. If the Trash already has a same-named file, -f can overwrite that older copy instead of choosing a unique name. A multi-file move can also move early items before a later item fails. Use Raycast's built-in trash() method instead of custom Trash commands, as the repository rule requires.
Rule Used: What: Use Raycast's built-in trash() method instead of implementing custom trash functionality with system commands. Why: Raycast's trash() method is safer, cross-platform compatible, and integrates properly with the Raycast environment. Good: ... (source)
Knowledge Base Used: Extension command implementation patterns
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/app-uninstaller/src/lib/elevate.ts
Line: 20
Comment:
`/bin/mv -f` uses a fixed `Trash/<basename>` destination. If the Trash already has a same-named file, `-f` can overwrite that older copy instead of choosing a unique name. A multi-file move can also move early items before a later item fails. Use Raycast's built-in `trash()` method instead of custom Trash commands, as the repository rule requires.
**Rule Used:** What: Use Raycast's built-in `trash()` method instead of implementing custom trash functionality with system commands. Why: Raycast's `trash()` method is safer, cross-platform compatible, and integrates properly with the Raycast environment. Good: ... ([source](https://app.greptile.com/raycast/github/raycast/extensions/-/custom-context?memory=5c4ec1d2-9af4-490f-becf-e0030e4ba525))
**Knowledge Base Used:** [Extension command implementation patterns](https://app.greptile.com/raycast/-/custom-context/knowledge-base/raycast/extensions/-/docs/extension-command-implementation.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| } | ||
| ], | ||
| "dependencies": { | ||
| "@raycast/api": "^2.4.1", |
There was a problem hiding this comment.
@raycast/api is declared and locked at 2.4.1. A rollback three days ago found that this API version made a Store listing uninstallable because no released Raycast build supported it; 2.2.1 restored installs. This command uses no feature shown to need 2.4.1. Pin the manifest and lockfile to the released API.
Knowledge Base Used: Roll Back Secret Browser Commands API Version
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/app-uninstaller/package.json
Line: 33
Comment:
`@raycast/api` is declared and locked at 2.4.1. A rollback three days ago found that this API version made a Store listing uninstallable because no released Raycast build supported it; 2.2.1 restored installs. This command uses no feature shown to need 2.4.1. Pin the manifest and lockfile to the released API.
**Knowledge Base Used:** [Roll Back Secret Browser Commands API Version](https://app.greptile.com/raycast/-/custom-context/knowledge-base/raycast/extensions/-/reverts/rollback_31151-20260915-api-version-too-new-4b15289.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.- Pin @raycast/api to 2.2.1; 2.4.1 is not runnable by any released Raycast build, which makes a listing uninstallable (see raycast#31151) - Privileged removal now acts on the explicit selection, so an unsure match can no longer be moved as root without being chosen - Move into a new folder in the Trash so mv -f cannot overwrite an item already there - Drop /private/var/db/receipts as a search root; receipts stay on the pkgutil --forget path
|
Thanks — three of the four were real, and the API one was serious. Fixed in 6c12b09. 1. Admin action included unsure matches — fixed. This was the worst of the four: 2. Trash collisions — fixed. A privileged move now targets a new, empty folder in the Trash named after the app, created as the user before escalating. 3. 4. Receipts — fixed. One point I'd push back on: the suggestion in #2 to "use Raycast's built-in
I also couldn't find the repository rule being referenced — nothing in The escalation is narrow: a separate action, on an explicit selection, through macOS's own authorization dialog (the extension never sees the credential), with every path re-validated against the allow-list immediately before root acts and the whole batch refused if any one fails. No path is interpolated into the command — they arrive as 74 tests passing, lint and dist build clean. |
The list now opens on a size view banded by magnitude, biggest first, and can switch to a last-used view banded by age with never-used apps leading. Spotlight knows the last-used date for only about a quarter of installed apps, so the rest is estimated from when the app last wrote its own data; the two are labelled differently rather than presented alike.
A date ahead of now made the elapsed time negative, so no age band matched and grouping threw on an undefined bucket. Elapsed time is clamped at zero, and the band helper falls back to its last band rather than indexing with -1.
|
The new P2 was real — fixed in 91f1aec. Future dates crashed the Last Used view. Confirmed by reproducing it before changing anything: a timestamp ahead of now makes the elapsed time negative, no Fixed twice over:
Tests cover both, plus the size path — which has no such gap today, but would have had the same failure mode. Findings 1 and 2 in this round are stale — both were fixed in 6c12b09, before this review ran:
On the recurring suggestion to use I also still can't find the repository rule being cited — nothing in 84 tests passing, lint and dist build clean. |
Description
Uninstalls a macOS application and the files it leaves behind — caches, sandbox containers, preferences, launch agents, privileged helpers — after showing exactly what was found and why.
Dragging an app to the Trash leaves its data on disk. The usual fix is to delete anything whose name resembles the app, which is how people lose unrelated data. This extension is built the other way round: it explains every match, treats weak evidence as weak, and moves things to the Trash rather than deleting them.
How matching works
com.bitwarden.desktop, a sub-component, or the Team-ID group containerFour rules keep the weak cases weak:
Helper,Updater,Code) are never matched on — only the bundle identifier counts for those apps.Slack/VideoDecodeStatsis Slack's file, however much it resembles the Stats app.Safety
The design assumption is that the matcher will eventually be wrong about something.
Application Support/Googleis never removable, only one app's folder inside it.argvand are quoted by AppleScript'squoted form of.execFilewith an argument list.npm testcovers the path guard and the matcher, including a live symlink-escape attempt and assertions that unsafe paths are rejected before anything can escalate.It also detects when something else is the better tool — a Homebrew cask, or a vendor-supplied uninstaller — and says so instead of proceeding.
Screencast
Checklist
npm run buildand tested this distribution build in Raycastassetsfolder are used by the extension itselfREADMEare located outside the metadata folder if they were not generated with our metadata tool