| name | macos-recipes |
| description | Exact macOS commands for IT diagnostics and safe actions — disk, memory, startup items, updates, Time Machine, battery, SMART, duplicate detection, Trash-based deletion, HEIC conversion, photo dates, launchd scheduling. Load before running any diagnostic or maintenance command on a Mac. |
macOS Recipes
Exact commands, expected output shape, and gotchas. Use these verbatim rather than improvising — the gotchas are the value.
Scope note
This skill covers command mechanics only — what to run, what the output means, and where each command lies or fails silently. It deliberately holds no policy.
Read the companions for the rules that govern using these commands: mac-it-guy-pro:it-core for the safety contract (Trash instead of delete, dry-run first, undo manifests, admin work handed to the user), mac-it-guy-pro:machine-profile for what may be written down afterwards, and mac-it-guy-pro:toolbox-contract for turning a recipe into a tool the user keeps. Network measurement lives in mac-it-guy-pro:home-network, not here.
A recipe in this file is never authority to run it — the contract decides that.
Diagnostics (read-only)
Disk
- Free space:
df -h / — use the Avail and Capacity columns of the /System/Volumes/Data or / line.
- What's big (one level):
du -x -h -d 1 ~ 2>/dev/null | sort -rh | head -15
- Top 10 largest files:
find ~ -xdev -type f -size +500M -not -path "*/Library/*" 2>/dev/null -exec du -h {} + | sort -rh | head -10
- Gotcha:
du over the whole home folder takes 1–4 minutes on a full disk. Say so before running; use a 300000 ms timeout.
- Gotcha: macOS "purgeable" space makes Finder and
df disagree. If they differ, trust df and explain the difference in one sentence.
Memory & CPU
- Memory pressure:
memory_pressure | tail -1 — reports "System-wide memory free percentage".
- Top consumers:
ps -Ao pid,pcpu,pmem,comm -r | head -8
- Load:
uptime
Hardware identity (for the profile)
- Model & RAM:
system_profiler SPHardwareDataType | grep -E "Model Name|Model Identifier|Memory|Chip" — do NOT record the serial number line.
- macOS version:
sw_vers
- Battery:
system_profiler SPPowerDataType | grep -E "Cycle Count|Condition|Maximum Capacity"
- SMART status:
diskutil info disk0 | grep SMART — anything other than "Verified" is a 🔴 finding.
Startup items
- Login items:
osascript -e 'tell application "System Events" to get the name of every login item'
- Gotcha: first run triggers an Automation permission prompt — tell the user to click Allow, and why.
- User launch agents:
ls -1 ~/Library/LaunchAgents 2>/dev/null
- All-users launch agents:
ls -1 /Library/LaunchAgents /Library/LaunchDaemons 2>/dev/null
- Interpreting plist names: reverse-DNS names their vendor (
com.google.keystone… = Google updater). Explain each in plain language; never call something safe to remove unless you can name what it belongs to.
Updates
- Pending:
softwareupdate -l — Gotcha: takes 30–90 s and needs network; run with a 120000 ms timeout and report 🟡 "couldn't check" on timeout rather than failing the checkup.
Time Machine
- Configured?
tmutil destinationinfo — "No destinations configured" = 🔴 no backup.
- Last backup:
tmutil latestbackup
- ⚠️ Judge these by their output, never by their exit code. Both commands exit 0 even when there is no backup and no destination — measured, not assumed. An agent branching on
$? will report a healthy backup that does not exist, which is the single worst wrong answer this plugin can give. Read the text:
| Output contains | Means | Report |
|---|
No destinations configured | Time Machine was never set up | 🔴 no backup at all |
Failed to mount backup destination | Configured, disk not connected today | 🟡 not "no backups" — say which |
| A dated backup path | Working | 🟢 with the date |
This distinction is the whole point: "you have never had a backup" and "your backup disk is unplugged" call for completely different conversations, and the exit code tells you neither.
Trash size
du -sh ~/.Trash 2>/dev/null — report it; only the user empties it.
Behavioral observation (for onboarding)
Safe actions
Delete = move to Trash (the only allowed deletion)
osascript -e 'on run argv' -e 'set p to POSIX file (item 1 of argv)' \
-e 'tell application "Finder" to delete p' -e 'end run' "/full/absolute/path"
- The path is passed as an argument, never interpolated into the AppleScript source — filenames containing quotes or apostrophes cannot break out of the script.
- One file/folder per call; for batches, loop and count.
- Preserves "Put Back" in Finder — this is why rm is banned.
- Gotcha: needs Automation permission for Finder on first use (prompt appears once).
Duplicate files (two-pass, bounded)
- Candidates by size:
find <dir> -xdev -type f -size +1M -exec stat -f "%z %N" {} + | sort -n — only same-size files can be duplicates.
- Confirm by checksum, same-size groups only:
md5 -q <file>.
- Never auto-delete duplicates. Present groups (keep newest-path suggestion pre-marked) and let the user choose.
HEIC → JPG
out="${f%.*}.jpg"; n=2
while [ -e "$out" ]; do out="${f%.*} ($n).jpg"; n=$((n+1)); done
sips -s format jpeg "$f" --out "$out"
Original is kept; the converted copy goes next to it or to a folder the user picked.
⚠️ sips --out overwrites an existing file silently and exits 0 — the collision loop above is not optional. Cameras that save IMG_1234.HEIC beside IMG_1234.JPG, and any second run of the same conversion, will destroy real data without it.
Photo date taken (for date-based renaming)
mdls -name kMDItemContentCreationDate -raw "photo.jpg" — Spotlight metadata, works for most photos.
- Fallback when Spotlight has nothing:
stat -f "%SB" -t "%Y-%m-%d" "photo.jpg" (file creation date — say it's a fallback, since it's the copy date, not the shoot date).
Compress images in place-adjacent
sips -Z 2048 --setProperty formatOptions 70 "big.jpg" --out "big-web.jpg"
(2048 px longest side, 70% quality; never overwrite the original.)
Schedule a tool (launchd, user-level)
Write ~/Library/LaunchAgents/com.itguy.<tool-name>.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.itguy.<tool-name></string>
<key>ProgramArguments</key><array>
<string>/bin/bash</string>
<string>/Users/NAME/ITGuy/toolbox/<tool-name>/run.sh</string>
<string>--go</string>
</array>
<key>StartCalendarInterval</key><dict>
<key>Hour</key><integer>9</integer><key>Minute0
StandardOutPath/Users/NAME/ITGuy/toolbox//runs.log
StandardErrorPath/Users/NAME/ITGuy/toolbox//runs.log
Load with launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.itguy.<tool-name>.plist. Only schedule tools that already pass their dry-run; tell the user how to stop it (launchctl bootout gui/$(id -u)/com.itguy.<tool-name>).
Permission gotchas (read before diagnosing "weird" failures)
| Symptom | Cause | Fix to walk the user through |
|---|
| "Operation not permitted" reading Desktop/Documents/Downloads/~/Library | Terminal lacks Full Disk Access | System Settings → Privacy & Security → Full Disk Access → enable the terminal app → restart it |
| osascript errors -1743 / "not authorized" | Automation permission not granted | System Settings → Privacy & Security → Automation → allow terminal to control Finder/System Events |
tmutil latestbackup prints Failed to mount backup destination (while still exiting 0) | Backup disk not connected | Ask the user to plug in the backup disk — not the same as having no backups |