Add macOS Fleet-maintained apps (A) - #47536
Conversation
Adds 43 Fleet-maintained app(s) whose cask token starts with 'A': inputs, outputs, app icons (TSX + website PNG), and the matching index.ts and apps.json entries. Split out from #47121.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #47536 +/- ##
==========================================
- Coverage 67.18% 67.17% -0.01%
==========================================
Files 3548 3591 +43
Lines 228890 228976 +86
Branches 11909 11909
==========================================
+ Hits 153782 153825 +43
- Misses 61264 61307 +43
Partials 13844 13844
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR expands Fleet’s macOS Fleet-maintained apps catalog for Homebrew casks starting with “A” by adding new app manifests (inputs + generated outputs) and wiring corresponding software icons into the Software page icon map.
Changes:
- Added many new Fleet-maintained app input manifests under
ee/maintained-apps/inputs/homebrew/(name, unique_identifier, token, installer format, categories). - Added corresponding generated macOS outputs under
ee/maintained-apps/outputs/*/darwin.json(versioning queries, installer URLs, install/uninstall script refs). - Updated
frontend/pages/SoftwarePage/components/icons/index.tsto import new icon components and map additional software names to icons.
Reviewed changes
Copilot reviewed 124 out of 174 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/pages/SoftwarePage/components/icons/index.ts | Imports new icon components and maps additional “A*” software names to their icons. |
| ee/maintained-apps/outputs/a-better-finder-rename/darwin.json | Adds maintained-app output (installer metadata + scripts) for A Better Finder Rename. |
| ee/maintained-apps/outputs/acorn/darwin.json | Adds maintained-app output for Acorn. |
| ee/maintained-apps/outputs/activedock/darwin.json | Adds maintained-app output for ActiveDock. |
| ee/maintained-apps/outputs/activitywatch/darwin.json | Adds maintained-app output for ActivityWatch. |
| ee/maintained-apps/outputs/actual/darwin.json | Adds maintained-app output for Actual. |
| ee/maintained-apps/outputs/advanced-renamer/darwin.json | Adds maintained-app output for Advanced Renamer. |
| ee/maintained-apps/outputs/affinity-designer/darwin.json | Adds maintained-app output for Affinity Designer 2. |
| ee/maintained-apps/outputs/affinity-designer@1/darwin.json | Adds maintained-app output for Affinity Designer (v1). |
| ee/maintained-apps/outputs/affinity-photo/darwin.json | Adds maintained-app output for Affinity Photo 2. |
| ee/maintained-apps/outputs/affinity-photo@1/darwin.json | Adds maintained-app output for Affinity Photo (v1). |
| ee/maintained-apps/outputs/affinity-publisher/darwin.json | Adds maintained-app output for Affinity Publisher 2. |
| ee/maintained-apps/outputs/affinity-publisher@1/darwin.json | Adds maintained-app output for Affinity Publisher (v1). |
| ee/maintained-apps/outputs/airbuddy/darwin.json | Adds maintained-app output for AirBuddy. |
| ee/maintained-apps/outputs/airdroid/darwin.json | Adds maintained-app output for AirDroid. |
| ee/maintained-apps/outputs/airparrot/darwin.json | Adds maintained-app output for AirParrot. |
| ee/maintained-apps/outputs/airserver/darwin.json | Adds maintained-app output for AirServer. |
| ee/maintained-apps/outputs/airtable/darwin.json | Adds maintained-app output for Airtable. |
| ee/maintained-apps/outputs/airy/darwin.json | Adds maintained-app output for Airy. |
| ee/maintained-apps/outputs/akiflow/darwin.json | Adds maintained-app output for Akiflow. |
| ee/maintained-apps/outputs/alcove/darwin.json | Adds maintained-app output for Alcove. |
| ee/maintained-apps/outputs/alloy/darwin.json | Adds maintained-app output for Alloy. |
| ee/maintained-apps/outputs/alt-tab/darwin.json | Adds maintained-app output for AltTab. |
| ee/maintained-apps/outputs/altair-graphql-client/darwin.json | Adds maintained-app output for Altair GraphQL Client. |
| ee/maintained-apps/outputs/amadeus-pro/darwin.json | Adds maintained-app output for Amadeus Pro. |
| ee/maintained-apps/outputs/amadine/darwin.json | Adds maintained-app output for Amadine. |
| ee/maintained-apps/outputs/amethyst/darwin.json | Adds maintained-app output for Amethyst. |
| ee/maintained-apps/outputs/amie/darwin.json | Adds maintained-app output for Amie. |
| ee/maintained-apps/outputs/angry-ip-scanner/darwin.json | Adds maintained-app output for Angry IP Scanner. |
| ee/maintained-apps/outputs/antinote/darwin.json | Adds maintained-app output for Antinote. |
| ee/maintained-apps/outputs/anydo/darwin.json | Adds maintained-app output for Any.do. |
| ee/maintained-apps/outputs/anytype/darwin.json | Adds maintained-app output for Anytype. |
| ee/maintained-apps/outputs/apidog/darwin.json | Adds maintained-app output for Apidog. |
| ee/maintained-apps/outputs/app-fair/darwin.json | Adds maintained-app output for App Fair. |
| ee/maintained-apps/outputs/appium-inspector/darwin.json | Adds maintained-app output for Appium Inspector GUI. |
| ee/maintained-apps/outputs/applite/darwin.json | Adds maintained-app output for Applite. |
| ee/maintained-apps/outputs/asset-catalog-tinkerer/darwin.json | Adds maintained-app output for Asset Catalog Tinkerer. |
| ee/maintained-apps/outputs/atext/darwin.json | Adds maintained-app output for aText. |
| ee/maintained-apps/outputs/audio-hijack/darwin.json | Adds maintained-app output for Audio Hijack. |
| ee/maintained-apps/outputs/axure-rp/darwin.json | Adds maintained-app output for Axure RP. |
| ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json | Adds maintained-app output for Aviatrix VPN Client. |
| ee/maintained-apps/inputs/homebrew/a-better-finder-rename.json | Adds Homebrew input manifest for A Better Finder Rename. |
| ee/maintained-apps/inputs/homebrew/acorn.json | Adds Homebrew input manifest for Acorn. |
| ee/maintained-apps/inputs/homebrew/activedock.json | Adds Homebrew input manifest for ActiveDock. |
| ee/maintained-apps/inputs/homebrew/activitywatch.json | Adds Homebrew input manifest for ActivityWatch. |
| ee/maintained-apps/inputs/homebrew/actual.json | Adds Homebrew input manifest for Actual. |
| ee/maintained-apps/inputs/homebrew/adguard.json | Adds Homebrew input manifest for AdGuard. |
| ee/maintained-apps/inputs/homebrew/adlock.json | Adds Homebrew input manifest for AdLock. |
| ee/maintained-apps/inputs/homebrew/advanced-renamer.json | Adds Homebrew input manifest for Advanced Renamer. |
| ee/maintained-apps/inputs/homebrew/affinity-designer.json | Adds Homebrew input manifest for Affinity Designer 2. |
| ee/maintained-apps/inputs/homebrew/affinity-designer@1.json | Adds Homebrew input manifest for Affinity Designer (v1). |
| ee/maintained-apps/inputs/homebrew/affinity-photo.json | Adds Homebrew input manifest for Affinity Photo 2. |
| ee/maintained-apps/inputs/homebrew/affinity-photo@1.json | Adds Homebrew input manifest for Affinity Photo (v1). |
| ee/maintained-apps/inputs/homebrew/affinity-publisher.json | Adds Homebrew input manifest for Affinity Publisher 2. |
| ee/maintained-apps/inputs/homebrew/affinity-publisher@1.json | Adds Homebrew input manifest for Affinity Publisher (v1). |
| ee/maintained-apps/inputs/homebrew/airbuddy.json | Adds Homebrew input manifest for AirBuddy. |
| ee/maintained-apps/inputs/homebrew/airdroid.json | Adds Homebrew input manifest for AirDroid. |
| ee/maintained-apps/inputs/homebrew/airparrot.json | Adds Homebrew input manifest for AirParrot. |
| ee/maintained-apps/inputs/homebrew/airserver.json | Adds Homebrew input manifest for AirServer. |
| ee/maintained-apps/inputs/homebrew/airtable.json | Adds Homebrew input manifest for Airtable. |
| ee/maintained-apps/inputs/homebrew/airy.json | Adds Homebrew input manifest for Airy. |
| ee/maintained-apps/inputs/homebrew/akiflow.json | Adds Homebrew input manifest for Akiflow. |
| ee/maintained-apps/inputs/homebrew/alcove.json | Adds Homebrew input manifest for Alcove. |
| ee/maintained-apps/inputs/homebrew/aldente.json | Adds Homebrew input manifest for AlDente. |
| ee/maintained-apps/inputs/homebrew/alloy.json | Adds Homebrew input manifest for Alloy. |
| ee/maintained-apps/inputs/homebrew/alt-tab.json | Adds Homebrew input manifest for AltTab. |
| ee/maintained-apps/inputs/homebrew/altair-graphql-client.json | Adds Homebrew input manifest for Altair GraphQL Client. |
| ee/maintained-apps/inputs/homebrew/amadeus-pro.json | Adds Homebrew input manifest for Amadeus Pro. |
| ee/maintained-apps/inputs/homebrew/amadine.json | Adds Homebrew input manifest for Amadine. |
| ee/maintained-apps/inputs/homebrew/amethyst.json | Adds Homebrew input manifest for Amethyst. |
| ee/maintained-apps/inputs/homebrew/amie.json | Adds Homebrew input manifest for Amie. |
| ee/maintained-apps/inputs/homebrew/angry-ip-scanner.json | Adds Homebrew input manifest for Angry IP Scanner. |
| ee/maintained-apps/inputs/homebrew/antinote.json | Adds Homebrew input manifest for Antinote. |
| ee/maintained-apps/inputs/homebrew/anydo.json | Adds Homebrew input manifest for Any.do. |
| ee/maintained-apps/inputs/homebrew/anytype.json | Adds Homebrew input manifest for Anytype. |
| ee/maintained-apps/inputs/homebrew/apidog.json | Adds Homebrew input manifest for Apidog. |
| ee/maintained-apps/inputs/homebrew/app-fair.json | Adds Homebrew input manifest for App Fair. |
| ee/maintained-apps/inputs/homebrew/appium-inspector.json | Adds Homebrew input manifest for Appium Inspector GUI. |
| ee/maintained-apps/inputs/homebrew/applite.json | Adds Homebrew input manifest for Applite. |
| ee/maintained-apps/inputs/homebrew/asset-catalog-tinkerer.json | Adds Homebrew input manifest for Asset Catalog Tinkerer. |
| ee/maintained-apps/inputs/homebrew/atext.json | Adds Homebrew input manifest for aText. |
| ee/maintained-apps/inputs/homebrew/audio-hijack.json | Adds Homebrew input manifest for Audio Hijack. |
| ee/maintained-apps/inputs/homebrew/aviatrix-vpn-client.json | Adds Homebrew input manifest for Aviatrix VPN Client. |
| ee/maintained-apps/inputs/homebrew/axure-rp.json | Adds Homebrew input manifest for Axure RP. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
WalkthroughThis PR adds new maintained-app metadata for a large set of macOS apps. It introduces Homebrew input manifests, corresponding Darwin release manifests with install/uninstall scripts, updates the aggregated Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 16
🧹 Nitpick comments (3)
ee/maintained-apps/outputs/audio-hijack/darwin.json (1)
12-12: ⚡ Quick winConsider adding SHA256 verification for installer integrity.
Using
"no_check"means the downloaded installer's integrity isn't verified. If the vendor provides stable checksums or if you can pin to a specific version URL, adding a SHA256 hash would protect against corrupted or tampered downloads.If this is intentional because the URL is a "latest version" endpoint with changing checksums, consider documenting that rationale.
🤖 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/audio-hijack/darwin.json` at line 12, Replace the placeholder "no_check" SHA256 in darwin.json's "sha256" field with a real checksum or document why verification is skipped: compute the installer SHA256 for the pinned installer URL and set "sha256": "<actual-sha256-hex>" in the JSON (or if the source is a rolling "latest" endpoint, change the download to a versioned URL and supply its checksum, or add a README entry explaining the intentional waiver of verification and linking the vendor's checksum source). Ensure you update any code that constructs the download URL to use the versioned artifact if you choose to pin.ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json (1)
12-12: ⚡ Quick winConsider adding SHA256 verification for installer integrity.
Using
"no_check"means the downloaded installer's integrity isn't verified. For a security-sensitive VPN client, consider adding a SHA256 hash if the vendor provides stable checksums.🤖 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/aviatrix-vpn-client/darwin.json` at line 12, Replace the placeholder "sha256": "no_check" with a real SHA256 checksum for the downloaded installer to enable integrity verification; obtain the stable SHA256 from the vendor (or compute it by downloading the exact installer artifact referenced in the manifest) and set that hex digest in the "sha256" field so the installer is validated during installation (if the vendor does not publish a checksum, include a reproducible source or verification step in the manifest and avoid using "no_check").ee/maintained-apps/outputs/angry-ip-scanner/darwin.json (1)
18-20: 💤 Low valueBackup file is created but never cleaned up on successful install.
The install script creates
$TMPDIR/Angry IP Scanner.app.bkpwhen upgrading but never removes it after a successful installation. This leaves orphaned backup files in temp directories.Consider adding cleanup logic after the copy succeeds:
sudo cp -R "$TMPDIR/Angry IP Scanner.app" "$APPDIR" +rm -rf "$TMPDIR/Angry IP Scanner.app.bkp" 2>/dev/null || true relaunch_application 'net.azib.ipscan'🤖 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/angry-ip-scanner/darwin.json` around lines 18 - 20, The backup "$TMPDIR/Angry IP Scanner.app.bkp" is created by the sudo mv call but never removed; after the install step (the sudo cp -R "$TMPDIR/Angry IP Scanner.app" "$APPDIR" in the script that uses TMPDIR, APPDIR and the helper functions quit_and_track_application/relaunch_application) add cleanup logic to detect a successful copy (check exit status of cp) and then remove the backup (rm -rf "$TMPDIR/Angry IP Scanner.app.bkp"); also ensure the cleanup runs only on success and consider also removing the backup in a failure/rollback path to avoid leaving orphaned files.
🤖 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/a-better-finder-rename/darwin.json`:
- Line 19: The current installer does non-atomic replacement (it may move/remove
the live app before the new copy is verified); modify the install sequence
around the copy/move operations so the new app is first fully copied to a
temporary verified location (use the existing TMPDIR or create a unique temp
path), verify the copy succeeded (check exit status and that the .app bundle
exists and is not empty), then atomically rename/move the original app to a
backup (e.g. rename to .bkp) and move/rename the verified temp app into $APPDIR
(use mv for atomic replacement), and if any step fails restore the original from
the backup and exit non-zero; update the logic that currently calls sudo cp -R
"$TMPDIR/A Better Finder Rename 12.app" "$APPDIR" and the preceding sudo mv
backup step to follow this verified-copy → backup-original → atomic-rename →
rollback-on-failure pattern, and apply the same pattern in the equivalent
acorn/darwin.json generator/template.
- Line 20: The issue is that LOGGED_IN_USER is passed unquoted to the trash
function (calls like trash $LOGGED_IN_USER '~/Library/...'), which allows
word-splitting or empty-value positional shifts; update every call to the trash
function (and similar invocations) to quote the variable (use trash
"$LOGGED_IN_USER" '...') so the trash function receives the intended single
argument; check both occurrences in the diff and the files referencing
LOGGED_IN_USER and the trash function to ensure all calls are updated.
In `@ee/maintained-apps/outputs/affinity-designer/darwin.json`:
- Line 19: The uninstall script uses an unvalidated LOGGED_IN_USER when calling
the trash function (trash) which can be root, loginwindow or empty; update the
script to validate LOGGED_IN_USER after it is set (LOGGED_IN_USER=$(scutil ...))
and skip or avoid calling trash when LOGGED_IN_USER is empty or equals "root" or
"loginwindow"; implement a small guard block that checks LOGGED_IN_USER and only
invokes the existing trash function calls if the check passes, and apply the
same guard to the other three manifest variants that also call trash (the files
that call trash with LOGGED_IN_USER for Affinity Designer/Photo/Publisher and
their `@1` variants).
In `@ee/maintained-apps/outputs/akiflow/darwin.json`:
- Around line 19-20: Install/uninstall scripts lack fail-fast/error propagation
causing silent partial success; enable strict failure handling and ensure
cleanup/traps. Add a top-level strict mode (set -euo pipefail) and a trap to
clean MOUNT_POINT/TMPDIR on exit, then update critical operations referenced in
quit_and_track_application, relaunch_application, the hdiutil/cp/mv sequence,
and the sudo rm -rf/trash flow to check exit status and exit non-zero on failure
(or let set -e propagate), and ensure
relaunch_application/quit_and_track_application return non-zero on unrecoverable
errors so the installer fails fast instead of reporting success after
destructive actions.
In `@ee/maintained-apps/outputs/aldente/darwin.json`:
- Line 20: The remove_launchctl_service function uses launchctl list to set
plist_status but always runs the non‑sudo command; update the status check so
when should_sudo == "true" you run sudo launchctl list "${service}" (and when
false run launchctl list "${service}") so privileged daemons are detected
correctly; change the assignment to plist_status to branch on should_sudo (refer
to remove_launchctl_service, plist_status, and should_sudo).
In `@ee/maintained-apps/outputs/altair-graphql-client/darwin.json`:
- Line 20: The install script can leave /Applications without the app because it
moves the existing app to a .bkp before verifying the new payload was
successfully copied; change to a rollback-safe pattern: after unzip
"$INSTALLER_PATH" to "$TMPDIR" validate the extracted "Altair GraphQL
Client.app" exists and is copyable, copy the new app to a temporary location
(not over APPDIR), only then move the existing "$APPDIR/Altair GraphQL
Client.app" to the backup name (Altair GraphQL Client.app.bkp) and atomically
rename/move the new app into $APPDIR; on any failure (unzip, cp, mv, or
relaunch_application) restore the .bkp back to its original location and exit
non-zero. Implement this around the existing functions/variables
quit_and_track_application, relaunch_application, APPDIR, TMPDIR,
INSTALLER_PATH, using traps to ensure rollback on errors.
In `@ee/maintained-apps/outputs/amadeus-pro/darwin.json`:
- Line 12: The manifest entries currently set the integrity field "sha256" to
"no_check" for the amadeus-pro and amadine darwin manifests; replace those
placeholders with the deterministic SHA-256 hex digest of the actual installer
binaries (compute the checksum for the downloaded .dmg/.zip you intend to ship
and paste the resulting hash string into the "sha256" field) and ensure both
manifests use the real pinned values rather than "no_check" so installer
integrity is enforced.
In `@ee/maintained-apps/outputs/angry-ip-scanner/darwin.json`:
- Line 9: The installer_url currently references an ARM64-only package
("ipscan-macArm64-3.9.3.zip"); update the catalog metadata to reflect that this
entry is architecture-specific by either replacing the URL with a
universal/intel-compatible download if a universal binary is available, or keep
the ARM URL and add an explicit arch field (e.g., "arch": "arm64") and/or create
a separate Intel macOS entry with the Intel-specific installer_url so Intel
hosts won't attempt to use the ARM-only package; locate and update the
"installer_url" key and related package metadata accordingly.
In `@ee/maintained-apps/outputs/anytype/darwin.json`:
- Around line 13-15: The default_categories array currently contains "Security"
which is incorrect for Anytype; update the "default_categories" entry in the
JSON (replace the "Security" string) to "Productivity" so the app is categorized
as a knowledge/productivity tool instead of a security tool, ensuring the value
is a string inside the existing default_categories array.
- Line 9: Replace the ARM64-only URL in
ee/maintained-apps/outputs/anytype/darwin.json by updating the "installer_url"
value (the installer_url key) to an architecture-neutral macOS installer URL
(e.g., Anytype-0.55.5-mac.dmg) or, if a universal build is not available, point
it to the Intel-compatible mac x64 image (e.g., Anytype-0.55.5-mac-x64.dmg) so
Intel Macs do not download an ARM-only DMG; if you cannot change the URL, add
arch-aware fields in the same JSON and ensure the maintained-apps generator
picks the correct key for x86_64 vs arm64.
In `@ee/maintained-apps/outputs/app-fair/darwin.json`:
- Line 19: The installer can report success even if unzip/copy fail; enable
strict-fail handling before the critical extract/copy steps in this script by
turning on bash strict mode (e.g., set -euo pipefail or equivalent) immediately
before the unzip \"$INSTALLER_PATH\" -d \"$TMPDIR\" and sudo cp -R \"$TMPDIR/App
Fair.app\" \"$APPDIR\" operations so any failure aborts the script; locate the
extract/copy sequence and add the strict-mode invocation (or explicit
exit-on-error checks) near the TMPDIR/INSTALLER_PATH usage and before calling
quit_and_track_application/relaunch_application so failures aren’t masked.
- Around line 19-20: The symlink is created and removed with a relative name
("app-fair"), making install/uninstall nondeterministic; update the installer ln
invocation (the line using ln -h -f -s -- "$APPDIR/App
Fair.app/Contents/MacOS/App Fair" "app-fair") to create the link at an absolute
path (e.g. use a canonical location such as "$TMPDIR/app-fair" or
"/usr/local/bin/app-fair"), remove the useless mkdir -p ., and update the
uninstaller removal (the sudo rm -rf 'app-fair' call) to remove the same
absolute path so both install and uninstall reference the identical absolute
symlink location.
In `@ee/maintained-apps/outputs/atext/darwin.json`:
- Line 12: Replace the weak integrity marker by pinning the artifact's SHA-256
digest: locate the JSON entry containing the "sha256" key for the manifest
version "3.21" and replace the value "no_check" with the actual SHA-256 checksum
of the installer binary for that version (compute or obtain the canonical digest
from the release artifact), ensuring the "sha256" value is a valid hex digest
string so the manifest enforces binary integrity.
- Line 19: The install script currently moves the live app to backup before
ensuring the new app is fully installed (risking uninstall-on-failure); change
the flow in the install block that uses APPDIR/TMPDIR and functions
quit_and_track_application/relaunch_application so you first copy the new bundle
into a temporary path (e.g. "$APPDIR/aText.app.new" or inside TMPDIR), validate
the copy succeeded, then perform atomic renames: move the existing
"$APPDIR/aText.app" to a backup name only after the new copy is complete, and
finally mv the new bundle into "$APPDIR/aText.app"; ensure all copy/move
operations use sudo as currently done and add rollback logic to restore the
original if any step fails so the app is never removed on a partial failure.
In `@ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json`:
- Line 20: The install script calls sudo installer -pkg "$TMPDIR/AVPNC_mac.pkg"
-target / which hardcodes the package filename and breaks when the downloaded
file uses a temp name; update the installer invocation to use the actual
installer path variable (use "$INSTALLER_PATH") instead of
"$TMPDIR/AVPNC_mac.pkg", preserving quoting, so the installer uses the real
downloaded file; locate this change in the embedded script near the end where
installer is invoked (function context: quit_and_track_application /
relaunch_application surrounding calls).
In `@frontend/pages/SoftwarePage/components/icons/Activedock.tsx`:
- Line 10: The Activedock.tsx currently embeds a large inline base64 PNG (the
data URI assigned to href) which bloats bundles; replace that data URI by
exporting the image as a static asset (e.g., put the PNG under your
public/static assets or src/assets and optimize/strip metadata first), then
update the Activedock component to reference the optimized asset via a URL
import or public path (replace the data URI used in the href with the asset URL)
so the bundler/server serves the image file instead of inlining it.
---
Nitpick comments:
In `@ee/maintained-apps/outputs/angry-ip-scanner/darwin.json`:
- Around line 18-20: The backup "$TMPDIR/Angry IP Scanner.app.bkp" is created by
the sudo mv call but never removed; after the install step (the sudo cp -R
"$TMPDIR/Angry IP Scanner.app" "$APPDIR" in the script that uses TMPDIR, APPDIR
and the helper functions quit_and_track_application/relaunch_application) add
cleanup logic to detect a successful copy (check exit status of cp) and then
remove the backup (rm -rf "$TMPDIR/Angry IP Scanner.app.bkp"); also ensure the
cleanup runs only on success and consider also removing the backup in a
failure/rollback path to avoid leaving orphaned files.
In `@ee/maintained-apps/outputs/audio-hijack/darwin.json`:
- Line 12: Replace the placeholder "no_check" SHA256 in darwin.json's "sha256"
field with a real checksum or document why verification is skipped: compute the
installer SHA256 for the pinned installer URL and set "sha256":
"<actual-sha256-hex>" in the JSON (or if the source is a rolling "latest"
endpoint, change the download to a versioned URL and supply its checksum, or add
a README entry explaining the intentional waiver of verification and linking the
vendor's checksum source). Ensure you update any code that constructs the
download URL to use the versioned artifact if you choose to pin.
In `@ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json`:
- Line 12: Replace the placeholder "sha256": "no_check" with a real SHA256
checksum for the downloaded installer to enable integrity verification; obtain
the stable SHA256 from the vendor (or compute it by downloading the exact
installer artifact referenced in the manifest) and set that hex digest in the
"sha256" field so the installer is validated during installation (if the vendor
does not publish a checksum, include a reproducible source or verification step
in the manifest and avoid using "no_check").
🪄 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
Run ID: c6bdca42-5691-4c0a-ae21-e38f6a57399b
⛔ Files ignored due to path filters (43)
website/assets/images/app-icon-a-better-finder-rename-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-acorn-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-activedock-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-activitywatch-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-actual-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-adguard-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-adlock-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-advanced-renamer-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-affinity-designer-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-affinity-designer@1-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-affinity-photo-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-affinity-photo@1-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-affinity-publisher-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-affinity-publisher@1-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-airbuddy-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-airdroid-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-airparrot-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-airserver-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-airtable-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-airy-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-akiflow-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-alcove-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-aldente-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-alloy-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-alt-tab-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-altair-graphql-client-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-amadeus-pro-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-amadine-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-amethyst-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-amie-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-angry-ip-scanner-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-antinote-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-anydo-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-anytype-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-apidog-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-app-fair-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-appium-inspector-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-applite-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-asset-catalog-tinkerer-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-atext-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-audio-hijack-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-aviatrix-vpn-client-60x60@2x.pngis excluded by!**/*.pngwebsite/assets/images/app-icon-axure-rp-60x60@2x.pngis excluded by!**/*.png
📒 Files selected for processing (131)
ee/maintained-apps/inputs/homebrew/a-better-finder-rename.jsonee/maintained-apps/inputs/homebrew/acorn.jsonee/maintained-apps/inputs/homebrew/activedock.jsonee/maintained-apps/inputs/homebrew/activitywatch.jsonee/maintained-apps/inputs/homebrew/actual.jsonee/maintained-apps/inputs/homebrew/adguard.jsonee/maintained-apps/inputs/homebrew/adlock.jsonee/maintained-apps/inputs/homebrew/advanced-renamer.jsonee/maintained-apps/inputs/homebrew/affinity-designer.jsonee/maintained-apps/inputs/homebrew/affinity-designer@1.jsonee/maintained-apps/inputs/homebrew/affinity-photo.jsonee/maintained-apps/inputs/homebrew/affinity-photo@1.jsonee/maintained-apps/inputs/homebrew/affinity-publisher.jsonee/maintained-apps/inputs/homebrew/affinity-publisher@1.jsonee/maintained-apps/inputs/homebrew/airbuddy.jsonee/maintained-apps/inputs/homebrew/airdroid.jsonee/maintained-apps/inputs/homebrew/airparrot.jsonee/maintained-apps/inputs/homebrew/airserver.jsonee/maintained-apps/inputs/homebrew/airtable.jsonee/maintained-apps/inputs/homebrew/airy.jsonee/maintained-apps/inputs/homebrew/akiflow.jsonee/maintained-apps/inputs/homebrew/alcove.jsonee/maintained-apps/inputs/homebrew/aldente.jsonee/maintained-apps/inputs/homebrew/alloy.jsonee/maintained-apps/inputs/homebrew/alt-tab.jsonee/maintained-apps/inputs/homebrew/altair-graphql-client.jsonee/maintained-apps/inputs/homebrew/amadeus-pro.jsonee/maintained-apps/inputs/homebrew/amadine.jsonee/maintained-apps/inputs/homebrew/amethyst.jsonee/maintained-apps/inputs/homebrew/amie.jsonee/maintained-apps/inputs/homebrew/angry-ip-scanner.jsonee/maintained-apps/inputs/homebrew/antinote.jsonee/maintained-apps/inputs/homebrew/anydo.jsonee/maintained-apps/inputs/homebrew/anytype.jsonee/maintained-apps/inputs/homebrew/apidog.jsonee/maintained-apps/inputs/homebrew/app-fair.jsonee/maintained-apps/inputs/homebrew/appium-inspector.jsonee/maintained-apps/inputs/homebrew/applite.jsonee/maintained-apps/inputs/homebrew/asset-catalog-tinkerer.jsonee/maintained-apps/inputs/homebrew/atext.jsonee/maintained-apps/inputs/homebrew/audio-hijack.jsonee/maintained-apps/inputs/homebrew/aviatrix-vpn-client.jsonee/maintained-apps/inputs/homebrew/axure-rp.jsonee/maintained-apps/outputs/a-better-finder-rename/darwin.jsonee/maintained-apps/outputs/acorn/darwin.jsonee/maintained-apps/outputs/activedock/darwin.jsonee/maintained-apps/outputs/activitywatch/darwin.jsonee/maintained-apps/outputs/actual/darwin.jsonee/maintained-apps/outputs/adguard/darwin.jsonee/maintained-apps/outputs/adlock/darwin.jsonee/maintained-apps/outputs/advanced-renamer/darwin.jsonee/maintained-apps/outputs/affinity-designer/darwin.jsonee/maintained-apps/outputs/affinity-designer@1/darwin.jsonee/maintained-apps/outputs/affinity-photo/darwin.jsonee/maintained-apps/outputs/affinity-photo@1/darwin.jsonee/maintained-apps/outputs/affinity-publisher/darwin.jsonee/maintained-apps/outputs/affinity-publisher@1/darwin.jsonee/maintained-apps/outputs/airbuddy/darwin.jsonee/maintained-apps/outputs/airdroid/darwin.jsonee/maintained-apps/outputs/airparrot/darwin.jsonee/maintained-apps/outputs/airserver/darwin.jsonee/maintained-apps/outputs/airtable/darwin.jsonee/maintained-apps/outputs/airy/darwin.jsonee/maintained-apps/outputs/akiflow/darwin.jsonee/maintained-apps/outputs/alcove/darwin.jsonee/maintained-apps/outputs/aldente/darwin.jsonee/maintained-apps/outputs/alloy/darwin.jsonee/maintained-apps/outputs/alt-tab/darwin.jsonee/maintained-apps/outputs/altair-graphql-client/darwin.jsonee/maintained-apps/outputs/amadeus-pro/darwin.jsonee/maintained-apps/outputs/amadine/darwin.jsonee/maintained-apps/outputs/amethyst/darwin.jsonee/maintained-apps/outputs/amie/darwin.jsonee/maintained-apps/outputs/angry-ip-scanner/darwin.jsonee/maintained-apps/outputs/antinote/darwin.jsonee/maintained-apps/outputs/anydo/darwin.jsonee/maintained-apps/outputs/anytype/darwin.jsonee/maintained-apps/outputs/apidog/darwin.jsonee/maintained-apps/outputs/app-fair/darwin.jsonee/maintained-apps/outputs/appium-inspector/darwin.jsonee/maintained-apps/outputs/applite/darwin.jsonee/maintained-apps/outputs/apps.jsonee/maintained-apps/outputs/asset-catalog-tinkerer/darwin.jsonee/maintained-apps/outputs/atext/darwin.jsonee/maintained-apps/outputs/audio-hijack/darwin.jsonee/maintained-apps/outputs/aviatrix-vpn-client/darwin.jsonee/maintained-apps/outputs/axure-rp/darwin.jsonfrontend/pages/SoftwarePage/components/icons/ABetterFinderRename.tsxfrontend/pages/SoftwarePage/components/icons/Acorn.tsxfrontend/pages/SoftwarePage/components/icons/Activedock.tsxfrontend/pages/SoftwarePage/components/icons/Activitywatch.tsxfrontend/pages/SoftwarePage/components/icons/Actual.tsxfrontend/pages/SoftwarePage/components/icons/Adguard.tsxfrontend/pages/SoftwarePage/components/icons/Adlock.tsxfrontend/pages/SoftwarePage/components/icons/AdvancedRenamer.tsxfrontend/pages/SoftwarePage/components/icons/AffinityDesigner.tsxfrontend/pages/SoftwarePage/components/icons/AffinityDesigner1.tsxfrontend/pages/SoftwarePage/components/icons/AffinityPhoto.tsxfrontend/pages/SoftwarePage/components/icons/AffinityPhoto1.tsxfrontend/pages/SoftwarePage/components/icons/AffinityPublisher.tsxfrontend/pages/SoftwarePage/components/icons/AffinityPublisher1.tsxfrontend/pages/SoftwarePage/components/icons/Airbuddy.tsxfrontend/pages/SoftwarePage/components/icons/Airdroid.tsxfrontend/pages/SoftwarePage/components/icons/Airparrot.tsxfrontend/pages/SoftwarePage/components/icons/Airserver.tsxfrontend/pages/SoftwarePage/components/icons/Airtable.tsxfrontend/pages/SoftwarePage/components/icons/Airy.tsxfrontend/pages/SoftwarePage/components/icons/Akiflow.tsxfrontend/pages/SoftwarePage/components/icons/Alcove.tsxfrontend/pages/SoftwarePage/components/icons/Aldente.tsxfrontend/pages/SoftwarePage/components/icons/Alloy.tsxfrontend/pages/SoftwarePage/components/icons/AltTab.tsxfrontend/pages/SoftwarePage/components/icons/AltairGraphqlClient.tsxfrontend/pages/SoftwarePage/components/icons/AmadeusPro.tsxfrontend/pages/SoftwarePage/components/icons/Amadine.tsxfrontend/pages/SoftwarePage/components/icons/Amethyst.tsxfrontend/pages/SoftwarePage/components/icons/Amie.tsxfrontend/pages/SoftwarePage/components/icons/AngryIpScanner.tsxfrontend/pages/SoftwarePage/components/icons/Antinote.tsxfrontend/pages/SoftwarePage/components/icons/Anydo.tsxfrontend/pages/SoftwarePage/components/icons/Anytype.tsxfrontend/pages/SoftwarePage/components/icons/Apidog.tsxfrontend/pages/SoftwarePage/components/icons/AppFair.tsxfrontend/pages/SoftwarePage/components/icons/AppiumInspector.tsxfrontend/pages/SoftwarePage/components/icons/Applite.tsxfrontend/pages/SoftwarePage/components/icons/AssetCatalogTinkerer.tsxfrontend/pages/SoftwarePage/components/icons/Atext.tsxfrontend/pages/SoftwarePage/components/icons/AudioHijack.tsxfrontend/pages/SoftwarePage/components/icons/AviatrixVpnClient.tsxfrontend/pages/SoftwarePage/components/icons/AxureRp.tsxfrontend/pages/SoftwarePage/components/icons/index.ts
| } | ||
| ], | ||
| "refs": { | ||
| "1cbc7e4f": "#!/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 'net.publicspace.abfr12'\nif [ -d \"$APPDIR/A Better Finder Rename 12.app\" ]; then\n\tsudo mv \"$APPDIR/A Better Finder Rename 12.app\" \"$TMPDIR/A Better Finder Rename 12.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/A Better Finder Rename 12.app\" \"$APPDIR\"\nrelaunch_application 'net.publicspace.abfr12'\n", |
There was a problem hiding this comment.
Shared installer root cause across ee/maintained-apps/outputs/a-better-finder-rename/darwin.json and ee/maintained-apps/outputs/acorn/darwin.json: non-atomic app replacement.
Both scripts can move/remove a working app before the replacement is validated, so a failed extract/copy can leave the app missing. Please enforce fail-fast checks and rollback-safe replacement logic in both files (ideally in the generator/template).
🤖 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/a-better-finder-rename/darwin.json` at line 19,
The current installer does non-atomic replacement (it may move/remove the live
app before the new copy is verified); modify the install sequence around the
copy/move operations so the new app is first fully copied to a temporary
verified location (use the existing TMPDIR or create a unique temp path), verify
the copy succeeded (check exit status and that the .app bundle exists and is not
empty), then atomically rename/move the original app to a backup (e.g. rename to
.bkp) and move/rename the verified temp app into $APPDIR (use mv for atomic
replacement), and if any step fails restore the original from the backup and
exit non-zero; update the logic that currently calls sudo cp -R "$TMPDIR/A
Better Finder Rename 12.app" "$APPDIR" and the preceding sudo mv backup step to
follow this verified-copy → backup-original → atomic-rename →
rollback-on-failure pattern, and apply the same pattern in the equivalent
acorn/darwin.json generator/template.
| ], | ||
| "refs": { | ||
| "1cbc7e4f": "#!/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 'net.publicspace.abfr12'\nif [ -d \"$APPDIR/A Better Finder Rename 12.app\" ]; then\n\tsudo mv \"$APPDIR/A Better Finder Rename 12.app\" \"$TMPDIR/A Better Finder Rename 12.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/A Better Finder Rename 12.app\" \"$APPDIR\"\nrelaunch_application 'net.publicspace.abfr12'\n", | ||
| "25b72319": "#!/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/A Better Finder Rename 12.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/A Better Finder Rename 12'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.apple.helpd/Generated/net.publicspace.abfr12.help*'\ntrash $LOGGED_IN_USER '~/Library/Caches/net.publicspace.abfr*'\ntrash $LOGGED_IN_USER '~/Library/Cookies/net.publicspace.abfr*.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/net.publicspace.abfr*'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/net.publicspace.abfr*.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/net.publicspace.abfr*.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/net.publicspace.abfr*.savedState'\n" |
There was a problem hiding this comment.
Shared uninstall root cause across ee/maintained-apps/outputs/a-better-finder-rename/darwin.json and ee/maintained-apps/outputs/acorn/darwin.json: unquoted LOGGED_IN_USER arguments.
Both files pass LOGGED_IN_USER to trash unquoted, which can shift positional parameters when empty/unexpected and break cleanup path targeting.
🤖 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/a-better-finder-rename/darwin.json` at line 20,
The issue is that LOGGED_IN_USER is passed unquoted to the trash function (calls
like trash $LOGGED_IN_USER '~/Library/...'), which allows word-splitting or
empty-value positional shifts; update every call to the trash function (and
similar invocations) to quote the variable (use trash "$LOGGED_IN_USER" '...')
so the trash function receives the intended single argument; check both
occurrences in the diff and the files referencing LOGGED_IN_USER and the trash
function to ensure all calls are updated.
| } | ||
| ], | ||
| "refs": { | ||
| "8f0b4ab9": "#!/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/Affinity Designer 2.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/Affinity Designer 2'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.seriflabs.affinitydesigner2'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.seriflabs.affinitydesigner2'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.seriflabs.affinitydesigner2.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.seriflabs.affinitydesigner2.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.seriflabs.affinitydesigner2'\n", |
There was a problem hiding this comment.
Root cause: uninstall scripts use unvalidated console-user identity for user-home cleanup.
ee/maintained-apps/outputs/affinity-designer/darwin.json, ee/maintained-apps/outputs/affinity-designer@1/darwin.json, ee/maintained-apps/outputs/affinity-photo/darwin.json, ee/maintained-apps/outputs/affinity-photo@1/darwin.json, ee/maintained-apps/outputs/affinity-publisher/darwin.json, and ee/maintained-apps/outputs/affinity-publisher@1/darwin.json all call trash using LOGGED_IN_USER without guarding root/loginwindow/empty cases. A shared guard + conditional trash block should be applied to all six uninstall refs.
🤖 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/affinity-designer/darwin.json` at line 19, The
uninstall script uses an unvalidated LOGGED_IN_USER when calling the trash
function (trash) which can be root, loginwindow or empty; update the script to
validate LOGGED_IN_USER after it is set (LOGGED_IN_USER=$(scutil ...)) and skip
or avoid calling trash when LOGGED_IN_USER is empty or equals "root" or
"loginwindow"; implement a small guard block that checks LOGGED_IN_USER and only
invokes the existing trash function calls if the check passes, and apply the
same guard to the other three manifest variants that also call trash (the files
that call trash with LOGGED_IN_USER for Affinity Designer/Photo/Publisher and
their `@1` variants).
| "19097683": "#!/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.akiflow.akiflow'\nif [ -d \"$APPDIR/Akiflow.app\" ]; then\n\tsudo mv \"$APPDIR/Akiflow.app\" \"$TMPDIR/Akiflow.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Akiflow.app\" \"$APPDIR\"\nrelaunch_application 'com.akiflow.akiflow'\n", | ||
| "9d10ecef": "#!/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/Akiflow.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/Akiflow'\ntrash $LOGGED_IN_USER '~/Library/Preferences/Akiflow'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.akiflow.akiflow.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.akiflow.akiflow.savedState'\n" |
There was a problem hiding this comment.
Shared root cause across Akiflow, Alcove, AlDente, Alloy, and AltTab manifests: missing fail-fast script behavior.
ee/maintained-apps/outputs/akiflow/darwin.json, ee/maintained-apps/outputs/alcove/darwin.json, ee/maintained-apps/outputs/aldente/darwin.json, ee/maintained-apps/outputs/alloy/darwin.json, and ee/maintained-apps/outputs/alt-tab/darwin.json all embed install/uninstall scripts that perform destructive operations without strict failure handling, enabling partial state with success exit codes.
🤖 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/akiflow/darwin.json` around lines 19 - 20,
Install/uninstall scripts lack fail-fast/error propagation causing silent
partial success; enable strict failure handling and ensure cleanup/traps. Add a
top-level strict mode (set -euo pipefail) and a trap to clean MOUNT_POINT/TMPDIR
on exit, then update critical operations referenced in
quit_and_track_application, relaunch_application, the hdiutil/cp/mv sequence,
and the sudo rm -rf/trash flow to check exit status and exit non-zero on failure
(or let set -e propagate), and ensure
relaunch_application/quit_and_track_application return non-zero on unrecoverable
errors so the installer fails fast instead of reporting success after
destructive actions.
| ], | ||
| "refs": { | ||
| "5e5ca0a4": "#!/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.apphousekitchen.aldente-pro'\nif [ -d \"$APPDIR/AlDente.app\" ]; then\n\tsudo mv \"$APPDIR/AlDente.app\" \"$TMPDIR/AlDente.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/AlDente.app\" \"$APPDIR\"\nrelaunch_application 'com.apphousekitchen.aldente-pro'\n", | ||
| "7abc4904": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.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}\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.apphousekitchen.aldente-pro.helper'\nquit_application 'com.apphousekitchen.aldente-pro'\nsudo rm -rf '/Library/PrivilegedHelperTools/com.apphousekitchen.aldente-pro.helper'\nsudo rm -rf \"$APPDIR/AlDente.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/AlDente'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.apphousekitchen.aldente-pro'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.apphousekitchen.aldente-pro'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.apphousekitchen.aldente-pro.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro_backup.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro_stats.sqlite3'\n" |
There was a problem hiding this comment.
remove_launchctl_service checks privileged services without sudo.
In Line 20, plist_status=$(launchctl list "${service}") is used for both branches. When should_sudo=="true", the status check should also use sudo launchctl list ...; otherwise system helper services may not be detected/removed in non-root runs.
Targeted fix
- plist_status=$(launchctl list "${service}" 2>/dev/null)
+ if [[ $should_sudo == "true" ]]; then
+ plist_status=$(sudo launchctl list "${service}" 2>/dev/null)
+ else
+ plist_status=$(launchctl list "${service}" 2>/dev/null)
+ fi📝 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.
| "7abc4904": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.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}\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.apphousekitchen.aldente-pro.helper'\nquit_application 'com.apphousekitchen.aldente-pro'\nsudo rm -rf '/Library/PrivilegedHelperTools/com.apphousekitchen.aldente-pro.helper'\nsudo rm -rf \"$APPDIR/AlDente.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/AlDente'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.apphousekitchen.aldente-pro'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.apphousekitchen.aldente-pro'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.apphousekitchen.aldente-pro.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro_backup.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro_stats.sqlite3'\n" | |
| "7abc4904": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n if [[ $should_sudo == \"true\" ]]; then\n plist_status=$(sudo launchctl list \"${service}\" 2>/dev/null)\n else\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n fi\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.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}\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.apphousekitchen.aldente-pro.helper'\nquit_application 'com.apphousekitchen.aldente-pro'\nsudo rm -rf '/Library/PrivilegedHelperTools/com.apphousekitchen.aldente-pro.helper'\nsudo rm -rf \"$APPDIR/AlDente.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/AlDente'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.apphousekitchen.aldente-pro'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.apphousekitchen.aldente-pro'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.apphousekitchen.aldente-pro.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro_backup.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.apphousekitchen.aldente-pro_stats.sqlite3'\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/aldente/darwin.json` at line 20, The
remove_launchctl_service function uses launchctl list to set plist_status but
always runs the non‑sudo command; update the status check so when should_sudo ==
"true" you run sudo launchctl list "${service}" (and when false run launchctl
list "${service}") so privileged daemons are detected correctly; change the
assignment to plist_status to branch on should_sudo (refer to
remove_launchctl_service, plist_status, and should_sudo).
| "29c2c125": "#!/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 'app.App-Fair'\nif [ -d \"$APPDIR/App Fair.app\" ]; then\n\tsudo mv \"$APPDIR/App Fair.app\" \"$TMPDIR/App Fair.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/App Fair.app\" \"$APPDIR\"\nrelaunch_application 'app.App-Fair'\nmkdir -p .\n/bin/ln -h -f -s -- \"$APPDIR/App Fair.app/Contents/MacOS/App Fair\" \"app-fair\"\n", | ||
| "576e1f90": "#!/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/App Fair.app\"\nsudo rm -rf 'app-fair'\nsudo rmdir '/Applications/App Fair'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/app.App-Fair'\ntrash $LOGGED_IN_USER '~/Library/Application Support/app.App-Fair'\ntrash $LOGGED_IN_USER '~/Library/Caches/app.App-Fair'\ntrash $LOGGED_IN_USER '~/Library/Containers/app.App-Fair'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/app.App-Fair'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/app.App-Fair.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/app.App-Fair.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/app.App-Fair.savedState'\n" |
There was a problem hiding this comment.
Use an absolute path for the app-fair symlink.
Line 19 creates app-fair in the current working directory, and Line 20 removes 'app-fair' relatively. That makes CLI link creation/removal nondeterministic across execution environments.
Suggested direction
- mkdir -p .
- /bin/ln -h -f -s -- "$APPDIR/App Fair.app/Contents/MacOS/App Fair" "app-fair"
+ BIN_DIR="/usr/local/bin"
+ sudo mkdir -p "$BIN_DIR"
+ sudo /bin/ln -h -f -s -- "$APPDIR/App Fair.app/Contents/MacOS/App Fair" "$BIN_DIR/app-fair"
- sudo rm -rf 'app-fair'
+ sudo rm -rf '/usr/local/bin/app-fair'🤖 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/app-fair/darwin.json` around lines 19 - 20, The
symlink is created and removed with a relative name ("app-fair"), making
install/uninstall nondeterministic; update the installer ln invocation (the line
using ln -h -f -s -- "$APPDIR/App Fair.app/Contents/MacOS/App Fair" "app-fair")
to create the link at an absolute path (e.g. use a canonical location such as
"$TMPDIR/app-fair" or "/usr/local/bin/app-fair"), remove the useless mkdir -p .,
and update the uninstaller removal (the sudo rm -rf 'app-fair' call) to remove
the same absolute path so both install and uninstall reference the identical
absolute symlink location.
| "installer_url": "https://www.trankynam.com/atext/downloads/aText.dmg", | ||
| "install_script_ref": "5d9d0a57", | ||
| "uninstall_script_ref": "6f097dc3", | ||
| "sha256": "no_check", |
There was a problem hiding this comment.
Pin installer integrity for this versioned artifact.
Line 12 uses "sha256": "no_check", which disables binary integrity verification. For a fixed manifest version ("3.21"), this weakens supply-chain guarantees and allows silent artifact drift.
🤖 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/atext/darwin.json` at line 12, Replace the weak
integrity marker by pinning the artifact's SHA-256 digest: locate the JSON entry
containing the "sha256" key for the manifest version "3.21" and replace the
value "no_check" with the actual SHA-256 checksum of the installer binary for
that version (compute or obtain the canonical digest from the release artifact),
ensuring the "sha256" value is a valid hex digest string so the manifest
enforces binary integrity.
| } | ||
| ], | ||
| "refs": { | ||
| "5d9d0a57": "#!/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.trankynam.aText'\nif [ -d \"$APPDIR/aText.app\" ]; then\n\tsudo mv \"$APPDIR/aText.app\" \"$TMPDIR/aText.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/aText.app\" \"$APPDIR\"\nrelaunch_application 'com.trankynam.aText'\n", |
There was a problem hiding this comment.
Make app replacement atomic to avoid uninstall-on-failure behavior.
On Line 19, the install script moves /Applications/aText.app to backup before the new app is fully validated in place. If the subsequent copy fails, the installed app can be left missing.
Suggested install-flow hardening
- if [ -d "$APPDIR/aText.app" ]; then
- sudo mv "$APPDIR/aText.app" "$TMPDIR/aText.app.bkp"
- fi
- sudo cp -R "$TMPDIR/aText.app" "$APPDIR"
+ STAGED_APP="$TMPDIR/aText.staged.app"
+ sudo rm -rf "$STAGED_APP"
+ sudo cp -R "$TMPDIR/aText.app" "$STAGED_APP" || exit 1
+ [ -d "$STAGED_APP" ] || exit 1
+ if [ -d "$APPDIR/aText.app" ]; then
+ sudo mv "$APPDIR/aText.app" "$TMPDIR/aText.app.bkp" || exit 1
+ fi
+ sudo mv "$STAGED_APP" "$APPDIR/aText.app" || {
+ [ -d "$TMPDIR/aText.app.bkp" ] && sudo mv "$TMPDIR/aText.app.bkp" "$APPDIR/aText.app"
+ exit 1
+ }🤖 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/atext/darwin.json` at line 19, The install script
currently moves the live app to backup before ensuring the new app is fully
installed (risking uninstall-on-failure); change the flow in the install block
that uses APPDIR/TMPDIR and functions
quit_and_track_application/relaunch_application so you first copy the new bundle
into a temporary path (e.g. "$APPDIR/aText.app.new" or inside TMPDIR), validate
the copy succeeded, then perform atomic renames: move the existing
"$APPDIR/aText.app" to a backup name only after the new copy is complete, and
finally mv the new bundle into "$APPDIR/aText.app"; ensure all copy/move
operations use sudo as currently done and add rollback logic to restore the
original if any step fails so the app is never removed on a partial failure.
| ], | ||
| "refs": { | ||
| "4ad80bc8": "#!/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\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 for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.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}\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 'aviatrix.vpn.client.rp.plist'\nremove_pkg_files 'com.Aviatrix.VPNClient'\nforget_pkg 'com.Aviatrix.VPNClient'\nsudo rm -rf '/Applications/Aviatrix VPN Client.app'\ntrash $LOGGED_IN_USER '~/Library/Aviatrix'\ntrash $LOGGED_IN_USER '~/Library/Logs/AviatrixVPNC'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.pythonmac.unspecified.AviatrixVPNClient.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.pythonmac.unspecified.AviatrixVPNClient.savedState'\n", | ||
| "717ae1f5": "#!/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 'org.pythonmac.unspecified.AviatrixVPNClient'\nsudo installer -pkg \"$TMPDIR/AVPNC_mac.pkg\" -target /\nrelaunch_application 'org.pythonmac.unspecified.AviatrixVPNClient'\n" |
There was a problem hiding this comment.
Install script uses hardcoded filename instead of $INSTALLER_PATH.
The install script constructs $TMPDIR/AVPNC_mac.pkg assuming the downloaded file retains its original name. If the download system uses a temporary filename, this will fail.
Use $INSTALLER_PATH directly:
Suggested fix
In the embedded install script, change:
-sudo installer -pkg "$TMPDIR/AVPNC_mac.pkg" -target /
+sudo installer -pkg "$INSTALLER_PATH" -target /🤖 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/aviatrix-vpn-client/darwin.json` at line 20, The
install script calls sudo installer -pkg "$TMPDIR/AVPNC_mac.pkg" -target / which
hardcodes the package filename and breaks when the downloaded file uses a temp
name; update the installer invocation to use the actual installer path variable
(use "$INSTALLER_PATH") instead of "$TMPDIR/AVPNC_mac.pkg", preserving quoting,
so the installer uses the real downloaded file; locate this change in the
embedded script near the end where installer is invoked (function context:
quit_and_track_application / relaunch_application surrounding calls).
| <image | ||
| width={32} | ||
| height={32} | ||
| href="data:image/png;base64,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" |
There was a problem hiding this comment.
Inline base64 PNG on Line 10 will bloat frontend bundles.
Embedding the full PNG payload in TSX significantly increases JS transfer and parse cost; with this PR pattern across many icons, this compounds on Software page load. Please externalize the PNG as a static asset (or optimized import) and reference the URL, and strip embedded metadata before shipping.
🤖 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 `@frontend/pages/SoftwarePage/components/icons/Activedock.tsx` at line 10, The
Activedock.tsx currently embeds a large inline base64 PNG (the data URI assigned
to href) which bloats bundles; replace that data URI by exporting the image as a
static asset (e.g., put the PNG under your public/static assets or src/assets
and optimize/strip metadata first), then update the Activedock component to
reference the optimized asset via a URL import or public path (replace the data
URI used in the href with the asset URL) so the bundler/server serves the image
file instead of inlining it.
…d upstream) main #47536 landed the 'A' Fleet-maintained apps that this branch also added. The line-level merge appended both copies; rebuild apps.json as main's authoritative entries plus only the apps unique to this branch.
Adds 43 Fleet-maintained app(s) whose cask token starts with 'A': inputs, outputs, app icons (TSX + website PNG), and the matching index.ts and apps.json entries.
Summary by CodeRabbit