Files
our-claude-skills/custom-skills/92-mac-optimizer/scripts/audit_cleanup.sh
Andrew Yim f1a973c42d 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>
2026-03-11 15:59:24 +09:00

161 lines
5.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# audit_cleanup.sh — Scan caches, logs, and clutter on macOS, report sizes
# 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":"cleanup","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# Get directory size in human-readable and bytes
dir_size() {
local path="$1"
if [ -d "$path" ]; then
local bytes=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
local human=$(du -sh "$path" 2>/dev/null | awk '{print $1}')
echo "${bytes:-0}|${human:-0B}"
else
echo "0|0B"
fi
}
classify_size() {
local kb="$1"
if [ "$kb" -gt 1048576 ] 2>/dev/null; then # > 1GB
echo "warning"
else
echo "info"
fi
}
total_reclaimable_kb=0
# --- User logs ---
result=$(dir_size "$HOME/Library/Logs")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "User logs: ${human}" "clean" "~/Library/Logs/ [safe, no sudo]"
# --- System logs ---
result=$(dir_size "/var/log" 2>/dev/null || echo "0|0B")
IFS='|' read -r kb human <<< "$result"
if [ "$kb" -gt 0 ] 2>/dev/null; then
severity=$(classify_size "$kb")
json_finding "$severity" "System logs: ${human}" "clean" "/var/log/ [moderate, requires sudo]"
fi
# --- User caches ---
result=$(dir_size "$HOME/Library/Caches")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "User caches: ${human}" "clean" "~/Library/Caches/ [safe, per-app]"
# --- Homebrew cache ---
if command -v brew &>/dev/null; then
brew_cache=$(brew --cache 2>/dev/null || echo "")
if [ -n "$brew_cache" ] && [ -d "$brew_cache" ]; then
result=$(dir_size "$brew_cache")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "Homebrew cache: ${human}" "brew cleanup" "$brew_cache [safe]"
fi
fi
# --- npm cache ---
npm_cache="$HOME/.npm/_cacache"
if [ -d "$npm_cache" ]; then
result=$(dir_size "$npm_cache")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "npm cache: ${human}" "npm cache clean --force" "$npm_cache [safe]"
fi
# --- pip cache ---
pip_cache="$HOME/Library/Caches/pip"
if [ -d "$pip_cache" ]; then
result=$(dir_size "$pip_cache")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "pip cache: ${human}" "pip cache purge" "$pip_cache [safe]"
fi
# --- Xcode derived data ---
xcode_derived="$HOME/Library/Developer/Xcode/DerivedData"
if [ -d "$xcode_derived" ]; then
result=$(dir_size "$xcode_derived")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "Xcode DerivedData: ${human}" "clean" "$xcode_derived [safe]"
fi
# --- Xcode archives ---
xcode_archives="$HOME/Library/Developer/Xcode/Archives"
if [ -d "$xcode_archives" ]; then
result=$(dir_size "$xcode_archives")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "Xcode Archives: ${human}" "clean" "$xcode_archives [moderate - check before deleting]"
fi
# --- iOS Device Support ---
ios_support="$HOME/Library/Developer/Xcode/iOS DeviceSupport"
if [ -d "$ios_support" ]; then
result=$(dir_size "$ios_support")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "iOS DeviceSupport: ${human}" "clean" "$ios_support [safe]"
fi
# --- Docker ---
if command -v docker &>/dev/null; then
docker_df=$(docker system df 2>/dev/null || echo "")
if [ -n "$docker_df" ]; then
reclaimable=$(echo "$docker_df" | grep -i "reclaimable" | head -1 || echo "")
json_finding "info" "Docker space usage detected" "docker system prune" "Run docker system df for details [use docker system prune to clean]"
fi
else
# Skip silently if docker not installed
true
fi
# --- Trash ---
trash_dir="$HOME/.Trash"
if [ -d "$trash_dir" ]; then
result=$(dir_size "$trash_dir")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
if [ "$kb" -gt 0 ] 2>/dev/null; then
severity=$(classify_size "$kb")
json_finding "$severity" "Trash: ${human}" "empty trash" "$trash_dir [safe]"
fi
fi
# --- Old downloads (report only) ---
if [ -d "$HOME/Downloads" ]; then
old_files=$(find "$HOME/Downloads" -maxdepth 1 -mtime +90 -type f 2>/dev/null | wc -l | tr -d ' ')
if [ "$old_files" -gt 0 ] 2>/dev/null; then
old_size=$(find "$HOME/Downloads" -maxdepth 1 -mtime +90 -type f -exec du -ck {} + 2>/dev/null | tail -1 | awk '{print $1}')
old_human=$(echo "$old_size" | awk '{printf "%.1fM", $1/1024}')
json_finding "info" "${old_files} files in Downloads older than 90 days (${old_human})" "review" "Info only - review manually"
fi
fi
# --- Total summary ---
total_human=$(echo "$total_reclaimable_kb" | awk '{if($1>1048576) printf "%.1fG",$1/1048576; else if($1>1024) printf "%.1fM",$1/1024; else printf "%dK",$1}')
json_finding "info" "Total scannable space: ${total_human}" "review" "Approx reclaimable across all categories"