feat: add mac-optimizer skill (92) — Claude Code only
macOS system health toolkit with 5 audit modules (packages, environment, security, cleanup, resources) and cleanup executor. Fixed bugs from review: - Replace GNU timeout with perl alarm (macOS compatible) - Remove Linux-only ps --sort flag, use portable sort - Add JSON escaping to all audit scripts - Remove redundant classify_size branch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
143
custom-skills/92-mac-optimizer/scripts/audit_resources.sh
Executable file
143
custom-skills/92-mac-optimizer/scripts/audit_resources.sh
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
# audit_resources.sh — Monitor CPU, memory, disk, battery on macOS
|
||||
# Output: JSON lines to stdout, one per finding
|
||||
set -euo pipefail
|
||||
|
||||
json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' '; }
|
||||
|
||||
json_finding() {
|
||||
local severity="$1" finding action details
|
||||
finding=$(json_escape "$2")
|
||||
action=$(json_escape "$3")
|
||||
details=$(json_escape "$4")
|
||||
printf '{"module":"resources","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
|
||||
"$severity" "$finding" "$action" "$details"
|
||||
}
|
||||
|
||||
# Process deny-list (never suggest termination)
|
||||
DENY_LIST="kernel_task|launchd|WindowServer|loginwindow|mds|mds_stores|opendirectoryd|coreaudiod|SystemUIServer|Finder|Dock"
|
||||
|
||||
# --- CPU ---
|
||||
load_avg=$(sysctl -n vm.loadavg 2>/dev/null | tr -d '{}' | awk '{print $1}')
|
||||
cpu_count=$(sysctl -n hw.ncpu 2>/dev/null || echo 1)
|
||||
|
||||
if command -v python3 &>/dev/null; then
|
||||
cpu_severity=$(python3 -c "
|
||||
load=$load_avg; cores=$cpu_count
|
||||
if load > cores * 2: print('critical')
|
||||
elif load > cores: print('warning')
|
||||
else: print('info')
|
||||
" 2>/dev/null || echo "info")
|
||||
else
|
||||
cpu_severity="info"
|
||||
fi
|
||||
|
||||
json_finding "$cpu_severity" "Load average: ${load_avg} (${cpu_count} cores)" "review" ""
|
||||
|
||||
# Top CPU-consuming processes (excluding deny-list from action suggestions)
|
||||
top_cpu=$(ps aux | sort -nrk 3 | head -5 || echo "")
|
||||
if [ -n "$top_cpu" ]; then
|
||||
while IFS= read -r line; do
|
||||
proc_name=$(echo "$line" | awk '{print $11}' | xargs basename 2>/dev/null || echo "unknown")
|
||||
cpu_pct=$(echo "$line" | awk '{print $3}')
|
||||
mem_pct=$(echo "$line" | awk '{print $4}')
|
||||
pid=$(echo "$line" | awk '{print $2}')
|
||||
|
||||
# Skip low-usage processes
|
||||
is_high=$(python3 -c "print('yes' if float('${cpu_pct}') > 10 else 'no')" 2>/dev/null || echo "no")
|
||||
if [ "$is_high" = "yes" ]; then
|
||||
if echo "$proc_name" | grep -qE "$DENY_LIST"; then
|
||||
json_finding "info" "High CPU: ${proc_name} (${cpu_pct}% CPU, PID ${pid})" "none" "System process - do not terminate"
|
||||
else
|
||||
json_finding "warning" "High CPU: ${proc_name} (${cpu_pct}% CPU, PID ${pid})" "quit" "User process - can be terminated"
|
||||
fi
|
||||
fi
|
||||
done <<< "$top_cpu"
|
||||
fi
|
||||
|
||||
# --- Memory ---
|
||||
vm_stat_output=$(vm_stat 2>/dev/null || echo "")
|
||||
if [ -n "$vm_stat_output" ]; then
|
||||
page_size=$(vm_stat | head -1 | grep -o '[0-9]*' || echo 16384)
|
||||
|
||||
free_pages=$(echo "$vm_stat_output" | grep "Pages free" | awk '{print $3}' | tr -d '.')
|
||||
active_pages=$(echo "$vm_stat_output" | grep "Pages active" | awk '{print $3}' | tr -d '.')
|
||||
inactive_pages=$(echo "$vm_stat_output" | grep "Pages inactive" | awk '{print $3}' | tr -d '.')
|
||||
wired_pages=$(echo "$vm_stat_output" | grep "Pages wired" | awk '{print $4}' | tr -d '.')
|
||||
compressed_pages=$(echo "$vm_stat_output" | grep "Pages stored in compressor" | awk '{print $5}' | tr -d '.' || echo 0)
|
||||
|
||||
if command -v python3 &>/dev/null; then
|
||||
mem_summary=$(python3 -c "
|
||||
ps=${page_size}
|
||||
free_mb = int(${free_pages:-0}) * ps / 1048576
|
||||
active_mb = int(${active_pages:-0}) * ps / 1048576
|
||||
inactive_mb = int(${inactive_pages:-0}) * ps / 1048576
|
||||
wired_mb = int(${wired_pages:-0}) * ps / 1048576
|
||||
compressed_mb = int(${compressed_pages:-0}) * ps / 1048576
|
||||
used_mb = active_mb + wired_mb + compressed_mb
|
||||
total_mb = free_mb + active_mb + inactive_mb + wired_mb + compressed_mb
|
||||
pct = used_mb / total_mb * 100 if total_mb > 0 else 0
|
||||
print(f'{used_mb:.0f}MB used / {total_mb:.0f}MB total ({pct:.0f}%)')
|
||||
if pct > 90: print('SEVERITY:critical')
|
||||
elif pct > 75: print('SEVERITY:warning')
|
||||
else: print('SEVERITY:info')
|
||||
" 2>/dev/null || echo "unknown")
|
||||
mem_severity=$(echo "$mem_summary" | grep "SEVERITY:" | cut -d: -f2)
|
||||
mem_info=$(echo "$mem_summary" | head -1)
|
||||
json_finding "${mem_severity:-info}" "Memory: ${mem_info}" "review" ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Swap ---
|
||||
swap_info=$(sysctl vm.swapusage 2>/dev/null || echo "")
|
||||
if [ -n "$swap_info" ]; then
|
||||
swap_used=$(echo "$swap_info" | grep -o "used = [0-9.]*M" | awk '{print $3}' | tr -d 'M')
|
||||
if [ -n "$swap_used" ] && command -v python3 &>/dev/null; then
|
||||
swap_severity=$(python3 -c "
|
||||
s=float('${swap_used}')
|
||||
if s > 4096: print('warning')
|
||||
elif s > 1024: print('info')
|
||||
else: print('info')
|
||||
" 2>/dev/null || echo "info")
|
||||
json_finding "$swap_severity" "Swap: ${swap_used}MB used" "review" "$swap_info"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Disk usage ---
|
||||
disk_info=$(df -h / 2>/dev/null | tail -1)
|
||||
if [ -n "$disk_info" ]; then
|
||||
disk_used_pct=$(echo "$disk_info" | awk '{print $5}' | tr -d '%')
|
||||
disk_avail=$(echo "$disk_info" | awk '{print $4}')
|
||||
disk_total=$(echo "$disk_info" | awk '{print $2}')
|
||||
|
||||
if [ "$disk_used_pct" -gt 90 ] 2>/dev/null; then
|
||||
json_finding "critical" "Disk ${disk_used_pct}% full (${disk_avail} free of ${disk_total})" "cleanup" "Consider running cleanup module"
|
||||
elif [ "$disk_used_pct" -gt 75 ] 2>/dev/null; then
|
||||
json_finding "warning" "Disk ${disk_used_pct}% full (${disk_avail} free of ${disk_total})" "monitor" ""
|
||||
else
|
||||
json_finding "info" "Disk ${disk_used_pct}% full (${disk_avail} free of ${disk_total})" "none" ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Large directories (top 5 in home) ---
|
||||
if command -v du &>/dev/null; then
|
||||
large_dirs=$(du -sh "$HOME"/*/ "$HOME"/.[!.]* 2>/dev/null | sort -hr | head -5 || echo "")
|
||||
if [ -n "$large_dirs" ]; then
|
||||
dir_summary=$(echo "$large_dirs" | awk '{printf "%s (%s), ", $2, $1}' | sed 's/, $//')
|
||||
json_finding "info" "Largest directories in home" "review" "$dir_summary"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Battery (laptops only) ---
|
||||
battery_info=$(system_profiler SPPowerDataType 2>/dev/null || echo "")
|
||||
if echo "$battery_info" | grep -q "Cycle Count"; then
|
||||
cycle_count=$(echo "$battery_info" | grep "Cycle Count" | awk '{print $NF}')
|
||||
condition=$(echo "$battery_info" | grep "Condition" | awk '{print $NF}')
|
||||
max_capacity=$(echo "$battery_info" | grep "Maximum Capacity" | awk '{print $NF}' || echo "unknown")
|
||||
|
||||
if [ "$condition" = "Normal" ]; then
|
||||
json_finding "info" "Battery: ${condition}, ${cycle_count} cycles, ${max_capacity} capacity" "none" ""
|
||||
else
|
||||
json_finding "warning" "Battery: ${condition}, ${cycle_count} cycles, ${max_capacity} capacity" "review" "Battery may need service"
|
||||
fi
|
||||
fi
|
||||
Reference in New Issue
Block a user