mac-ops Helps with Slow Mac that used to be fast — bloat accumulation across the four startup mechanisms (Login Items, , , ). The same machine still boots fast once those are inventoried and trimmed. Failing drives that nobody's spotted yet. macOS doesn't shout the way Windows does — IO errors live in and APFS surfaces them via / provider messages. Healthy SSDs produce zero of these per month; dozens means active failure even when "About This Mac → Storage" still shows green. Kernel panics with no obvious cause. The / files in carry the panic string, kernel call stack, and (critically) the lo…

\\n'/\\\\n}\"\n s=\"${s//

mac-ops Helps with Slow Mac that used to be fast — bloat accumulation across the four startup mechanisms (Login Items, , , ). The same machine still boots fast once those are inventoried and trimmed. Failing drives that nobody's spotted yet. macOS doesn't shout the way Windows does — IO errors live in and APFS surfaces them via / provider messages. Healthy SSDs produce zero of these per month; dozens means active failure even when "About This Mac → Storage" still shows green. Kernel panics with no obvious cause. The / files in carry the panic string, kernel call stack, and (critically) the lo…

\\r'/\\\\r}\"\n s=\"${s//

mac-ops Helps with Slow Mac that used to be fast — bloat accumulation across the four startup mechanisms (Login Items, , , ). The same machine still boots fast once those are inventoried and trimmed. Failing drives that nobody's spotted yet. macOS doesn't shout the way Windows does — IO errors live in and APFS surfaces them via / provider messages. Healthy SSDs produce zero of these per month; dozens means active failure even when "About This Mac → Storage" still shows green. Kernel panics with no obvious cause. The / files in carry the panic string, kernel call stack, and (critically) the lo…

\\t'/\\\\t}\"\n printf '%s' \"$s\"\n}\n\n# Section header — sets CURRENT_SECTION and prints a banner (or JSON record).\nsection() {\n CURRENT_SECTION=\"$1\"\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"section\",\"name\":\"%s\"}\\n' \"$(_json_escape \"$1\")\"\n else\n [[ \"$QUIET\" -eq 1 ]] || { echo; echo \"=== $1 ===\"; }\n fi\n}\n\n# Check result emitters\nlog_pass() {\n PASS_COUNT=$((PASS_COUNT + 1))\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"pass\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[PASS] $1${2:+ :: $2}\"\n fi\n}\n\nlog_fail() {\n FAIL_COUNT=$((FAIL_COUNT + 1))\n [[ -z \"$FIRST_FAIL\" ]] && FIRST_FAIL=\"[$CURRENT_SECTION] $1\"\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"fail\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[FAIL] $1${2:+ :: $2}\"\n fi\n}\n\nlog_warn() {\n WARN_COUNT=$((WARN_COUNT + 1))\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"warn\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[WARN] $1${2:+ :: $2}\"\n fi\n}\n\nlog_info() {\n INFO_COUNT=$((INFO_COUNT + 1))\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"info\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[INFO] $1${2:+ :: $2}\"\n fi\n}\n\n# Free-form info text — text in default mode, suppressed in JSON\nnote() {\n [[ \"$JSON_MODE\" -eq 1 ]] && return 0\n [[ \"$QUIET\" -eq 1 ]] && return 0\n echo \"$@\"\n}\n\nemit_summary() {\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"summary\",\"pass\":%d,\"fail\":%d,\"warn\":%d,\"info\":%d,\"first_fail\":\"%s\"}\\n' \\\n \"$PASS_COUNT\" \"$FAIL_COUNT\" \"$WARN_COUNT\" \"$INFO_COUNT\" \"$(_json_escape \"$FIRST_FAIL\")\"\n else\n echo\n echo \"=== SUMMARY ===\"\n echo \" PASS: $PASS_COUNT FAIL: $FAIL_COUNT WARN: $WARN_COUNT INFO: $INFO_COUNT\"\n if [[ -n \"$FIRST_FAIL\" ]]; then\n echo \" First failure: $FIRST_FAIL\"\n else\n echo \" No failures.\"\n fi\n fi\n}\n\n# Redact filter: same regex set as net-ops's, plus macOS-specific patterns.\n# Preserves Tailscale's 100.100.100.100 and public DNS anchors.\nredact_filter() {\n if [[ \"$REDACT\" -eq 0 ]]; then cat; return; fi\n perl -pe '\n s/100\\.100\\.100\\.100/__TS_MAGIC__/g;\n s/\\b10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/10.X.X.X/g;\n s/\\b172\\.(1[6-9]|2[0-9]|3[01])\\.\\d{1,3}\\.\\d{1,3}\\b/172.X.X.X/g;\n s/\\b192\\.168\\.\\d{1,3}\\.\\d{1,3}\\b/192.168.X.X/g;\n s/\\b100\\.(6[4-9]|[7-9]\\d|1[01]\\d|12[0-7])\\.\\d{1,3}\\.\\d{1,3}\\b/100.X.X.X/g;\n s/\\b169\\.254\\.\\d{1,3}\\.\\d{1,3}\\b/169.254.X.X/g;\n s/\\b[0-9a-fA-F]{2}([:-])[0-9a-fA-F]{2}\\1[0-9a-fA-F]{2}\\1[0-9a-fA-F]{2}\\1[0-9a-fA-F]{2}\\1[0-9a-fA-F]{2}\\b/XX:XX:XX:XX:XX:XX/g;\n s/\\b[a-z0-9-]+\\.ts\\.net\\b/REDACTED.ts.net/g;\n # macOS specifics: hostnames matching \u003cname>.local or \u003cname>.lan\n s/\\b([a-zA-Z0-9-]+)\\.local\\b/HOSTNAME.local/g;\n # macOS serial numbers (12-char base32-ish, only when prefixed by Serial)\n s/Serial(?:Number)?[:= ]+\\K[A-Z0-9]{10,14}/REDACTED/g;\n # UUIDs (long volume / device identifiers)\n s/\\b[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\\b/UUID-REDACTED/gi;\n s/__TS_MAGIC__/100.100.100.100/g;\n '\n}\n\n# Self-reinvoke filter — same pattern as net-ops to handle --redact + --json\n# compose. Strips --redact from inner argv to prevent infinite recursion.\nmaybe_filter_self() {\n [[ \"$REDACT\" -eq 1 ]] || [[ \"$JSON_MODE\" -eq 1 ]] || return 0\n [[ \"${_MACOPS_FILTERED:-0}\" -eq 1 ]] && return 0\n export _MACOPS_FILTERED=1\n local cleaned_args=()\n for a in \"$@\"; do [[ \"$a\" != \"--redact\" ]] && cleaned_args+=(\"$a\"); done\n if [[ \"$JSON_MODE\" -eq 1 ]] && [[ \"$REDACT\" -eq 1 ]]; then\n \"$0\" ${cleaned_args[@]+\"${cleaned_args[@]}\"} | grep '^{' | redact_filter\n elif [[ \"$JSON_MODE\" -eq 1 ]]; then\n \"$0\" ${cleaned_args[@]+\"${cleaned_args[@]}\"} | grep '^{'\n else\n \"$0\" ${cleaned_args[@]+\"${cleaned_args[@]}\"} | redact_filter\n fi\n exit \"${PIPESTATUS[0]}\"\n}\n\n# Convenience: macOS major version (12, 13, 14, 15, 26...)\nmacos_major() {\n sw_vers -productVersion 2>/dev/null | awk -F. '{print $1}'\n}\n\n# Convenience: am I on Apple Silicon?\nis_apple_silicon() {\n [[ \"$(uname -m)\" == \"arm64\" ]]\n}\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6010,"content_sha256":"06626404ca78c000cbdcf0a62e08251854dc06707e19dfbd5ea6a4ac3a01384b"},{"filename":"scripts/_lib/panel.sh","content":"# mac-ops :: _lib/panel.sh\n# Panel-rendering helper that wraps the shared skills/_lib/term.sh API.\n# Source AFTER common.sh. Provides:\n#\n# panel_init — detect TTY, source term.sh\n# panel_findings_open — start collecting findings (called once at top)\n# panel_render \u003cname> \u003cindicator>\n# — emit the state-grouped panel using collected\n# findings + log_pass/log_fail/log_warn counts\n#\n# Collection happens transparently: the existing log_pass / log_fail /\n# log_warn / log_info from common.sh ALSO push (state, section, label,\n# detail) tuples into MAC_PANEL_FINDINGS when panel mode is on.\n\n# Locate the project-level term.sh (4 levels up from script dir, then _lib)\n__MACOPS_SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n__MACOPS_TERM_LIB=\"$(cd \"$__MACOPS_SCRIPT_DIR/../../../_lib\" 2>/dev/null && pwd)/term.sh\"\n\n# Panel mode default: auto. Force on with FORCE_PANEL=1, off with NO_PANEL=1.\nPANEL_MODE=\"${PANEL_MODE:-auto}\"\nMAC_PANEL_ENABLED=0\nMAC_PANEL_FINDINGS=() # each entry: \"state|section|label|detail\"\n\npanel_init() {\n # If JSON output requested, never enable panels — JSON is the contract\n if [[ \"${JSON_MODE:-0}\" -eq 1 ]]; then return 0; fi\n\n # NO_PANEL forces off; FORCE_PANEL forces on; otherwise TTY-detect\n if [[ \"${NO_PANEL:-0}\" -eq 1 ]]; then return 0; fi\n\n # Source term.sh if available.\n if [[ -f \"$__MACOPS_TERM_LIB\" ]]; then\n # shellcheck source=/dev/null\n . \"$__MACOPS_TERM_LIB\"\n term_init\n else\n return 0\n fi\n\n # Use panel mode if stdout is a TTY or forced\n if [[ \"${FORCE_PANEL:-0}\" -eq 1 ]] || [[ \"$TERM_TTY\" -eq 1 ]]; then\n MAC_PANEL_ENABLED=1\n fi\n}\n\npanel_enabled() { [[ \"$MAC_PANEL_ENABLED\" -eq 1 ]]; }\n\n# Override common.sh log_* functions to also collect findings.\n# (These are sourced AFTER common.sh and will override its versions.)\n__MAC_ORIGINAL_PASS=$(declare -f log_pass || true)\n__MAC_ORIGINAL_FAIL=$(declare -f log_fail || true)\n__MAC_ORIGINAL_WARN=$(declare -f log_warn || true)\n__MAC_ORIGINAL_INFO=$(declare -f log_info || true)\n\n# Wrap each log function so it both updates counters AND collects findings.\n# When panel mode is on, ALSO suppress the inline echo — we'll render at end.\n\nlog_pass() {\n PASS_COUNT=$((PASS_COUNT + 1))\n MAC_PANEL_FINDINGS+=(\"pass|${CURRENT_SECTION}|$1|${2:-}\")\n if [[ \"$MAC_PANEL_ENABLED\" -eq 1 ]]; then return 0; fi\n if [[ \"${JSON_MODE:-0}\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"pass\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[PASS] $1${2:+ :: $2}\"\n fi\n}\n\nlog_fail() {\n FAIL_COUNT=$((FAIL_COUNT + 1))\n [[ -z \"$FIRST_FAIL\" ]] && FIRST_FAIL=\"[$CURRENT_SECTION] $1\"\n MAC_PANEL_FINDINGS+=(\"fail|${CURRENT_SECTION}|$1|${2:-}\")\n if [[ \"$MAC_PANEL_ENABLED\" -eq 1 ]]; then return 0; fi\n if [[ \"${JSON_MODE:-0}\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"fail\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[FAIL] $1${2:+ :: $2}\"\n fi\n}\n\nlog_warn() {\n WARN_COUNT=$((WARN_COUNT + 1))\n MAC_PANEL_FINDINGS+=(\"warn|${CURRENT_SECTION}|$1|${2:-}\")\n if [[ \"$MAC_PANEL_ENABLED\" -eq 1 ]]; then return 0; fi\n if [[ \"${JSON_MODE:-0}\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"warn\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[WARN] $1${2:+ :: $2}\"\n fi\n}\n\nlog_info() {\n INFO_COUNT=$((INFO_COUNT + 1))\n MAC_PANEL_FINDINGS+=(\"info|${CURRENT_SECTION}|$1|${2:-}\")\n if [[ \"$MAC_PANEL_ENABLED\" -eq 1 ]]; then return 0; fi\n if [[ \"${JSON_MODE:-0}\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"info\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[INFO] $1${2:+ :: $2}\"\n fi\n}\n\n# In panel mode, suppress `note` and `section` so the body stays clean —\n# rendering happens at the end via panel_render.\n__MAC_ORIGINAL_NOTE=$(declare -f note || true)\n__MAC_ORIGINAL_SECTION=$(declare -f section || true)\n\nnote() {\n [[ \"$MAC_PANEL_ENABLED\" -eq 1 ]] && return 0\n [[ \"${JSON_MODE:-0}\" -eq 1 ]] && return 0\n [[ \"${QUIET:-0}\" -eq 1 ]] && return 0\n echo \"$@\"\n}\n\nsection() {\n CURRENT_SECTION=\"$1\"\n if [[ \"$MAC_PANEL_ENABLED\" -eq 1 ]]; then return 0; fi\n if [[ \"${JSON_MODE:-0}\" -eq 1 ]]; then\n printf '{\"type\":\"section\",\"name\":\"%s\"}\\n' \"$(_json_escape \"$1\")\"\n else\n [[ \"${QUIET:-0}\" -eq 1 ]] || { echo; echo \"=== $1 ===\"; }\n fi\n}\n\n# Render the collected findings as a state-grouped tree panel.\n# Args:\n# $1 = script tag (e.g. \"health-audit\")\n# $2 = right indicator (e.g. hostname)\npanel_render() {\n if [[ \"$MAC_PANEL_ENABLED\" -ne 1 ]]; then\n # Not panel mode — just emit the standard summary block\n emit_summary\n return\n fi\n\n local tag=\"${1:-mac-ops}\"\n local indicator=\"${2:-}\"\n\n echo \"\"\n term_panel_open mac-ops \"mac-ops · $tag\" \"$indicator\"\n term_panel_vert\n term_summary_line \"$((PASS_COUNT+FAIL_COUNT+WARN_COUNT+INFO_COUNT)) checks · $FAIL_COUNT fail · $WARN_COUNT warn\"\n term_panel_vert\n\n # Render in order: fail, warn, pass (count only), info (count only)\n local order=(fail warn)\n local labels=(failing warning)\n local i\n for i in 0 1; do\n local st=\"${order[$i]}\"\n local label=\"${labels[$i]}\"\n local count=0\n local entries=()\n local entry\n for entry in ${MAC_PANEL_FINDINGS[@]+\"${MAC_PANEL_FINDINGS[@]}\"}; do\n [[ \"${entry%%|*}\" == \"$st\" ]] && { entries+=(\"$entry\"); count=$((count+1)); }\n done\n [[ \"$count\" -eq 0 ]] && continue\n term_section \"$st\" \"$label\" \"$count\"\n local n=${#entries[@]}\n local idx=0\n for entry in \"${entries[@]}\"; do\n local rest=\"${entry#*|}\"\n local section_name=\"${rest%%|*}\"; rest=\"${rest#*|}\"\n local lbl=\"${rest%%|*}\"; rest=\"${rest#*|}\"\n local detail=\"$rest\"\n local connector\n if [[ $((idx + 1)) -eq $n ]]; then connector=\"$TERM_TREE_LAST\"; else connector=\"$TERM_TREE_BRANCH\"; fi\n local sec_short=\"${section_name%% *}\"\n sec_short=\"${sec_short%.}\"\n local one_line\n if [[ -n \"$detail\" ]]; then\n one_line=\"$(printf '%-40s %s' \"$lbl\" \"$detail\")\"\n else\n one_line=\"$lbl\"\n fi\n # 100-char hard truncate\n (( ${#one_line} > 100 )) && one_line=\"${one_line:0:97}...\"\n printf '%s %s [%s] %s\\n' \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$connector\" \\\n \"$(term_color dim \"$sec_short\")\" \\\n \"$one_line\"\n done\n term_panel_vert\n idx=$((idx+1))\n done\n\n # Pass + info counts on a single line\n term_section \"ok\" \"pass\" \"$PASS_COUNT\"\n if [[ \"$INFO_COUNT\" -gt 0 ]]; then\n term_section \"info\" \"info\" \"$INFO_COUNT\"\n fi\n term_panel_vert\n\n # Determine overall health indicator\n local health_state=\"healthy\"\n [[ \"$WARN_COUNT\" -gt 0 ]] && health_state=\"warning\"\n [[ \"$FAIL_COUNT\" -gt 0 ]] && health_state=\"critical\"\n local right_health\n right_health=\"$(term_health \"$health_state\" \"$FAIL_COUNT fail · $WARN_COUNT warn\")\"\n term_panel_close \"\" \"$right_health\"\n echo \"\"\n\n # Also emit a plain SUMMARY line for parseability\n echo \" PASS: $PASS_COUNT FAIL: $FAIL_COUNT WARN: $WARN_COUNT INFO: $INFO_COUNT\"\n if [[ -n \"$FIRST_FAIL\" ]]; then\n echo \" First failure: $FIRST_FAIL\"\n fi\n}\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7954,"content_sha256":"d84a69075fae970e030052aa81ee68fbc6e81c7db1718fa628d66ca65133f038"},{"filename":"scripts/bluetooth-audit.sh","content":"#!/usr/bin/env bash\n# mac-ops :: bluetooth-audit.sh\n# Bluetooth state: paired devices, currently connected, recent connection\n# issues, the Bluetooth daemon state.\n#\n# Common Bluetooth pains on Mac: keyboard/mouse disconnects, AirPods audio\n# quality drops, Magic Mouse cursor stutter, HomePod handoff failures. The\n# log usually has the smoking gun.\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nReports:\n 1. Bluetooth power state + daemon health (bluetoothd)\n 2. Paired devices + connection state\n 3. Recent connection/disconnection events (24h)\n 4. Recent Bluetooth errors / faults\n 5. Wake-on-Bluetooth setting\n\nCommon fixes:\n - sudo pkill bluetoothd # daemon restart\n - sudo defaults delete ~/Library/Preferences/com.apple.Bluetooth.plist # reset (drastic)\n - Remove + re-pair the misbehaving device\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. BLUETOOTH POWER + DAEMON\"\n# ----------------------------------------------------------------------------\n# Use system_profiler — slow but reliable\nbt_summary=$(system_profiler SPBluetoothDataType 2>/dev/null)\nstate=$(echo \"$bt_summary\" | awk -F': *' '/State:/{print $2; exit}')\naddr=$(echo \"$bt_summary\" | awk -F': *' '/Address:/{print $2; exit}')\n\ncase \"$state\" in\n *On*) log_pass \"Bluetooth state\" \"On\" ;;\n *Off*) log_info \"Bluetooth state\" \"Off\" ;;\n *) log_info \"Bluetooth state\" \"${state:-unknown}\" ;;\nesac\n[[ -n \"$addr\" ]] && note \" Adapter address: $addr\"\n\n# bluetoothd process\nif pgrep -x bluetoothd >/dev/null; then\n pid=$(pgrep -x bluetoothd | head -1)\n cpu=$(ps -p \"$pid\" -o pcpu= 2>/dev/null | tr -d ' ')\n log_pass \"bluetoothd\" \"PID $pid (CPU ${cpu:-0}%)\"\nelse\n log_fail \"bluetoothd\" \"not running\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. PAIRED DEVICES\"\n# ----------------------------------------------------------------------------\n# Extract device blocks from system_profiler output\nnote \" Paired devices (name | connected | type):\"\necho \"$bt_summary\" | awk '\n /Address:/ && /[0-9A-F][0-9A-F]-/{\n # We are inside the device list (not the adapter section)\n device_name=prev_line\n }\n /Connected:/ && device_name {\n connected=$0; sub(/^[[:space:]]+Connected:[[:space:]]+/, \"\", connected)\n }\n /Minor Type:|Major Type:/ && device_name {\n type=$0; sub(/^[[:space:]]+[^:]+:[[:space:]]+/, \"\", type)\n }\n /^$/ && device_name && connected {\n printf \" %-35s %-10s %s\\n\", device_name, connected, (type ? type : \"?\")\n device_name=\"\"; connected=\"\"; type=\"\"\n }\n {prev_line=$0}\n' | head -20\n\n# ----------------------------------------------------------------------------\nsection \"3. RECENT CONNECTION EVENTS (24h)\"\n# ----------------------------------------------------------------------------\nconn_events=$(log show --last 24h --style compact \\\n --predicate '(subsystem == \"com.apple.bluetooth\" OR process == \"bluetoothd\") AND (eventMessage CONTAINS[c] \"connect\" OR eventMessage CONTAINS[c] \"disconnect\")' \\\n 2>/dev/null | grep -iE \"(connect|disconnect)\" | tail -15)\n\nif [[ -n \"$conn_events\" ]]; then\n n=$(echo \"$conn_events\" | wc -l | tr -d ' \\n')\n log_info \"Bluetooth connect/disconnect events (24h)\" \"$n\"\n note \" Recent (last 10):\"\n echo \"$conn_events\" | tail -10 | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. RECENT BLUETOOTH ERRORS\"\n# ----------------------------------------------------------------------------\nbt_errors=$(log show --last 24h --style compact \\\n --predicate '(subsystem == \"com.apple.bluetooth\" OR process == \"bluetoothd\") AND (messageType == \"Error\" OR messageType == \"Fault\")' \\\n 2>/dev/null | head -10)\n\nif [[ -n \"$bt_errors\" ]]; then\n n=$(echo \"$bt_errors\" | wc -l | tr -d ' \\n')\n log_warn \"Bluetooth error/fault events (24h)\" \"$n\"\n echo \"$bt_errors\" | head -5 | sed 's/^/ /'\nelse\n log_pass \"Bluetooth error/fault events (24h)\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. WAKE FOR BLUETOOTH\"\n# ----------------------------------------------------------------------------\n# \"Wake for Bluetooth devices\" — often the culprit for 3am wakes\nwake_setting=$(pmset -g | awk '/ttyskeepawake/{print $2; exit}')\nnote \" pmset settings (lookout for wake-for-bluetooth):\"\npmset -g 2>/dev/null | grep -iE \"bluetooth|womp|hidwake\" | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nsection \"6. BLUETOOTH PROCESS NAMES TO KNOW\"\n# ----------------------------------------------------------------------------\nnote \" Active BT-related processes:\"\nps -Ao pid,comm 2>/dev/null | grep -iE \"bluetoothd|BTSupport|BTLE|AirPort|AirDrop\" | head -10 | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5195,"content_sha256":"10f85942c212707a8864be8028cc95c0816f47140a4d255b51e8b356d00e67ac"},{"filename":"scripts/boot-perf.sh","content":"#!/usr/bin/env bash\n# mac-ops :: boot-perf.sh\n# Measure boot duration and identify slow startup components.\n# macOS records boot events in the unified log; we extract the markers.\n\nset -u\n\nDAYS=7\nSHOW_N=10\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --days) DAYS=\"$2\"; shift 2 ;;\n --show) SHOW_N=\"$2\"; shift 2 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --days N How many days of boot history to scan (default: 7)\n --show N How many recent boots to show (default: 10)\n --json, --redact, --quiet, --verbose\n\nHealthy:\n Apple Silicon Mac to login: 10-20s\n Intel Mac (SSD): 20-35s\n Intel Mac (HDD, vintage): 45-90s\n Failing storage: 60s+ with stalls\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. RECENT BOOT TIMES\"\n# ----------------------------------------------------------------------------\n# Approach: find each \"kernel boot\" marker, then compute time until loginwindow\n# completes its initial setup. The unified log has BOOT_TIME / \"kernel boot\"\n# markers as well as loginwindow setup messages.\nnote \" Scanning unified log for last ${DAYS}d of boot events...\"\n\n# macOS marks the kernel boot start with \"boot complete\" + \"boot session\" + \"first user event\"\n# We grep for kernel-version + UUID lines that mark a fresh boot.\nboots_raw=$(log show --last \"${DAYS}d\" --style compact --predicate \\\n 'process == \"kernel\" AND (eventMessage CONTAINS \"Darwin Kernel Version\" OR eventMessage CONTAINS \"boot args\")' \\\n 2>/dev/null | head -100)\n\nif [[ -z \"$boots_raw\" ]]; then\n log_info \"Boot events\" \"no boot markers found in window — try a wider --days N\"\nelse\n # Each fresh boot logs \"Darwin Kernel Version\" once; count them\n boot_count=$(echo \"$boots_raw\" | grep -c \"Darwin Kernel Version\" || echo 0)\n log_info \"Boots in last ${DAYS}d\" \"${boot_count:-0}\"\n note \" Recent boot markers (most recent ${SHOW_N}):\"\n echo \"$boots_raw\" | grep \"Darwin Kernel Version\" | tail -\"$SHOW_N\" | awk '{print $1, $2}' | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. CURRENT BOOT DURATION ESTIMATE\"\n# ----------------------------------------------------------------------------\n# Find the most recent boot marker (Darwin Kernel Version line)\nboot_start_line=$(log show --last \"${DAYS}d\" --style compact --predicate \\\n 'process == \"kernel\" AND eventMessage CONTAINS \"Darwin Kernel Version\"' \\\n 2>/dev/null | tail -1)\nboot_start_ts=$(echo \"$boot_start_line\" | awk '{print $1, $2}')\n\nif [[ -z \"$boot_start_ts\" ]]; then\n log_warn \"Boot start timestamp\" \"could not extract\"\nelse\n note \" Boot start: $boot_start_ts\"\n # Find first WindowServer / loginwindow ready event AFTER boot\n if loginwindow_evt=$(log show --start \"$boot_start_ts\" --style compact 2>/dev/null \\\n | grep -E \"(loginwindow.*started|WindowServer.*started|opendirectoryd started)\" \\\n | head -3); then\n note \" Earliest user-space events after boot:\"\n echo \"$loginwindow_evt\" | sed 's/^/ /'\n fi\n\n # Attempt to compute seconds from boot to loginwindow\n first_user_event=$(echo \"$loginwindow_evt\" | head -1 | awk '{print $1, $2}')\n if [[ -n \"$first_user_event\" ]] && command -v gdate >/dev/null 2>&1; then\n b=$(gdate -d \"$boot_start_ts\" +%s 2>/dev/null)\n f=$(gdate -d \"$first_user_event\" +%s 2>/dev/null)\n if [[ -n \"$b\" ]] && [[ -n \"$f\" ]]; then\n diff=$((f - b))\n log_info \"Boot duration (boot → user-space)\" \"${diff}s\"\n fi\n else\n log_info \"Boot duration calc\" \"install coreutils (brew install coreutils) for gdate-based timing\"\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. SLOW LAUNCH AGENTS\"\n# ----------------------------------------------------------------------------\n# Find agents that took long to start. Narrow to launchd messages specifically;\n# avoid matching Wi-Fi/airportd \"throttled=0\" noise.\nslow_events=$(log show --last \"${DAYS}d\" --style compact --predicate \\\n 'process == \"launchd\" AND (eventMessage CONTAINS \"took longer than\" OR eventMessage CONTAINS \"throttled by\" OR eventMessage CONTAINS \"exited with abnormal code\")' \\\n 2>/dev/null | head -20)\n\nif [[ -z \"$slow_events\" ]]; then\n log_pass \"Slow launchd events\" \"none found\"\nelse\n n=$(echo \"$slow_events\" | wc -l | tr -d ' ')\n log_warn \"Slow launchd events\" \"$n events — see below\"\n echo \"$slow_events\" | head -10 | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. LOGINWINDOW DELAYS\"\n# ----------------------------------------------------------------------------\n# loginwindow logs assertions about slow login items\nloginwindow_delays=$(log show --last \"${DAYS}d\" --style compact \\\n --predicate 'process == \"loginwindow\"' 2>/dev/null \\\n | grep -iE \"(delay|slow|timed out|waited)\" \\\n | head -10)\n\nif [[ -n \"$loginwindow_delays\" ]]; then\n log_warn \"loginwindow delay messages\" \"see below\"\n echo \"$loginwindow_delays\" | sed 's/^/ /'\nelse\n log_pass \"loginwindow delays\" \"none reported\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. SAFE-BOOT / VERBOSE-BOOT INDICATORS\"\n# ----------------------------------------------------------------------------\n# nvram for boot args\nboot_args=$(nvram boot-args 2>/dev/null | awk '{print $2}')\nif [[ \"$boot_args\" == *\"-v\"* ]] || [[ \"$boot_args\" == *\"-x\"* ]]; then\n log_warn \"NVRAM boot-args\" \"$boot_args — non-default boot mode\"\nelse\n log_pass \"NVRAM boot-args\" \"default (${boot_args:-empty})\"\nfi\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5947,"content_sha256":"34ddb92d5c0cbdea2bdd3ece9bceda4999804d428fde27683617031b9f00938c"},{"filename":"scripts/brew-health.sh","content":"#!/usr/bin/env bash\n# mac-ops :: brew-health.sh\n# Homebrew state audit. Most Mac developers have brew installed; outdated\n# packages, broken casks, and brew doctor warnings are a frequent silent\n# cause of \"this dev tool stopped working\".\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nReports:\n 1. brew + paths sanity (Intel /usr/local vs Apple Silicon /opt/homebrew)\n 2. brew doctor summary\n 3. Outdated formulae + casks\n 4. Cleanup opportunities (caches, deprecated)\n 5. Pinned formulae (held back on purpose vs by accident)\n 6. brew services state\n 7. Tap inventory\n\nIf brew isn't installed, the script exits cleanly with [INFO] markers.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. BREW INSTALLATION\"\n# ----------------------------------------------------------------------------\nif ! command -v brew >/dev/null 2>&1; then\n log_info \"Homebrew\" \"not installed on this Mac\"\n emit_summary\n exit 0\nfi\n\nbrew_path=$(command -v brew)\nbrew_prefix=$(brew --prefix 2>/dev/null)\nbrew_version=$(brew --version 2>/dev/null | head -1)\nlog_pass \"Homebrew\" \"$brew_version\"\nnote \" brew: $brew_path\"\nnote \" prefix: $brew_prefix\"\n\n# Architecture sanity\nif is_apple_silicon; then\n if [[ \"$brew_prefix\" == \"/opt/homebrew\"* ]]; then\n log_pass \"Architecture match\" \"Apple Silicon + native /opt/homebrew\"\n elif [[ \"$brew_prefix\" == \"/usr/local\"* ]]; then\n log_warn \"Architecture mismatch\" \"Apple Silicon Mac running x86_64 brew via Rosetta\"\n fi\nelse\n if [[ \"$brew_prefix\" == \"/usr/local\"* ]]; then\n log_pass \"Architecture match\" \"Intel + /usr/local\"\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. BREW DOCTOR\"\n# ----------------------------------------------------------------------------\ndoctor_out=$(brew doctor 2>&1)\nif echo \"$doctor_out\" | grep -q \"Your system is ready to brew\"; then\n log_pass \"brew doctor\" \"Your system is ready to brew\"\nelse\n warnings=$(echo \"$doctor_out\" | grep -c \"^Warning:\" || echo 0)\n log_warn \"brew doctor\" \"$warnings warning(s) — see below\"\n note \" Top doctor messages (first 15 lines):\"\n echo \"$doctor_out\" | head -15 | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. OUTDATED PACKAGES\"\n# ----------------------------------------------------------------------------\nformulae_out=$(brew outdated --formula 2>/dev/null)\nformulae_count=$(echo \"$formulae_out\" | grep -c . 2>/dev/null || echo 0)\ncasks_out=$(brew outdated --cask 2>/dev/null)\ncasks_count=$(echo \"$casks_out\" | grep -c . 2>/dev/null || echo 0)\n\nif [[ \"$formulae_count\" -gt 0 ]]; then\n log_info \"Outdated formulae\" \"$formulae_count\"\n echo \"$formulae_out\" | head -10 | sed 's/^/ /'\nelse\n log_pass \"Outdated formulae\" \"0\"\nfi\n\nif [[ \"$casks_count\" -gt 0 ]]; then\n log_info \"Outdated casks\" \"$casks_count\"\n echo \"$casks_out\" | head -10 | sed 's/^/ /'\nelse\n log_pass \"Outdated casks\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. CLEANUP OPPORTUNITIES\"\n# ----------------------------------------------------------------------------\n# brew cleanup --dry-run reports what would be removed\ncleanup_dry=$(brew cleanup --dry-run 2>/dev/null | tail -5)\nif [[ -n \"$cleanup_dry\" ]]; then\n note \" brew cleanup --dry-run (last 5 lines):\"\n echo \"$cleanup_dry\" | sed 's/^/ /'\nfi\n\n# Cache size\ncache_dir=$(brew --cache 2>/dev/null)\nif [[ -d \"$cache_dir\" ]]; then\n cache_size=$(du -sh \"$cache_dir\" 2>/dev/null | awk '{print $1}')\n log_info \"Brew cache size\" \"${cache_size:-?}\"\nfi\n\n# Deprecated/abandoned packages\ndeprecated=$(brew list --formula 2>/dev/null | while read -r f; do\n if brew info --json=v1 \"$f\" 2>/dev/null | grep -q '\"deprecated\":true\\|\"disabled\":true'; then\n echo \"$f\"\n fi\ndone | head -10)\nif [[ -n \"$deprecated\" ]]; then\n n=$(echo \"$deprecated\" | wc -l | tr -d ' ')\n log_warn \"Deprecated/disabled formulae installed\" \"$n\"\n echo \"$deprecated\" | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. PINNED FORMULAE\"\n# ----------------------------------------------------------------------------\npinned=$(brew list --pinned 2>/dev/null)\nif [[ -n \"$pinned\" ]]; then\n n=$(echo \"$pinned\" | wc -l | tr -d ' ')\n log_info \"Pinned formulae\" \"$n\"\n echo \"$pinned\" | sed 's/^/ /'\n note \"\"\n note \" Pinned packages don't get upgraded by 'brew upgrade'. Unpin:\"\n note \" brew unpin \u003cname>\"\nelse\n log_pass \"Pinned formulae\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"6. BREW SERVICES\"\n# ----------------------------------------------------------------------------\nif command -v brew >/dev/null 2>&1 && brew help services >/dev/null 2>&1; then\n services_out=$(brew services list 2>/dev/null | tail -n +2)\n if [[ -n \"$services_out\" ]]; then\n running=$(echo \"$services_out\" | awk '$2==\"started\"' | wc -l | tr -d ' ')\n total=$(echo \"$services_out\" | wc -l | tr -d ' ')\n log_info \"Brew services\" \"$running running of $total\"\n echo \"$services_out\" | sed 's/^/ /' | head -15\n else\n log_pass \"Brew services\" \"none configured\"\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"7. TAPS\"\n# ----------------------------------------------------------------------------\ntaps=$(brew tap 2>/dev/null)\ntap_count=$(echo \"$taps\" | grep -c . 2>/dev/null || echo 0)\nlog_info \"Brew taps\" \"$tap_count\"\necho \"$taps\" | head -10 | sed 's/^/ /'\n\n# Third-party taps (not homebrew/*)\nthird_party_taps=$(echo \"$taps\" | grep -v \"^homebrew/\" || true)\nif [[ -n \"$third_party_taps\" ]]; then\n n=$(echo \"$third_party_taps\" | wc -l | tr -d ' ')\n note \"\"\n note \" Third-party taps ($n) — these are external trust:\"\n echo \"$third_party_taps\" | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n note \" Quick cleanup playbook:\"\n note \" brew update && brew upgrade # update everything\"\n note \" brew cleanup -s # remove old versions + caches\"\n note \" brew autoremove # remove orphaned dependencies\"\n note \" brew doctor # full doctor scan\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6709,"content_sha256":"81a42b2fdda18365487cbe14d5766a38f41adf74a1fe8d3ebc72658fbf6d1d27"},{"filename":"scripts/disk-health.sh","content":"#!/usr/bin/env bash\n# mac-ops :: disk-health.sh\n# Per-disk / per-volume deep dive. Maps APFS containers, surfaces IO errors\n# from the unified log, reports SMART status (where macOS exposes it), and\n# checks snapshot bloat.\n#\n# Usage:\n# scripts/disk-health.sh # all disks\n# scripts/disk-health.sh -d disk2 # by /dev/diskN\n# scripts/disk-health.sh -v /Volumes/Foo # by mount point\n# scripts/disk-health.sh -v / # boot volume\n\nset -u\n\nTARGET_DEV=\"\"\nTARGET_VOL=\"\"\nDAYS=30\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -d|--disk) TARGET_DEV=\"$2\"; shift 2 ;;\n -v|--volume) TARGET_VOL=\"$2\"; shift 2 ;;\n --days) DAYS=\"$2\"; shift 2 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n -d, --disk diskN Inspect specific device (e.g. disk2)\n -v, --volume PATH Inspect by mount point (e.g. / or /Volumes/X)\n --days N Log lookback window (default: 30)\n --json, --redact, --quiet, --verbose Standard flags\n\nOutput sections:\n 1. Device summary (model, size, bus, SMART status)\n 2. APFS container + volume layout (if APFS)\n 3. IO errors via unified log (last --days)\n 4. Snapshot bloat (Time Machine local snapshots)\n 5. Free space / purgeable space breakdown\n 6. Mount + fsck verification status\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# Resolve target → /dev/diskN\nresolve_target() {\n if [[ -n \"$TARGET_DEV\" ]]; then\n echo \"${TARGET_DEV#/dev/}\"\n return\n fi\n if [[ -n \"$TARGET_VOL\" ]]; then\n diskutil info \"$TARGET_VOL\" 2>/dev/null | awk -F': *' '/Device Identifier/{print $2; exit}'\n return\n fi\n # No target — return empty (we'll iterate all)\n echo \"\"\n}\n\ndisk_id=$(resolve_target)\n\n# ----------------------------------------------------------------------------\nsection \"1. DEVICE SUMMARY\"\n# ----------------------------------------------------------------------------\nif [[ -n \"$disk_id\" ]]; then\n targets=(\"$disk_id\")\nelse\n # All physical disks (not partitions / synthesized)\n mapfile -t targets \u003c \u003c(diskutil list 2>/dev/null | awk '/^\\/dev\\/disk[0-9]+ /{gsub(\"/dev/\",\"\"); print $1}' | sort -u | head -20)\nfi\n\nfor d in \"${targets[@]}\"; do\n [[ -z \"$d\" ]] && continue\n info=$(diskutil info \"$d\" 2>/dev/null)\n [[ -z \"$info\" ]] && { log_warn \"diskutil info $d\" \"no data\"; continue; }\n\n model=$(echo \"$info\" | awk -F': *' '/Device \\/ Media Name/{print $2; exit}')\n bus=$(echo \"$info\" | awk -F': *' '/Protocol/{print $2; exit}')\n size=$(echo \"$info\" | awk -F': *' '/Disk Size/{print $2; exit}')\n smart=$(echo \"$info\" | awk -F': *' '/SMART Status/{print $2; exit}')\n internal=$(echo \"$info\" | awk -F': *' '/Device Location/{print $2; exit}')\n\n note \" /dev/$d\"\n note \" Model: ${model:-(unknown)}\"\n note \" Bus: ${bus:-?} Location: ${internal:-?}\"\n note \" Size: ${size:-?}\"\n\n case \"$smart\" in\n Verified)\n log_pass \"/dev/$d SMART status\" \"Verified\" ;;\n Failing|Failed)\n log_fail \"/dev/$d SMART status\" \"$smart — back up immediately, do not write to drive\" ;;\n \"Not Supported\"|\"\")\n log_info \"/dev/$d SMART status\" \"${smart:-(not exposed; macOS limitation for many NVMe drives)}\" ;;\n *)\n log_warn \"/dev/$d SMART status\" \"$smart\" ;;\n esac\ndone\n\n# ----------------------------------------------------------------------------\nsection \"2. APFS CONTAINERS + VOLUMES\"\n# ----------------------------------------------------------------------------\nif [[ -n \"$disk_id\" ]]; then\n diskutil apfs list \"$disk_id\" 2>/dev/null | sed 's/^/ /' | head -60\nelse\n diskutil apfs list 2>/dev/null | sed 's/^/ /' | head -80\nfi\n\n# Volumes per target (with free space)\nnote \"\"\nnote \" Mounted APFS volumes:\"\ndf -h | awk 'NR==1 || /\\/dev\\/disk.* apfs|\\/dev\\/disk.*\\/Volumes/{print \" \" $0}' | head -12\n\n# ----------------------------------------------------------------------------\nsection \"3. IO ERRORS (unified log, last ${DAYS}d)\"\n# ----------------------------------------------------------------------------\nio_lines=$(log show --last \"${DAYS}d\" --style compact \\\n --predicate '(subsystem == \"com.apple.iokit\" OR subsystem == \"com.apple.kernel\") AND (eventMessage CONTAINS[c] \"I/O error\" OR eventMessage CONTAINS[c] \"media error\" OR eventMessage CONTAINS[c] \"MEDIA_ERROR\" OR eventMessage CONTAINS[c] \"device timeout\")' \\\n 2>/dev/null)\nio_count=$(echo \"$io_lines\" | grep -c . || echo 0)\n\nif [[ \"$io_count\" -gt 50 ]]; then\n log_fail \"IO errors in log\" \"$io_count events — active failure\"\n note \" Sample (first 5):\"\n echo \"$io_lines\" | head -5 | sed 's/^/ /'\nelif [[ \"$io_count\" -gt 5 ]]; then\n log_warn \"IO errors in log\" \"$io_count events\"\n note \" Sample (first 3):\"\n echo \"$io_lines\" | head -3 | sed 's/^/ /'\nelif [[ \"$io_count\" -gt 0 ]]; then\n log_info \"IO errors in log\" \"$io_count events (occasional events normal)\"\nelse\n log_pass \"IO errors in log\" \"0\"\nfi\n\n# APFS-specific corruption signal\napfs_errors=$(log show --last \"${DAYS}d\" --style compact \\\n --predicate 'eventMessage CONTAINS \"apfs\" AND (messageType == \"Error\" OR messageType == \"Fault\")' \\\n 2>/dev/null | wc -l | tr -d ' ')\nif [[ \"$apfs_errors\" -gt 10 ]]; then\n log_warn \"APFS error/fault events\" \"$apfs_errors\"\nelse\n log_pass \"APFS error/fault events\" \"$apfs_errors\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. APFS SNAPSHOT BLOAT\"\n# ----------------------------------------------------------------------------\n# Per-volume snapshot count\nmount | awk '/apfs/{print $3}' | while read -r mnt; do\n [[ -z \"$mnt\" ]] && continue\n snap_count=$(tmutil listlocalsnapshots \"$mnt\" 2>/dev/null | grep -c \"com.apple\" | tr -d ' \\n')\n snap_count=\"${snap_count:-0}\"\n if (( snap_count > 20 )); then\n log_warn \"Snapshots on $mnt\" \"$snap_count — purgeable space tied up\"\n elif (( snap_count > 0 )); then\n log_info \"Snapshots on $mnt\" \"$snap_count\"\n else\n log_pass \"Snapshots on $mnt\" \"0\"\n fi\ndone\n\n# ----------------------------------------------------------------------------\nsection \"5. FREE SPACE / PURGEABLE BREAKDOWN\"\n# ----------------------------------------------------------------------------\nif [[ -n \"$TARGET_VOL\" ]]; then\n volumes=(\"$TARGET_VOL\")\nelse\n mapfile -t volumes \u003c \u003c(mount | awk '/apfs/{print $3}' | head -6)\nfi\n\nfor v in \"${volumes[@]}\"; do\n [[ -d \"$v\" ]] || continue\n df_line=$(df -h \"$v\" 2>/dev/null | tail -1)\n free_pct=$(echo \"$df_line\" | awk '{gsub(\"%\",\"\",$5); print 100-$5}')\n free_gb=$(echo \"$df_line\" | awk '{print $4}')\n note \" $v: ${free_gb} free (${free_pct}%)\"\n if [[ \"$free_pct\" -lt 10 ]]; then\n log_warn \"Free space on $v\" \"${free_pct}% — low\"\n else\n log_pass \"Free space on $v\" \"${free_pct}%\"\n fi\n # Purgeable space from APFS (requires diskutil apfs)\n purgeable=$(diskutil apfs list 2>/dev/null | awk -v vol=\"$v\" '\n $0 ~ vol {found=1}\n found && /Capacity In Use/{print $NF; found=0; exit}\n ')\ndone\n\n# ----------------------------------------------------------------------------\nsection \"6. VOLUME VERIFICATION (read-only)\"\n# ----------------------------------------------------------------------------\n# Only verify the target if we have one; iterating all volumes is slow + noisy.\nif [[ -n \"$TARGET_VOL\" ]]; then\n verify_target=\"$TARGET_VOL\"\nelif [[ -n \"$disk_id\" ]]; then\n verify_target=\"$disk_id\"\nelse\n verify_target=\"\"\nfi\n\nif [[ -n \"$verify_target\" ]]; then\n note \" Running: diskutil verifyVolume $verify_target (read-only)\"\n if diskutil verifyVolume \"$verify_target\" 2>&1 | grep -q \"appears to be OK\"; then\n log_pass \"verifyVolume $verify_target\" \"OK\"\n else\n log_warn \"verifyVolume $verify_target\" \"did not return clean (may need sudo or already in use)\"\n fi\nelse\n note \" (skipped — pass -v or -d to verify a specific target)\"\nfi\n\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n note \" Drilldowns:\"\n note \" drive-dependencies.sh -v \u003cmount> # check what references a volume\"\n note \" storage-pressure.sh # snapshot bloat detail\"\n note \" recover-clone.sh # safely image data off a failing drive\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":8452,"content_sha256":"5bb7d843df1ae2d44e6aee4191a311ce78996df8dd0420151df8375f50a1ccc9"},{"filename":"scripts/drive-dependencies.sh","content":"#!/usr/bin/env bash\n# mac-ops :: drive-dependencies.sh\n# \"Is it safe to eject this volume?\" — find every reference to a volume\n# before you yank the cable / unmount / destroy a snapshot.\n#\n# Checks:\n# - Open files via lsof\n# - Spotlight index state\n# - Time Machine destination\n# - Photos / Music / TV library locations\n# - Helper-tool security-scoped bookmarks (best-effort)\n# - Symlinks pointing into the volume from common locations\n# - Background processes with cwd inside the volume\n\nset -u\n\nTARGET=\"\"\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -v|--volume) TARGET=\"$2\"; shift 2 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 -v \u003cmount-point> [options]\n\n -v, --volume PATH Volume to check (e.g. /Volumes/Backup, /)\n --json, --redact, --quiet, --verbose Standard flags\n\nVerdict: \"safe to eject\" requires PASS on every check. Any FAIL/WARN means\nsomething would break or lose state on disconnect.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nif [[ -z \"$TARGET\" ]]; then\n echo \"Error: -v \u003cmount-point> required (e.g. -v /Volumes/Backup)\" >&2\n exit 2\nfi\n\nif [[ ! -d \"$TARGET\" ]]; then\n echo \"Error: $TARGET is not a directory / not mounted\" >&2\n exit 3\nfi\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\nnote \" Volume: $TARGET\"\n\n# ----------------------------------------------------------------------------\nsection \"1. OPEN FILES (lsof)\"\n# ----------------------------------------------------------------------------\n# `lsof +D` is recursive and VERY slow on large volumes (especially $HOME).\n# Use `lsof` without +D and grep by mount point — much faster, equivalent\n# accuracy for \"is anything open under this path\".\ntarget_real=$(cd \"$TARGET\" 2>/dev/null && pwd -P || echo \"$TARGET\")\nopen_lines=$(lsof -F n 2>/dev/null | grep \"^n${target_real}/\" 2>/dev/null || true)\nopen_count=$(printf '%s\\n' \"$open_lines\" | grep -c . 2>/dev/null || echo 0)\nif [[ \"$open_count\" -gt 0 ]]; then\n log_fail \"Open file handles\" \"$open_count — unmount will fail or corrupt\"\n note \" Top processes holding files (sample):\"\n # lsof -F format is column-based; use plain lsof for the process listing\n lsof 2>/dev/null | awk -v t=\"$target_real\" '$NF ~ \"^\"t\"/\"{print $1, $2}' | sort -u | head -10 | sed 's/^/ /'\nelse\n log_pass \"Open file handles\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. PROCESSES WITH CWD INSIDE VOLUME\"\n# ----------------------------------------------------------------------------\n# Use lsof -c with -d cwd for current working directories\ncwd_procs=$(lsof -d cwd 2>/dev/null | awk -v t=\"$TARGET\" '$NF ~ t {print $1, $2}' | sort -u)\nif [[ -n \"$cwd_procs\" ]]; then\n cwd_count=$(echo \"$cwd_procs\" | wc -l | tr -d ' ')\n log_warn \"Processes with cwd inside\" \"$cwd_count\"\n echo \"$cwd_procs\" | head -5 | sed 's/^/ /'\nelse\n log_pass \"Processes with cwd inside\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. SPOTLIGHT INDEX STATE\"\n# ----------------------------------------------------------------------------\nspotlight_status=$(mdutil -s \"$TARGET\" 2>/dev/null | tail -1 | sed 's/^[[:space:]]*//')\nnote \" $spotlight_status\"\ncase \"$spotlight_status\" in\n *\"Indexing enabled\"*) log_warn \"Spotlight indexing\" \"enabled on this volume — eject may corrupt index\" ;;\n *\"Indexing disabled\"*) log_pass \"Spotlight indexing\" \"disabled\" ;;\n *\"unknown\"*) log_pass \"Spotlight indexing\" \"(no user index — system or read-only volume)\" ;;\n *) log_info \"Spotlight indexing\" \"${spotlight_status:-(no response)}\" ;;\nesac\n\n# ----------------------------------------------------------------------------\nsection \"4. TIME MACHINE DESTINATION CHECK\"\n# ----------------------------------------------------------------------------\ntm_dest=$(tmutil destinationinfo 2>/dev/null | awk -F': *' '/Mount Point/{print $2}')\n# Empty tm_dest matches /tmp via prefix logic if not careful; require non-empty + exact prefix\nif [[ -n \"$tm_dest\" ]] && { [[ \"$tm_dest\" == \"$TARGET\" ]] || [[ \"$TARGET\" == \"$tm_dest\"/* ]]; }; then\n log_fail \"Time Machine destination\" \"this volume IS the TM target — eject will fail current/next backup\"\nelif [[ -n \"$tm_dest\" ]]; then\n log_pass \"Time Machine destination\" \"different volume ($tm_dest)\"\nelse\n log_pass \"Time Machine destination\" \"none configured\"\nfi\n\n# Recent TM activity touching this volume\ntm_active=$(tmutil currentphase 2>/dev/null)\nif [[ \"$tm_active\" != \"BackupNotRunning\" ]] && [[ -n \"$tm_active\" ]]; then\n log_warn \"Time Machine current phase\" \"$tm_active — wait before eject\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. MEDIA LIBRARY LOCATIONS\"\n# ----------------------------------------------------------------------------\n# Photos library\nphotos_lib=$(defaults read com.apple.Photos UserLibrarySelectionMethod 2>/dev/null || true)\n# Best-effort: check common Photos library paths under this volume\nphotos_libs=$(find \"$TARGET\" -maxdepth 3 -name \"Photos Library.photoslibrary\" -type d 2>/dev/null | head -3)\nif [[ -n \"$photos_libs\" ]]; then\n log_warn \"Photos library detected on volume\" \"$(echo \"$photos_libs\" | head -1)\"\nfi\n\n# Music library\nmusic_libs=$(find \"$TARGET\" -maxdepth 3 -name \"*.musiclibrary\" -type d 2>/dev/null | head -3)\nif [[ -n \"$music_libs\" ]]; then\n log_warn \"Music library detected on volume\" \"$(echo \"$music_libs\" | head -1)\"\nfi\n\n# Final Cut / Logic / iMovie libraries\nfcp_libs=$(find \"$TARGET\" -maxdepth 3 \\( -name \"*.fcpbundle\" -o -name \"*.logicx\" -o -name \"*.imovielibrary\" \\) -type d 2>/dev/null | head -3)\nif [[ -n \"$fcp_libs\" ]]; then\n log_warn \"Pro app library detected on volume\" \"$(echo \"$fcp_libs\" | head -1)\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"6. SYMLINKS POINTING INTO VOLUME\"\n# ----------------------------------------------------------------------------\n# Common places where symlinks land\ndeclare -a check_dirs=(\n \"$HOME/Documents\"\n \"$HOME/Desktop\"\n \"$HOME/Movies\"\n \"$HOME/Music\"\n \"$HOME/Pictures\"\n \"$HOME/Library/Mobile Documents\"\n)\nsymlink_count=0\nfor d in \"${check_dirs[@]}\"; do\n [[ -d \"$d\" ]] || continue\n found=$(find \"$d\" -maxdepth 2 -type l 2>/dev/null | while read -r link; do\n dest=$(readlink \"$link\")\n [[ \"$dest\" == \"$TARGET\"/* ]] && echo \"$link -> $dest\"\n done)\n if [[ -n \"$found\" ]]; then\n n=$(echo \"$found\" | wc -l | tr -d ' ')\n symlink_count=$((symlink_count + n))\n echo \"$found\" | head -3 | sed 's/^/ /'\n fi\ndone\n\nif [[ \"$symlink_count\" -gt 0 ]]; then\n log_warn \"Symlinks pointing into volume\" \"$symlink_count — they'll dangle on eject\"\nelse\n log_pass \"Symlinks pointing into volume\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"7. PRIVILEGED HELPER / LAUNCH ITEMS REFERENCING VOLUME\"\n# ----------------------------------------------------------------------------\n# Grep launchd plists for paths inside the target\nhelper_refs=0\nfor d in \"$HOME/Library/LaunchAgents\" /Library/LaunchAgents /Library/LaunchDaemons; do\n [[ -d \"$d\" ]] || continue\n matches=$(grep -l \"$TARGET\" \"$d\"/*.plist 2>/dev/null || true)\n if [[ -n \"$matches\" ]]; then\n helper_refs=$((helper_refs + $(echo \"$matches\" | wc -l | tr -d ' ')))\n echo \"$matches\" | head -3 | sed 's|^| |'\n fi\ndone\n\nif [[ \"$helper_refs\" -gt 0 ]]; then\n log_warn \"Launchd plists referencing volume\" \"$helper_refs — daemons will fail on eject\"\nelse\n log_pass \"Launchd plists referencing volume\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"8. APP BOOKMARKS / RECENTS\"\n# ----------------------------------------------------------------------------\n# Sandboxed apps store security-scoped bookmarks; we can't decode them without\n# the app, but we can list which apps have recents pointing at this volume.\nnote \" (App security-scoped bookmarks aren't directly inspectable — this is informational)\"\n\n# ----------------------------------------------------------------------------\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n if [[ \"$FAIL_COUNT\" -eq 0 ]] && [[ \"$WARN_COUNT\" -eq 0 ]]; then\n echo \" ✓ Safe to eject $TARGET — no system references detected.\"\n elif [[ \"$FAIL_COUNT\" -gt 0 ]]; then\n echo \" ✗ NOT safe to eject $TARGET — eject will fail or break the items above.\"\n else\n echo \" ⚠ Ejecting will work, but the items above will dangle or stop working until remount.\"\n fi\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":8656,"content_sha256":"b03ac3408289640347fdaa724810ed973060bd87e9db6ad034eeecdd977e7df4"},{"filename":"scripts/firewall-audit.sh","content":"#!/usr/bin/env bash\n# mac-ops :: firewall-audit.sh\n# Inventory macOS firewall state across all layers:\n# 1. Application Layer Firewall (ALF) — System Settings → Network → Firewall\n# 2. Packet Filter (pf) — BSD-level packet filtering\n# 3. Network Extension content filters (Little Snitch, Lulu, Cisco AnyConnect, etc.)\n# 4. Stealth mode + logging state\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nmacOS firewall stack:\n ALF (socketfilterfw) Application-layer firewall — blocks incoming\n connections per-app. Visible in System Settings.\n pf (packet filter) BSD-style packet filtering. Configured via\n /etc/pf.conf and /etc/pf.anchors/. Usually\n inactive on desktop Macs.\n Network Extension filters Third-party (Little Snitch, Lulu, AnyConnect)\n implement custom filtering as content filters.\n Persist after app quit until disabled.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\nALF=\"/usr/libexec/ApplicationFirewall/socketfilterfw\"\n\n# ----------------------------------------------------------------------------\nsection \"1. APPLICATION LAYER FIREWALL (ALF)\"\n# ----------------------------------------------------------------------------\nif [[ ! -x \"$ALF\" ]]; then\n log_warn \"socketfilterfw binary\" \"not found at $ALF\"\nelse\n state=$(\"$ALF\" --getglobalstate 2>/dev/null | tail -1)\n case \"$state\" in\n *\"enabled\"*) log_pass \"ALF state\" \"$state\" ;;\n *\"disabled\"*) log_warn \"ALF state\" \"$state — incoming connections unblocked\" ;;\n *) log_info \"ALF state\" \"$state\" ;;\n esac\n\n stealth=$(\"$ALF\" --getstealthmode 2>/dev/null | tail -1)\n note \" $stealth\"\n\n block_all=$(\"$ALF\" --getblockall 2>/dev/null | tail -1)\n note \" $block_all\"\n\n allow_signed=$(\"$ALF\" --getallowsigned 2>/dev/null | tail -1)\n note \" $allow_signed\"\n\n # Per-app rules (may require sudo)\n rules=$(\"$ALF\" --listapps 2>/dev/null | grep -c \"^[0-9]\" || echo 0)\n log_info \"ALF per-app rules\" \"$rules\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. PACKET FILTER (pf)\"\n# ----------------------------------------------------------------------------\nif pf_info=$(pfctl -s info 2>&1); then\n if echo \"$pf_info\" | grep -q \"Status: Enabled\"; then\n log_warn \"pf state\" \"Enabled — packet filter active\"\n note \" $(echo \"$pf_info\" | head -3 | sed 's/^/ /')\"\n else\n log_pass \"pf state\" \"Disabled (default for desktop Macs)\"\n fi\nelse\n log_info \"pf state\" \"could not query (needs sudo)\"\nfi\n\n# Anchors loaded\nif pf_anchors=$(sudo -n pfctl -s Anchors 2>/dev/null); then\n if [[ -n \"$pf_anchors\" ]]; then\n log_info \"pf anchors loaded\" \"$(echo \"$pf_anchors\" | wc -l | tr -d ' ')\"\n echo \"$pf_anchors\" | head -10 | sed 's/^/ /'\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. NETWORK EXTENSION CONTENT FILTERS\"\n# ----------------------------------------------------------------------------\n# These are third-party filters that operate in their own NetworkExtension\n# rather than via ALF. They persist after the parent app quits.\nne_filters=$(scutil --nc list 2>/dev/null | grep -iE \"filter|firewall\" || true)\nif [[ -n \"$ne_filters\" ]]; then\n log_info \"Network Extension content filters\" \"$(echo \"$ne_filters\" | wc -l | tr -d ' ')\"\n echo \"$ne_filters\" | sed 's/^/ /'\nelse\n log_pass \"Network Extension content filters\" \"none configured\"\nfi\n\n# Check for common third-party firewall apps\nnote \"\"\nnote \" Installed firewall/network-monitoring apps:\"\nfor app in \"Little Snitch\" \"LuLu\" \"Murus\" \"Hands Off\" \"Radio Silence\" \"NetIQuette\"; do\n if [[ -e \"/Applications/$app.app\" ]]; then\n note \" /Applications/$app.app\"\n fi\ndone\n\n# ----------------------------------------------------------------------------\nsection \"4. FIREWALL LOG SAMPLE\"\n# ----------------------------------------------------------------------------\n# Anything ALF dropped in last hour\nrecent_blocks=$(log show --last 1h --style compact \\\n --predicate 'process == \"socketfilterfw\" OR eventMessage CONTAINS \"Deny\"' \\\n 2>/dev/null | grep -iE \"(deny|block|drop)\" | tail -5)\nif [[ -n \"$recent_blocks\" ]]; then\n log_info \"Recent firewall denials (1h)\" \"see below\"\n echo \"$recent_blocks\" | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. VPN / TUNNEL CONFIGURATION\"\n# ----------------------------------------------------------------------------\nnote \" Active network services (VPN/tunnel filter):\"\nnetworksetup -listallnetworkservices 2>/dev/null | tail -n +2 | \\\n grep -iE \"vpn|tunnel|wireguard|openvpn|warp|nextdns|tailscale|cisco|anyconnect|proton|mullvad\" | \\\n sed 's/^/ /'\n\n# Active tunnels right now\nnote \"\"\nnote \" Active utun interfaces:\"\nifconfig 2>/dev/null | awk '/^utun[0-9]+:/{ifn=$1; sub(\":\",\"\",ifn)} /inet[6]? /{if(ifn!=\"\" && $1!~/^fe80/){print \" \"ifn\": \"$0; ifn=\"\"}}' | head -10\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5409,"content_sha256":"236c51d50e3e5ef572b2181ba7ccbd31b3eaef7a51296478a38e6bce4e6212e6"},{"filename":"scripts/font-audit.sh","content":"#!/usr/bin/env bash\n# mac-ops :: font-audit.sh\n# Font inventory + duplicate detection. Font conflicts cause real problems —\n# Office crashes, Adobe app crashes, font rendering glitches, login slowness\n# (Font Book validates ALL fonts at login if registry is corrupt).\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nReports:\n 1. Font directories + counts (system, user, library, fontd cache)\n 2. fontd process state + recent errors\n 3. Duplicate fonts (same family in multiple locations)\n 4. Disabled fonts in Font Book\n 5. Suspicious / corrupt font files\n\nCommon fixes:\n - Open Font Book → File → Validate fonts → resolve duplicates\n - sudo atsutil databases -remove # clear font cache, reboot\n - sudo atsutil server -shutdown # restart font server\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. FONT DIRECTORIES\"\n# ----------------------------------------------------------------------------\ndeclare -a font_dirs=(\n \"/System/Library/Fonts\"\n \"/Library/Fonts\"\n \"$HOME/Library/Fonts\"\n)\n\nfor d in \"${font_dirs[@]}\"; do\n if [[ -d \"$d\" ]]; then\n count=$(find \"$d\" -maxdepth 2 -type f \\( -name \"*.ttf\" -o -name \"*.otf\" -o -name \"*.ttc\" -o -name \"*.dfont\" \\) 2>/dev/null | wc -l | tr -d ' \\n')\n size=$(du -sh \"$d\" 2>/dev/null | awk '{print $1}')\n log_info \"$d\" \"$count fonts, $size\"\n fi\ndone\n\n# Adobe / third-party font cache directories\nadobe_fonts=$(find /Users -maxdepth 5 -path \"*/Library/Application Support/Adobe/CoreSync/plugins/livetype/r\" -type d 2>/dev/null | head -3)\nif [[ -n \"$adobe_fonts\" ]]; then\n while IFS= read -r d; do\n count=$(find \"$d\" -maxdepth 2 -type f \\( -name \"*.ttf\" -o -name \"*.otf\" \\) 2>/dev/null | wc -l | tr -d ' \\n')\n note \" $d ($count Adobe Fonts)\"\n done \u003c\u003c\u003c \"$adobe_fonts\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. fontd PROCESS\"\n# ----------------------------------------------------------------------------\nif pgrep -x fontd >/dev/null; then\n pid=$(pgrep -x fontd | head -1)\n cpu=$(ps -p \"$pid\" -o pcpu= 2>/dev/null | tr -d ' ')\n rss=$(ps -p \"$pid\" -o rss= 2>/dev/null | tr -d ' ')\n log_pass \"fontd running\" \"PID $pid (CPU ${cpu:-0}%, RSS ${rss:-?} KB)\"\nelse\n log_info \"fontd\" \"not running (Font Book may not be open)\"\nfi\n\n# Recent fontd errors\nfont_errors=$(log show --last 24h --style compact \\\n --predicate '(process == \"fontd\" OR process == \"FontRegistry\" OR eventMessage CONTAINS \"font\") AND (messageType == \"Error\" OR messageType == \"Fault\")' \\\n 2>/dev/null | grep -iE \"font\" | tail -5)\nif [[ -n \"$font_errors\" ]]; then\n n=$(echo \"$font_errors\" | wc -l | tr -d ' \\n')\n log_warn \"Font errors in log (24h)\" \"$n\"\n echo \"$font_errors\" | head -3 | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. DUPLICATE FONTS\"\n# ----------------------------------------------------------------------------\n# Find filename collisions across all font dirs\nall_fonts=$(for d in \"${font_dirs[@]}\"; do\n [[ -d \"$d\" ]] && find \"$d\" -maxdepth 2 -type f \\( -name \"*.ttf\" -o -name \"*.otf\" -o -name \"*.ttc\" \\) 2>/dev/null\ndone)\n\ndupes=$(echo \"$all_fonts\" | awk -F/ '{print $NF}' | sort | uniq -d)\nif [[ -n \"$dupes\" ]]; then\n n=$(echo \"$dupes\" | wc -l | tr -d ' \\n')\n log_warn \"Duplicate font filenames\" \"$n\"\n note \" Duplicates (filename, then locations):\"\n echo \"$dupes\" | head -10 | while read -r f; do\n printf \" %s\\n\" \"$f\"\n echo \"$all_fonts\" | grep \"/$f\\$\" | sed 's/^/ /'\n done\nelse\n log_pass \"Duplicate font filenames\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. FONT BOOK DISABLED FONTS\"\n# ----------------------------------------------------------------------------\n# Font Book stores disabled-state in a plist (path varies by macOS version)\ndisabled_plist=$(find \"$HOME/Library/FontCollections\" -maxdepth 2 -name \"*.collection\" 2>/dev/null | head -3)\nif [[ -n \"$disabled_plist\" ]]; then\n note \" Font collections found:\"\n echo \"$disabled_plist\" | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. CACHE / VALIDATION\"\n# ----------------------------------------------------------------------------\nnote \" Font cache reset (drastic) requires sudo:\"\nnote \" sudo atsutil databases -remove # clears all font caches\"\nnote \" sudo atsutil server -shutdown # restarts font server\"\nnote \"\"\nnote \" To open Font Book and validate:\"\nnote \" open -a 'Font Book' # then File → Validate Fonts\"\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5012,"content_sha256":"c3b6de2582fcf544f88590a84eda934a5a37b8fc1e1ec3ba052f306a246fb3ee"},{"filename":"scripts/health-audit.sh","content":"#!/usr/bin/env bash\n# mac-ops :: health-audit.sh\n# Comprehensive macOS workstation audit — the orchestrator.\n# Walks the 8-rung diagnostic ladder and emits a verdict.\n#\n# Usage:\n# scripts/health-audit.sh [--json] [--redact] [--quiet] [--verbose] [--days N]\n#\n# Stdout = data (text by default, NDJSON when --json).\n# Stderr = section banners (suppressed with --quiet).\n\nset -u\n\nDAYS=30\n\n# Use a while loop instead of for-arg-in to correctly handle --days N (two tokens)\nSAVED_ARGS=(\"$@\")\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --days) DAYS=\"${2:-30}\"; shift 2 ;;\n --days=*) DAYS=\"${1#--days=}\"; shift ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --days N Days back to scan logs (default: 30)\n --json Emit NDJSON for piping to jq\n --redact Mask private IPs, MACs, UUIDs, hostnames\n --quiet|-q Suppress section banners\n --verbose|-v Include extra detail (e.g. per-volume APFS dump)\n\nExit codes (reflect whether the audit RAN, not what it found):\n 0 audit completed (findings in output)\n 1 general error\n 2 usage error\n 5 precondition missing\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n# Restore $@ for downstream parse_common_flags / maybe_filter_self\nset -- ${SAVED_ARGS[@]+\"${SAVED_ARGS[@]}\"}\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\nsource \"$(dirname \"$0\")/_lib/panel.sh\"\npanel_init\n\n# ----------------------------------------------------------------------------\nsection \"1. HARDWARE HEALTH\"\n# ----------------------------------------------------------------------------\n# Thermal events, low-battery shutdown, SMC errors\nthermal_events=$(log show --last \"${DAYS}d\" --style compact \\\n --predicate 'subsystem == \"com.apple.thermalmonitord\"' 2>/dev/null | wc -l | tr -d ' ')\nif [[ \"$thermal_events\" -gt 100 ]]; then\n log_warn \"Thermal monitor events ($DAYS days)\" \"$thermal_events — possible sustained throttling\"\nelse\n log_pass \"Thermal monitor events ($DAYS days)\" \"$thermal_events\"\nfi\n\n# Unclean shutdowns (power assertions + kernel)\nunclean=$(log show --last \"${DAYS}d\" --style compact \\\n --predicate 'eventMessage CONTAINS[c] \"previous shutdown cause\" AND eventMessage CONTAINS \"-\"' 2>/dev/null \\\n | grep -cE \"previous shutdown cause:\\s*-[0-9]+\" || true)\nif [[ \"$unclean\" -gt 2 ]]; then\n log_warn \"Unclean shutdowns ($DAYS days)\" \"$unclean recorded\"\nelif [[ \"$unclean\" -gt 0 ]]; then\n log_info \"Unclean shutdowns ($DAYS days)\" \"$unclean\"\nelse\n log_pass \"Unclean shutdowns ($DAYS days)\" \"0\"\nfi\n\n# Battery condition (laptops only)\nif pmset -g batt 2>/dev/null | grep -q \"InternalBattery\"; then\n cycles=$(system_profiler SPPowerDataType 2>/dev/null | awk '/Cycle Count/{print $3; exit}')\n condition=$(system_profiler SPPowerDataType 2>/dev/null | awk -F': ' '/Condition/{print $2; exit}')\n if [[ \"$condition\" == \"Normal\" ]]; then\n log_pass \"Battery condition\" \"$condition (cycles=$cycles)\"\n else\n log_warn \"Battery condition\" \"$condition (cycles=$cycles)\"\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. STORAGE HEALTH\"\n# ----------------------------------------------------------------------------\n# IO errors via unified log\nio_errors=$(log show --last \"${DAYS}d\" --style compact \\\n --predicate '(subsystem == \"com.apple.iokit\" OR subsystem == \"com.apple.kernel\") AND (eventMessage CONTAINS[c] \"I/O error\" OR eventMessage CONTAINS[c] \"media error\" OR eventMessage CONTAINS[c] \"media is not present\")' \\\n 2>/dev/null | wc -l | tr -d ' ')\nif [[ \"$io_errors\" -gt 20 ]]; then\n log_fail \"IO errors via log ($DAYS days)\" \"$io_errors — investigate per-volume with disk-health.sh\"\nelif [[ \"$io_errors\" -gt 0 ]]; then\n log_warn \"IO errors via log ($DAYS days)\" \"$io_errors\"\nelse\n log_pass \"IO errors via log ($DAYS days)\" \"0\"\nfi\n\n# APFS verify per mounted APFS volume (read-only — safe)\nnote \" APFS volumes:\"\nwhile read -r line; do\n [[ -z \"$line\" ]] && continue\n disk=$(echo \"$line\" | awk '{print $1}')\n mount=$(echo \"$line\" | awk '{print $NF}')\n # diskutil apfs verifyVolume is read-only; skip noisy ones we can't auth for\n if diskutil apfs verifyVolume \"$disk\" 2>&1 | grep -q \"successfully verified\"; then\n log_pass \"APFS volume $disk\" \"$mount — verified\"\n else\n # Probably needs privileges or is in use; soft-pass with info\n log_info \"APFS volume $disk\" \"$mount — verify skipped (may need sudo or volume in use)\"\n fi\ndone \u003c \u003c(diskutil list 2>/dev/null | awk '/Apple_APFS_Container/{print $NF, $NF}' | sort -u)\n\n# APFS snapshot bloat — local Time Machine snapshots\nsnap_count=$(tmutil listlocalsnapshots / 2>/dev/null | wc -l | tr -d ' ')\nif [[ \"$snap_count\" -gt 10 ]]; then\n log_warn \"Local Time Machine snapshots on /\" \"$snap_count — may eat purgeable space\"\nelse\n log_pass \"Local Time Machine snapshots on /\" \"$snap_count\"\nfi\n\n# Free space on root volume\nroot_free_pct=$(df -h / | awk 'NR==2{gsub(\"%\",\"\",$5); print 100-$5}')\nif [[ \"$root_free_pct\" -lt 5 ]]; then\n log_fail \"Free space on /\" \"${root_free_pct}% — critical\"\nelif [[ \"$root_free_pct\" -lt 15 ]]; then\n log_warn \"Free space on /\" \"${root_free_pct}%\"\nelse\n log_pass \"Free space on /\" \"${root_free_pct}%\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. PANIC RECORDS\"\n# ----------------------------------------------------------------------------\npanic_dir=\"/Library/Logs/DiagnosticReports\"\npanics_recent=$(find \"$panic_dir\" -maxdepth 1 \\( -name \"*.panic\" -o -name \"Kernel*.ips\" \\) \\\n -mtime \"-${DAYS}\" 2>/dev/null | wc -l | tr -d ' ')\nif [[ \"$panics_recent\" -gt 0 ]]; then\n log_fail \"Kernel panics ($DAYS days)\" \"$panics_recent — drill with panic-triage.sh\"\n note \" Most recent:\"\n find \"$panic_dir\" -maxdepth 1 \\( -name \"*.panic\" -o -name \"Kernel*.ips\" \\) -mtime \"-${DAYS}\" 2>/dev/null \\\n | head -3 | sed 's|.*/| |'\nelse\n log_pass \"Kernel panics ($DAYS days)\" \"0\"\nfi\n\n# User app crashes (informational — they don't crash the system but indicate flaky software)\nuser_crashes=$(find ~/Library/Logs/DiagnosticReports -maxdepth 1 -name \"*.ips\" -mtime \"-7\" 2>/dev/null | wc -l | tr -d ' ')\nif [[ \"$user_crashes\" -gt 20 ]]; then\n log_warn \"User-space app crashes (7 days)\" \"$user_crashes — frequent crashes\"\nelse\n log_info \"User-space app crashes (7 days)\" \"$user_crashes\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. STARTUP INVENTORY\"\n# ----------------------------------------------------------------------------\n# Login Items (visible in System Settings)\nlogin_items=$(osascript -e 'tell application \"System Events\" to count of login items' 2>/dev/null || echo 0)\nlog_info \"Login Items (System Settings)\" \"$login_items\"\n\n# User LaunchAgents\nuser_agents=$(find \"$HOME/Library/LaunchAgents\" -maxdepth 1 -name \"*.plist\" 2>/dev/null | wc -l | tr -d ' ')\nlog_info \"User LaunchAgents (~/Library/LaunchAgents)\" \"$user_agents\"\n\n# System LaunchAgents\nsys_agents=$(find \"/Library/LaunchAgents\" -maxdepth 1 -name \"*.plist\" 2>/dev/null | wc -l | tr -d ' ')\nlog_info \"System LaunchAgents (/Library/LaunchAgents)\" \"$sys_agents\"\n\n# System LaunchDaemons\nsys_daemons=$(find \"/Library/LaunchDaemons\" -maxdepth 1 -name \"*.plist\" 2>/dev/null | wc -l | tr -d ' ')\nlog_info \"System LaunchDaemons (/Library/LaunchDaemons)\" \"$sys_daemons\"\n\n# Privileged helper tools (often orphaned after app uninstall)\nhelpers=$(find \"/Library/PrivilegedHelperTools\" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' ')\nif [[ \"$helpers\" -gt 5 ]]; then\n log_warn \"Privileged helper tools\" \"$helpers — some may be orphans from uninstalled apps\"\nelse\n log_info \"Privileged helper tools\" \"$helpers\"\nfi\n\ntotal_startup=$((login_items + user_agents + sys_agents + sys_daemons))\nnote \" Total startup items: $total_startup (drill with startup-audit.sh)\"\n\n# ----------------------------------------------------------------------------\nsection \"5. RESOURCE PRESSURE (snapshot)\"\n# ----------------------------------------------------------------------------\n# Top 5 CPU consumers — `-o command` includes full path/args; we keep it short with cut\nnote \" Top 5 by CPU%:\"\nps -ArcS -o pcpu,pid,command 2>/dev/null | head -6 | tail -5 | \\\n awk '{pcpu=$1; pid=$2; $1=\"\"; $2=\"\"; sub(/^ /,\"\"); printf \" %5s%% [%6s] %s\\n\", pcpu, pid, $0}' | \\\n cut -c1-100\n\n# Notable noisy processes\nfor proc in mds_stores mdworker_shared photoanalysisd cloudd bird WindowServer; do\n if cpu=$(ps -ArcS -o pcpu,comm 2>/dev/null | awk -v p=\"$proc\" '$2==p{print $1; exit}'); then\n if [[ -n \"$cpu\" ]]; then\n cpu_int=${cpu%.*}\n if [[ \"${cpu_int:-0}\" -gt 50 ]]; then\n log_warn \"$proc CPU\" \"${cpu}% — sustained spike?\"\n else\n log_info \"$proc CPU\" \"${cpu}%\"\n fi\n fi\n fi\ndone\n\n# ----------------------------------------------------------------------------\nsection \"6. WAKE PATTERN (last 24h)\"\n# ----------------------------------------------------------------------------\nwake_count=$(pmset -g log 2>/dev/null | grep -cE \"Wake from\" | head -1)\nwake_count=\"${wake_count:-0}\"\nif [[ \"$wake_count\" -gt 50 ]]; then\n log_warn \"Wakes in pmset log (full history)\" \"$wake_count — drill with wake-reasons.sh\"\nelse\n log_info \"Wakes in pmset log (full history)\" \"$wake_count\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"7. TCC (PERMISSIONS)\"\n# ----------------------------------------------------------------------------\n# Read-only check — does the user TCC.db exist? How many entries?\nuser_tcc=\"$HOME/Library/Application Support/com.apple.TCC/TCC.db\"\nif [[ -r \"$user_tcc\" ]]; then\n grants=$(sqlite3 \"$user_tcc\" \"SELECT COUNT(*) FROM access WHERE auth_value > 0\" 2>/dev/null || echo \"?\")\n denied=$(sqlite3 \"$user_tcc\" \"SELECT COUNT(*) FROM access WHERE auth_value = 0\" 2>/dev/null || echo \"?\")\n log_info \"User TCC grants (allowed)\" \"$grants\"\n if [[ \"$denied\" != \"?\" ]] && [[ \"$denied\" -gt 0 ]]; then\n log_warn \"User TCC grants (denied)\" \"$denied — drill with tcc-audit.sh\"\n else\n log_pass \"User TCC denials\" \"0\"\n fi\nelse\n log_info \"User TCC.db\" \"not readable (run tcc-audit.sh for details)\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"8. SYSTEM INFO\"\n# ----------------------------------------------------------------------------\nnote \" macOS: $(sw_vers -productVersion) ($(sw_vers -buildVersion))\"\nnote \" Arch: $(uname -m)\"\nnote \" Uptime: $(uptime | awk -F'up ' '{split($2,a,\",\"); print a[1]}')\"\nnote \" Hostname: $(scutil --get LocalHostName 2>/dev/null || hostname)\"\n\n# ----------------------------------------------------------------------------\nhostname_short=$(scutil --get LocalHostName 2>/dev/null | head -c 30 || hostname -s | head -c 30)\npanel_render \"health-audit\" \"$hostname_short\"\n\nif [[ \"$JSON_MODE\" -eq 0 ]] && [[ \"$MAC_PANEL_ENABLED\" -eq 0 ]] && [[ -n \"$FIRST_FAIL\" ]]; then\n case \"$FIRST_FAIL\" in\n *\"PANIC\"*|*\"panic\"*)\n echo \" Next: scripts/panic-triage.sh # decode the most recent panic + pre-panic timeline\" ;;\n *\"STORAGE\"*|*\"IO errors\"*|*\"APFS\"*)\n echo \" Next: scripts/disk-health.sh -v / # APFS + IO errors + snapshot bloat\" ;;\n *\"snapshot\"*|*\"Free space\"*|*\"Local Time Machine\"*)\n echo \" Next: scripts/storage-pressure.sh # explain disk pressure / snapshot bloat\" ;;\n *\"STARTUP\"*|*\"LaunchAgent\"*|*\"LaunchDaemon\"*|*\"Login Items\"*)\n echo \" Next: scripts/startup-audit.sh # full inventory; safe-disable-startup.sh to cull\" ;;\n *\"TCC\"*|*\"denial\"*)\n echo \" Next: scripts/tcc-audit.sh --denied # see which app/service is being denied\" ;;\n *\"Wake\"*|*\"WAKE\"*)\n echo \" Next: scripts/wake-reasons.sh --since 7d # classify wakes by cause\" ;;\n *\"Thermal\"*|*\"Battery\"*|*\"shutdown\"*)\n echo \" Next: open System Settings → Battery → Options; check pmset -g custom\" ;;\n *\"helper tool\"*)\n echo \" Next: ls /Library/PrivilegedHelperTools/ # remove orphans manually with sudo rm\" ;;\n *)\n echo \" Next: re-run with --verbose, then check references/\" ;;\n esac\nfi\n\n# Friendly status if nothing failed\nif [[ \"$JSON_MODE\" -eq 0 ]] && [[ -z \"$FIRST_FAIL\" ]] && [[ \"$WARN_COUNT\" -eq 0 ]]; then\n echo \" ✓ System looks clean across all 8 rungs.\"\nelif [[ \"$JSON_MODE\" -eq 0 ]] && [[ -z \"$FIRST_FAIL\" ]] && [[ \"$WARN_COUNT\" -gt 0 ]]; then\n echo \" ✓ No FAILs. $WARN_COUNT WARN entries above worth scanning.\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":12722,"content_sha256":"ba5773565498c3851f014c26c59fcb41a80af304425206e82c75f7834e0d5424"},{"filename":"scripts/kext-audit.sh","content":"#!/usr/bin/env bash\n# mac-ops :: kext-audit.sh\n# Inventory loaded kernel extensions + system extensions.\n#\n# Why: kexts and system extensions run with kernel privileges. A misbehaving\n# one can panic the system, leak memory, or hold a system-wide lock. They're\n# the #1 cause of \"Mac kernel panic\" on machines that get them.\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nReports:\n 1. Loaded kexts (kextstat) — third-party highlighted\n 2. Installed system extensions (systemextensionsctl list)\n 3. Kext load failures from log\n 4. Pending kext approval (apps that requested kext but were denied)\n 5. SIP and kernel security policy state\n\nOn Apple Silicon (M1+), kexts are deprecated in favor of system extensions\nwhich run in userspace. This script reports both because some legacy products\nstill ship kexts even on Apple Silicon (via boot policy reduction).\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. LOADED KEXTS\"\n# ----------------------------------------------------------------------------\ntotal_kexts=$(kextstat -l 2>/dev/null | wc -l | tr -d ' ')\nlog_info \"Loaded kexts (total)\" \"$total_kexts\"\n\n# Third-party kexts — anything not com.apple.*\nthird_party=$(kextstat -l 2>/dev/null | awk '{print $6}' | grep -v \"^com.apple\\.\" | grep -v \"^$\" | sort -u)\nthird_party_count=$(echo \"$third_party\" | grep -c . 2>/dev/null || echo 0)\n\nif [[ \"$third_party_count\" -gt 0 ]]; then\n log_warn \"Third-party kexts\" \"$third_party_count — primary panic suspects\"\n note \" Third-party kexts (loaded right now):\"\n echo \"$third_party\" | sed 's/^/ /'\nelse\n log_pass \"Third-party kexts\" \"0 — clean kernel\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. SYSTEM EXTENSIONS\"\n# ----------------------------------------------------------------------------\nif command -v systemextensionsctl >/dev/null 2>&1; then\n sysext_out=$(systemextensionsctl list 2>/dev/null)\n if [[ -n \"$sysext_out\" ]]; then\n # The output has 0+ extensions per team. Skip the header line.\n ext_lines=$(echo \"$sysext_out\" | grep -E \"^\\s*\\*?\\s+[a-fA-F0-9]\" || true)\n if [[ -n \"$ext_lines\" ]]; then\n ext_count=$(echo \"$ext_lines\" | wc -l | tr -d ' ')\n log_info \"Installed system extensions\" \"$ext_count\"\n note \" System extensions (team-id, bundle-id, name, state):\"\n echo \"$ext_lines\" | head -20 | sed 's/^/ /'\n else\n log_pass \"Installed system extensions\" \"0\"\n fi\n fi\nelse\n log_info \"systemextensionsctl\" \"not available (older macOS?)\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. RECENT KEXT LOAD FAILURES\"\n# ----------------------------------------------------------------------------\nload_fails=$(log show --last 7d --style compact \\\n --predicate '(process == \"kextd\" OR process == \"kernel\") AND (eventMessage CONTAINS[c] \"kext\" AND (messageType == \"Error\" OR messageType == \"Fault\"))' \\\n 2>/dev/null | head -20)\n\nif [[ -n \"$load_fails\" ]]; then\n n=$(echo \"$load_fails\" | wc -l | tr -d ' ')\n log_warn \"Kext load failures (7d)\" \"$n events\"\n echo \"$load_fails\" | head -5 | sed 's/^/ /'\nelse\n log_pass \"Kext load failures (7d)\" \"none\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. PENDING KEXT APPROVAL\"\n# ----------------------------------------------------------------------------\n# Apps that have requested kext load but were denied — usually because user\n# hasn't approved in System Settings → Privacy & Security\npending=$(log show --last 30d --style compact \\\n --predicate 'eventMessage CONTAINS[c] \"kext approval\"' \\\n 2>/dev/null | tail -5)\nif [[ -n \"$pending\" ]]; then\n log_warn \"Pending kext approvals (30d)\" \"see below\"\n echo \"$pending\" | sed 's/^/ /'\nelse\n log_pass \"Pending kext approvals\" \"none\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. SECURITY POLICY\"\n# ----------------------------------------------------------------------------\n# SIP status\nsip_status=$(csrutil status 2>/dev/null | head -1 | awk -F': *' '{print $2}' | tr -d '.')\ncase \"$sip_status\" in\n *enabled*) log_pass \"SIP\" \"$sip_status\" ;;\n *disabled*) log_warn \"SIP\" \"$sip_status — kernel security weakened\" ;;\n *) log_info \"SIP\" \"${sip_status:-unknown}\" ;;\nesac\n\n# On Apple Silicon: bputil reports boot policy\nif is_apple_silicon && command -v bputil >/dev/null 2>&1; then\n note \" Apple Silicon boot policy (requires sudo for detail):\"\n sudo -n bputil -d 2>/dev/null | grep -E \"Security Policy|Manage Kernel Extensions|Allow User Kernel Extensions\" | head -5 | sed 's/^/ /' || \\\n note \" (sudo required for full bputil read)\"\nfi\n\n# Apple Silicon specific kext loading state\nif is_apple_silicon; then\n kext_loading=$(kmutil showloaded 2>/dev/null | wc -l | tr -d ' ')\n log_info \"kmutil showloaded count\" \"$kext_loading\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"6. VENDOR PATTERNS\"\n# ----------------------------------------------------------------------------\n# Known panic-prone vendors\nnote \" Scanning for known-problematic kexts:\"\nfor pattern in \"eltima\" \"paragon\" \"eset\" \"kaspersky\" \"norton\" \"sophos\" \"bitdefender\"; do\n matches=$(kextstat -l 2>/dev/null | awk '{print $6}' | grep -i \"$pattern\" || true)\n if [[ -n \"$matches\" ]]; then\n log_warn \"Vendor kext: $pattern\" \"$(echo \"$matches\" | wc -l | tr -d ' ') loaded\"\n echo \"$matches\" | head -3 | sed 's/^/ /'\n fi\ndone\n\n# ----------------------------------------------------------------------------\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n note \" To uninstall a system extension:\"\n note \" systemextensionsctl uninstall \u003cteam-id> \u003cbundle-id>\"\n note \" To inspect a specific kext:\"\n note \" kextstat -l | grep \u003cname>\"\n note \" kmutil showloaded | grep \u003cname> # Apple Silicon\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6287,"content_sha256":"68dea81ae5d26b6bc84d0f7f13cd89753e518eecd13479029e17082ef6eac78f"},{"filename":"scripts/keychain-audit.sh","content":"#!/usr/bin/env bash\n# mac-ops :: keychain-audit.sh\n# Audit Keychain health: login keychain status, certificate trust chain,\n# securityd activity, recurring password prompts.\n#\n# \"macOS keeps asking for my password\" is the #2 most common Mac complaint\n# after \"this app won't open the camera\". Root cause is usually a damaged\n# login.keychain-db, an out-of-sync iCloud Keychain, or a recurring TCC\n# prompt being confused for a Keychain prompt.\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nReports:\n 1. Login keychain location + last-modified time + lock state\n 2. System / iCloud keychain detection\n 3. securityd / trustd recent error activity\n 4. Expired certificates in user keychain\n 5. Apple developer codesign trust state\n 6. Common \"password keeps prompting\" causes\n\nCommon fix sequence for \"keeps prompting\":\n 1. Keychain Access → File → Lock All Keychains → quit\n 2. Quit Keychain Access; open it; \"Update Keychain Password\" if prompted\n 3. Or worst case: rename login.keychain-db (loses cached passwords)\n cd ~/Library/Keychains/\u003cUUID>\n mv login.keychain-db login.keychain-db.broken\n (reboot — a fresh one will be created)\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. LOGIN KEYCHAIN\"\n# ----------------------------------------------------------------------------\n# Modern macOS stores keychains in ~/Library/Keychains/\u003cUUID>/\nkeychain_dir=$(ls -d \"$HOME/Library/Keychains\"/* 2>/dev/null | head -1)\nif [[ -n \"$keychain_dir\" ]]; then\n note \" Keychain directory: $keychain_dir\"\n if [[ -f \"$keychain_dir/login.keychain-db\" ]]; then\n size=$(ls -lh \"$keychain_dir/login.keychain-db\" | awk '{print $5}')\n mtime=$(stat -f \"%Sm\" -t \"%Y-%m-%d %H:%M:%S\" \"$keychain_dir/login.keychain-db\")\n log_pass \"login.keychain-db\" \"$size, modified $mtime\"\n else\n log_warn \"login.keychain-db\" \"missing in $keychain_dir\"\n fi\nelse\n log_warn \"Keychain directory\" \"not found at standard location\"\nfi\n\n# Show keychain list as the security tool sees it\nnote \"\"\nnote \" security list-keychains:\"\nsecurity list-keychains -d user 2>/dev/null | sed 's/^/ /' | head -10\n\n# ----------------------------------------------------------------------------\nsection \"2. KEYCHAIN LOCK STATE\"\n# ----------------------------------------------------------------------------\n# Use 'security show-keychain-info' on the login keychain\nif security show-keychain-info \"$HOME/Library/Keychains/login.keychain-db\" 2>/dev/null; then\n log_pass \"Login keychain unlocked\"\nelse\n # Either locked or doesn't exist at that path; check modern path\n login_db=$(security default-keychain 2>/dev/null | tr -d '\"' | awk '{print $1}')\n if [[ -n \"$login_db\" ]]; then\n if security show-keychain-info \"$login_db\" 2>/dev/null; then\n log_pass \"Default keychain unlocked\" \"$login_db\"\n else\n log_info \"Default keychain\" \"may be locked or auto-locked\"\n fi\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. SECURITYD / TRUSTD ACTIVITY (recent errors)\"\n# ----------------------------------------------------------------------------\nsec_errors=$(log show --last 24h --style compact \\\n --predicate '(process == \"securityd\" OR process == \"trustd\" OR process == \"keychainsharingmessaging\") AND (messageType == \"Error\" OR messageType == \"Fault\")' \\\n 2>/dev/null | head -10)\n\nif [[ -n \"$sec_errors\" ]]; then\n n=$(echo \"$sec_errors\" | wc -l | tr -d ' \\n')\n log_warn \"securityd/trustd errors (24h)\" \"$n events\"\n echo \"$sec_errors\" | head -5 | sed 's/^/ /'\nelse\n log_pass \"securityd/trustd errors (24h)\" \"none\"\nfi\n\n# Specifically: keychain password prompts in log\nprompt_events=$(log show --last 24h --style compact \\\n --predicate 'eventMessage CONTAINS[c] \"keychain\" AND (eventMessage CONTAINS[c] \"prompt\" OR eventMessage CONTAINS[c] \"password\")' \\\n 2>/dev/null | head -5)\nif [[ -n \"$prompt_events\" ]]; then\n log_info \"Keychain prompt events (24h)\" \"see below\"\n echo \"$prompt_events\" | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. CERTIFICATE INVENTORY\"\n# ----------------------------------------------------------------------------\n# Count certs in user keychain\ncert_count=$(security find-certificate -a 2>/dev/null | grep -c \"^keychain:\" | tr -d ' \\n')\ncert_count=\"${cert_count:-0}\"\nlog_info \"Certs in user keychain\" \"$cert_count\"\n\n# Expired certs check (most certs in system keychain rotate naturally; here we\n# check the user's own certs)\nexpired=$(security find-certificate -a -p 2>/dev/null | awk '\n /-----BEGIN CERTIFICATE-----/{flag=1; buf=\"\"}\n flag{buf=buf\"\\n\"$0}\n /-----END CERTIFICATE-----/{\n cmd=\"openssl x509 -noout -enddate 2>/dev/null\"\n print buf | cmd\n close(cmd)\n flag=0\n }' 2>/dev/null | grep \"notAfter\" | head -5)\n# This is best-effort; full expiry scan requires more work\n\n# ----------------------------------------------------------------------------\nsection \"5. CODESIGN / GATEKEEPER STATE\"\n# ----------------------------------------------------------------------------\ngk_status=$(spctl --status 2>&1)\ncase \"$gk_status\" in\n *enabled*) log_pass \"Gatekeeper\" \"enabled\" ;;\n *disabled*) log_warn \"Gatekeeper\" \"disabled — system is less secure\" ;;\n *) log_info \"Gatekeeper\" \"$gk_status\" ;;\nesac\n\n# Check developer mode (on Apple Silicon, controls things like unsigned dylib loading)\nif is_apple_silicon; then\n dev_mode=$(spctl developer-mode status 2>/dev/null || echo \"(needs sudo to query)\")\n note \" Apple Silicon developer mode: $dev_mode\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"6. iCLOUD KEYCHAIN STATE\"\n# ----------------------------------------------------------------------------\n# Check if iCloud Keychain is enabled by looking for the keychain-sync daemons\nif pgrep -x securityd >/dev/null && \\\n log show --last 1h --style compact --predicate 'process == \"securityd\" AND eventMessage CONTAINS \"circle\"' 2>/dev/null | grep -q \"joined\"; then\n log_info \"iCloud Keychain\" \"appears to be in sync circle\"\nelse\n log_info \"iCloud Keychain\" \"state could not be determined from log\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"7. COMMON ISSUES\"\n# ----------------------------------------------------------------------------\nnote \" If \\\"macOS keeps asking for password\\\":\"\nnote \" Most common cause: login.keychain-db password drifted from account password.\"\nnote \" Fix: Keychain Access → preferences → Reset My Default Keychain\"\nnote \" (loses cached passwords but is the cleanest reset)\"\nnote \"\"\nnote \" If \\\"This connection is not private\\\" for valid sites:\"\nnote \" Check system clock (mac-ops health-audit reports clock drift).\"\nnote \" Run: scripts/health-audit.sh --days 1\"\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7288,"content_sha256":"38b52fe3e6310492605b8c38cf4f76c8af4e1c4392cbe190d85d70c6bfcdd284"},{"filename":"scripts/media-libraries.sh","content":"#!/usr/bin/env bash\n# mac-ops :: media-libraries.sh\n# Audit Photos, Music, TV, and other media libraries: sizes, locations,\n# integrity status, and the sync daemons that manage them.\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nReports:\n 1. Photos library locations + size\n 2. Music / TV library\n 3. Pro app libraries (Final Cut, Logic, iMovie)\n 4. iCloud Drive / Mobile Documents size\n 5. Photos / Music sync daemons (photolibraryd, cloudphotod, photoanalysisd,\n amsondemandinstalld, cloudd, bird) — CPU + memory\n 6. Suspended sync state (frequent silent cause of \"Photos won't sync\")\n\nThese libraries can be 10s-100s of GB and show as \"Other\" in About This Mac.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. PHOTOS LIBRARIES\"\n# ----------------------------------------------------------------------------\n# Default location:\ndefault_photos=\"$HOME/Pictures/Photos Library.photoslibrary\"\n# Find any .photoslibrary anywhere reasonable\nphoto_libs=$(find \"$HOME/Pictures\" /Users/Shared \"$HOME/Documents\" -maxdepth 2 -name \"*.photoslibrary\" -type d 2>/dev/null | head -5)\n# Also check external volumes if mounted\next_photo_libs=$(find /Volumes -maxdepth 3 -name \"*.photoslibrary\" -type d 2>/dev/null | head -5)\nphoto_libs=\"$photo_libs\"

mac-ops Helps with Slow Mac that used to be fast — bloat accumulation across the four startup mechanisms (Login Items, , , ). The same machine still boots fast once those are inventoried and trimmed. Failing drives that nobody's spotted yet. macOS doesn't shout the way Windows does — IO errors live in and APFS surfaces them via / provider messages. Healthy SSDs produce zero of these per month; dozens means active failure even when "About This Mac → Storage" still shows green. Kernel panics with no obvious cause. The / files in carry the panic string, kernel call stack, and (critically) the lo…

\\n'\"$ext_photo_libs\"\nphoto_libs=$(echo \"$photo_libs\" | grep -v '^

mac-ops Helps with Slow Mac that used to be fast — bloat accumulation across the four startup mechanisms (Login Items, , , ). The same machine still boots fast once those are inventoried and trimmed. Failing drives that nobody's spotted yet. macOS doesn't shout the way Windows does — IO errors live in and APFS surfaces them via / provider messages. Healthy SSDs produce zero of these per month; dozens means active failure even when "About This Mac → Storage" still shows green. Kernel panics with no obvious cause. The / files in carry the panic string, kernel call stack, and (critically) the lo…

)\n\nif [[ -z \"$photo_libs\" ]]; then\n log_info \"Photos libraries\" \"none found\"\nelse\n n=$(echo \"$photo_libs\" | wc -l | tr -d ' ')\n log_info \"Photos libraries\" \"$n found\"\n while IFS= read -r lib; do\n [[ -z \"$lib\" ]] && continue\n size=$(du -sh \"$lib\" 2>/dev/null | awk '{print $1}')\n printf \" %s = %s\\n\" \"$lib\" \"${size:-?}\"\n done \u003c\u003c\u003c \"$photo_libs\"\nfi\n\n# Current Photos system library\nsys_photos=$(defaults read com.apple.Photos UserLibrarySelectionMethod 2>/dev/null || echo \"\")\nif [[ -n \"$sys_photos\" ]]; then\n note \"\"\n note \" System Photos library: per Photos.app preferences\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. MUSIC / TV LIBRARIES\"\n# ----------------------------------------------------------------------------\nmusic_lib=\"$HOME/Music/Music\"\nif [[ -d \"$music_lib\" ]]; then\n size=$(du -sh \"$music_lib\" 2>/dev/null | awk '{print $1}')\n log_info \"Music library\" \"${size:-?} ($music_lib)\"\nfi\n\n# .musiclibrary\nmusiclibs=$(find \"$HOME/Music\" -maxdepth 2 -name \"*.musiclibrary\" -type d 2>/dev/null | head -3)\nif [[ -n \"$musiclibs\" ]]; then\n while IFS= read -r lib; do\n [[ -z \"$lib\" ]] && continue\n size=$(du -sh \"$lib\" 2>/dev/null | awk '{print $1}')\n note \" $lib = ${size:-?}\"\n done \u003c\u003c\u003c \"$musiclibs\"\nfi\n\n# TV\ntv_dir=\"$HOME/Movies/TV\"\nif [[ -d \"$tv_dir\" ]]; then\n size=$(du -sh \"$tv_dir\" 2>/dev/null | awk '{print $1}')\n log_info \"TV library\" \"${size:-?} ($tv_dir)\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. PRO APP LIBRARIES\"\n# ----------------------------------------------------------------------------\nnote \" Scanning for FCP / Logic / iMovie libraries...\"\npro_libs=$(find \"$HOME/Movies\" \"$HOME/Documents\" \"$HOME/Logic\" /Volumes -maxdepth 3 \\\n \\( -name \"*.fcpbundle\" -o -name \"*.logicx\" -o -name \"*.imovielibrary\" -o -name \"*.band\" \\) -type d 2>/dev/null | head -10)\nif [[ -z \"$pro_libs\" ]]; then\n log_info \"Pro app libraries\" \"none found in standard locations\"\nelse\n n=$(echo \"$pro_libs\" | wc -l | tr -d ' ')\n log_info \"Pro app libraries\" \"$n found\"\n while IFS= read -r lib; do\n [[ -z \"$lib\" ]] && continue\n size=$(du -sh \"$lib\" 2>/dev/null | awk '{print $1}')\n printf \" %s = %s\\n\" \"$lib\" \"${size:-?}\"\n done \u003c\u003c\u003c \"$pro_libs\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. iCLOUD DRIVE / MOBILE DOCUMENTS\"\n# ----------------------------------------------------------------------------\nicloud=\"$HOME/Library/Mobile Documents\"\nif [[ -d \"$icloud\" ]]; then\n size=$(du -sh \"$icloud\" 2>/dev/null | awk '{print $1}')\n log_info \"iCloud Drive cache\" \"${size:-?}\"\n note \" (Subset is downloaded; rest is placeholders. macOS evicts under pressure.)\"\nfi\n\n# Top iCloud Drive consumers\nnote \"\"\nnote \" Top consumers under iCloud Drive (du -sh, may be slow):\"\nfind \"$icloud\" -maxdepth 2 -type d 2>/dev/null | head -10 | while read -r d; do\n [[ \"$d\" == \"$icloud\" ]] && continue\n size=$(du -sh \"$d\" 2>/dev/null | awk '{print $1}')\n printf \" %s = %s\\n\" \"$d\" \"${size:-?}\"\ndone | sort -k3 -h -r | head -5\n\n# ----------------------------------------------------------------------------\nsection \"5. SYNC DAEMONS — CPU SNAPSHOT\"\n# ----------------------------------------------------------------------------\nnote \" Process CPU%:\"\nfor proc in photolibraryd cloudphotod photoanalysisd amsondemandinstalld cloudd bird fileproviderd; do\n cpu=$(ps -ArcS -o pcpu,comm 2>/dev/null | awk -v p=\"$proc\" '$2==p{print $1; exit}')\n cpu=\"${cpu:-0}\"\n cpu_int=${cpu%.*}\n if [[ \"${cpu_int:-0}\" -gt 50 ]]; then\n log_warn \"$proc\" \"${cpu}% — sustained sync activity\"\n elif [[ \"${cpu_int:-0}\" -gt 0 ]]; then\n log_info \"$proc\" \"${cpu}%\"\n fi\ndone\n\n# ----------------------------------------------------------------------------\nsection \"6. SYNC HEALTH\"\n# ----------------------------------------------------------------------------\n# Check for suspended sync / iCloud sign-out / etc.\nicloud_status=$(brctl status 2>/dev/null || true)\nif [[ -n \"$icloud_status\" ]] && echo \"$icloud_status\" | grep -q \"iCloud Drive Disabled\"; then\n log_warn \"iCloud Drive\" \"disabled\"\nfi\n\n# Photos cloud status\nnote \"\"\nnote \" Recent photolibraryd / cloudphotod errors (24h):\"\nphoto_errs=$(log show --last 24h --style compact \\\n --predicate 'process == \"photolibraryd\" OR process == \"cloudphotod\"' \\\n 2>/dev/null | grep -iE \"(error|fail|fault)\" | tail -5)\nif [[ -n \"$photo_errs\" ]]; then\n echo \"$photo_errs\" | sed 's/^/ /'\nelse\n note \" (none)\"\nfi\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6353,"content_sha256":"c12b3359a42dd8bc60515f9aaafb8ba472399643a318f90db5b9dd0c567bf2bd"},{"filename":"scripts/network-locations.sh","content":"#!/usr/bin/env bash\n# mac-ops :: network-locations.sh\n# Inventory macOS Network Locations (System Settings → Network → ... → Locations).\n# Each location is a separate set of network preferences — useful for \"home vs\n# office vs cafe\" profiles. A stale location may have wrong DNS or proxy.\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nReports:\n 1. Configured network locations + active location\n 2. Per-location DNS / proxy / search-domain config\n 3. Stale locations referencing missing network services\n 4. Network service order (which interface \"wins\" for the default route)\n\nSwitch location:\n System Settings → Network → ... → Locations → choose\n Or CLI: networksetup -switchtolocation \"Location Name\"\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. CONFIGURED LOCATIONS\"\n# ----------------------------------------------------------------------------\ncurrent=$(networksetup -getcurrentlocation 2>/dev/null)\nlocations=$(networksetup -listlocations 2>/dev/null)\nloc_count=$(echo \"$locations\" | grep -c . 2>/dev/null || echo 0)\n\nlog_info \"Network locations configured\" \"$loc_count\"\nnote \" Current location: $current\"\nnote \" All locations:\"\necho \"$locations\" | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nsection \"2. NETWORK SERVICE ORDER\"\n# ----------------------------------------------------------------------------\nnote \" Priority order (highest first — first reachable wins default route):\"\nnetworksetup -listnetworkserviceorder 2>/dev/null | head -20 | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nsection \"3. ACTIVE LOCATION DNS / PROXY STATE\"\n# ----------------------------------------------------------------------------\nnote \" Per-service DNS:\"\nnetworksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do\n [[ \"$svc\" == \\** ]] && continue # disabled\n dns=$(networksetup -getdnsservers \"$svc\" 2>/dev/null)\n [[ \"$dns\" == *\"aren't any\"* ]] && dns=\"(none)\"\n printf \" %-35s %s\\n\" \"$svc:\" \"$dns\"\ndone\n\nnote \"\"\nnote \" Per-service search domains:\"\nnetworksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do\n [[ \"$svc\" == \\** ]] && continue\n sd=$(networksetup -getsearchdomains \"$svc\" 2>/dev/null)\n [[ \"$sd\" == *\"aren't any\"* ]] && continue\n printf \" %-35s %s\\n\" \"$svc:\" \"$sd\"\ndone\n\n# Web proxy state\nnote \"\"\nnote \" Web proxy state:\"\nscutil --proxy 2>/dev/null | grep -E \"HTTPEnable|HTTPSEnable|ProxyAutoConfigEnable|ProxyAutoConfigURLString\" | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nsection \"4. NETWORK PREFERENCES PLIST INSPECTION\"\n# ----------------------------------------------------------------------------\n# /Library/Preferences/SystemConfiguration/preferences.plist holds all locations\n# We can extract the location list defensively\nprefs_plist=\"/Library/Preferences/SystemConfiguration/preferences.plist\"\nif [[ -r \"$prefs_plist\" ]]; then\n # Extract just the Sets dict, which has one entry per location\n set_count=$(plutil -extract Sets raw -o - \"$prefs_plist\" 2>/dev/null | wc -l | tr -d ' ')\n log_info \"preferences.plist Sets count\" \"$set_count\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. STALE / DISABLED SERVICES\"\n# ----------------------------------------------------------------------------\n# Asterisked services in listallnetworkservices are disabled\ndisabled=$(networksetup -listallnetworkservices 2>/dev/null | grep '^\\*' || true)\nif [[ -n \"$disabled\" ]]; then\n n=$(echo \"$disabled\" | wc -l | tr -d ' ')\n log_info \"Disabled network services\" \"$n\"\n echo \"$disabled\" | sed 's/^/ /'\nelse\n log_pass \"Disabled network services\" \"0\"\nfi\n\n# Services referencing missing hardware\nnote \"\"\nnote \" Network services check:\"\nnetworksetup -listallhardwareports 2>/dev/null | awk '/Hardware Port|Device/{print}' | head -15 | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":4386,"content_sha256":"e2f2cd452f3b1a50c3a8aa1c150da9430977a3ed9e0f2cf9568dc69f615df706"},{"filename":"scripts/panic-triage.sh","content":"#!/usr/bin/env bash\n# mac-ops :: panic-triage.sh\n# Decode the most recent kernel panic (or one specified by path/time).\n# Emits panic string, suspect kext, and the pre-panic timeline window.\n#\n# Usage:\n# scripts/panic-triage.sh # most recent panic\n# scripts/panic-triage.sh -f \u003cpath> # specific report file\n# scripts/panic-triage.sh -t '2026-05-14 03:14:22' # by timestamp (UTC)\n# scripts/panic-triage.sh -m 15 # widen pre-panic window to 15 min\n\nset -u\n\nPANIC_FILE=\"\"\nPANIC_TIME=\"\"\nWINDOW_MIN=10\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -f|--file) PANIC_FILE=\"$2\"; shift 2 ;;\n -t|--time) PANIC_TIME=\"$2\"; shift 2 ;;\n -m|--minutes) WINDOW_MIN=\"$2\"; shift 2 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n -f, --file PATH Specific .panic or Kernel*.ips file to decode\n -t, --time 'YYYY-MM-DD HH:MM:SS' Timestamp anchor for pre-panic window\n -m, --minutes N Pre-panic window in minutes (default: 10)\n --json, --redact, --quiet, --verbose Standard flags\n\nExit codes:\n 0 success\n 3 no panic reports found\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\npanic_dir=\"/Library/Logs/DiagnosticReports\"\n\n# ----------------------------------------------------------------------------\nsection \"1. PANIC REPORT SELECTION\"\n# ----------------------------------------------------------------------------\nif [[ -z \"$PANIC_FILE\" ]]; then\n # Find newest panic report\n PANIC_FILE=$(find \"$panic_dir\" -maxdepth 1 \\( -name \"*.panic\" -o -name \"Kernel*.ips\" \\) 2>/dev/null \\\n | xargs ls -t 2>/dev/null | head -1)\nfi\n\nif [[ -z \"$PANIC_FILE\" ]] || [[ ! -f \"$PANIC_FILE\" ]]; then\n log_info \"Panic reports\" \"none found in $panic_dir\"\n emit_summary\n exit \"$EXIT_NOT_FOUND\"\nfi\n\nlog_pass \"Panic report selected\" \"$PANIC_FILE\"\npanic_mtime=$(stat -f \"%Sm\" -t \"%Y-%m-%d %H:%M:%S\" \"$PANIC_FILE\" 2>/dev/null)\nnote \" Last modified: $panic_mtime\"\n\n# ----------------------------------------------------------------------------\nsection \"2. PANIC STRING + KEXT EXTRACTION\"\n# ----------------------------------------------------------------------------\nif [[ \"$PANIC_FILE\" == *.ips ]]; then\n # .ips files are JSON-with-extras. The first line is a JSON header,\n # the rest of the file is structured but not strict JSON.\n panic_string=$(head -200 \"$PANIC_FILE\" | grep -m1 \"panic(\" | head -1)\n # Extract the bundleID of the panicking kext (best-effort)\n suspect_kext=$(grep -m1 -oE '\"bundleID\":\"[^\"]+\"' \"$PANIC_FILE\" | head -1 | sed 's/.*\"://; s/\"//g')\nelse\n panic_string=$(grep -m1 \"^panic(\" \"$PANIC_FILE\")\n # In old .panic format the \"Kernel Extensions in backtrace\" line lists suspects\n suspect_kext=$(awk '/Kernel Extensions in backtrace:/{getline; print; exit}' \"$PANIC_FILE\" | awk -F'[()]' '{print $2}')\nfi\n\nif [[ -n \"$panic_string\" ]]; then\n log_pass \"Panic string extracted\"\n note \" $panic_string\"\nfi\n\nif [[ -n \"$suspect_kext\" ]]; then\n case \"$suspect_kext\" in\n com.apple.*) log_warn \"Suspect kext\" \"$suspect_kext (Apple — harder to fix; check macOS update)\" ;;\n *) log_fail \"Suspect kext\" \"$suspect_kext (third-party — primary suspect)\" ;;\n esac\nelse\n log_info \"Suspect kext\" \"could not extract from report — check report manually\"\nfi\n\n# Match panic string against the common-causes catalog\nnote \" Pattern match (quick lookup; see references/panic-codes.md for full catalog):\"\ncase \"$panic_string\" in\n *\"Sleep wake failure\"*)\n note \" → Driver power-state bug. Often USB / Bluetooth / GPU. Check kext list around panic.\" ;;\n *\"Unresponsive bootstrap subsystem\"*)\n note \" → launchd deadlock. Usually a third-party LaunchDaemon. Audit /Library/LaunchDaemons/.\" ;;\n *\"WindowServer\"*)\n note \" → GPU driver / display kext fault. Try disabling external display, alternative GPU mode.\" ;;\n *\"double_fault\"*|*\"page_fault\"*)\n note \" → Kernel-mode memory corruption. Bad RAM or buggy kext. Run memtest from recoveryOS.\" ;;\n *\"panic_kthread\"*)\n note \" → Kernel watchdog timeout. A driver hung in infinite loop. Examine pre-panic kext activity.\" ;;\n *\"Unable to find driver\"*)\n note \" → Boot-time kext failed to load. Often after macOS update. Try safe-boot.\" ;;\n *)\n note \" → No quick-pattern match. See references/panic-codes.md.\" ;;\nesac\n\n# ----------------------------------------------------------------------------\nsection \"3. PRE-PANIC TIMELINE\"\n# ----------------------------------------------------------------------------\nif [[ -z \"$PANIC_TIME\" ]]; then\n PANIC_TIME=$(stat -f \"%Sm\" -t \"%Y-%m-%d %H:%M:%S\" \"$PANIC_FILE\" 2>/dev/null)\nfi\nnote \" Anchor: $PANIC_TIME (window: ${WINDOW_MIN} min before)\"\n\n# Convert anchor to epoch, compute start\nif anchor_epoch=$(date -j -f \"%Y-%m-%d %H:%M:%S\" \"$PANIC_TIME\" \"+%s\" 2>/dev/null); then\n start_epoch=$((anchor_epoch - WINDOW_MIN * 60))\n start_str=$(date -r \"$start_epoch\" \"+%Y-%m-%d %H:%M:%S\")\n note \" Searching unified log from $start_str to $PANIC_TIME ...\"\n\n # Filter the noisy stuff out; surface kernel + kext + IO + power events\n log show --start \"$start_str\" --end \"$PANIC_TIME\" --style compact \\\n --predicate '(subsystem == \"com.apple.kernel\" OR subsystem == \"com.apple.iokit\" OR processImagePath CONTAINS \"kernel\" OR senderImagePath CONTAINS \".kext\") AND (messageType == \"Default\" OR messageType == \"Error\" OR messageType == \"Fault\")' \\\n 2>/dev/null | tail -50 | sed 's/^/ /'\n\n log_info \"Pre-panic events captured\" \"${WINDOW_MIN} min window\"\nelse\n log_warn \"Pre-panic timeline\" \"could not parse panic timestamp; pass -t explicitly\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. CONTEXT: RECENT PANICS\"\n# ----------------------------------------------------------------------------\nrecent_panics=$(find \"$panic_dir\" -maxdepth 1 \\( -name \"*.panic\" -o -name \"Kernel*.ips\" \\) \\\n -mtime -30 2>/dev/null | wc -l | tr -d ' ')\nlog_info \"Panics in last 30 days\" \"$recent_panics\"\n\nif [[ \"$recent_panics\" -gt 1 ]]; then\n note \" Recent panic files:\"\n find \"$panic_dir\" -maxdepth 1 \\( -name \"*.panic\" -o -name \"Kernel*.ips\" \\) -mtime -30 2>/dev/null \\\n | xargs ls -lt 2>/dev/null | head -5 | awk '{print \" \"$NF\" — \"$6\" \"$7\" \"$8}'\nfi\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6597,"content_sha256":"0d4c4d08041764d812cd60f5997248aefb8b670d561df31bafd3e8bff43e85e2"},{"filename":"scripts/quickrun.sh","content":"#!/usr/bin/env bash\n# mac-ops :: quickrun.sh\n# One-shot \"what's wrong with my Mac?\" runner. Sequences the 5 highest-yield\n# audits and emits a single consolidated report.\n#\n# This is the script to run if you have 60 seconds and want to know whether\n# something needs attention. For deep dives, drill into individual scripts.\n\nset -u\n\nSHORT_DAYS=7\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --days) SHORT_DAYS=\"$2\"; shift 2 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --days N Lookback for log-based audits (default: 7)\n --json Emit consolidated NDJSON\n --redact Mask private addrs / hostnames / serials\n\nRuns (in order):\n 1. health-audit — orchestrator across 8 rungs\n 2. startup-audit — login items + launchd inventory\n 3. storage-pressure — APFS snapshots + caches breakdown\n 4. wake-reasons — pmset log analysis (last week)\n 5. tcc-audit (denied) — what privacy permissions were denied\n\nOutput: one consolidated SUMMARY at end with all PASS/FAIL counts and the\ntop 3 issues to address.\n\nTime: 60-90 seconds typical (log show queries dominate).\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\nsource \"$(dirname \"$0\")/_lib/panel.sh\"\npanel_init\n\nSCRIPTS_DIR=\"$(dirname \"$0\")\"\n\n# Aggregate counters across all sub-scripts\nTOTAL_PASS=0\nTOTAL_FAIL=0\nTOTAL_WARN=0\nTOTAL_INFO=0\nALL_FAILS=()\nALL_WARNS=()\n\nrun_subscript() {\n local label=\"$1\"\n local script=\"$2\"\n shift 2\n note \"\"\n note \"════════════════════════════════════════════════════════════════\"\n note \" $label\"\n note \"════════════════════════════════════════════════════════════════\"\n\n # Capture sub-script output (suppress its own SUMMARY since we'll do our own).\n # Force NO_PANEL so children emit parseable plain text — we render our own panel.\n local out\n out=$(NO_PANEL=1 bash \"$SCRIPTS_DIR/$script\" --quiet \"$@\" 2>&1 || true)\n\n # Echo the sub-script's findings (everything before its SUMMARY)\n echo \"$out\" | sed -n '/^=== /,/^=== SUMMARY ===/p' | sed '/^=== SUMMARY ===/q' | sed '$d'\n\n # Extract sub-script's pass/fail/warn counts from its SUMMARY line\n local summary_line\n summary_line=$(echo \"$out\" | grep -E \"^ PASS: [0-9]+\" | head -1)\n if [[ -n \"$summary_line\" ]]; then\n local pass fail warn info\n pass=$(echo \"$summary_line\" | grep -oE \"PASS: [0-9]+\" | awk '{print $2}')\n fail=$(echo \"$summary_line\" | grep -oE \"FAIL: [0-9]+\" | awk '{print $2}')\n warn=$(echo \"$summary_line\" | grep -oE \"WARN: [0-9]+\" | awk '{print $2}')\n info=$(echo \"$summary_line\" | grep -oE \"INFO: [0-9]+\" | awk '{print $2}')\n TOTAL_PASS=$((TOTAL_PASS + ${pass:-0}))\n TOTAL_FAIL=$((TOTAL_FAIL + ${fail:-0}))\n TOTAL_WARN=$((TOTAL_WARN + ${warn:-0}))\n TOTAL_INFO=$((TOTAL_INFO + ${info:-0}))\n fi\n\n # Collect FAIL and WARN lines for the consolidated summary\n while IFS= read -r line; do\n [[ -n \"$line\" ]] && ALL_FAILS+=(\"[$label] $line\")\n done \u003c \u003c(echo \"$out\" | grep -E \"^\\[FAIL\\]\" | sed 's/^\\[FAIL\\] //')\n while IFS= read -r line; do\n [[ -n \"$line\" ]] && ALL_WARNS+=(\"[$label] $line\")\n done \u003c \u003c(echo \"$out\" | grep -E \"^\\[WARN\\]\" | sed 's/^\\[WARN\\] //')\n}\n\nnote \" Starting mac-ops quickrun (this takes ~60-90s due to log show queries)...\"\n\nrun_subscript \"1. HEALTH AUDIT\" \"health-audit.sh\" --days \"$SHORT_DAYS\"\nrun_subscript \"2. STARTUP INVENTORY\" \"startup-audit.sh\"\nrun_subscript \"3. STORAGE PRESSURE\" \"storage-pressure.sh\"\nrun_subscript \"4. WAKE REASONS (last ${SHORT_DAYS}d)\" \"wake-reasons.sh\" --since \"${SHORT_DAYS}d\"\nrun_subscript \"5. TCC DENIALS\" \"tcc-audit.sh\" --denied\n\n# ----------------------------------------------------------------------------\nnote \"\"\nnote \"════════════════════════════════════════════════════════════════\"\nnote \" CONSOLIDATED VERDICT\"\nnote \"════════════════════════════════════════════════════════════════\"\n\nif [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"quickrun_summary\",\"pass\":%d,\"fail\":%d,\"warn\":%d,\"info\":%d,\"fail_count\":%d,\"warn_count\":%d}\\n' \\\n \"$TOTAL_PASS\" \"$TOTAL_FAIL\" \"$TOTAL_WARN\" \"$TOTAL_INFO\" \\\n \"${#ALL_FAILS[@]}\" \"${#ALL_WARNS[@]}\"\nelif panel_enabled; then\n hostname_short=$(scutil --get LocalHostName 2>/dev/null | head -c 30 || hostname -s | head -c 30)\n echo \"\"\n term_panel_open mac-ops \"mac-ops · quickrun\" \"$hostname_short\"\n term_panel_vert\n term_summary_line \"$((TOTAL_PASS+TOTAL_FAIL+TOTAL_WARN+TOTAL_INFO)) checks across 5 audits · $TOTAL_FAIL fail · $TOTAL_WARN warn\"\n term_panel_vert\n\n if [[ \"${#ALL_FAILS[@]}\" -gt 0 ]]; then\n term_section \"FAILED\" \"failing\" \"${#ALL_FAILS[@]}\"\n n=${#ALL_FAILS[@]}\n i=0\n for f in \"${ALL_FAILS[@]}\"; do\n i=$((i+1))\n if [[ \"$i\" -eq \"$n\" ]]; then connector=\"$TERM_TREE_LAST\"; else connector=\"$TERM_TREE_BRANCH\"; fi\n line=\"$f\"\n (( ${#line} > 100 )) && line=\"${line:0:97}...\"\n printf '%s %s %s\\n' \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$connector\" \\\n \"$line\"\n [[ \"$i\" -ge 10 ]] && { printf '%s %s %s\\n' \"$(term_color dim \"$TERM_TREE_VERT\")\" \"$TERM_TREE_LAST\" \"$(term_color dim \"... +$((n-10)) more\")\"; break; }\n done\n term_panel_vert\n fi\n\n if [[ \"${#ALL_WARNS[@]}\" -gt 0 ]]; then\n term_section \"WARN\" \"warning\" \"${#ALL_WARNS[@]}\"\n n=${#ALL_WARNS[@]}\n i=0\n for w in \"${ALL_WARNS[@]}\"; do\n i=$((i+1))\n if [[ \"$i\" -eq \"$n\" ]]; then connector=\"$TERM_TREE_LAST\"; else connector=\"$TERM_TREE_BRANCH\"; fi\n line=\"$w\"\n (( ${#line} > 100 )) && line=\"${line:0:97}...\"\n printf '%s %s %s\\n' \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$connector\" \\\n \"$line\"\n [[ \"$i\" -ge 10 ]] && { printf '%s %s %s\\n' \"$(term_color dim \"$TERM_TREE_VERT\")\" \"$TERM_TREE_LAST\" \"$(term_color dim \"... +$((n-10)) more\")\"; break; }\n done\n term_panel_vert\n fi\n\n term_section \"OK\" \"pass\" \"$TOTAL_PASS\"\n if [[ \"$TOTAL_INFO\" -gt 0 ]]; then\n term_section \"info\" \"info\" \"$TOTAL_INFO\"\n fi\n term_panel_vert\n\n health_state=\"healthy\"\n [[ \"$TOTAL_WARN\" -gt 0 ]] && health_state=\"warning\"\n [[ \"$TOTAL_FAIL\" -gt 0 ]] && health_state=\"critical\"\n right_health=\"$(term_health \"$health_state\" \"$TOTAL_FAIL fail · $TOTAL_WARN warn\")\"\n term_panel_close \"\" \"$right_health\"\n echo \"\"\n\n if [[ \"${#ALL_FAILS[@]}\" -eq 0 ]] && [[ \"${#ALL_WARNS[@]}\" -eq 0 ]]; then\n echo \" ✓ System looks clean. No FAILs or WARNs across 5 audits.\"\n fi\n echo \" Next: drill any FAIL into its source script (--verbose for detail)\"\nelse\n echo\n echo \" Aggregate: PASS $TOTAL_PASS FAIL $TOTAL_FAIL WARN $TOTAL_WARN INFO $TOTAL_INFO\"\n echo\n if [[ \"${#ALL_FAILS[@]}\" -gt 0 ]]; then\n echo \" ⚠ FAILURES (${#ALL_FAILS[@]}):\"\n for f in ${ALL_FAILS[@]+\"${ALL_FAILS[@]}\"}; do\n echo \" • $f\"\n done | head -10\n fi\n if [[ \"${#ALL_WARNS[@]}\" -gt 0 ]]; then\n echo\n echo \" ⚠ WARNINGS (${#ALL_WARNS[@]}):\"\n for w in ${ALL_WARNS[@]+\"${ALL_WARNS[@]}\"}; do\n echo \" • $w\"\n done | head -10\n fi\n if [[ \"${#ALL_FAILS[@]}\" -eq 0 ]] && [[ \"${#ALL_WARNS[@]}\" -eq 0 ]]; then\n echo \" ✓ System looks clean. No FAILs or WARNs across 5 audits.\"\n fi\n echo\n echo \" Next steps:\"\n echo \" - Drill into any FAIL: see 'Next:' lines emitted by each sub-script\"\n echo \" - Run individual scripts with --verbose for more detail\"\n echo \" - See references/worked-examples.md for diagnostic patterns\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":8353,"content_sha256":"054221b1f688dbdfefef9253d500f388e48b034eabbc978864a85c7ac2db3dbb"},{"filename":"scripts/recover-clone.sh","content":"#!/usr/bin/env bash\n# mac-ops :: recover-clone.sh\n# Safely image data off a failing drive using rsync with no retries.\n#\n# Cardinal rules (enforced):\n# 1. NEVER write to the source. Read-only operations only.\n# 2. NEVER use -y or --force on fsck against a failing drive.\n# 3. Default mode is DRY RUN — show what would be copied.\n#\n# Strategies (in order of safety):\n# --strategy=rsync Default. Resumable, skips errors, partial files OK.\n# --strategy=ditto macOS native. Preserves resource forks & xattrs.\n# --strategy=ddrescue Bit-level. Requires brew install gddrescue.\n\nset -u\n\nSOURCE=\"\"\nDEST=\"\"\nSTRATEGY=\"rsync\"\nAPPLY=0\nEXCLUDES=()\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -s|--source) SOURCE=\"$2\"; shift 2 ;;\n -d|--destination) DEST=\"$2\"; shift 2 ;;\n --strategy) STRATEGY=\"$2\"; shift 2 ;;\n --exclude) EXCLUDES+=(\"$2\"); shift 2 ;;\n --apply) APPLY=1; shift ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 -s \u003csource> -d \u003cdestination> [options]\n\n -s, --source PATH Source path (file or directory on failing drive)\n -d, --destination PATH Destination path (healthy drive)\n --strategy NAME rsync (default) | ditto | ddrescue\n --exclude PATTERN Add exclusion (can repeat)\n --apply Actually perform the clone (default: dry-run)\n --json, --redact, --quiet, --verbose\n\nExamples:\n $0 -s /Volumes/Failing/work -d /Volumes/Rescue/work\n $0 -s ~/Documents -d /Volumes/Backup/Documents --apply\n $0 -s /Volumes/Failing -d /Volumes/Rescue --strategy=ditto --apply\n\nStrategy reference:\n rsync Best general-purpose. --partial --inplace --no-whole-file\n --append-verify. Skips errors, resumable.\n ditto macOS-native. Preserves metadata, xattrs, ACLs, resource forks.\n Use when source has Pro app libraries (Final Cut etc).\n ddrescue For drives with many bad sectors. Bit-level, resumable via map\n file. Requires: brew install gddrescue.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nif [[ -z \"$SOURCE\" ]] || [[ -z \"$DEST\" ]]; then\n echo \"Error: -s and -d required\" >&2\n exit 2\nfi\n\nif [[ ! -e \"$SOURCE\" ]]; then\n echo \"Error: source does not exist: $SOURCE\" >&2\n exit 3\nfi\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\nnote \" Source: $SOURCE\"\nnote \" Destination: $DEST\"\nnote \" Strategy: $STRATEGY\"\nnote \" Mode: $([[ \"$APPLY\" -eq 1 ]] && echo APPLY || echo DRY-RUN)\"\n\n# ----------------------------------------------------------------------------\nsection \"1. PREFLIGHT\"\n# ----------------------------------------------------------------------------\n# Source size (read-only)\nsrc_size=$(du -sh \"$SOURCE\" 2>/dev/null | awk '{print $1}')\nlog_info \"Source size (du)\" \"${src_size:-?}\"\n\n# Destination free space\ndest_parent=$(dirname \"$DEST\")\n[[ -d \"$dest_parent\" ]] || { log_fail \"Destination parent dir\" \"$dest_parent does not exist\"; exit 3; }\ndest_free=$(df -h \"$dest_parent\" | awk 'NR==2{print $4}')\nlog_info \"Destination free space\" \"$dest_free\"\n\n# Sanity: source and dest on different volumes?\nsrc_vol=$(df \"$SOURCE\" 2>/dev/null | awk 'NR==2{print $1}')\ndest_vol=$(df \"$dest_parent\" 2>/dev/null | awk 'NR==2{print $1}')\nif [[ \"$src_vol\" == \"$dest_vol\" ]]; then\n log_warn \"Source/dest volume\" \"same volume — defeats purpose of cloning off failing drive\"\nelse\n log_pass \"Source/dest volume\" \"different volumes\"\nfi\n\n# Strategy availability check\ncase \"$STRATEGY\" in\n rsync)\n command -v rsync >/dev/null || { log_fail \"rsync\" \"not installed\"; exit 5; }\n log_pass \"rsync available\" \"$(rsync --version | head -1)\"\n ;;\n ditto)\n command -v ditto >/dev/null || { log_fail \"ditto\" \"not installed (built-in on macOS — shouldn't happen)\"; exit 5; }\n log_pass \"ditto available\"\n ;;\n ddrescue)\n if ! command -v ddrescue >/dev/null; then\n log_fail \"ddrescue\" \"not installed — run: brew install gddrescue\"\n exit 5\n fi\n log_pass \"ddrescue available\"\n ;;\n *)\n log_fail \"Strategy\" \"unknown: $STRATEGY\"; exit 2 ;;\nesac\n\n# ----------------------------------------------------------------------------\nsection \"2. BUILD COMMAND\"\n# ----------------------------------------------------------------------------\ncase \"$STRATEGY\" in\n rsync)\n cmd=(rsync -avh\n --partial --inplace --no-whole-file --append-verify\n --no-perms --no-owner --no-group\n --human-readable --info=progress2,stats2\n --ignore-errors)\n for e in ${EXCLUDES[@]+\"${EXCLUDES[@]}\"}; do cmd+=(\"--exclude=$e\"); done\n cmd+=(\"$SOURCE/\" \"$DEST/\")\n ;;\n ditto)\n cmd=(ditto --rsrc --extattr \"$SOURCE\" \"$DEST\")\n ;;\n ddrescue)\n # ddrescue needs a map file for resumability\n mapfile=\"${DEST}.ddrescue.map\"\n cmd=(ddrescue -n --idirect \"$SOURCE\" \"$DEST\" \"$mapfile\")\n note \" ddrescue map file: $mapfile\"\n ;;\nesac\n\nnote \" Command:\"\nnote \" ${cmd[*]}\"\n\n# ----------------------------------------------------------------------------\nsection \"3. EXECUTE\"\n# ----------------------------------------------------------------------------\nif [[ \"$APPLY\" -eq 0 ]]; then\n note \" (dry-run — pass --apply to actually clone)\"\n if [[ \"$STRATEGY\" == \"rsync\" ]]; then\n # rsync has its own --dry-run that previews actions\n rsync --dry-run -ah \"$SOURCE/\" \"$DEST/\" 2>&1 | tail -10 | sed 's/^/ /'\n fi\n emit_summary\n exit 0\nfi\n\n# Apply mode\nmkdir -p \"$DEST\" || { log_fail \"mkdir $DEST\" \"failed\"; exit 1; }\nlog_info \"Starting clone\" \"$STRATEGY\"\n\"${cmd[@]}\"\nrc=$?\nif [[ \"$rc\" -eq 0 ]]; then\n log_pass \"Clone finished\" \"exit 0\"\nelif [[ \"$rc\" -le 24 ]] && [[ \"$STRATEGY\" == \"rsync\" ]]; then\n # rsync 23-24 = partial transfer (some files failed); acceptable for failing drive\n log_warn \"Clone finished with rsync exit $rc\" \"some files unreadable — expected on failing drive\"\nelse\n log_fail \"Clone exit code\" \"$rc\"\nfi\n\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6072,"content_sha256":"f3047465dbba547484703936a8a3fd79d5296efd64751353b1e06f442d735113"},{"filename":"scripts/safe-disable-startup.sh","content":"#!/usr/bin/env bash\n# mac-ops :: safe-disable-startup.sh\n# Disable a startup item by name pattern. Reversible.\n#\n# Mechanisms handled (no sudo for user-scope):\n# - Login Items (via osascript / System Events)\n# - User LaunchAgents (launchctl disable gui/$UID/\u003clabel>)\n# - System LaunchAgents (launchctl disable gui/$UID/\u003clabel>)\n#\n# Mechanisms handled (sudo required):\n# - System LaunchDaemons (sudo launchctl disable system/\u003clabel>)\n#\n# Default mode is DRY RUN. Pass --apply to actually disable.\n# Use --enable to reverse a prior disable.\n\nset -u\n\nNAME_PATTERN=\"\"\nAPPLY=0\nENABLE=0\nLIST_ONLY=0\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -n|--name) NAME_PATTERN=\"$2\"; shift 2 ;;\n --list) LIST_ONLY=1; shift ;;\n --apply) APPLY=1; shift ;;\n --enable) ENABLE=1; APPLY=1; shift ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n -n, --name PATTERN Glob pattern matching the entry label / name\n --list List all currently-disabled launchd entries\n --apply Actually perform the disable (default: dry-run)\n --enable Re-enable a previously disabled item (implies --apply)\n\n --json, --redact, --quiet, --verbose Standard flags\n\nExamples:\n $0 --list # show current disable state\n $0 -n 'com.adobe.*' # dry-run: what would be disabled?\n $0 -n 'com.adobe.*' --apply # disable Adobe agents\n $0 -n 'com.adobe.*' --enable # re-enable\n $0 -n 'Adobe Updater' --apply # also matches Login Item by name\n\nNote: System LaunchDaemons (/Library/LaunchDaemons) require sudo and operate\non system/\u003clabel> instead of gui/\\$UID/\u003clabel>. The script asks for sudo only\nwhen needed.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\n# --list mode: show all disabled launchctl entries\n# ----------------------------------------------------------------------------\nif [[ \"$LIST_ONLY\" -eq 1 ]]; then\n section \"DISABLED LAUNCHD ENTRIES (user domain)\"\n if launchctl print-disabled \"gui/$UID\" 2>/dev/null | grep -E \"=> (true|disabled)$\" | sed 's/^/ /'; then\n :\n else\n note \" (no user-domain disables, or print-disabled requires newer macOS)\"\n fi\n section \"DISABLED LAUNCHD ENTRIES (system domain — sudo)\"\n if sudo -n launchctl print-disabled system 2>/dev/null | grep -E \"=> (true|disabled)$\" | sed 's/^/ /'; then\n :\n else\n note \" (system domain requires sudo, or no entries disabled)\"\n fi\n emit_summary\n exit 0\nfi\n\nif [[ -z \"$NAME_PATTERN\" ]]; then\n echo \"Error: -n PATTERN required (or --list)\" >&2\n exit \"$EXIT_USAGE\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"1. SEARCH MATCHES\"\n# ----------------------------------------------------------------------------\n\n# Find matching LaunchAgent plists (user + system)\nuser_matches=()\nsys_agent_matches=()\nsys_daemon_matches=()\n\nfor p in \"$HOME/Library/LaunchAgents\"/*.plist /Library/LaunchAgents/*.plist; do\n [[ -f \"$p\" ]] || continue\n label=$(/usr/libexec/PlistBuddy -c \"Print :Label\" \"$p\" 2>/dev/null || basename \"$p\" .plist)\n # Match by label OR filename\n if [[ \"$label\" == $NAME_PATTERN ]] || [[ \"$(basename \"$p\" .plist)\" == $NAME_PATTERN ]]; then\n case \"$p\" in\n \"$HOME\"/*) user_matches+=(\"$label|$p\") ;;\n *) sys_agent_matches+=(\"$label|$p\") ;;\n esac\n fi\ndone\n\nfor p in /Library/LaunchDaemons/*.plist; do\n [[ -f \"$p\" ]] || continue\n label=$(/usr/libexec/PlistBuddy -c \"Print :Label\" \"$p\" 2>/dev/null || basename \"$p\" .plist)\n if [[ \"$label\" == $NAME_PATTERN ]] || [[ \"$(basename \"$p\" .plist)\" == $NAME_PATTERN ]]; then\n sys_daemon_matches+=(\"$label|$p\")\n fi\ndone\n\n# Find matching Login Items (by name only; AppleScript glob match)\nlogin_item_matches=()\nif items=$(osascript \u003c\u003cAPPLESCRIPT 2>/dev/null\ntell application \"System Events\"\n set output to \"\"\n repeat with li in (every login item)\n set itemName to name of li\n if itemName is like \"$NAME_PATTERN\" then\n set output to output & itemName & linefeed\n end if\n end repeat\n return output\nend tell\nAPPLESCRIPT\n); then\n while IFS= read -r name; do\n [[ -n \"$name\" ]] && login_item_matches+=(\"$name\")\n done \u003c\u003c\u003c \"$items\"\nfi\n\ntotal_matches=$(( ${#user_matches[@]} + ${#sys_agent_matches[@]} + ${#sys_daemon_matches[@]} + ${#login_item_matches[@]} ))\n\nif [[ \"$total_matches\" -eq 0 ]]; then\n log_warn \"Matches for '$NAME_PATTERN'\" \"0 — nothing to do\"\n emit_summary\n exit \"$EXIT_NOT_FOUND\"\nfi\n\nlog_pass \"Matches for '$NAME_PATTERN'\" \"$total_matches\"\n[[ ${#user_matches[@]} -gt 0 ]] && note \" User LaunchAgents:\" && printf \" %s\\n\" \"${user_matches[@]%|*}\"\n[[ ${#sys_agent_matches[@]} -gt 0 ]] && note \" System LaunchAgents:\" && printf \" %s\\n\" \"${sys_agent_matches[@]%|*}\"\n[[ ${#sys_daemon_matches[@]} -gt 0 ]] && note \" System LaunchDaemons:\" && printf \" %s\\n\" \"${sys_daemon_matches[@]%|*}\"\n[[ ${#login_item_matches[@]} -gt 0 ]] && note \" Login Items:\" && printf \" %s\\n\" \"${login_item_matches[@]}\"\n\n# ----------------------------------------------------------------------------\nif [[ \"$APPLY\" -eq 0 ]]; then\n section \"2. DRY RUN — would $([[ \"$ENABLE\" -eq 1 ]] && echo enable || echo disable)\"\n note \" Pass --apply to perform the action.\"\n emit_summary\n exit 0\nfi\n# ----------------------------------------------------------------------------\n\nverb=$([[ \"$ENABLE\" -eq 1 ]] && echo enable || echo disable)\nsection \"2. APPLY — ${verb}\"\n\n# launchctl verb selection\nlctl_verb=$([[ \"$ENABLE\" -eq 1 ]] && echo enable || echo disable)\n\n# Disable user agents (no sudo)\nfor entry in ${user_matches[@]+\"${user_matches[@]}\"} ${sys_agent_matches[@]+\"${sys_agent_matches[@]}\"}; do\n label=\"${entry%|*}\"\n if launchctl \"$lctl_verb\" \"gui/$UID/$label\" 2>/dev/null; then\n log_pass \"launchctl $lctl_verb gui/$UID/$label\"\n else\n log_warn \"launchctl $lctl_verb gui/$UID/$label\" \"may already be in target state\"\n fi\ndone\n\n# Disable system daemons (sudo)\nif [[ ${#sys_daemon_matches[@]} -gt 0 ]]; then\n note \" System daemons require sudo:\"\n for entry in \"${sys_daemon_matches[@]}\"; do\n label=\"${entry%|*}\"\n if sudo launchctl \"$lctl_verb\" \"system/$label\" 2>/dev/null; then\n log_pass \"sudo launchctl $lctl_verb system/$label\"\n else\n log_warn \"sudo launchctl $lctl_verb system/$label\" \"may need sudo or already in state\"\n fi\n done\nfi\n\n# Login Items (via osascript)\nfor name in ${login_item_matches[@]+\"${login_item_matches[@]}\"}; do\n if [[ \"$ENABLE\" -eq 1 ]]; then\n log_warn \"Login Item '$name'\" \"re-enable requires manual re-add (System Settings → Login Items)\"\n else\n if osascript -e \"tell application \\\"System Events\\\" to delete login item \\\"$name\\\"\" 2>/dev/null; then\n log_pass \"Removed Login Item\" \"$name\"\n else\n log_warn \"Login Item '$name'\" \"removal failed (TCC may be blocking System Events)\"\n fi\n fi\ndone\n\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7336,"content_sha256":"c37857cb2f4865e710bc0c1355a96ccc7e22c0b09b9f9cdeacd6c2ea69ca1dbe"},{"filename":"scripts/spotlight-status.sh","content":"#!/usr/bin/env bash\n# mac-ops :: spotlight-status.sh\n# Spotlight (mds) health: indexing state per volume, daemon CPU/IO,\n# common reindex/repair operations.\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nCommon Spotlight fixes (in order of severity):\n 1. Wait — initial indexing on a new volume can take hours\n 2. mdutil -E /Volumes/X Erase + rebuild index for a volume\n 3. mdutil -i off /Volumes/X Disable Spotlight on a volume entirely\n 4. (Reboot — clears mds daemon state)\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. MDS / MDWORKER PROCESS HEALTH\"\n# ----------------------------------------------------------------------------\nnote \" Top mds-family processes by CPU:\"\nps -ArcS -o pcpu,rss,pid,comm 2>/dev/null | awk '/mds|mdworker|mdsync/' | head -10 | \\\n awk '{printf \" %5s%% RSS=%-8s PID=%s %s\\n\", $1, $2, $3, $4}'\n\n# Specifically check mds_stores — the kernel-side indexer doing the heavy lifting\nmds_cpu=$(ps -ArcS -o pcpu,comm 2>/dev/null | awk '$2==\"mds_stores\"{print $1; exit}')\nmds_cpu=\"${mds_cpu:-0}\"\nmds_int=${mds_cpu%.*}\nif [[ \"${mds_int:-0}\" -gt 80 ]]; then\n log_warn \"mds_stores CPU\" \"${mds_cpu}% — heavy indexing in progress\"\nelif [[ \"${mds_int:-0}\" -gt 30 ]]; then\n log_info \"mds_stores CPU\" \"${mds_cpu}% — moderate indexing\"\nelse\n log_pass \"mds_stores CPU\" \"${mds_cpu}%\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. INDEX STATUS PER VOLUME\"\n# ----------------------------------------------------------------------------\nmount | awk '/apfs/{print $3}' | while read -r vol; do\n [[ -z \"$vol\" ]] && continue\n # Skip system-managed read-only volumes — they never have indexes\n case \"$vol\" in\n /System/Volumes/VM|/System/Volumes/xarts|/System/Volumes/Hardware|/System/Volumes/iSCPreboot|/System/Volumes/Update*) continue ;;\n esac\n status=$(mdutil -s \"$vol\" 2>/dev/null | tail -1)\n case \"$status\" in\n *\"Indexing enabled\"*)\n log_pass \"$vol indexing\" \"enabled\"\n ;;\n *\"Indexing disabled\"*)\n log_info \"$vol indexing\" \"disabled (Spotlight will not search this volume)\"\n ;;\n *\"No index\"*|*\"not registered\"*)\n log_warn \"$vol indexing\" \"no index store on disk — search empty until rebuild\"\n ;;\n *\"unknown\"*)\n log_info \"$vol indexing\" \"system volume (no user index)\"\n ;;\n *)\n log_info \"$vol indexing\" \"$status\"\n ;;\n esac\ndone\n\n# ----------------------------------------------------------------------------\nsection \"3. INDEX STORE SIZES\"\n# ----------------------------------------------------------------------------\nnote \" On-disk Spotlight index size per volume:\"\nmount | awk '/apfs/{print $3}' | while read -r vol; do\n [[ -z \"$vol\" ]] && continue\n spot_dir=\"$vol/.Spotlight-V100\"\n if [[ -d \"$spot_dir\" ]]; then\n size=$(du -sh \"$spot_dir\" 2>/dev/null | awk '{print $1}')\n printf \" %-30s %s\\n\" \"$vol\" \"${size:-?}\"\n fi\ndone\n\n# ----------------------------------------------------------------------------\nsection \"4. RECENT MDS LOG ACTIVITY\"\n# ----------------------------------------------------------------------------\nmds_errors=$(log show --last 24h --style compact \\\n --predicate 'process == \"mds\" OR process == \"mds_stores\" OR process == \"mdworker_shared\"' \\\n 2>/dev/null | grep -iE \"(error|fault|crash)\" | head -10)\n\nif [[ -n \"$mds_errors\" ]]; then\n log_warn \"mds errors (24h)\" \"see below\"\n echo \"$mds_errors\" | sed 's/^/ /'\nelse\n log_pass \"mds errors (24h)\" \"none\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. INDEX PERMANENT EXCLUSIONS\"\n# ----------------------------------------------------------------------------\nexclusions=\"$HOME/Library/Preferences/com.apple.spotlight.plist\"\nif [[ -f \"$exclusions\" ]]; then\n note \" Per-user Spotlight preferences exist.\"\nfi\nsys_exclusions=\"/.Spotlight-V100\"\nif [[ -d \"$sys_exclusions\" ]]; then\n note \" Boot volume index dir present at /.Spotlight-V100\"\nfi\n\nnote \"\"\nnote \" To exclude a path from Spotlight (per-user):\"\nnote \" System Settings → Spotlight → Search Privacy → +\"\n\n# ----------------------------------------------------------------------------\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n note \" Common operations:\"\n note \" mdutil -s /Volumes/X Status per volume\"\n note \" sudo mdutil -E /Volumes/X Erase + rebuild index (heavy operation)\"\n note \" sudo mdutil -i off / Disable Spotlight on boot volume (drastic)\"\n note \" sudo mdutil -i on / Re-enable\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":4964,"content_sha256":"d46008321756aa8bacc52f2144b733db66b733476fab619cd3b73de1bb2f3466"},{"filename":"scripts/startup-audit.sh","content":"#!/usr/bin/env bash\n# mac-ops :: startup-audit.sh\n# Inventory every auto-start mechanism on this Mac.\n#\n# Covers:\n# - System Settings → Login Items (user-visible)\n# - User LaunchAgents ~/Library/LaunchAgents\n# - System LaunchAgents /Library/LaunchAgents\n# - System LaunchDaemons /Library/LaunchDaemons\n# - Apple LaunchAgents /System/Library/LaunchAgents (system-managed, usually skip)\n# - Privileged helpers /Library/PrivilegedHelperTools\n# - Legacy LoginHook `sudo defaults read com.apple.loginwindow LoginHook`\n\nset -u\n\nfor arg in \"$@\"; do\n case \"$arg\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json Emit NDJSON\n --redact Mask private addrs / hostnames\n --verbose Include /System/Library/LaunchAgents (Apple's own — usually noise)\n --quiet Suppress section banners\n\nReports total counts per mechanism + per-entry detail. To DISABLE an entry,\nuse scripts/safe-disable-startup.sh.\nEOF\n exit 0 ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. LOGIN ITEMS (System Settings → General → Login Items)\"\n# ----------------------------------------------------------------------------\nif items=$(osascript \u003c\u003c'APPLESCRIPT' 2>/dev/null\ntell application \"System Events\"\n set output to \"\"\n repeat with li in (every login item)\n set output to output & (name of li) & \"|\" & (path of li) & \"|\" & (hidden of li) & linefeed\n end repeat\n return output\nend tell\nAPPLESCRIPT\n); then\n if [[ -z \"$items\" ]]; then\n log_pass \"Login Items count\" \"0\"\n else\n count=$(echo \"$items\" | grep -c '|' || echo 0)\n log_info \"Login Items count\" \"$count\"\n note \" Items (name | path | hidden):\"\n echo \"$items\" | sed 's/^/ /'\n fi\nelse\n log_warn \"Login Items\" \"could not query System Events (TCC may be denying Automation)\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. USER LAUNCHAGENTS (~/Library/LaunchAgents)\"\n# ----------------------------------------------------------------------------\nagents_dir=\"$HOME/Library/LaunchAgents\"\nif [[ -d \"$agents_dir\" ]]; then\n count=$(find \"$agents_dir\" -maxdepth 1 -name \"*.plist\" 2>/dev/null | wc -l | tr -d ' ')\n log_info \"User LaunchAgents count\" \"$count\"\n if [[ \"$count\" -gt 0 ]]; then\n note \" Plists (label · RunAtLoad · KeepAlive · path):\"\n for p in \"$agents_dir\"/*.plist; do\n [[ -f \"$p\" ]] || continue\n # Try PlistBuddy first; fall back to plutil; fall back to filename\n label=$(/usr/libexec/PlistBuddy -c \"Print :Label\" \"$p\" 2>/dev/null) || label=\"\"\n [[ -z \"$label\" ]] && { label=$(plutil -extract Label raw -o - \"$p\" 2>/dev/null) || label=\"\"; }\n [[ -z \"$label\" ]] && label=\"$(basename \"$p\" .plist) (label unread)\"\n run_at_load=$(plutil -extract RunAtLoad raw -o - \"$p\" 2>/dev/null) || run_at_load=\"\"\n [[ -z \"$run_at_load\" ]] && run_at_load=\"no\"\n keep_alive=$(plutil -extract KeepAlive raw -o - \"$p\" 2>/dev/null) || keep_alive=\"\"\n [[ -z \"$keep_alive\" ]] && keep_alive=\"no\"\n prog=$(plutil -extract ProgramArguments.0 raw -o - \"$p\" 2>/dev/null) || prog=\"\"\n if [[ -z \"$prog\" ]]; then\n prog=$(plutil -extract Program raw -o - \"$p\" 2>/dev/null) || prog=\"\"\n fi\n [[ -z \"$prog\" ]] && prog=\"(no Program/ProgramArguments)\"\n printf \" %-45s · RunAtLoad=%s · KeepAlive=%s\\n %s\\n\" \"$label\" \"$run_at_load\" \"$keep_alive\" \"$prog\"\n done\n fi\nelse\n log_info \"User LaunchAgents directory\" \"absent\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. SYSTEM LAUNCHAGENTS (/Library/LaunchAgents)\"\n# ----------------------------------------------------------------------------\nsys_agents_dir=\"/Library/LaunchAgents\"\nif [[ -d \"$sys_agents_dir\" ]]; then\n count=$(find \"$sys_agents_dir\" -maxdepth 1 -name \"*.plist\" 2>/dev/null | wc -l | tr -d ' ')\n log_info \"System LaunchAgents count\" \"$count\"\n if [[ \"$count\" -gt 0 ]]; then\n note \" Plists (label · vendor-pattern hint):\"\n for p in \"$sys_agents_dir\"/*.plist; do\n [[ -f \"$p\" ]] || continue\n label=$(/usr/libexec/PlistBuddy -c \"Print :Label\" \"$p\" 2>/dev/null) || label=\"\"\n [[ -z \"$label\" ]] && { label=$(plutil -extract Label raw -o - \"$p\" 2>/dev/null) || label=\"\"; }\n [[ -z \"$label\" ]] && label=\"$(basename \"$p\" .plist) (label unread)\"\n hint=\"\"\n case \"$label\" in\n com.adobe.*) hint=\"Adobe (Creative Cloud helpers)\" ;;\n com.docker.*) hint=\"Docker Desktop\" ;;\n com.microsoft.*) hint=\"Microsoft (Office / Edge / Teams)\" ;;\n com.google.*) hint=\"Google (Chrome / Drive)\" ;;\n com.dropbox.*) hint=\"Dropbox\" ;;\n com.cisco.*) hint=\"Cisco (AnyConnect / WebEx)\" ;;\n com.paragon-*) hint=\"Paragon (NTFS / ExtFS)\" ;;\n org.openvpn.*) hint=\"OpenVPN / Tunnelblick\" ;;\n com.tailscale.*) hint=\"Tailscale\" ;;\n io.nextdns.*) hint=\"NextDNS\" ;;\n ch.protonvpn.*) hint=\"Proton VPN\" ;;\n esac\n printf \" %-50s%s\\n\" \"$label\" \"${hint:+— $hint}\"\n done\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. SYSTEM LAUNCHDAEMONS (/Library/LaunchDaemons)\"\n# ----------------------------------------------------------------------------\ndaemons_dir=\"/Library/LaunchDaemons\"\nif [[ -d \"$daemons_dir\" ]]; then\n count=$(find \"$daemons_dir\" -maxdepth 1 -name \"*.plist\" 2>/dev/null | wc -l | tr -d ' ')\n log_info \"System LaunchDaemons count\" \"$count\"\n if [[ \"$count\" -gt 0 ]]; then\n note \" Plists (label):\"\n for p in \"$daemons_dir\"/*.plist; do\n [[ -f \"$p\" ]] || continue\n label=$(/usr/libexec/PlistBuddy -c \"Print :Label\" \"$p\" 2>/dev/null) || label=\"\"\n [[ -z \"$label\" ]] && { label=$(plutil -extract Label raw -o - \"$p\" 2>/dev/null) || label=\"\"; }\n [[ -z \"$label\" ]] && label=\"$(basename \"$p\" .plist) (label unread)\"\n printf \" %s\\n\" \"$label\"\n done\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. PRIVILEGED HELPER TOOLS\"\n# ----------------------------------------------------------------------------\nhelpers_dir=\"/Library/PrivilegedHelperTools\"\nif [[ -d \"$helpers_dir\" ]]; then\n count=$(find \"$helpers_dir\" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' ')\n if [[ \"$count\" -gt 5 ]]; then\n log_warn \"Privileged helper tools\" \"$count — may include orphans from uninstalled apps\"\n else\n log_info \"Privileged helper tools\" \"$count\"\n fi\n if [[ \"$count\" -gt 0 ]]; then\n note \" Helpers:\"\n find \"$helpers_dir\" -maxdepth 1 -type f 2>/dev/null | sed 's/^/ /'\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"6. LEGACY LoginHook (rarely used these days)\"\n# ----------------------------------------------------------------------------\nif hook=$(sudo -n defaults read com.apple.loginwindow LoginHook 2>/dev/null); then\n log_warn \"LoginHook present\" \"$hook\"\nelse\n log_pass \"LoginHook\" \"none (or sudo declined)\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"7. CONFIGURATION PROFILES (may add login items / restrictions)\"\n# ----------------------------------------------------------------------------\nif profile_count=$(profiles list -type configuration 2>/dev/null | grep -c \"attribute:\"); then\n profile_count=\"${profile_count:-0}\"\n if [[ \"$profile_count\" -gt 0 ]]; then\n log_info \"Configuration profiles (user)\" \"$profile_count\"\n note \" Run 'sudo profiles list -type configuration' for system-wide profile list.\"\n else\n log_pass \"Configuration profiles (user)\" \"0\"\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"8. /System/Library/LaunchAgents (Apple's own — usually noise)\"\n# ----------------------------------------------------------------------------\nif [[ \"$VERBOSE\" -eq 1 ]]; then\n apple_agents=$(find /System/Library/LaunchAgents -maxdepth 1 -name \"*.plist\" 2>/dev/null | wc -l | tr -d ' ')\n log_info \"Apple-managed LaunchAgents\" \"$apple_agents (system-protected; informational only)\"\nelse\n note \" (skipped — pass --verbose to include Apple-managed agents)\"\nfi\n\n# ----------------------------------------------------------------------------\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n note \" To DISABLE an entry: scripts/safe-disable-startup.sh -n \u003cpattern>\"\n note \" To RE-ENABLE: scripts/safe-disable-startup.sh -n \u003cpattern> --enable\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":9089,"content_sha256":"f4563813a8f43b6ee2a87f18c4a3d18218b15e1b3795e2926d12f79070204f88"},{"filename":"scripts/storage-pressure.sh","content":"#!/usr/bin/env bash\n# mac-ops :: storage-pressure.sh\n# \"Disk is full but I deleted everything\" — explain macOS's purgeable space\n# accounting and surface the actual consumers (APFS snapshots, local Time\n# Machine backups, Spotlight index, iCloud cached files, etc).\n\nset -u\n\nVOL=\"/\"\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -v|--volume) VOL=\"$2\"; shift 2 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n -v, --volume PATH Volume to analyze (default: /)\n --json, --redact, --quiet, --verbose\n\nWhy \"About This Mac → Storage\" doesn't match du:\n - APFS local Time Machine snapshots: data deleted but retained for TM\n - iCloud cached files: shown as \"Purgeable\" — frees automatically under pressure\n - Spotlight index: ~.Spotlight-V100 hidden dir\n - Cached files in ~/Library/Caches, /var/folders\n - Sleepimage, swap files (in dynamic_pager dirs)\n\nCommon reclaims:\n tmutil thinlocalsnapshots / # remove eligible TM snapshots\n tmutil deletelocalsnapshots \u003cname> # specific snapshot\n diskutil apfs deleteSnapshot diskNsM \u003cname>\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\nif [[ ! -d \"$VOL\" ]]; then\n echo \"Error: $VOL is not a directory\" >&2\n exit 3\nfi\n\nnote \" Volume: $VOL\"\n\n# ----------------------------------------------------------------------------\nsection \"1. df vs APFS reality\"\n# ----------------------------------------------------------------------------\ndf -h \"$VOL\" 2>/dev/null | head -2 | sed 's/^/ /'\n\n# diskutil info gives the APFS-aware view including snapshot space\nnote \"\"\nnote \" diskutil info (APFS-aware):\"\ndisk_id=$(diskutil info \"$VOL\" 2>/dev/null | awk -F': *' '/Device Identifier/{print $2; exit}')\nif [[ -n \"$disk_id\" ]]; then\n diskutil info \"$disk_id\" 2>/dev/null | grep -E \"Allocation Block Size|Container Total Space|Container Free Space|Volume Used Space|Volume Free Space|APFS Snapshot|Capacity In Use\" | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. APFS SNAPSHOTS\"\n# ----------------------------------------------------------------------------\nsnap_count=$(tmutil listlocalsnapshots \"$VOL\" 2>/dev/null | grep -c \"com.apple\" | tr -d ' \\n')\nsnap_count=\"${snap_count:-0}\"\nif (( snap_count > 0 )); then\n log_info \"Local Time Machine snapshots\" \"$snap_count\"\n note \" Recent (last 10):\"\n tmutil listlocalsnapshots \"$VOL\" 2>/dev/null | tail -10 | sed 's/^/ /'\n\n # Calculate approximate space held by snapshots\n if [[ -n \"$disk_id\" ]]; then\n snap_space=$(diskutil apfs list 2>/dev/null | awk -v d=\"$disk_id\" '\n $0 ~ d {found=1}\n found && /Snapshot/ {print; if (++n >= 5) exit}\n ' | head -8)\n if [[ -n \"$snap_space\" ]]; then\n note \"\"\n note \" Snapshot space (from diskutil apfs list):\"\n echo \"$snap_space\" | sed 's/^/ /'\n fi\n fi\n\n if (( snap_count > 20 )); then\n log_warn \"Snapshot count\" \"$snap_count — consider 'tmutil thinlocalsnapshots $VOL'\"\n fi\nelse\n log_pass \"Local Time Machine snapshots\" \"0\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. iCLOUD CACHED FILES\"\n# ----------------------------------------------------------------------------\nicloud_dir=\"$HOME/Library/Mobile Documents\"\nif [[ -d \"$icloud_dir\" ]]; then\n icloud_size=$(du -sh \"$icloud_dir\" 2>/dev/null | awk '{print $1}')\n log_info \"iCloud Drive cache size\" \"${icloud_size:-?}\"\n note \" These are typically marked 'Purgeable' — macOS evicts under pressure.\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. CACHE / TEMPORARY DIRECTORIES\"\n# ----------------------------------------------------------------------------\nnote \" User caches:\"\nfor d in \"$HOME/Library/Caches\" \"$HOME/Library/Application Support/Caches\"; do\n if [[ -d \"$d\" ]]; then\n size=$(du -sh \"$d\" 2>/dev/null | awk '{print $1}')\n printf \" %s = %s\\n\" \"$d\" \"${size:-?}\"\n fi\ndone\n\nnote \"\"\nnote \" System caches:\"\nfor d in /Library/Caches /var/folders /private/var/log; do\n if [[ -d \"$d\" ]]; then\n size=$(sudo -n du -sh \"$d\" 2>/dev/null | awk '{print $1}')\n if [[ -z \"$size\" ]]; then\n # No sudo — try without\n size=$(du -sh \"$d\" 2>/dev/null | awk '{print $1}')\n fi\n printf \" %s = %s\\n\" \"$d\" \"${size:-?}\"\n fi\ndone\n\n# ----------------------------------------------------------------------------\nsection \"5. SLEEPIMAGE + SWAP\"\n# ----------------------------------------------------------------------------\nif [[ -f /private/var/vm/sleepimage ]]; then\n size=$(ls -lh /private/var/vm/sleepimage 2>/dev/null | awk '{print $5}')\n log_info \"Sleep image\" \"${size:-?} — equals RAM size; safe to ignore\"\nfi\n\nswap_files=$(ls /private/var/vm/swapfile* 2>/dev/null | wc -l | tr -d ' ')\nif [[ \"$swap_files\" -gt 0 ]]; then\n swap_total=$(ls -lh /private/var/vm/swapfile* 2>/dev/null | awk '{sum+=$5}END{print sum/1024/1024\" GB\"}')\n log_info \"Swap files\" \"$swap_files files (~$swap_total) — grows under memory pressure\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"6. SPOTLIGHT INDEX SIZE\"\n# ----------------------------------------------------------------------------\nspot_dir=\"$VOL/.Spotlight-V100\"\nif [[ -d \"$spot_dir\" ]]; then\n spot_size=$(sudo -n du -sh \"$spot_dir\" 2>/dev/null | awk '{print $1}')\n [[ -z \"$spot_size\" ]] && spot_size=\"(needs sudo to size)\"\n log_info \"Spotlight index size\" \"$spot_size\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"7. TOP 10 LARGEST DIRECTORIES IN ~ (heuristic)\"\n# ----------------------------------------------------------------------------\nnote \" This walks ~ — may take a moment on large home dirs.\"\ndu -sh \"$HOME\"/* 2>/dev/null | sort -rh | head -10 | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n note \" Reclaim playbook:\"\n note \" tmutil thinlocalsnapshots $VOL # trim eligible local TM snapshots\"\n note \" rm -rf ~/Library/Caches/* # clear per-user caches\"\n note \" docker system prune -a # Docker images/volumes\"\n note \" brew cleanup -s # Homebrew cached downloads\"\n note \" sudo periodic daily weekly monthly # rotate system logs\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6622,"content_sha256":"9a211714824cfc2b726d7ca0bc89f87596e07658bda70a184e591e0b91c84f75"},{"filename":"scripts/sysdiagnose-helper.sh","content":"#!/usr/bin/env bash\n# mac-ops :: sysdiagnose-helper.sh\n# Run Apple's sysdiagnose tool, inspect its output, and prepare a sanitized\n# version for sharing.\n#\n# sysdiagnose captures the WORKS — unified log dumps, system_profiler, kext\n# inventory, process listings, network state, IOReg, accessibility config,\n# spindump, etc. The output is huge (often 500MB-2GB compressed) and contains\n# personal data, hostnames, paths under /Users/, network info. Don't share\n# without inspection.\n\nset -u\n\nACTION=\"run\"\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --inspect) ACTION=\"inspect\"; shift ;;\n --inspect=*) ACTION=\"inspect\"; BUNDLE=\"${1#--inspect=}\"; shift ;;\n --list) ACTION=\"list\"; shift ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n (no args) Trigger sysdiagnose; report where the bundle is written\n --list List existing sysdiagnose bundles on this machine\n --inspect[=PATH] Inspect a bundle and report what it contains\n --json, --redact, --quiet, --verbose\n\nApple's sysdiagnose:\n Bundle location: /var/tmp/sysdiagnose_*.tar.gz\n Trigger via:\n sudo sysdiagnose CLI, prompts for trigger\n Option-Cmd-Ctrl-Shift-. Keyboard chord (system-wide)\n\nWhat's in a sysdiagnose bundle (high level):\n - Full unified log dump (last few hours / days)\n - system_profiler full report\n - kextstat / kext info\n - Process listing + memory usage\n - Network state (ifconfig, netstat, route, scutil)\n - pmset state and log\n - DiskUtil and APFS state\n - DiagnosticReports/ (crashes + panics)\n - Spindump (samples of running processes)\n - IORegistry dump\n - Configuration profiles\n\nPrivacy: bundles contain hostnames, usernames, paths under /Users/, IP\naddresses, sometimes app-specific identifiers. Inspect before sharing.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\ncase \"$ACTION\" in\n list)\n section \"1. EXISTING SYSDIAGNOSE BUNDLES\"\n bundles=$(ls -lt /var/tmp/sysdiagnose_*.tar.gz 2>/dev/null | head -10)\n if [[ -z \"$bundles\" ]]; then\n log_info \"Sysdiagnose bundles\" \"none found in /var/tmp\"\n else\n count=$(echo \"$bundles\" | wc -l | tr -d ' ')\n log_info \"Sysdiagnose bundles\" \"$count found\"\n echo \"$bundles\" | sed 's/^/ /'\n fi\n ;;\n\n inspect)\n section \"1. BUNDLE INSPECTION\"\n BUNDLE=\"${BUNDLE:-$(ls -t /var/tmp/sysdiagnose_*.tar.gz 2>/dev/null | head -1)}\"\n if [[ -z \"$BUNDLE\" ]] || [[ ! -f \"$BUNDLE\" ]]; then\n log_fail \"Bundle\" \"not found: $BUNDLE\"\n exit 3\n fi\n log_pass \"Bundle\" \"$BUNDLE\"\n size=$(ls -lh \"$BUNDLE\" 2>/dev/null | awk '{print $5}')\n note \" Size: $size\"\n\n # Probe contents without extracting\n note \"\"\n note \" Top-level contents:\"\n tar tzf \"$BUNDLE\" 2>/dev/null | awk -F/ '{print $1\"/\"$2}' | sort -u | head -20 | sed 's/^/ /'\n\n # Sensitive contents inventory\n note \"\"\n note \" Potentially sensitive contents:\"\n for pattern in \"logarchive\" \"DiagnosticReports\" \"system_profiler\" \"ifconfig\" \"scutil\" \"profiles\" \"TCC\"; do\n count=$(tar tzf \"$BUNDLE\" 2>/dev/null | grep -c \"$pattern\" || echo 0)\n count=\"${count:-0}\"\n if [[ \"$count\" -gt 0 ]]; then\n printf \" %-25s %s files\\n\" \"$pattern\" \"$count\"\n fi\n done\n\n note \"\"\n note \" Before sharing this bundle:\"\n note \" 1. Extract: tar xzf $BUNDLE -C /tmp/inspect\"\n note \" 2. Review: less /tmp/inspect/sysdiagnose_*/system_profiler.spx\"\n note \" 3. Search for sensitive content: grep -r 'private-data-pattern' /tmp/inspect\"\n note \" 4. If sharing publicly: use redaction tools (BBEdit, sed) on extracted log files first\"\n ;;\n\n run)\n section \"1. PRECONDITION CHECK\"\n # sysdiagnose needs sudo\n if ! sudo -n true 2>/dev/null; then\n log_warn \"sudo\" \"this script needs sudo to invoke sysdiagnose\"\n note \" Run with: sudo bash $0\"\n note \" Or trigger via keyboard chord: Option-Cmd-Ctrl-Shift-.\"\n note \" Or: sudo sysdiagnose -f /tmp/ (saves to /tmp instead of /var/tmp)\"\n emit_summary\n exit 5\n fi\n\n log_pass \"sudo\" \"available\"\n\n # Free space check\n free_mb=$(df -m /var/tmp 2>/dev/null | awk 'NR==2{print $4}')\n if [[ \"${free_mb:-0}\" -lt 2048 ]]; then\n log_warn \"Free space on /var/tmp\" \"${free_mb} MB — sysdiagnose may need 1-2 GB\"\n else\n log_pass \"Free space on /var/tmp\" \"${free_mb} MB\"\n fi\n\n section \"2. RUNNING SYSDIAGNOSE\"\n note \" This takes 5-15 minutes and produces a large bundle in /var/tmp.\"\n note \" Skipping the privacy prompt with -u (no UI) and not generating a profile (-Q):\"\n note \"\"\n sudo sysdiagnose -u -Q -A \"macops-helper\" 2>&1 | tail -10 | sed 's/^/ /'\n\n bundle=$(ls -t /var/tmp/sysdiagnose_*.tar.gz 2>/dev/null | head -1)\n if [[ -n \"$bundle\" ]] && [[ -f \"$bundle\" ]]; then\n log_pass \"Bundle written\" \"$bundle\"\n size=$(ls -lh \"$bundle\" | awk '{print $5}')\n note \" Size: $size\"\n note \"\"\n note \" Next:\"\n note \" bash $0 --inspect # see what's in the bundle\"\n note \" bash $0 --list # all bundles on this machine\"\n note \" To share with Apple support, upload directly via Apple Feedback Assistant.\"\n else\n log_warn \"Bundle\" \"not found after sysdiagnose ran\"\n fi\n ;;\nesac\n\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5768,"content_sha256":"6c2e5775cb39f84bb6f2627d026a4836c7fd30988bb8d4ff97328a51244e8101"},{"filename":"scripts/tcc-audit.sh","content":"#!/usr/bin/env bash\n# mac-ops :: tcc-audit.sh\n# Read the TCC (Transparency, Consent, Control) databases to surface which\n# apps have which permissions, what's been denied, and where to fix it.\n#\n# TCC databases:\n# ~/Library/Application Support/com.apple.TCC/TCC.db (user-scope)\n# /Library/Application Support/com.apple.TCC/TCC.db (system-scope, requires sudo)\n#\n# The user DB is readable in some macOS releases under SIP/FDA assumptions;\n# this script gracefully degrades when access is denied.\n\nset -u\n\nAPP_FILTER=\"\"\nSERVICE_FILTER=\"\"\nSHOW_DENIED_ONLY=0\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -a|--app) APP_FILTER=\"$2\"; shift 2 ;;\n -s|--service) SERVICE_FILTER=\"$2\"; shift 2 ;;\n --denied) SHOW_DENIED_ONLY=1; shift ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n -a, --app PATTERN Filter by bundle ID or name (e.g. -a slack, -a com.slack.*)\n -s, --service PATTERN Filter by TCC service (e.g. -s ScreenCapture, -s Camera)\n --denied Show only denied grants (the most common \"broken\" cause)\n\n --json, --redact, --quiet, --verbose\n\nExamples:\n $0 # all grants on this user\n $0 --denied # what apps were denied something\n $0 -a Slack # Slack's permission state\n $0 -s ScreenCapture # who has Screen Recording\n\nService catalog (most common):\n kTCCServiceScreenCapture Screen Recording\n kTCCServiceMicrophone Microphone\n kTCCServiceCamera Camera\n kTCCServiceAccessibility Accessibility (control your Mac)\n kTCCServiceSystemPolicyAllFiles Full Disk Access\n kTCCServicePostEvent Synthetic input events\n kTCCServiceListenEvent Input event listening\n kTCCServiceAppleEvents Automation (controlling other apps)\n kTCCServicePhotos Photos library\n kTCCServiceContactsFull Contacts\n kTCCServiceCalendar Calendars\n kTCCServiceReminders Reminders\n\nIf a script-controlled app has lost permission, the typical fix is:\n System Settings → Privacy & Security → \u003cService> → toggle the app off, then on\nor:\n tccutil reset \u003cService> \u003cbundle-id> (resets to \"Ask again\" — re-prompts user)\n\nRead references/tcc-mechanics.md for the deep dive.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\nuser_tcc=\"$HOME/Library/Application Support/com.apple.TCC/TCC.db\"\nsys_tcc=\"/Library/Application Support/com.apple.TCC/TCC.db\"\n\n# ----------------------------------------------------------------------------\nsection \"1. TCC.db ACCESSIBILITY CHECK\"\n# ----------------------------------------------------------------------------\nif [[ -r \"$user_tcc\" ]]; then\n log_pass \"User TCC.db readable\" \"$user_tcc\"\n user_readable=1\nelse\n log_warn \"User TCC.db readable\" \"no (this terminal needs Full Disk Access)\"\n user_readable=0\nfi\n\nif [[ -r \"$sys_tcc\" ]]; then\n log_pass \"System TCC.db readable\" \"$sys_tcc\"\n sys_readable=1\nelif sudo -n true 2>/dev/null; then\n if sudo -n test -r \"$sys_tcc\"; then\n log_info \"System TCC.db\" \"readable via sudo (cached credential)\"\n sys_readable=1\n else\n log_info \"System TCC.db\" \"would need sudo\"\n sys_readable=0\n fi\nelse\n log_info \"System TCC.db\" \"requires sudo (skipped)\"\n sys_readable=0\nfi\n\nif [[ \"$user_readable\" -eq 0 ]] && [[ \"$sys_readable\" -eq 0 ]]; then\n note \"\"\n note \" Neither TCC.db is readable from this terminal.\"\n note \" To grant Full Disk Access to your terminal:\"\n note \" System Settings → Privacy & Security → Full Disk Access → +\"\n note \" Add: /Applications/Utilities/Terminal.app (or your terminal app)\"\n note \" Then restart the terminal session.\"\n emit_summary\n exit 0\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. PERMISSION GRANTS\"\n# ----------------------------------------------------------------------------\n# auth_value semantics:\n# 0 = Denied\n# 1 = Unknown\n# 2 = Allowed\n# 3 = Limited (e.g. partial Photos access)\n# The 'service' column is kTCC* string; 'client' is bundle ID; 'client_type' is 0=bundle, 1=path\n# Modern TCC.db schemas have additional columns; we select defensively.\n\nbuild_filter() {\n local where=\"1=1\"\n [[ -n \"$APP_FILTER\" ]] && where=\"$where AND (client LIKE '%${APP_FILTER//\\'/}%' COLLATE NOCASE)\"\n [[ -n \"$SERVICE_FILTER\" ]] && where=\"$where AND (service LIKE '%${SERVICE_FILTER//\\'/}%' COLLATE NOCASE)\"\n [[ \"$SHOW_DENIED_ONLY\" -eq 1 ]] && where=\"$where AND auth_value = 0\"\n echo \"$where\"\n}\n\nquery_tcc() {\n local db=\"$1\"\n local where\n where=$(build_filter)\n sqlite3 -separator '|' \"$db\" \\\n \"SELECT service, client, auth_value, datetime(last_modified, 'unixepoch') FROM access WHERE $where ORDER BY auth_value, service, client\" \\\n 2>/dev/null\n}\n\nif [[ \"$user_readable\" -eq 1 ]]; then\n note \" --- User-scope (per-user permission grants) ---\"\n rows=$(query_tcc \"$user_tcc\")\n if [[ -z \"$rows\" ]]; then\n log_pass \"User TCC grants matching filter\" \"0 rows\"\n else\n count=$(echo \"$rows\" | wc -l | tr -d ' ')\n log_info \"User TCC grants\" \"$count rows\"\n note \" service | client | auth | last modified\"\n note \" -----------------------------|----------------------------------------------------|------|------------------------\"\n echo \"$rows\" | head -50 | awk -F'|' '{\n svc = substr($1, 1, 28)\n cli = substr($2, 1, 50)\n auth = $3\n ts = $4\n label = (auth == 0 ? \"DENY\" : (auth == 2 ? \"ALLOW\" : (auth == 3 ? \"LIM\" : \"?\")))\n printf \" %-28s | %-50s | %-4s | %s\\n\", svc, cli, label, ts\n }'\n denied=$(echo \"$rows\" | awk -F'|' '$3 == 0' | wc -l | tr -d ' ')\n if [[ \"$denied\" -gt 0 ]]; then\n log_warn \"User TCC denials\" \"$denied — see DENY rows above\"\n fi\n fi\nfi\n\nif [[ \"$sys_readable\" -eq 1 ]]; then\n note \"\"\n note \" --- System-scope (machine-wide grants, e.g. Full Disk Access) ---\"\n if [[ -r \"$sys_tcc\" ]]; then\n rows=$(query_tcc \"$sys_tcc\")\n else\n rows=$(sudo sqlite3 -separator '|' \"$sys_tcc\" \\\n \"SELECT service, client, auth_value, datetime(last_modified, 'unixepoch') FROM access WHERE $(build_filter) ORDER BY auth_value, service, client\" 2>/dev/null)\n fi\n if [[ -z \"$rows\" ]]; then\n log_pass \"System TCC grants matching filter\" \"0 rows\"\n else\n count=$(echo \"$rows\" | wc -l | tr -d ' ')\n log_info \"System TCC grants\" \"$count rows\"\n echo \"$rows\" | head -30 | awk -F'|' '{\n svc = substr($1, 1, 28)\n cli = substr($2, 1, 50)\n auth = $3\n ts = $4\n label = (auth == 0 ? \"DENY\" : (auth == 2 ? \"ALLOW\" : (auth == 3 ? \"LIM\" : \"?\")))\n printf \" %-28s | %-50s | %-4s | %s\\n\", svc, cli, label, ts\n }'\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"3. RECENT TCC PROMPTS\"\n# ----------------------------------------------------------------------------\n# Look for tccd / system extension prompt activity in the last 7 days\nprompts=$(log show --last 7d --style compact \\\n --predicate 'process == \"tccd\"' 2>/dev/null \\\n | grep -iE \"(prompt|denied|auth)\" | head -10)\n\nif [[ -n \"$prompts\" ]]; then\n log_info \"Recent tccd activity (7d)\" \"see below\"\n echo \"$prompts\" | sed 's/^/ /'\nelse\n log_pass \"Recent tccd activity\" \"quiet\"\nfi\n\n# ----------------------------------------------------------------------------\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n note \" Fix a denied grant:\"\n note \" 1) System Settings → Privacy & Security → \u003cService> → toggle app off then on\"\n note \" 2) Or: tccutil reset \u003cServiceShortName> \u003cbundle-id>\"\n note \" e.g. tccutil reset ScreenCapture com.tinyspeck.slackmacgap\"\n note \" See references/tcc-mechanics.md for the full service catalog.\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":8197,"content_sha256":"c6cf365af2b5c629182a285509412d436f6b05c6c13798e2889f51dbe02ee98c"},{"filename":"scripts/update-state.sh","content":"#!/usr/bin/env bash\n# mac-ops :: update-state.sh\n# macOS Software Update audit: auto-update settings, pending updates,\n# update history, App Store update settings.\n\nset -u\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --json, --redact, --quiet, --verbose\n\nReports:\n 1. macOS Software Update auto-policy\n 2. Pending updates (softwareupdate -l)\n 3. macOS version + build\n 4. Update install history (last 10)\n 5. App Store auto-update settings\n 6. Pending app updates from Mac App Store\n\nSkip the spinner: 'softwareupdate -l' contacts Apple's servers and can take\n30-60 seconds. The script prints expected-delay markers.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. AUTO-UPDATE POLICY\"\n# ----------------------------------------------------------------------------\n# Read from com.apple.SoftwareUpdate\nauto_check=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled 2>/dev/null)\nauto_download=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload 2>/dev/null)\nauto_install_macos=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates 2>/dev/null)\nauto_install_app=$(defaults read /Library/Preferences/com.apple.commerce AutoUpdate 2>/dev/null)\nauto_install_security=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate ConfigDataInstall 2>/dev/null)\n\nshow_bool() {\n case \"$1\" in\n 1) echo \"ON\" ;;\n 0) echo \"OFF\" ;;\n *) echo \"(unset)\" ;;\n esac\n}\n\nnote \" Software Update preferences:\"\nnote \" Check for updates automatically: $(show_bool \"$auto_check\")\"\nnote \" Download updates when available: $(show_bool \"$auto_download\")\"\nnote \" Install macOS updates: $(show_bool \"$auto_install_macos\")\"\nnote \" Install app updates from App Store: $(show_bool \"$auto_install_app\")\"\nnote \" Install system data + security: $(show_bool \"$auto_install_security\")\"\n\nif [[ \"$auto_check\" == \"1\" ]] && [[ \"$auto_install_security\" == \"1\" ]]; then\n log_pass \"Security updates\" \"auto-install ON\"\nelse\n log_warn \"Security updates\" \"auto-install OFF — patches won't apply without manual action\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"2. MACOS VERSION\"\n# ----------------------------------------------------------------------------\nprod=$(sw_vers -productName 2>/dev/null)\nver=$(sw_vers -productVersion 2>/dev/null)\nbuild=$(sw_vers -buildVersion 2>/dev/null)\nnote \" $prod $ver ($build)\"\nlog_info \"macOS version\" \"$ver build $build\"\n\n# ----------------------------------------------------------------------------\nsection \"3. PENDING UPDATES\"\n# ----------------------------------------------------------------------------\nnote \" Checking with Apple's servers (this can take 30-60s)...\"\npending=$(softwareupdate -l 2>&1 | tail -20)\nif echo \"$pending\" | grep -q \"No new software available\"; then\n log_pass \"Pending updates\" \"none — system is current\"\nelif echo \"$pending\" | grep -q \"Software Update found\"; then\n note \" Pending list:\"\n echo \"$pending\" | grep -E \"(\\*|^Software|Title:|Action:|Recommended:)\" | head -20 | sed 's/^/ /'\n update_count=$(echo \"$pending\" | grep -c \"Title:\" || echo 0)\n log_warn \"Pending updates\" \"$update_count items pending\"\nelse\n log_info \"softwareupdate output\" \"$(echo \"$pending\" | head -3 | tr '\\n' ' ')\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. RECENT UPDATE HISTORY\"\n# ----------------------------------------------------------------------------\nhistory_plist=\"/Library/Updates/index.plist\"\nnote \" Last 10 install events from /Library/Updates (best-effort):\"\nif [[ -r \"$history_plist\" ]]; then\n # Can't easily parse the plist without knowing the schema; show\n # mtime of the directory entries as a proxy\n ls -lt /Library/Updates 2>/dev/null | head -10 | sed 's/^/ /'\nfi\n\n# Also check installer logs\nrecent_installs=$(log show --last 30d --style compact \\\n --predicate 'process == \"softwareupdated\" AND eventMessage CONTAINS[c] \"installed\"' \\\n 2>/dev/null | tail -5)\nif [[ -n \"$recent_installs\" ]]; then\n note \"\"\n note \" Recent softwareupdated install entries (log, last 30d):\"\n echo \"$recent_installs\" | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nsection \"5. MAS / APP STORE\"\n# ----------------------------------------------------------------------------\nmas_check=$(defaults read /Library/Preferences/com.apple.commerce AutoUpdate 2>/dev/null)\nnote \" App Store auto-update: $(show_bool \"$mas_check\")\"\n\n# Is mas installed (third-party MAS CLI)?\nif command -v mas >/dev/null 2>&1; then\n note \"\"\n note \" mas (Mac App Store CLI) is installed.\"\n mas_outdated=$(mas outdated 2>/dev/null)\n if [[ -n \"$mas_outdated\" ]]; then\n n=$(echo \"$mas_outdated\" | wc -l | tr -d ' ')\n log_info \"Pending MAS updates\" \"$n (via mas outdated)\"\n echo \"$mas_outdated\" | head -10 | sed 's/^/ /'\n else\n log_pass \"MAS apps\" \"all up to date\"\n fi\nfi\n\n# ----------------------------------------------------------------------------\nsection \"6. RECOMMENDED UPDATES\"\n# ----------------------------------------------------------------------------\n# Recommended security/system updates that Apple wants you to install\n# (subset of softwareupdate -l output)\nrecommended=$(softwareupdate -l 2>&1 | awk '/Recommended: YES/{print prev} {prev=$0}' | head -5)\nif [[ -n \"$recommended\" ]]; then\n log_warn \"Recommended updates pending\" \"see list\"\n echo \"$recommended\" | sed 's/^/ /'\nfi\n\n# ----------------------------------------------------------------------------\nemit_summary\n\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n echo\n note \" Install playbook:\"\n note \" softwareupdate -l # list pending\"\n note \" sudo softwareupdate -i -a -R # install all (recommended), reboot if needed\"\n note \" softwareupdate --install \u003cname> # specific update\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6238,"content_sha256":"3322a29a7798e3e063586fb6239964e16a7b8fc4d03e4978c01524bdf4e0c32e"},{"filename":"scripts/wake-reasons.sh","content":"#!/usr/bin/env bash\n# mac-ops :: wake-reasons.sh\n# Why does this Mac wake up? Breakdown of pmset -g log wake events by cause.\n#\n# Common wake reason classes:\n# UserActivity / EHCx — user touched the keyboard / trackpad / a peripheral\n# BT.HID — Bluetooth keyboard/mouse activity\n# RTC / SMC — scheduled wake (Power Nap, Time Machine, calendar)\n# PWRB — power button pressed\n# USB.lid / Notifier — lid open or wake-via-USB device\n# Maintenance — system maintenance wake (dark wake)\n# Network — Wake-on-LAN / Bluetooth proximity\n\nset -u\n\nSINCE_DAYS=7\nTOP_N=15\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --since)\n # Accept '7d' or '24h' or just N (days)\n v=\"$2\"; shift 2\n case \"$v\" in\n *d) SINCE_DAYS=\"${v%d}\" ;;\n *h) SINCE_DAYS=$(( ${v%h} / 24 )); [[ \"$SINCE_DAYS\" -lt 1 ]] && SINCE_DAYS=1 ;;\n *) SINCE_DAYS=\"$v\" ;;\n esac\n ;;\n --top) TOP_N=\"$2\"; shift 2 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [options]\n\n --since 7d|24h|N Lookback window (default: 7d)\n --top N Show top N wake reasons (default: 15)\n --json, --redact, --quiet, --verbose\n\nWake reason quick reference:\n UserActivity Display/keyboard/trackpad — user-driven, expected\n BT.HID Bluetooth keyboard/mouse activity (often phantom at night)\n RTC Real-time clock — scheduled wake (Power Nap, calendar)\n PWRB Power button — manual wake\n USB.lid Lid open\n Maintenance Background maintenance (dark wake)\n Network WoL or Bluetooth proximity peer\n\nHeavy BT.HID wakes overnight usually mean a Bluetooth keyboard is \"tapping\" the\ndisplay awake — easy fix is to disable \"Wake for Bluetooth\" or unpair the device.\n\nHeavy RTC wakes can mean Power Nap is enabled with too much background work.\nEOF\n exit 0 ;;\n *) shift ;;\n esac\ndone\n\nsource \"$(dirname \"$0\")/_lib/common.sh\"\nparse_common_flags \"$@\"\nmaybe_filter_self \"$@\"\n\n# ----------------------------------------------------------------------------\nsection \"1. WAKE PATTERN OVERVIEW\"\n# ----------------------------------------------------------------------------\nnote \" Lookback: ${SINCE_DAYS}d (pmset log retains roughly 7-14 days)\"\n\n# pmset -g log format on modern macOS:\n# \"2026-05-10 02:40:55 +1000 DarkWake DarkWake from Deep Idle [CDNPB] : due to NUB.SPMI0Sw3IRQ nub-spmi-a0.0x59 ... rtc/Maintenance ...\"\n# Wake reasons appear after \"due to\" and end at \"Using\" or end-of-line.\n# Categories: rtc/Maintenance, rtc/SleepService, SMC.OutboxNotEmpty, NUB.SPMI*, etc.\nsince_epoch=$(($(date +%s) - SINCE_DAYS * 86400))\nsince_str=$(date -r \"$since_epoch\" \"+%Y-%m-%d\")\n\nraw=$(pmset -g log 2>/dev/null | awk -v since=\"$since_str\" '\n $1 >= since && ($0 ~ /DarkWake/ || $0 ~ /[[:space:]]Wake[[:space:]]/) {print}\n')\n\nwake_count=$(echo \"$raw\" | grep -c . || echo 0)\nif [[ \"$wake_count\" -eq 0 ]]; then\n log_info \"Wakes (since $since_str)\" \"0 — Mac hasn't slept, or pmset log was cleared\"\n emit_summary\n exit 0\nfi\n\nlog_info \"Total wake events (since $since_str)\" \"$wake_count\"\n\n# ----------------------------------------------------------------------------\nsection \"2. WAKE REASONS BY CLASS\"\n# ----------------------------------------------------------------------------\nnote \" Wake-cause class | count | pct\"\nnote \" -----------------|-------|----\"\n\n# Extract the bit after \"due to\" up to \"Using\" — these are the cause tokens.\n# Then classify by first significant token.\nreasons_raw=$(echo \"$raw\" | sed -nE 's/.*due to (.*) Using.*/\\1/p; s/.*due to (.*)/\\1/p' \\\n | awk '\n {\n # Each line is a series of tokens. The most informative is usually the last\n # one before category-style \"rtc/X\" or \"wifi/\" or similar slash-form.\n for (i=1; i\u003c=NF; i++) {\n if ($i ~ /\\//) { print $i; next }\n }\n print $1 # fallback to first token\n }\n ')\n\necho \"$reasons_raw\" | sort | uniq -c | sort -rn | head -\"$TOP_N\" | \\\nwhile read -r count reason; do\n pct=$(( count * 100 / (wake_count > 0 ? wake_count : 1) ))\n class=\"?\"\n case \"$reason\" in\n rtc/Maintenance*|rtc/Power*) class=\"rtc scheduled\" ;;\n rtc/SleepService*) class=\"push-svc wake\" ;;\n rtc/*) class=\"rtc\" ;;\n SMC.OutboxNotEmpty*|smc/*) class=\"hardware (SMC)\" ;;\n NUB.SPMI*|nub-spmi*) class=\"USB/peripheral\" ;;\n wifibt/*|wlan/*) class=\"wifi/bluetooth\" ;;\n EHC*|HID*|UserActivity) class=\"user input\" ;;\n BT*) class=\"bluetooth peer\" ;;\n PWRB*|PowerButton*) class=\"power button\" ;;\n Maintenance*) class=\"maintenance\" ;;\n Network*|WoL*) class=\"network\" ;;\n *) class=\"other\" ;;\n esac\n printf \" %-18s | %5d | %3d%% (%s)\\n\" \"$class\" \"$count\" \"$pct\" \"$reason\"\ndone\n\n# ----------------------------------------------------------------------------\nsection \"3. DARK WAKES (background maintenance)\"\n# ----------------------------------------------------------------------------\n# pmset log line format: \"DATE TIME TZ DarkWake \\tDarkWake from ...\"\n# The literal \"DarkWake\" appears in column 4 (after date/time/tz) AND in the message\ndark_wakes=$(echo \"$raw\" | awk '$4==\"DarkWake\"' | wc -l | tr -d ' ')\nlog_info \"Dark wakes\" \"$dark_wakes\"\n\nif [[ \"$dark_wakes\" -gt 50 ]]; then\n log_warn \"Dark wake count\" \"$dark_wakes — frequent background wakes drain battery\"\n note \" Common causes:\"\n note \" • Power Nap enabled (System Settings → Battery → Options)\"\n note \" • Backup destinations (Time Machine, Backblaze) running\"\n note \" • Calendar / Contacts / iCloud sync\"\nfi\n\n# ----------------------------------------------------------------------------\nsection \"4. WAKE TIMING (last 24h)\"\n# ----------------------------------------------------------------------------\nyesterday=$(date -v-1d \"+%Y-%m-%d\")\nnote \" Wakes since $yesterday:\"\necho \"$raw\" | awk -v y=\"$yesterday\" '$1 >= y {print \" \"$1, $2, $0}' | grep -oE \"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9:]+ .* Wake reason: [A-Za-z0-9_.]+\" | tail -20\n\n# ----------------------------------------------------------------------------\nsection \"5. ASSERTIONS HOLDING SYSTEM AWAKE\"\n# ----------------------------------------------------------------------------\nnote \" Current pmset assertions (who's preventing sleep right now):\"\npmset -g assertions 2>/dev/null | grep -E \"(IDLE|PreventUserIdleSystemSleep|PreventSystemSleep|PreventDisplay)\" | head -10 | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nsection \"6. SLEEP/WAKE PREFERENCES\"\n# ----------------------------------------------------------------------------\nnote \" pmset -g (custom settings):\"\npmset -g custom 2>/dev/null | head -25 | sed 's/^/ /'\n\n# ----------------------------------------------------------------------------\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7179,"content_sha256":"d99f445147ce44b3f004cfaa1d5488a60738e2c0450cf54b639307d55c458ec2"},{"filename":"tests/run.sh","content":"#!/usr/bin/env bash\n# mac-ops :: tests/run.sh\n# Lightweight self-tests. Run from repo root:\n# bash skills/mac-ops/tests/run.sh\n#\n# Validates structural and output invariants WITHOUT trying to simulate\n# broken macOS state. Catches regressions in:\n# - bash syntax / unbound vars / set -u trips\n# - section headers + ordering\n# - --json producing parseable NDJSON\n# - --redact masking private addrs / tailnet names\n# - --help working for every script\n# - summary block format\n\nset -u\n\nPASS=0\nFAIL=0\nFAILED_TESTS=()\n\nassert() {\n local name=\"$1\"; shift\n if \"$@\"; then\n PASS=$((PASS+1))\n printf \" [PASS] %s\\n\" \"$name\"\n else\n FAIL=$((FAIL+1))\n FAILED_TESTS+=(\"$name\")\n printf \" [FAIL] %s\\n\" \"$name\"\n fi\n}\n\ncontains() { local hay=\"$1\" needle=\"$2\"; [[ \"$hay\" == *\"$needle\"* ]]; }\nnot_contains() { local hay=\"$1\" needle=\"$2\"; [[ \"$hay\" != *\"$needle\"* ]]; }\n\n# Skip on non-macOS\nif [[ \"$(uname -s)\" != \"Darwin\" ]]; then\n echo \"Skipping: mac-ops tests only run on macOS (this is $(uname -s))\"\n exit 0\nfi\n\nhere=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nroot=\"$(cd \"$here/..\" && pwd)\"\n\necho \"=== mac-ops self-tests ===\"\necho \"Root: $root\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- Script parse + permissions ---\"\n# ----------------------------------------------------------------------------\n\nfor f in \"$root\"/scripts/*.sh; do\n name=$(basename \"$f\")\n # bash -n parses without executing\n if bash -n \"$f\" 2>/dev/null; then\n assert \"parse: $name\" true\n else\n assert \"parse: $name\" false\n fi\n # executable bit set\n if [[ -x \"$f\" ]]; then\n assert \"executable: $name\" true\n else\n assert \"executable: $name\" false\n fi\ndone\n\n# ----------------------------------------------------------------------------\necho\necho \"--- --help works for every script ---\"\n# ----------------------------------------------------------------------------\n\nfor f in \"$root\"/scripts/*.sh; do\n name=$(basename \"$f\")\n out=$(bash \"$f\" --help 2>&1)\n assert \"--help: $name returns usage\" contains \"$out\" \"Usage:\"\ndone\n\n# ----------------------------------------------------------------------------\necho\necho \"--- health-audit structural ---\"\n# ----------------------------------------------------------------------------\n\naudit_out=$(bash \"$root/scripts/health-audit.sh\" --days 1 --quiet 2>&1)\nassert \"health-audit emits SUMMARY block\" contains \"$audit_out\" \"=== SUMMARY ===\"\nassert \"health-audit shows PASS counts\" contains \"$audit_out\" \"PASS:\"\nassert \"health-audit runs without unbound vars\" not_contains \"$audit_out\" \"unbound variable\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- --json produces pure NDJSON ---\"\n# ----------------------------------------------------------------------------\n\n# Capture stdout only — JSON contract is \"stdout = NDJSON, stderr may have noise\"\njson_out=$(bash \"$root/scripts/health-audit.sh\" --days 1 --json 2>/dev/null)\njson_lines=$(echo \"$json_out\" | grep -c '^{' | tr -d '\\n ')\nnon_json=$(echo \"$json_out\" | grep -v '^{' | grep -c . | tr -d '\\n ')\nassert \"--json: at least one JSON record\" bash -c \"[[ \\\"$json_lines\\\" -ge 1 ]]\"\nassert \"--json: stdout is pure NDJSON (no non-JSON)\" bash -c \"[[ \\\"$non_json\\\" -eq 0 ]]\"\nassert \"--json: includes summary record\" contains \"$json_out\" '\"type\":\"summary\"'\n\n# ----------------------------------------------------------------------------\necho\necho \"--- --redact masks private addrs ---\"\n# ----------------------------------------------------------------------------\n\n# Use startup-audit since it lists Adobe-style paths under /Users/...\nredact_out=$(bash \"$root/scripts/startup-audit.sh\" --redact --quiet 2>&1)\n# Should NOT contain raw 192.168.x.x or .ts.net hostnames\nleaks=$(echo \"$redact_out\" | grep -E '\\b192\\.168\\.[0-9]+\\.[0-9]+\\b' | grep -v '192.168.X.X')\nassert \"--redact: no 192.168.* leak in startup-audit\" bash -c \"[[ -z \\\"$leaks\\\" ]]\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- startup-audit produces clean output ---\"\n# ----------------------------------------------------------------------------\n\nstartup_out=$(bash \"$root/scripts/startup-audit.sh\" --quiet 2>&1)\n# Plutil errors should be filtered (we use || val=\"\" pattern)\nassert \"startup-audit: no plutil 'Could not extract'\" not_contains \"$startup_out\" \"Could not extract value\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- safe-disable-startup --list works ---\"\n# ----------------------------------------------------------------------------\n\nlist_out=$(bash \"$root/scripts/safe-disable-startup.sh\" --list 2>&1)\nassert \"--list returns SUMMARY\" contains \"$list_out\" \"SUMMARY\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- panic-triage handles 'no panics' gracefully ---\"\n# ----------------------------------------------------------------------------\n\n# Most dev Macs have no recent panics. Verify the script doesn't error.\npanic_out=$(bash \"$root/scripts/panic-triage.sh\" --quiet 2>&1 || true)\nassert \"panic-triage runs without crashing\" contains \"$panic_out\" \"PANIC REPORT\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- tcc-audit gracefully handles permission denial ---\"\n# ----------------------------------------------------------------------------\n\ntcc_out=$(bash \"$root/scripts/tcc-audit.sh\" --quiet 2>&1)\n# Should exit cleanly even without TCC.db read access\nassert \"tcc-audit reaches SUMMARY (or handles no-access path)\" bash -c \"echo '$tcc_out' | grep -qE 'SUMMARY|TCC.db readable'\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- wake-reasons parses real pmset log ---\"\n# ----------------------------------------------------------------------------\n\nwake_out=$(bash \"$root/scripts/wake-reasons.sh\" --since 1d --quiet 2>&1)\nassert \"wake-reasons reaches SUMMARY\" contains \"$wake_out\" \"SUMMARY\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- spotlight-status filters system volumes ---\"\n# ----------------------------------------------------------------------------\n\nspot_out=$(bash \"$root/scripts/spotlight-status.sh\" --quiet 2>/dev/null)\n# Should NOT have \"Error: unknown indexing state\" leak from system vols\nerr_leaks=$(echo \"$spot_out\" | grep -c \"unknown indexing state\" | tr -d '\\n ')\nassert \"spotlight-status: system-vol error filtering\" bash -c \"[[ \\\"${err_leaks:-0}\\\" -le 0 ]]\"\n\n# ----------------------------------------------------------------------------\necho\necho \"--- All 12 scripts present ---\"\n# ----------------------------------------------------------------------------\n\nexpected_scripts=(\n health-audit.sh panic-triage.sh startup-audit.sh safe-disable-startup.sh\n disk-health.sh drive-dependencies.sh boot-perf.sh recover-clone.sh\n tcc-audit.sh wake-reasons.sh spotlight-status.sh storage-pressure.sh\n kext-audit.sh firewall-audit.sh network-locations.sh\n sysdiagnose-helper.sh brew-health.sh update-state.sh media-libraries.sh\n keychain-audit.sh bluetooth-audit.sh font-audit.sh quickrun.sh\n)\nfor s in \"${expected_scripts[@]}\"; do\n assert \"script exists: $s\" test -f \"$root/scripts/$s\"\ndone\n\n# ----------------------------------------------------------------------------\necho\necho \"--- All 7 reference docs present ---\"\n# ----------------------------------------------------------------------------\n\nexpected_refs=(\n storage-events.md recovery-patterns.md tcc-mechanics.md\n launchd-deep-dive.md panic-codes.md startup-mechanisms.md\n remote-diagnostics.md apple-silicon-specifics.md\n mac-vs-windows-ops.md worked-examples.md\n)\nfor r in \"${expected_refs[@]}\"; do\n assert \"reference exists: $r\" test -f \"$root/references/$r\"\ndone\n\n# ----------------------------------------------------------------------------\necho\necho \"=== TOTAL: $PASS pass, $FAIL fail ===\"\nif [[ \"$FAIL\" -gt 0 ]]; then\n echo \"Failed tests:\"\n for t in \"${FAILED_TESTS[@]}\"; do echo \" - $t\"; done\n exit 1\nfi\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":8165,"content_sha256":"d8d552578735316674a577026a58b4f10ac7f80f2965527d19e37b7e7137adf8"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"mac-ops","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Helps with","type":"text"}]},{"type":"paragraph","content":[{"text":"Slow Mac that used to be fast — bloat accumulation across the four startup mechanisms (Login Items, ","type":"text"},{"text":"~/Library/LaunchAgents","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"/Library/LaunchAgents","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"/Library/LaunchDaemons","type":"text","marks":[{"type":"code_inline"}]},{"text":"). The same machine still boots fast once those are inventoried and trimmed.","type":"text"}]},{"type":"paragraph","content":[{"text":"Failing drives that nobody's spotted yet. macOS doesn't shout the way Windows does — IO errors live in ","type":"text"},{"text":"log show --predicate 'subsystem == \"com.apple.iokit\"'","type":"text","marks":[{"type":"code_inline"}]},{"text":" and APFS surfaces them via ","type":"text"},{"text":"AppleAPFSContainerScheme","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"AppleNVMe*","type":"text","marks":[{"type":"code_inline"}]},{"text":" provider messages. Healthy SSDs produce zero of these per month; dozens means active failure even when \"About This Mac → Storage\" still shows green.","type":"text"}]},{"type":"paragraph","content":[{"text":"Kernel panics with no obvious cause. The ","type":"text"},{"text":".panic","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":".ips","type":"text","marks":[{"type":"code_inline"}]},{"text":" files in ","type":"text"},{"text":"/Library/Logs/DiagnosticReports/","type":"text","marks":[{"type":"code_inline"}]},{"text":" carry the panic string, kernel call stack, and (critically) the loaded kext list. A panic mentioning a third-party kext (","type":"text"},{"text":"com.eltima.ProductX","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"com.paragon.NTFS","type":"text","marks":[{"type":"code_inline"}]},{"text":", anti-virus drivers) tells a completely different story than a panic in core Apple code (","type":"text"},{"text":"AppleIntelKBL Graphics","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"IOPlatformPluginUtil","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"\"My Mac is slow\" diagnosed by chasing the wrong symptom. Activity Monitor shows what's running NOW; ","type":"text"},{"text":"log show","type":"text","marks":[{"type":"code_inline"}]},{"text":" shows what failed at boot, what's been panicking, and what storage / power events preceded each freeze. Always audit before treating.","type":"text"}]},{"type":"paragraph","content":[{"text":"Apps that \"don't work right\" but aren't crashing — usually a ","type":"text"},{"text":"TCC","type":"text","marks":[{"type":"strong"}]},{"text":" (Transparency, Consent, Control) denial nobody explicitly clicked No to. Screen Recording, Accessibility, Full Disk Access, Camera, Microphone, Contacts, Calendars, Reminders, Photos, Automation — each has its own permission grant. Reading the TCC databases tells you exactly what's been denied and when.","type":"text"}]},{"type":"paragraph","content":[{"text":"\"Macintosh HD is full but I deleted everything\" — APFS local Time Machine snapshots plus purgeable space breakdowns. ","type":"text"},{"text":"tmutil listlocalsnapshots /","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"diskutil apfs list","type":"text","marks":[{"type":"code_inline"}]},{"text":" reveal the actual space accounting that Finder hides.","type":"text"}]},{"type":"paragraph","content":[{"text":"Mac waking up at 3am for no apparent reason. ","type":"text"},{"text":"pmset -g log","type":"text","marks":[{"type":"code_inline"}]},{"text":" records every wake with a reason string (","type":"text"},{"text":"UserActivity","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"BT.HID","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"EHC0","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"RTC","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Maintenance","type":"text","marks":[{"type":"code_inline"}]},{"text":"). The pattern across a week tells you whether it's the keyboard, a Bluetooth peer, a kext, or scheduled maintenance.","type":"text"}]},{"type":"paragraph","content":[{"text":"mds_stores","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"mdworker_shared","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"photoanalysisd","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"cloudd","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"bird","type":"text","marks":[{"type":"code_inline"}]},{"text":" chewing CPU. Each has a specific cause (Spotlight reindex on a new volume, Photos analyzing faces, iCloud Drive metadata sync) and a specific remedy (per-volume mdutil control, throttling, or waiting it out informedly).","type":"text"}]},{"type":"paragraph","content":[{"text":"Login loops, gray screen at boot, \"kernel\" hangs in ","type":"text"},{"text":"loginwindow","type":"text","marks":[{"type":"code_inline"}]},{"text":". The boot-sequence layers (EFI → bootloader → kernel → launchd → loginwindow → WindowServer → shell) each fail differently; this skill packages the recoveryOS / single-user / verbose-boot patterns.","type":"text"}]},{"type":"paragraph","content":[{"text":"\"Is it safe to eject this disk?\" — ","type":"text"},{"text":"lsof +D /Volumes/X","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"mdutil -s","type":"text","marks":[{"type":"code_inline"}]},{"text":", Time Machine target check, Photos library location, helper-tool security-scoped bookmarks. The wrong answer corrupts the volume; the right answer is a one-line verdict.","type":"text"}]},{"type":"paragraph","content":[{"text":"Cloning data off a failing drive without finishing it off. ","type":"text"},{"text":"ditto","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"--rsrc","type":"text","marks":[{"type":"code_inline"}]},{"text":" for HFS+ metadata, ","type":"text"},{"text":"rsync --partial --inplace --no-whole-file --append-verify","type":"text","marks":[{"type":"code_inline"}]},{"text":" for resumable transfers. NEVER ","type":"text"},{"text":"fsck_apfs -y","type":"text","marks":[{"type":"code_inline"}]},{"text":" a failing drive — verify-only first (","type":"text"},{"text":"fsck_apfs -n","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and prefer reading from an APFS snapshot.","type":"text"}]},{"type":"paragraph","content":[{"text":"Remote macOS diagnostics across the network — SSH (universal on macOS 13+), ","type":"text"},{"text":"kickstart","type":"text","marks":[{"type":"code_inline"}]},{"text":" to enable ARD without a UI, staging the skill folder via ","type":"text"},{"text":"scp -r","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Apple Silicon vs Intel reality — most diagnostic surface is identical. Where it isn't (Secure Enclave vs T2, panic provenance, boot recovery modes), the differences are flagged explicitly.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"The Universal Insight","type":"text"}]},{"type":"paragraph","content":[{"text":"macOS tells you what's wrong if you ask the right log in the right way.","type":"text","marks":[{"type":"strong"}]},{"text":" Most users (and most tutorials) reach for Activity Monitor or \"About This Mac\". The actual diagnostic signal lives in ","type":"text"},{"text":"log show","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the unified logging system), in ","type":"text"},{"text":"/Library/Logs/DiagnosticReports/","type":"text","marks":[{"type":"code_inline"}]},{"text":", in ","type":"text"},{"text":"pmset -g log","type":"text","marks":[{"type":"code_inline"}]},{"text":", in the TCC databases, and in ","type":"text"},{"text":"launchctl print","type":"text","marks":[{"type":"code_inline"}]},{"text":". This skill packages the queries that turn noise into a verdict.","type":"text"}]},{"type":"paragraph","content":[{"text":"The most common diagnostic failure: treating symptoms in isolation. \"Slow boot\" → disable login items. \"Kernel panic\" → reinstall macOS. \"Random freezes\" → reset SMC/NVRAM. These are reasonable last resorts, but the data to identify the ","type":"text"},{"text":"actual","type":"text","marks":[{"type":"em"}]},{"text":" cause is sitting in the unified log untouched. Always audit before treating.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"The Diagnostic Ladder","type":"text"}]},{"type":"paragraph","content":[{"text":"Walk down the layers in order. Each rung has a binary outcome:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"1. Hardware health — pmset, SMC errors, thermal events, Secure Enclave\n2. Storage health — APFS state, IO errors, snapshot bloat\n3. Panic record — DiagnosticReports/*.{panic,ips} + kext provenance\n4. Pre-panic timeline — log show last 10 minutes before each panic\n5. Startup inventory — Login Items + LaunchAgents + LaunchDaemons + profiles\n6. Resource pressure — top CPU/mem, mds_stores, photoanalysisd, cloudd\n7. Permissions / TCC — what app is denied what (the macOS-unique rung)\n8. Verdict — what's failing, what to do","type":"text"}]},{"type":"paragraph","content":[{"text":"The most interesting failures cluster at rungs 2 (storage), 5 (startup bloat), and 7 (TCC denials). The least interesting (but most-treated) is rung 6.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Run the comprehensive audit","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"scripts/health-audit.sh","type":"text"}]},{"type":"paragraph","content":[{"text":"Produces a verdict block: hardware events, storage health per volume, recent panics, top resource consumers, startup inventory, TCC denials. Scan for ","type":"text"},{"text":"[FAIL]","type":"text","marks":[{"type":"code_inline"}]},{"text":" markers — that's where to drill.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Drill into the failing layer","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Symptom","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Script","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Storage errors flagged","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/disk-health.sh -v /Volumes/X","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"-d disk2","type":"text","marks":[{"type":"code_inline"}]},{"text":") — focused per-volume deep dive: APFS state, IO errors, snapshot bloat, verdict","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recent panic","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/panic-triage.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (latest by default) or ","type":"text"},{"text":"-f /Library/Logs/DiagnosticReports/Kernel_*.panic","type":"text","marks":[{"type":"code_inline"}]},{"text":" — kext + pre-panic timeline","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Is it safe to eject volume X?\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/drive-dependencies.sh -v /Volumes/X","type":"text","marks":[{"type":"code_inline"}]},{"text":" — open files, Spotlight index, TM target, Photos lib, helper-tool bookmarks","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Why is boot taking so long?\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/boot-perf.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — per-boot durations from log show, with slow-component flags","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"App can't see screen/mic/files","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/tcc-audit.sh -a \u003cbundle-id-or-name>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — what TCC has granted, what's been denied recently","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mac waking at night","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/wake-reasons.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — pmset log breakdown by reason class","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Spotlight broken / mds CPU spike","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/spotlight-status.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — index state per volume, common fixes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Storage \"full\" but disk usage doesn't add up","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/storage-pressure.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — APFS snapshots, local Time Machine, purgeable bytes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Kernel panic blames a kext / loaded kext audit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/kext-audit.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — third-party kexts + system extensions + SIP/security policy state","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Firewall behavior / VPN tunnel inventory","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/firewall-audit.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — ALF + pf + Network Extension content filters + utun inventory","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Network preferences across location profiles","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/network-locations.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — DNS / proxy / search domains per location, service order","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Apply the minimum reversible fix","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Script","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Disable startup item by name","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/safe-disable-startup.sh -n \u003cpattern>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — works across Login Items + LaunchAgents (no sudo for user-scope)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"List current state of all startup entries","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/safe-disable-startup.sh --list","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Re-enable previously disabled","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/safe-disable-startup.sh -n \u003cpattern> --enable","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Disable system-scope daemon (admin)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sudo launchctl disable system/\u003clabel>","type":"text","marks":[{"type":"code_inline"}]},{"text":" then ","type":"text"},{"text":"sudo launchctl bootout system/\u003clabel>","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reset TCC for a specific service+bundle","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"tccutil reset \u003cService> \u003cbundle-id>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (per-service, not global)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Safe clone from failing drive","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/recover-clone.sh -s \u003csource> -d \u003cdestination>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — rsync ","type":"text"},{"text":"--partial --inplace --no-whole-file","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"All disables are reversible — Login Items via ","type":"text"},{"text":"osascript","type":"text","marks":[{"type":"code_inline"}]},{"text":" System Events, LaunchAgents via ","type":"text"},{"text":"launchctl disable","type":"text","marks":[{"type":"code_inline"}]},{"text":". The inverse re-enables.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Storage Health & Failure Detection","type":"text"}]},{"type":"paragraph","content":[{"text":"The highest-yield audit. Failing drives cause slow boots (kernel waits on probe timeouts), instability (IO retries cascade into kernel hangs), and panics (paging failures kill ","type":"text"},{"text":"WindowServer","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Three independent data sources to cross-reference:","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"IO error events","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"log show --last 30d --style compact \\\n --predicate 'subsystem == \"com.apple.iokit\" AND (eventMessage CONTAINS \"I/O error\" OR eventMessage CONTAINS \"media error\")' \\\n 2>/dev/null | head -30","type":"text"}]},{"type":"paragraph","content":[{"text":"Healthy drives produce zero of these per month. Dozens = active failure regardless of what \"About This Mac → Storage\" claims.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"APFS health","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"diskutil apfs list\ndiskutil apfs verifyVolume / # READ-ONLY — does not write","type":"text"}]},{"type":"paragraph","content":[{"text":"Look for ","type":"text"},{"text":"Verify failed","type":"text","marks":[{"type":"code_inline"}]},{"text":" per-volume, container free-space mismatches, or snapshot trees growing without bound.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"SMART status (via vendor / smartmontools)","type":"text"}]},{"type":"paragraph","content":[{"text":"macOS's built-in SMART status (","type":"text"},{"text":"diskutil info /dev/diskN","type":"text","marks":[{"type":"code_inline"}]},{"text":") reports only ","type":"text"},{"text":"Verified","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"Failing","type":"text","marks":[{"type":"code_inline"}]},{"text":". For real attributes, install ","type":"text"},{"text":"smartmontools","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"brew install smartmontools","type":"text","marks":[{"type":"code_inline"}]},{"text":") and use ","type":"text"},{"text":"smartctl -a /dev/diskN","type":"text","marks":[{"type":"code_inline"}]},{"text":". NVMe drives often return blank — fall back to vendor tools or the per-vendor utilities.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Disk → volume mapping","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"diskutil list\ndiskutil info disk2s1","type":"text"}]},{"type":"paragraph","content":[{"text":"Cross-reference with ","type":"text"},{"text":"df -h","type":"text","marks":[{"type":"code_inline"}]},{"text":" for mount point.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Boot Performance & Startup Management","type":"text"}]},{"type":"paragraph","content":[{"text":"macOS has ","type":"text"},{"text":"four primary startup mechanisms","type":"text","marks":[{"type":"strong"}]},{"text":", each requiring different tooling. System Settings only shows one of them (Login Items). Full inventory in ","type":"text"},{"text":"references/startup-mechanisms.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mechanism","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Where","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"How to inspect","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"How to disable","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Login Items","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"System Settings → General → Login Items","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"osascript","type":"text","marks":[{"type":"code_inline"}]},{"text":" System Events","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"osascript","type":"text","marks":[{"type":"code_inline"}]},{"text":" (no sudo)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User LaunchAgents","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/Library/LaunchAgents/*.plist","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launchctl print gui/$UID","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launchctl disable gui/$UID/\u003clabel>","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"System LaunchAgents (per-user)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/Library/LaunchAgents/*.plist","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launchctl print gui/$UID","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launchctl disable gui/$UID/\u003clabel>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (no sudo for current user)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"System LaunchDaemons","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/Library/LaunchDaemons/*.plist","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sudo launchctl print system","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sudo launchctl disable system/\u003clabel>","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"(Legacy) LoginHook","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"com.apple.loginwindow","type":"text","marks":[{"type":"code_inline"}]},{"text":" LoginHook key","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sudo defaults read com.apple.loginwindow LoginHook","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sudo defaults delete com.apple.loginwindow LoginHook","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"launchctl disable","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs ","type":"text"},{"text":"bootout","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Command","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Effect","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launchctl disable \u003cdomain>/\u003clabel>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Persistently disabled across reboots. ","type":"text"},{"text":"Reversible","type":"text","marks":[{"type":"strong"}]},{"text":" with ","type":"text"},{"text":"enable","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launchctl bootout \u003cdomain>/\u003clabel>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Unloads the running service NOW. Comes back on next reboot if not also ","type":"text"},{"text":"disable","type":"text","marks":[{"type":"code_inline"}]},{"text":"d.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launchctl unload \u003cplist>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Legacy form. Avoid in new scripts.","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"The right pair for \"kill this daemon permanently\": ","type":"text"},{"text":"disable","type":"text","marks":[{"type":"code_inline"}]},{"text":" then ","type":"text"},{"text":"bootout","type":"text","marks":[{"type":"code_inline"}]},{"text":". The script ","type":"text"},{"text":"scripts/safe-disable-startup.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" does both.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Boot duration measurement","type":"text"}]},{"type":"paragraph","content":[{"text":"macOS records boot timing in the unified log. Approximate via:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"log show --last 1h --style compact \\\n --predicate 'eventMessage CONTAINS \"BOOT_TIME\" OR eventMessage CONTAINS \"loginwindow\"' \\\n | head -50","type":"text"}]},{"type":"paragraph","content":[{"text":"Healthy Apple Silicon Mac: 10-20s to login screen. Intel Mac with spinning disk (vintage 2015 Mini): 25-45s. Failing storage: 60+s with stalls.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Panic Analysis & Diagnostic Reports","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Where panics live","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/Library/Logs/DiagnosticReports/*.panic (legacy Intel + early Apple Silicon)\n/Library/Logs/DiagnosticReports/*.ips (modern format, all panics on macOS 12+)\n~/Library/Logs/DiagnosticReports/ (per-user crashes, not panics)","type":"text"}]},{"type":"paragraph","content":[{"text":".ips","type":"text","marks":[{"type":"code_inline"}]},{"text":" files are JSON. The ","type":"text"},{"text":".panic","type":"text","marks":[{"type":"code_inline"}]},{"text":" files are plain text but follow a strict structure.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Anatomy of a panic report","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"panic(cpu N caller 0x...): \"Sleep wake failure in EFI\"\nLoaded kexts:\n com.apple.driver.AppleEFIRuntime ...\n com.eltima.ProductX 2.1.7 \u003c— third-party suspect\n ...","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"kext list","type":"text","marks":[{"type":"strong"}]},{"text":" is the most actionable signal. A panic that loaded only Apple kexts is harder to fix than one with a clear third-party suspect — kext-extraction-and-removal is the first move.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Common panic strings (full catalog in ","type":"text"},{"text":"references/panic-codes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"String fragment","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Likely cause","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Sleep wake failure\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Driver hung during S3/S4 transition (often USB, Bluetooth, GPU)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Unable to find driver\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Boot-time kext load failure — likely after macOS update","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Unresponsive bootstrap subsystem\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launchd","type":"text","marks":[{"type":"code_inline"}]},{"text":" deadlock — usually third-party LaunchDaemon","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"WindowServer panic\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GPU driver or display kext fault","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"double_fault\" / \"page_fault\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Kernel-mode memory corruption — kext bug or RAM fault","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"panic_kthread\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Kernel watchdog timeout — driver in infinite loop","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pre-panic timeline","type":"text"}]},{"type":"paragraph","content":[{"text":"The panic record alone rarely tells you the cause. The ","type":"text"},{"text":"events in the 10 minutes before","type":"text","marks":[{"type":"strong"}]},{"text":" are where the story is:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"scripts/panic-triage.sh -t '2026-05-15 03:14:22' -m 10","type":"text"}]},{"type":"paragraph","content":[{"text":"Look for:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Disk arbitration errors before panic → storage failure cascade","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"kernel","type":"text","marks":[{"type":"code_inline"}]},{"text":" warnings naming a third-party kext before panic → driver hang","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"powerd","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"assertion","type":"text","marks":[{"type":"code_inline"}]},{"text":" messages before sleep panic → pmset assertion held by misbehaving app","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"TCC (Privacy Permissions) Audit","type":"text"}]},{"type":"paragraph","content":[{"text":"A macOS-unique diagnostic layer. The TCC databases at:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/Library/Application Support/com.apple.TCC/TCC.db (system, requires sudo)\n~/Library/Application Support/com.apple.TCC/TCC.db (per-user)","type":"text"}]},{"type":"paragraph","content":[{"text":"…store every \"Allow / Deny\" grant ever made. Reading them tells you exactly which app has Screen Recording, Camera, Microphone, Full Disk Access, Accessibility, Automation, etc. — and which apps have been ","type":"text"},{"text":"denied","type":"text","marks":[{"type":"strong"}]},{"text":" recently (the most common \"this app doesn't work\" cause).","type":"text"}]},{"type":"paragraph","content":[{"text":"Full schema and reset procedures in ","type":"text"},{"text":"references/tcc-mechanics.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". The ","type":"text"},{"text":"scripts/tcc-audit.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" script wraps the common queries.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Failure Modes","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Symptom","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"First check","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Common cause","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Slow boot, used to be fast","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"startup-audit.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Login Item / LaunchAgent bloat (Adobe CC, Docker, Setapp)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Slow boot, getting worse","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"disk-health.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Failing SSD — APFS retries inflating boot time","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Random freezes + hard restart","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"disk-health.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"panic-triage.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"IO errors cascading into kernel hang","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Kernel panic on wake","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"panic-triage.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (look for \"Sleep wake failure\")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Driver power-state bug (often USB, GPU, Bluetooth)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"App can't access screen/mic","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"tcc-audit.sh -a \u003capp>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TCC denial, often from a recent system update","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Macintosh HD almost full\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"storage-pressure.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Local Time Machine snapshots + purgeable cache","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mac wakes at 3am","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"wake-reasons.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scheduled maintenance, BT keyboard tap, or kext bug","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mds_stores","type":"text","marks":[{"type":"code_inline"}]},{"text":" CPU 100%","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"spotlight-status.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reindex on volume with no on-disk index store","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Login loop / gray screen","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"recoveryOS + safe boot + this skill's recovery docs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bad LaunchAgent, corrupt Login Items, kext panic at boot","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Recovery Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Failing-drive data recovery","type":"text"}]},{"type":"paragraph","content":[{"text":"Never ","type":"text","marks":[{"type":"strong"}]},{"text":"fsck_apfs -y","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" a failing drive","type":"text","marks":[{"type":"strong"}]},{"text":" — ","type":"text"},{"text":"-y","type":"text","marks":[{"type":"code_inline"}]},{"text":" answers Yes to repairs, which writes back. Use ","type":"text"},{"text":"fsck_apfs -n","type":"text","marks":[{"type":"code_inline"}]},{"text":" (verify-only) first. Image first, repair the image second.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Safe clone with no retries (skips bad sectors fast)\nrsync -avh --partial --inplace --no-whole-file --append-verify \\\n /Volumes/Failing/important/ /Volumes/Rescue/important/","type":"text"}]},{"type":"paragraph","content":[{"text":"For bit-level recovery, install ","type":"text"},{"text":"gddrescue","type":"text","marks":[{"type":"code_inline"}]},{"text":" via Homebrew and use a map file so the operation is resumable. Documented in ","type":"text"},{"text":"references/recovery-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Physically removing a failing drive","type":"text"}]},{"type":"paragraph","content":[{"text":"If a drive is causing boot stalls or panics:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify via ","type":"text"},{"text":"disk-health.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify nothing critical points at it (","type":"text"},{"text":"drive-dependencies.sh -v /Volumes/X","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"diskutil unmount /Volumes/X","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"diskutil eject /dev/diskN","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the whole device)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Physically disconnect / power down before remount attempts","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Voice & Output Style","type":"text"}]},{"type":"paragraph","content":[{"text":"Output follows the claude-mods diagnostic convention:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"[PASS]","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"[FAIL]","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"[WARN]","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"[INFO]","type":"text","marks":[{"type":"code_inline"}]},{"text":" prefixes for scan rows","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verdict block at the bottom with specific findings + recommended actions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Volume identifications include disk identifier, mount point, APFS role","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Panic references include UTC timestamp, panic string, primary suspect kext","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Panel rendering via ","type":"text"},{"text":"skills/_lib/term.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" when a TTY is present; raw text otherwise","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--json","type":"text","marks":[{"type":"code_inline"}]},{"text":" emits NDJSON for piping; ","type":"text"},{"text":"--redact","type":"text","marks":[{"type":"code_inline"}]},{"text":" masks private addrs / hostnames / serial numbers","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"What This Skill Doesn't Cover","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Network diagnostics","type":"text","marks":[{"type":"strong"}]},{"text":" → use ","type":"text"},{"text":"net-ops","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Windows-side workstation issues","type":"text","marks":[{"type":"strong"}]},{"text":" → use ","type":"text"},{"text":"windows-ops","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Specific application performance profiling","type":"text","marks":[{"type":"strong"}]},{"text":" → use ","type":"text"},{"text":"perf-ops","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Source-code-level debugging","type":"text","marks":[{"type":"strong"}]},{"text":" → use ","type":"text"},{"text":"debug-ops","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"iOS / iPadOS device issues","type":"text","marks":[{"type":"strong"}]},{"text":" — different platform","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MDM authoring","type":"text","marks":[{"type":"strong"}]},{"text":" (creating configuration profiles) — out of scope; we read them, not author them","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Cross-References","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"When","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Triaging a remote Mac","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"net-ops/ssh-bootstrap.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" to land, then this skill's scripts","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Panic blames a network kext","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Combine with ","type":"text"},{"text":"net-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" for VPN/DNS interactions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Same pattern on multiple Macs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"health-audit.sh --json","type":"text","marks":[{"type":"code_inline"}]},{"text":" on each, diff outputs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Suspect Windows + Mac in same household","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"windows-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"mac-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":", same conventions","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/storage-events.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — IO error patterns in unified log, APFS-specific event vocabulary, disk arbitration messages. Load when investigating volume errors or correlating IO failures to a specific device.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/panic-codes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Common kernel panic strings, kext-extraction patterns, Apple Silicon vs Intel panic format differences. Load when decoding a non-trivial ","type":"text"},{"text":".panic","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":".ips","type":"text","marks":[{"type":"code_inline"}]},{"text":" file or matching a symptom to a likely cause.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/startup-mechanisms.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Deep dive on Login Items, LaunchAgents (user + system), LaunchDaemons, legacy LoginHook, configuration profile login items. Load when doing a full startup audit or hunting vendor-installed auto-launch hooks.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/recovery-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Failing-drive recovery (rsync, gddrescue), APFS snapshot rollback, target disk mode, recoveryOS, single-user mode on Apple Silicon vs Intel. Load when responding to \"my drive is dying\" or any destructive operation.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/remote-diagnostics.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — SSH staging pattern (","type":"text"},{"text":"scp -r mac-ops/ remote:","type":"text","marks":[{"type":"code_inline"}]},{"text":"), ","type":"text"},{"text":"kickstart","type":"text","marks":[{"type":"code_inline"}]},{"text":" for enabling ARD, sudo over SSH considerations. Load when troubleshooting \"my parents' Mac across town\".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/tcc-mechanics.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — How TCC works under the hood. Both TCC.db locations, \"service\" string catalog (kTCCServiceScreenCapture, kTCCServiceAccessibility, etc.), grant types, when to use ","type":"text"},{"text":"tccutil reset","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs editing the DB, SIP interaction. Load when an app silently fails to access screen / mic / files.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/launchd-deep-dive.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — launchd plist semantics, ","type":"text"},{"text":"RunAtLoad","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs ","type":"text"},{"text":"KeepAlive","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ThrottleInterval","type":"text","marks":[{"type":"code_inline"}]},{"text":", why daemons fail to load, ","type":"text"},{"text":"disable","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs ","type":"text"},{"text":"bootout","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs ","type":"text"},{"text":"unload","type":"text","marks":[{"type":"code_inline"}]},{"text":", domain targets (system, user, gui), and Apple Silicon specifics (system extensions replacing kexts).","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Worked example","type":"text"}]},{"type":"paragraph","content":[{"text":"A user reports \"my Mac wakes itself at 3am and is slow during the day.\" Running ","type":"text"},{"text":"scripts/health-audit.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" produces a panel that follows the ","type":"text"},{"text":"Terminal Panel Design System","type":"text","marks":[{"type":"link","attrs":{"href":"../../docs/TERMINAL-DESIGN.md","title":null}}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"╭── 🩺 mac-ops · health-audit ───────────────────────────────────── macks-mbp ───●\n│\n├── 3 volumes · 1 panic · 4 wakes/24h · 12 startup items\n│\n├── failing (4)\n│ ├── [panic] 2026-05-14 03:14 Sleep wake failure (com.kext.example.AcmeUSB)\n│ ├── [wake] 3 wakes from BT.HID Bluetooth keyboard activity at night\n│ ├── [tcc] Slack denied Screen Recording (granted previously, lost after update)\n│ └── [start] 12 login items, 3 disabled, 2 unsigned\n│ │ ▲ remove AcmeUSB kext; pair BT keyboard to phone for night; re-grant Slack TCC\n│\n├── warn (2) · pass (9) · info (3)\n│\n╰── R refresh · D drill · ? help ─────────────────── ⬤ panic • bt-wake • tcc ───●","type":"text"}]},{"type":"paragraph","content":[{"text":"Three commands solve it: ","type":"text"},{"text":"panic-triage.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" decodes the panic; ","type":"text"},{"text":"wake-reasons.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" shows BT.HID is the dominant wake class; ","type":"text"},{"text":"tcc-audit.sh -a slack","type":"text","marks":[{"type":"code_inline"}]},{"text":" confirms denied. The data was always there — this skill just asks for it correctly ","type":"text"},{"text":"and renders it like a proper instrument","type":"text","marks":[{"type":"em"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Legacy / non-panel mode","type":"text"}]},{"type":"paragraph","content":[{"text":"All scripts accept ","type":"text"},{"text":"--json","type":"text","marks":[{"type":"code_inline"}]},{"text":" for NDJSON output (parses with ","type":"text"},{"text":"jq","type":"text","marks":[{"type":"code_inline"}]},{"text":") and ","type":"text"},{"text":"--redact","type":"text","marks":[{"type":"code_inline"}]},{"text":" for opsec-clean diagnostic dumps. When stdout is not a TTY, panel chrome auto-disables and plain text emits.","type":"text"}]},{"type":"paragraph","content":[{"text":"Full command sequence for the example:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"scripts/health-audit.sh # diagnose\nscripts/panic-triage.sh # decode most recent panic\nscripts/wake-reasons.sh --since 7d # weekly wake pattern\nscripts/tcc-audit.sh -a Slack # check denied permissions\nscripts/safe-disable-startup.sh --list # audit startup state\nscripts/safe-disable-startup.sh -n 'Adobe*' # cull bloat\nsudo launchctl disable system/com.kext.example.AcmeUSB.daemon\n# (then reboot to confirm panic doesn't return)\nscripts/health-audit.sh # verify clean","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"mac-ops","author":"@skillopedia","source":{"stars":21,"repo_name":"claude-mods","origin_url":"https://github.com/0xdarkmatter/claude-mods/blob/HEAD/skills/mac-ops/SKILL.md","repo_owner":"0xdarkmatter","body_sha256":"01e49e739f5c3b7f5fd1d168e3d212424578868d41eccee83679b531c7658a0f","cluster_key":"e4040f7098991ec720fe862758e27e83e5b6bcfcdb6b2cfbd8df883c8c3ac22c","clean_bundle":{"format":"clean-skill-bundle-v1","source":"0xdarkmatter/claude-mods/skills/mac-ops/SKILL.md","attachments":[{"id":"2b58c4fd-28b7-5d7f-84d2-65b9bc44fbd2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2b58c4fd-28b7-5d7f-84d2-65b9bc44fbd2/attachment","path":"assets/.gitkeep","size":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","contentType":"text/plain; charset=utf-8"},{"id":"1bc7957c-ca50-5ae7-9047-d103f4ac8964","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1bc7957c-ca50-5ae7-9047-d103f4ac8964/attachment.md","path":"references/apple-silicon-specifics.md","size":7107,"sha256":"3e6d0e3cf8743c989a9c51543fe9300d04f5bbab48795c0b9298513c9b865b9e","contentType":"text/markdown; charset=utf-8"},{"id":"a203cc9e-f90a-5908-90cb-bcb786ff2d66","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a203cc9e-f90a-5908-90cb-bcb786ff2d66/attachment.md","path":"references/launchd-deep-dive.md","size":10053,"sha256":"c71ddc62a17959358e2ea9bc0e965341624ece031b5bd3eea592270528767425","contentType":"text/markdown; charset=utf-8"},{"id":"abf6f218-98fa-53ad-9b44-433f80d5c15c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/abf6f218-98fa-53ad-9b44-433f80d5c15c/attachment.md","path":"references/mac-vs-windows-ops.md","size":7856,"sha256":"d0acb11e1b047587a1bc637d1eee253ef618a89040e98ac94ef13720e85058ab","contentType":"text/markdown; charset=utf-8"},{"id":"3d18bec3-6936-543b-b188-d202a8367b99","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3d18bec3-6936-543b-b188-d202a8367b99/attachment.md","path":"references/panic-codes.md","size":8110,"sha256":"76620652dea98832226d3c5afcea9b84b662780acb93d051993d714a8d12d0a7","contentType":"text/markdown; charset=utf-8"},{"id":"0ba8491b-6dde-534a-9629-8a65de7bf69d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0ba8491b-6dde-534a-9629-8a65de7bf69d/attachment.md","path":"references/recovery-patterns.md","size":8554,"sha256":"4a68a7b12e9a9d0d3e138e155d771e0ae0e1095072728b2164f285fef3aa890c","contentType":"text/markdown; charset=utf-8"},{"id":"04702e4e-cb43-5a98-a0ed-f4f2752b29bc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/04702e4e-cb43-5a98-a0ed-f4f2752b29bc/attachment.md","path":"references/remote-diagnostics.md","size":7014,"sha256":"a61a927bff30a9cc8d2621e148bf8cd5858c9ad3769a6aabfde8e932d6a5b218","contentType":"text/markdown; charset=utf-8"},{"id":"902a23f1-6090-536d-b467-b101f0ac2df8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/902a23f1-6090-536d-b467-b101f0ac2df8/attachment.md","path":"references/startup-mechanisms.md","size":9953,"sha256":"4dec795dc2e22b81c3970315579422f6b1cc490a41775a242395136ccdd6e202","contentType":"text/markdown; charset=utf-8"},{"id":"6d0c4994-553d-5770-bece-5034f6a045ed","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6d0c4994-553d-5770-bece-5034f6a045ed/attachment.md","path":"references/storage-events.md","size":6237,"sha256":"18c2e1db2d5b8af04ebe01ff2f072a4afabc01579d86e35cc8a30534dea72747","contentType":"text/markdown; charset=utf-8"},{"id":"9adeb3d8-85d0-513f-b97c-5eefd2f3a9de","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9adeb3d8-85d0-513f-b97c-5eefd2f3a9de/attachment.md","path":"references/tcc-mechanics.md","size":10205,"sha256":"164c9f53892fb071e71e069982a71b490da7f3f7285ca941583c77f2417d3f5a","contentType":"text/markdown; charset=utf-8"},{"id":"6d35f4c0-26f8-5371-bf37-69037efae9aa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6d35f4c0-26f8-5371-bf37-69037efae9aa/attachment.md","path":"references/worked-examples.md","size":6420,"sha256":"4c81d4f70b1fee415c02119ed7ab38334be8731a14c64c00fd1ff4b4cb45feb4","contentType":"text/markdown; charset=utf-8"},{"id":"9e0af3fb-e3f9-5348-bddb-884f703447d4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9e0af3fb-e3f9-5348-bddb-884f703447d4/attachment.sh","path":"scripts/_lib/common.sh","size":6010,"sha256":"06626404ca78c000cbdcf0a62e08251854dc06707e19dfbd5ea6a4ac3a01384b","contentType":"application/x-sh; charset=utf-8"},{"id":"e815b7b8-279f-52cd-9590-5f14c729492a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e815b7b8-279f-52cd-9590-5f14c729492a/attachment.sh","path":"scripts/_lib/panel.sh","size":7954,"sha256":"d84a69075fae970e030052aa81ee68fbc6e81c7db1718fa628d66ca65133f038","contentType":"application/x-sh; charset=utf-8"},{"id":"083d220f-7e68-5037-91ac-b63b568d5a5e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/083d220f-7e68-5037-91ac-b63b568d5a5e/attachment.sh","path":"scripts/bluetooth-audit.sh","size":5195,"sha256":"10f85942c212707a8864be8028cc95c0816f47140a4d255b51e8b356d00e67ac","contentType":"application/x-sh; charset=utf-8"},{"id":"6ed8b76e-3111-5f65-a0b7-a30159f4675a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6ed8b76e-3111-5f65-a0b7-a30159f4675a/attachment.sh","path":"scripts/boot-perf.sh","size":5947,"sha256":"34ddb92d5c0cbdea2bdd3ece9bceda4999804d428fde27683617031b9f00938c","contentType":"application/x-sh; charset=utf-8"},{"id":"32a3fda6-c75b-5484-9131-c457420c6bb1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/32a3fda6-c75b-5484-9131-c457420c6bb1/attachment.sh","path":"scripts/brew-health.sh","size":6709,"sha256":"81a42b2fdda18365487cbe14d5766a38f41adf74a1fe8d3ebc72658fbf6d1d27","contentType":"application/x-sh; charset=utf-8"},{"id":"cba2dc00-21ef-5147-bc9d-cfd463a50d3d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cba2dc00-21ef-5147-bc9d-cfd463a50d3d/attachment.sh","path":"scripts/disk-health.sh","size":8452,"sha256":"5bb7d843df1ae2d44e6aee4191a311ce78996df8dd0420151df8375f50a1ccc9","contentType":"application/x-sh; charset=utf-8"},{"id":"c7c9f218-e7e3-5a60-b39c-b8f24d108c47","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c7c9f218-e7e3-5a60-b39c-b8f24d108c47/attachment.sh","path":"scripts/drive-dependencies.sh","size":8656,"sha256":"b03ac3408289640347fdaa724810ed973060bd87e9db6ad034eeecdd977e7df4","contentType":"application/x-sh; charset=utf-8"},{"id":"0346498e-4841-57f8-a6bd-7202ea0ef35b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0346498e-4841-57f8-a6bd-7202ea0ef35b/attachment.sh","path":"scripts/firewall-audit.sh","size":5409,"sha256":"236c51d50e3e5ef572b2181ba7ccbd31b3eaef7a51296478a38e6bce4e6212e6","contentType":"application/x-sh; charset=utf-8"},{"id":"ba6fc3f0-9f44-5d14-b583-8f4334142b7a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ba6fc3f0-9f44-5d14-b583-8f4334142b7a/attachment.sh","path":"scripts/font-audit.sh","size":5012,"sha256":"c3b6de2582fcf544f88590a84eda934a5a37b8fc1e1ec3ba052f306a246fb3ee","contentType":"application/x-sh; charset=utf-8"},{"id":"a16a76e8-7227-5526-bade-7164a50cfe0d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a16a76e8-7227-5526-bade-7164a50cfe0d/attachment.sh","path":"scripts/health-audit.sh","size":12722,"sha256":"ba5773565498c3851f014c26c59fcb41a80af304425206e82c75f7834e0d5424","contentType":"application/x-sh; charset=utf-8"},{"id":"054eb5be-9a64-5bdf-b7ad-6d002396230d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/054eb5be-9a64-5bdf-b7ad-6d002396230d/attachment.sh","path":"scripts/kext-audit.sh","size":6287,"sha256":"68dea81ae5d26b6bc84d0f7f13cd89753e518eecd13479029e17082ef6eac78f","contentType":"application/x-sh; charset=utf-8"},{"id":"e8032c2d-7d5e-5b32-88ec-73dec8e59b11","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e8032c2d-7d5e-5b32-88ec-73dec8e59b11/attachment.sh","path":"scripts/keychain-audit.sh","size":7288,"sha256":"38b52fe3e6310492605b8c38cf4f76c8af4e1c4392cbe190d85d70c6bfcdd284","contentType":"application/x-sh; charset=utf-8"},{"id":"8bc7230e-6449-58e4-a7e9-a7a22b55dceb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8bc7230e-6449-58e4-a7e9-a7a22b55dceb/attachment.sh","path":"scripts/media-libraries.sh","size":6353,"sha256":"c12b3359a42dd8bc60515f9aaafb8ba472399643a318f90db5b9dd0c567bf2bd","contentType":"application/x-sh; charset=utf-8"},{"id":"f7fe59dc-d899-56a5-a909-6361b48efd72","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f7fe59dc-d899-56a5-a909-6361b48efd72/attachment.sh","path":"scripts/network-locations.sh","size":4386,"sha256":"e2f2cd452f3b1a50c3a8aa1c150da9430977a3ed9e0f2cf9568dc69f615df706","contentType":"application/x-sh; charset=utf-8"},{"id":"cabcb94e-eb19-518c-884e-5dc564f304d6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cabcb94e-eb19-518c-884e-5dc564f304d6/attachment.sh","path":"scripts/panic-triage.sh","size":6597,"sha256":"0d4c4d08041764d812cd60f5997248aefb8b670d561df31bafd3e8bff43e85e2","contentType":"application/x-sh; charset=utf-8"},{"id":"0fa54183-6a77-5f0b-af0c-62d4a92caabf","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0fa54183-6a77-5f0b-af0c-62d4a92caabf/attachment.sh","path":"scripts/quickrun.sh","size":8353,"sha256":"054221b1f688dbdfefef9253d500f388e48b034eabbc978864a85c7ac2db3dbb","contentType":"application/x-sh; charset=utf-8"},{"id":"b4410c5d-8893-589b-9d24-a3023de55c44","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b4410c5d-8893-589b-9d24-a3023de55c44/attachment.sh","path":"scripts/recover-clone.sh","size":6072,"sha256":"f3047465dbba547484703936a8a3fd79d5296efd64751353b1e06f442d735113","contentType":"application/x-sh; charset=utf-8"},{"id":"15f625de-a896-568d-8872-0a07c9fca3d2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/15f625de-a896-568d-8872-0a07c9fca3d2/attachment.sh","path":"scripts/safe-disable-startup.sh","size":7336,"sha256":"c37857cb2f4865e710bc0c1355a96ccc7e22c0b09b9f9cdeacd6c2ea69ca1dbe","contentType":"application/x-sh; charset=utf-8"},{"id":"6cf64f6b-723f-5a5c-914e-c1636b5183a1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6cf64f6b-723f-5a5c-914e-c1636b5183a1/attachment.sh","path":"scripts/spotlight-status.sh","size":4964,"sha256":"d46008321756aa8bacc52f2144b733db66b733476fab619cd3b73de1bb2f3466","contentType":"application/x-sh; charset=utf-8"},{"id":"be18148c-f3e2-546d-9316-dc726bb21882","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/be18148c-f3e2-546d-9316-dc726bb21882/attachment.sh","path":"scripts/startup-audit.sh","size":9089,"sha256":"f4563813a8f43b6ee2a87f18c4a3d18218b15e1b3795e2926d12f79070204f88","contentType":"application/x-sh; charset=utf-8"},{"id":"0ec26ff5-52ed-5e2e-ab99-4a05a9f8cbe6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0ec26ff5-52ed-5e2e-ab99-4a05a9f8cbe6/attachment.sh","path":"scripts/storage-pressure.sh","size":6622,"sha256":"9a211714824cfc2b726d7ca0bc89f87596e07658bda70a184e591e0b91c84f75","contentType":"application/x-sh; charset=utf-8"},{"id":"c6ffb2a9-165d-5243-bc91-1dafba4b60bd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c6ffb2a9-165d-5243-bc91-1dafba4b60bd/attachment.sh","path":"scripts/sysdiagnose-helper.sh","size":5768,"sha256":"6c2e5775cb39f84bb6f2627d026a4836c7fd30988bb8d4ff97328a51244e8101","contentType":"application/x-sh; charset=utf-8"},{"id":"51d41bef-15f3-52c6-9209-8e7484e21ed0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/51d41bef-15f3-52c6-9209-8e7484e21ed0/attachment.sh","path":"scripts/tcc-audit.sh","size":8197,"sha256":"c6cf365af2b5c629182a285509412d436f6b05c6c13798e2889f51dbe02ee98c","contentType":"application/x-sh; charset=utf-8"},{"id":"e3f3d137-c001-568d-a630-681806b050b0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e3f3d137-c001-568d-a630-681806b050b0/attachment.sh","path":"scripts/update-state.sh","size":6238,"sha256":"3322a29a7798e3e063586fb6239964e16a7b8fc4d03e4978c01524bdf4e0c32e","contentType":"application/x-sh; charset=utf-8"},{"id":"121f6bcf-6d95-5456-aaab-735afa4e00d2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/121f6bcf-6d95-5456-aaab-735afa4e00d2/attachment.sh","path":"scripts/wake-reasons.sh","size":7179,"sha256":"d99f445147ce44b3f004cfaa1d5488a60738e2c0450cf54b639307d55c458ec2","contentType":"application/x-sh; charset=utf-8"},{"id":"1b865150-e854-5e0f-8000-5c9757dbaa7c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1b865150-e854-5e0f-8000-5c9757dbaa7c/attachment.sh","path":"tests/run.sh","size":8165,"sha256":"d8d552578735316674a577026a58b4f10ac7f80f2965527d19e37b7e7137adf8","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"61a0bf826cb99e44ab544aed5a06db8102c00fc92b2292b780a3e61e56bf1f59","attachment_count":37,"text_attachments":36,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":1,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/mac-ops/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"security","metadata":{"author":"claude-mods","related-skills":"windows-ops, net-ops, debug-ops, perf-ops"},"import_tag":"clean-skills-v1","description":"Comprehensive macOS workstation operations — diagnose kernel panics, identify failing drives, audit launchd startup items, decode wake reasons, triage TCC permission denials, manage APFS snapshots, recover from no-boot. Use for: Mac is slow, slow bootup, won't boot, kernel panic, kernel_task hot, mds_stores CPU, photoanalysisd, cloudd, login loop, gray screen, sleep wake failure, drive failing, IO errors, APFS snapshots eating space, Time Machine local snapshots, Spotlight indexing, launchd, LaunchAgent, LaunchDaemon, login items, TCC permissions, Full Disk Access, Screen Recording denied, Gatekeeper, quarantine, com.apple.quarantine, app is damaged, helper tool, /Library/PrivilegedHelperTools, pmset, wake reasons, dark wake, sysdiagnose, panic.ips, DiagnosticReports, configuration profile, MDM profile, remote diagnostics over SSH.","allowed-tools":"Read Write Bash"}},"renderedAt":1782979655674}

mac-ops Helps with Slow Mac that used to be fast — bloat accumulation across the four startup mechanisms (Login Items, , , ). The same machine still boots fast once those are inventoried and trimmed. Failing drives that nobody's spotted yet. macOS doesn't shout the way Windows does — IO errors live in and APFS surfaces them via / provider messages. Healthy SSDs produce zero of these per month; dozens means active failure even when "About This Mac → Storage" still shows green. Kernel panics with no obvious cause. The / files in carry the panic string, kernel call stack, and (critically) the lo…