mac-app-install
Install, inspect and unblock software on a Mac from a session — Homebrew formulae and casks, Mac App Store apps with mas (and the GUI fallback when mas needs root), plus quarantine and Gatekeeper ("ap
SKILL.mdskills/mac-app-install/SKILL.md
GitHubInstalling software on a Mac
Check before installing. Most "install X" requests are already satisfied, and a lot of
tools ship with macOS now (jq is at /usr/bin/jq on recent macOS, for example).
Is it already here?
command -v jq; which -a python3 # every copy on PATH, first one wins
brew list --versions jq # installed by brew? (empty = no)
mdfind "kMDItemCFBundleIdentifier == 'com.example.App'" 2>/dev/null # an app, anywhere
ls /Applications ~/Applications
mas list # App Store apps: id, name, version
which jq finding /usr/bin/jq while brew list says not installed is normal: the
system copy, not brew's. Know which one a script will run.
Homebrew: read without writing
Many brew commands auto-update brew first, downloading and rewriting its taps (verified:
brew outdated pulled a new portable Ruby and updated three taps). For read-only queries:
export HOMEBREW_NO_AUTO_UPDATE=1
brew info --json=v2 jq | jq -r '.formulae[0] | "\(.name) \(.versions.stable) installed=\(.installed|length)"'
brew outdated --quiet
brew uses --installed jq # what depends on it
brew info --cask --json=v2 visual-studio-code | jq '.casks[0].artifacts'
curl -s https://formulae.brew.sh/api/formula/jq.json | jq -r '.versions.stable, .license'
The JSON API at formulae.brew.sh/api/formula/<name>.json (and /api/cask/<name>.json)
answers "what version, what licence, what does it depend on" without touching the local
install.
Homebrew: install (the user asked for it)
brew install jq # CLI tool (formula), no sudo on Apple silicon
brew install --cask firefox # GUI app into /Applications
brew upgrade jq
brew uninstall jq
- Prefix is
/opt/homebrewon Apple silicon,/usr/localon Intel. A LaunchAgent won't find either without a PATH (mac-launchd). - A cask that installs a
.pkgor a system extension asks for an admin password; a non-interactive session cannot answer. Stop and hand it to the user. Untrusted tapwarnings mean a third-party tap was added at some point; installing from it runs that tap author's code.
Mac App Store
mas search "Xcode" # id + name + version
mas list
mas install 497799835 # by id
mas v7 needs root to install (requires root privileges), which a non-interactive
shell can't give. The fallback that works for a free app under the signed-in account:
open its page and GUI-script the buttons through System Events (mac-automation):
open "macappstore://apps.apple.com/app/id497799835"
# then walk `UI elements of window 1 of process "App Store"` to find the buttons:
# first launch shows a welcome sheet (Continue), then Get, then Install on a confirm sheet
Screenshot between steps (mac-screenshots), because button names aren't discoverable
without walking the tree. Never do this while the user is working at the keyboard (it takes
focus), and never for a paid app: that is spending the user's money.
"Damaged", "can't be opened": quarantine and Gatekeeper
Files downloaded by a browser or mail client get a com.apple.quarantine xattr, and
Gatekeeper checks the first launch. curl and brew downloads do not get it (verified:
a curl download carried only com.apple.provenance).
xattr file.dmg # list attributes
xattr -p com.apple.quarantine App.app # 0081;<hex time>;<agent>;<uuid>
spctl --status # assessments enabled = Gatekeeper on
spctl -a -vv -t exec /Applications/App.app # accepted / rejected, and why
codesign -dv /Applications/App.app 2>&1 | grep -E "Identifier|TeamIdentifier"
To open an app the user trusts and chose to download:
xattr -d com.apple.quarantine /Applications/App.app # one file
xattr -dr com.apple.quarantine /Applications/App.app # recursively, for a bundle
This turns off a security check for that app. Only do it for software the user
deliberately downloaded from a source they trust, and say what you did. Never disable
Gatekeeper globally (spctl --master-disable).
Uninstall
brew uninstall, brew uninstall --cask, or drag from /Applications. Leftovers live in
~/Library/Application Support/<app>, ~/Library/Preferences/<bundle-id>.plist,
~/Library/Caches/<bundle-id>, ~/Library/Containers/<bundle-id>. Removing those
deletes the user's data for that app; list them, let the user decide.