refactor: reorganize skill numbering, remove obsolete skills, rename shared libs

- Rename: 00→80 claude-settings-optimizer, 88→79 dintel-skill-update,
  92→81 mac-optimizer, 93→82 tui-design-template
- Rename: dintel-shared → _dintel-shared (consistent with _ourdigital-shared)
- Remove: 61-gtm-manager, 62-gtm-guardian (obsolete), 99_archive
- Update all dintel-* skill refs (114 occurrences across 31 files)
- Sync README.md, CLAUDE.md, AGENTS.md with new structure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 19:32:44 +09:00
parent ee355479b7
commit 877db1aa0f
115 changed files with 200 additions and 10979 deletions

View File

@@ -0,0 +1,45 @@
{
"name": "mac-optimizer",
"version": "1.0.0",
"description": "Modular macOS system health toolkit with read-only audits, cleanup, and security checks",
"author": {
"name": "OurDigital"
},
"keywords": [
"macos",
"system",
"optimization",
"cleanup",
"security",
"homebrew",
"packages",
"disk-space"
],
"license": "MIT",
"commands": [
{
"name": "mac-doctor",
"description": "Full macOS system health check — runs all 5 audit modules and presents unified findings"
},
{
"name": "mac-packages",
"description": "Audit package managers (Homebrew, npm, pip, pyenv) for outdated packages and issues"
},
{
"name": "mac-environment",
"description": "Audit shell environment — PATH, symlinks, shell configs, and startup time"
},
{
"name": "mac-security",
"description": "Security posture assessment — SIP, Gatekeeper, Firewall, FileVault, SSH, ports"
},
{
"name": "mac-cleanup",
"description": "Scan and clean caches, logs, and clutter — shows sizes first, cleans only with consent"
},
{
"name": "mac-resources",
"description": "Monitor CPU, memory, disk, battery, and identify resource-hungry processes"
}
]
}

View File

@@ -0,0 +1,53 @@
---
allowed-tools: Bash(bash *audit_cleanup.sh*), Bash(bash *cleanup_execute.sh*), Bash(du *), Bash(brew *), Bash(npm *), Bash(pip *), Bash(docker *), Bash(rm *)
description: Scan and clean caches, logs, and clutter — shows sizes first, cleans only with consent
---
## Your task
Scan for reclaimable disk space and offer cleanup.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_cleanup.sh
```
### Step 2 — Report
Parse JSON output and present as a table sorted by size (largest first):
| Category | Size | Risk | Target |
|---|---|---|---|
Show the total reclaimable space.
### Step 3 — Ask for consent
Ask which categories to clean. Group by risk level (Safe / Moderate / Risky).
### Step 4 — Dry run first
For approved targets, always run dry-run first:
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/cleanup_execute.sh --dry-run <targets>
```
Show the output and ask for final confirmation.
### Step 5 — Execute
Only after explicit confirmation:
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/cleanup_execute.sh --execute <targets>
```
### Safety rules
- Never execute cleanup without explicit user approval
- Always show sizes before deleting anything
- Always run --dry-run before --execute
- Docker cleanup uses `docker system prune`, never direct file deletion
- No sudo by default — if an action needs sudo, state why and ask first

View File

@@ -0,0 +1,60 @@
---
allowed-tools: Bash(bash *audit_*.sh*), Bash(bash *cleanup_execute.sh*), Bash(du *), Bash(df *), Bash(brew *), Bash(npm *), Bash(pip *), Bash(docker *), Bash(cp *)
description: Full macOS system health check — runs all 5 audit modules and presents unified findings
---
## Your task
Run a full macOS system health check using all 5 audit modules. Follow this workflow exactly:
### Step 1 — Run all audits
Run these scripts sequentially, collecting all JSON output:
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_packages.sh
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_environment.sh
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_security.sh
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_cleanup.sh
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_resources.sh
```
If a script exits non-zero, report the error and continue with other modules.
### Step 2 — Parse and report
Parse all JSON lines. Each has: `{"module":"...","severity":"...","finding":"...","action":"...","details":"..."}`
Present findings grouped by severity (critical first, then warning, then info) as markdown tables:
```
### Critical
| Module | Finding | Recommended Action |
|---|---|---|
### Warning
| Module | Finding | Recommended Action |
|---|---|---|
### Info
| Module | Finding | Recommended Action |
|---|---|---|
```
### Step 3 — Ask for consent
STOP and ask: "Which actions would you like me to perform? You can approve by category (e.g., 'update packages and clean caches') or review each item."
### Step 4 — Execute approved actions only
For cleanup actions, always run `--dry-run` first and show what will happen before running `--execute`.
### Safety rules
- Never execute cleanup without explicit user approval
- Always show sizes before deleting anything
- Security module is read-only — present findings and remediation guidance only
- Back up shell configs before modifying: `cp ~/.zshrc ~/.config/mac-optimizer-backups/.zshrc.$(date +%s)`
- Process deny-list: never suggest killing kernel_task, launchd, WindowServer, loginwindow, mds, mds_stores, opendirectoryd, coreaudiod, SystemUIServer, Finder, Dock
- No sudo by default — if an action needs sudo, state why and ask first
- Docker cleanup uses `docker system prune`, never direct file deletion

View File

@@ -0,0 +1,30 @@
---
allowed-tools: Bash(bash *audit_environment.sh*), Bash(echo *), Bash(cp *)
description: Audit shell environment — PATH, symlinks, shell configs, and startup time
---
## Your task
Audit the shell environment on this Mac.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_environment.sh
```
### Step 2 — Report
Parse JSON output and present as a severity-ranked markdown table:
| Severity | Finding | Recommended Action | Details |
|---|---|---|---|
### Step 3 — Ask for consent
If there are fixable issues (duplicate PATH entries, broken symlinks, non-existent directories), list them and ask which to fix.
### Safety rules
- Back up shell configs before modifying: `cp ~/.zshrc ~/.config/mac-optimizer-backups/.zshrc.$(date +%s)`
- Present PATH consolidation suggestions but don't auto-modify

View File

@@ -0,0 +1,32 @@
---
allowed-tools: Bash(bash *audit_packages.sh*), Bash(brew *), Bash(npm *), Bash(pip *), Bash(nvm *), Bash(pyenv *)
description: Audit package managers (Homebrew, npm, pip, pyenv) for outdated packages and issues
---
## Your task
Audit all package managers on this Mac.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_packages.sh
```
### Step 2 — Report
Parse JSON output and present as a severity-ranked markdown table:
| Severity | Finding | Recommended Action | Details |
|---|---|---|---|
### Step 3 — Ask for consent
List available update actions grouped by risk. Ask which actions to perform.
### Safety notes
- `brew upgrade` is generally safe but can break projects pinned to specific versions
- Global npm packages are version-independent from project dependencies
- pip upgrades can break dependency chains — suggest `--dry-run` first
- Only audit pyenv-managed environments (not system Python)

View File

@@ -0,0 +1,29 @@
---
allowed-tools: Bash(bash *audit_resources.sh*), Bash(ps *), Bash(kill *), Bash(df *)
description: Monitor CPU, memory, disk, battery, and identify resource-hungry processes
---
## Your task
Check system resource usage on this Mac.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_resources.sh
```
### Step 2 — Report
Parse JSON output and present as a severity-ranked markdown table:
| Severity | Finding | Recommended Action | Details |
|---|---|---|---|
### Step 3 — Offer actions
If high-resource processes are found, offer to help. But respect the deny-list:
**Never suggest killing**: kernel_task, launchd, WindowServer, loginwindow, mds, mds_stores, opendirectoryd, coreaudiod, SystemUIServer, Finder, Dock
For user processes consuming excessive resources, ask before terminating.

View File

@@ -0,0 +1,25 @@
---
allowed-tools: Bash(bash *audit_security.sh*)
description: Security posture assessment — SIP, Gatekeeper, Firewall, FileVault, SSH, ports
---
## Your task
Run a read-only security posture assessment.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_security.sh
```
### Step 2 — Report
Parse JSON output and present as a severity-ranked markdown table:
| Severity | Finding | Recommended Action | Details |
|---|---|---|---|
### Step 3 — Guidance only
The security module is **read-only**. Present findings and remediation guidance. Do NOT attempt to change security settings. Explain how the user can fix issues themselves via System Settings or Terminal commands.

View File

@@ -0,0 +1,44 @@
---
name: mac-optimizer
description: macOS system health toolkit — use when user mentions system optimization, cleanup, packages, security audit, disk space, performance, brew updates, cache clearing, or system health on Mac
version: 1.0.0
---
# Mac Optimizer
Modular macOS system health toolkit. Runs read-only audits first, then recommends actions with user consent.
## Module Routing
| Keywords | Command |
|---|---|
| brew, homebrew, npm, nvm, pip, pyenv, packages, update, outdated | /mac-packages |
| path, shell, zshrc, environment, env, config, symlink | /mac-environment |
| security, firewall, sip, gatekeeper, filevault, ports, ssh | /mac-security |
| cache, cleanup, clean, logs, clutter, disk space, free space, trash | /mac-cleanup |
| cpu, memory, ram, disk, battery, processes, resources, slow | /mac-resources |
| doctor, audit, health, full check, everything, system check | /mac-doctor (all) |
Default to **/mac-doctor** when the request is ambiguous.
When the user's request matches a specific module, suggest the appropriate slash command. When the request is broad (e.g., "check my system", "run a health check"), use /mac-doctor.
## Execution Model
Every module follows this flow:
1. **Audit** — run the module's script (read-only)
2. **Report** — parse JSON output, present findings as a severity-ranked table
3. **Recommend** — list available actions grouped by risk
4. **Consent** — ask user which actions to approve
5. **Act** — execute only approved actions
## Safety Rules
- **Never execute cleanup without explicit user approval**
- **Always show sizes before deleting anything**
- **Security module is read-only** — present findings and remediation guidance only
- **Back up shell configs** before modifying
- **Process deny-list**: never suggest killing kernel_task, launchd, WindowServer, loginwindow, mds, mds_stores, opendirectoryd, coreaudiod, SystemUIServer, Finder, Dock
- **No sudo by default** — if an action needs sudo, state why and ask first
- Docker cleanup uses `docker system prune`, never direct file deletion

View File

@@ -0,0 +1,74 @@
# Cleanup Targets Reference
## Risk Ratings
| Risk | Meaning |
|---|---|
| Safe | Can be deleted without consequence; regenerated automatically |
| Moderate | Review before deleting; may lose useful data |
| Risky | May break applications; requires careful inspection |
| Info only | Report size but do not offer deletion |
## Target Details
### User Logs (Safe)
- **Path**: `~/Library/Logs/`
- **Cleanup target**: `user-logs`
- **Notes**: Application crash reports and debug logs. Regenerated as needed.
### System Logs (Moderate, requires sudo)
- **Path**: `/var/log/`
- **Cleanup target**: not available via cleanup_execute.sh (requires sudo)
- **Notes**: System-level logs. Only clean old/rotated files. Active logs should not be deleted.
### User Caches (Safe, per-app)
- **Path**: `~/Library/Caches/`
- **Cleanup target**: `user-caches`
- **Notes**: App caches regenerated on next use. Some apps may need to re-download data. Browser caches can be large.
### Homebrew Cache (Safe)
- **Path**: `$(brew --cache)` (typically `~/Library/Caches/Homebrew/`)
- **Cleanup target**: `brew-cache`
- **Notes**: Downloaded bottles and source archives. `brew cleanup` handles this properly.
### npm Cache (Safe)
- **Path**: `~/.npm/_cacache/`
- **Cleanup target**: `npm-cache`
- **Notes**: Package download cache. `npm cache clean --force` is the proper way to clear.
### pip Cache (Safe)
- **Path**: `~/Library/Caches/pip/`
- **Cleanup target**: `pip-cache`
- **Notes**: Downloaded wheel/sdist cache. `pip cache purge` is the proper way to clear.
### Xcode DerivedData (Safe)
- **Path**: `~/Library/Developer/Xcode/DerivedData/`
- **Cleanup target**: `xcode-derived`
- **Notes**: Build artifacts and indexes. Rebuilt on next Xcode build. Can grow very large.
### Xcode Archives (Moderate)
- **Path**: `~/Library/Developer/Xcode/Archives/`
- **Cleanup target**: `xcode-archives`
- **Notes**: App Store submission archives. May be needed for symbolication of crash reports. Review before deleting.
### iOS DeviceSupport (Safe)
- **Path**: `~/Library/Developer/Xcode/iOS DeviceSupport/`
- **Cleanup target**: `ios-support`
- **Notes**: Debug symbols for connected iOS devices. Re-downloaded when device connects again.
### Docker (Moderate)
- **Cleanup target**: `docker`
- **Notes**: Uses `docker system prune -f`. Removes stopped containers, unused networks, dangling images. Does NOT remove named volumes. Never delete Docker's filesystem directly.
### Trash (Safe)
- **Path**: `~/.Trash/`
- **Cleanup target**: `trash`
- **Notes**: Items in Trash. User has already "deleted" these.
### Old Downloads (Info only)
- **Path**: `~/Downloads/` (files >90 days old)
- **Notes**: Report only. User should review manually. Never auto-delete downloads.
### Application Support Remnants (Risky)
- **Path**: `~/Library/Application Support/` orphaned directories
- **Notes**: Report only. Directories from uninstalled apps. Difficult to determine automatically which are truly orphaned. Manual review required.

View File

@@ -0,0 +1,52 @@
# Package Manager Reference
## Homebrew
### Update workflow
```bash
brew update # Update Homebrew itself and formulae list
brew outdated # List outdated formulae
brew outdated --cask # List outdated casks
brew upgrade # Upgrade all outdated formulae
brew upgrade <name> # Upgrade specific formula
brew upgrade --cask # Upgrade all casks
brew cleanup --prune=0 # Remove all cached downloads
brew autoremove # Remove orphaned dependencies
brew doctor # Diagnose issues
```
### Safety notes
- `brew upgrade` is generally safe but can break projects pinned to specific versions
- Always check `brew doctor` warnings — some are cosmetic, some indicate real issues
- `brew autoremove` only removes packages not depended on by anything
## npm / nvm
### Update workflow
```bash
nvm ls # List installed Node versions
nvm install --lts # Install latest LTS
npm outdated -g # Check global packages
npm update -g # Update all global packages
npm cache clean --force # Clear npm cache
```
### Safety notes
- Global npm packages are version-independent from project dependencies
- Updating global packages won't affect project-level node_modules
## Python / pyenv
### Update workflow
```bash
pyenv versions # List installed versions
pyenv install --list # Available versions
pip list --outdated # Outdated packages in current env
pip install --upgrade <pkg> # Upgrade specific package
pip cache purge # Clear pip cache
```
### Safety notes
- Only audit pyenv-managed environments (not system Python)
- pip upgrades can break dependency chains — suggest `--dry-run` first
- Virtual environments in project directories are not scanned (out of scope v1)

View File

@@ -0,0 +1,53 @@
# Security Checks Reference
## SIP (System Integrity Protection)
- **Check**: `csrutil status`
- **Expected**: "System Integrity Protection status: enabled."
- **Remediation**: Reboot to Recovery Mode (Cmd+R), open Terminal, run `csrutil enable`
- **Severity**: Critical if disabled
## Gatekeeper
- **Check**: `spctl --status`
- **Expected**: "assessments enabled"
- **Remediation**: `sudo spctl --master-enable`
- **Severity**: Critical if disabled
## Firewall
- **Check**: `/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate`
- **Fallback**: `defaults read /Library/Preferences/com.apple.alf globalstate` (0=off, 1=on, 2=essential)
- **Remediation**: System Settings > Network > Firewall > Turn On
- **Severity**: Critical if disabled
## FileVault
- **Check**: `fdesetup status`
- **Expected**: "FileVault is On."
- **Remediation**: System Settings > Privacy & Security > FileVault > Turn On
- **Severity**: Critical if disabled (disk not encrypted)
## Open Ports
- **Check**: `lsof -iTCP -sTCP:LISTEN -nP`
- **Review**: unexpected services listening on all interfaces (0.0.0.0 or *)
- **Severity**: Info (review needed, not automatically bad)
## SSH Configuration
- **Check**: `/etc/ssh/sshd_config`
- **Flags**: `PasswordAuthentication yes`, `PermitRootLogin yes`
- **Remediation**: Set to `no` and restart sshd
- **Severity**: Warning
## Remote Services
- **Remote Login**: `systemsetup -getremotelogin`
- **Remote Management**: `defaults read /Library/Preferences/com.apple.RemoteManagement ARD_AllLocalUsers`
- **Remediation**: Disable in System Settings > General > Sharing
- **Severity**: Info/Warning depending on context
## Software Updates
- **Check**: `softwareupdate -l` (can be slow, 30s timeout)
- **Remediation**: System Settings > General > Software Update
- **Severity**: Warning if updates available
## Screen Lock
- **Check**: `defaults -currentHost read com.apple.screensaver idleTime`
- **Check**: `defaults read com.apple.screensaver askForPassword`
- **Remediation**: System Settings > Lock Screen
- **Severity**: Warning if password not required

View File

@@ -0,0 +1,160 @@
#!/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"

View File

@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# audit_environment.sh — Audit shell environment and paths 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":"environment","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# --- PATH analysis ---
IFS=':' read -ra path_dirs <<< "$PATH"
total_dirs=${#path_dirs[@]}
# Duplicate detection (compatible with bash 3)
duplicates=()
for i in "${!path_dirs[@]}"; do
dir="${path_dirs[$i]}"
is_dup=false
for j in "${!path_dirs[@]}"; do
if [ "$j" -lt "$i" ] && [ "${path_dirs[$j]}" = "$dir" ]; then
is_dup=true
break
fi
done
if $is_dup; then
duplicates+=("$dir")
fi
done
if [ ${#duplicates[@]} -gt 0 ]; then
dup_list=$(printf '%s, ' "${duplicates[@]}" | sed 's/, $//')
json_finding "warning" "${#duplicates[@]} duplicate PATH entries" "deduplicate" "$dup_list"
else
json_finding "info" "No duplicate PATH entries" "none" "${total_dirs} directories in PATH"
fi
# Non-existent directories
missing=()
for dir in "${path_dirs[@]}"; do
if [ ! -d "$dir" ]; then
missing+=("$dir")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
missing_list=$(printf '%s, ' "${missing[@]}" | sed 's/, $//')
json_finding "warning" "${#missing[@]} non-existent directories in PATH" "remove" "$missing_list"
fi
# --- Broken symlinks ---
broken_symlinks=()
for check_dir in /usr/local/bin "$HOME/.local/bin"; do
if [ -d "$check_dir" ]; then
while IFS= read -r link; do
broken_symlinks+=("$link")
done < <(find "$check_dir" -maxdepth 1 -type l ! -exec test -e {} \; -print 2>/dev/null || true)
fi
done
if [ ${#broken_symlinks[@]} -gt 0 ]; then
broken_list=$(printf '%s, ' "${broken_symlinks[@]}" | sed 's/, $//')
json_finding "warning" "${#broken_symlinks[@]} broken symlinks found" "remove" "$broken_list"
else
json_finding "info" "No broken symlinks in bin directories" "none" ""
fi
# --- Shell config analysis ---
shell_configs=()
for cfg in "$HOME/.zshrc" "$HOME/.zprofile" "$HOME/.zshenv" "$HOME/.bash_profile" "$HOME/.bashrc"; do
if [ -f "$cfg" ]; then
shell_configs+=("$(basename "$cfg")")
fi
done
json_finding "info" "Shell configs found: ${shell_configs[*]}" "none" ""
# Check for PATH modifications across multiple files
path_mods=0
for cfg in "$HOME/.zshrc" "$HOME/.zprofile" "$HOME/.zshenv" "$HOME/.bash_profile" "$HOME/.bashrc"; do
if [ -f "$cfg" ]; then
count=$(grep -c -E 'export PATH|PATH=' "$cfg" 2>/dev/null || echo 0)
count=$(echo "$count" | tr -d '[:space:]')
path_mods=$((path_mods + count))
fi
done
if [ "$path_mods" -gt 5 ]; then
json_finding "warning" "PATH modified ${path_mods} times across shell configs" "consolidate" "Consider consolidating PATH modifications"
fi
# --- Environment variable checks ---
if [ -z "${ANTHROPIC_API_KEY:-}" ]; then
json_finding "info" "ANTHROPIC_API_KEY not set" "none" "Set if using Claude API"
fi
if [ -n "${JAVA_HOME:-}" ] && [ ! -d "$JAVA_HOME" ]; then
json_finding "warning" "JAVA_HOME points to non-existent directory" "fix" "$JAVA_HOME"
fi
# --- Shell startup time ---
if command -v zsh &>/dev/null; then
startup_time=$( { time zsh -i -c exit; } 2>&1 | grep real | awk '{print $2}' || echo "unknown")
# Parse the time - handle both formats like "0m0.123s" and "0.123"
if echo "$startup_time" | grep -q 'm'; then
seconds=$(echo "$startup_time" | sed 's/.*m//' | sed 's/s//')
else
seconds="$startup_time"
fi
if command -v python3 &>/dev/null; then
is_slow=$(python3 -c "print('slow' if float('${seconds}') > 0.5 else 'ok')" 2>/dev/null || echo "ok")
else
is_slow="ok"
fi
if [ "$is_slow" = "slow" ]; then
json_finding "warning" "Shell startup is slow (${startup_time})" "optimize" "Consider profiling with zsh -xv"
else
json_finding "info" "Shell startup time: ${startup_time}" "none" ""
fi
fi

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# audit_packages.sh — Audit package managers 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":"packages","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# --- Homebrew ---
if command -v brew &>/dev/null; then
echo '{"module":"packages","severity":"info","finding":"Homebrew detected","action":"none","details":"'$(brew --version | head -1)'"}'
# brew update
brew update --quiet 2>/dev/null || true
# outdated formulae
outdated=$(brew outdated --json=v2 2>/dev/null || echo '{}')
formula_count=$(echo "$outdated" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('formulae',[])))" 2>/dev/null || echo 0)
cask_count=$(echo "$outdated" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('casks',[])))" 2>/dev/null || echo 0)
if [ "$formula_count" -gt 0 ] 2>/dev/null; then
formula_list=$(echo "$outdated" | python3 -c "
import sys,json
d=json.load(sys.stdin)
names=[f['name'] for f in d.get('formulae',[])]
print(', '.join(names[:10]))
if len(names)>10: print(f'... and {len(names)-10} more')
" 2>/dev/null || echo "")
json_finding "warning" "${formula_count} Homebrew formulae outdated" "brew upgrade" "$formula_list"
fi
if [ "$cask_count" -gt 0 ] 2>/dev/null; then
json_finding "warning" "${cask_count} Homebrew casks outdated" "brew upgrade --cask" ""
fi
if [ "$formula_count" -eq 0 ] && [ "$cask_count" -eq 0 ] 2>/dev/null; then
json_finding "info" "All Homebrew packages up to date" "none" ""
fi
# brew doctor
doctor_output=$(brew doctor 2>&1 || true)
doctor_warnings=$(echo "$doctor_output" | grep -c "Warning:" 2>/dev/null || echo 0)
if [ "$doctor_warnings" -gt 0 ] 2>/dev/null; then
json_finding "warning" "brew doctor found ${doctor_warnings} warning(s)" "brew doctor" "Run brew doctor for details"
fi
# autoremove candidates
autoremove_output=$(brew autoremove --dry-run 2>/dev/null || echo "")
orphan_count=$(echo "$autoremove_output" | grep -c "^=" 2>/dev/null || echo 0)
if echo "$autoremove_output" | grep -q "would remove" 2>/dev/null; then
json_finding "info" "Orphaned Homebrew dependencies found" "brew autoremove" "$autoremove_output"
fi
else
json_finding "info" "Homebrew not installed" "none" "Consider installing from https://brew.sh"
fi
# --- npm / nvm ---
if command -v nvm &>/dev/null || [ -d "$HOME/.nvm" ]; then
# Source nvm if available
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
[ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" 2>/dev/null
if command -v nvm &>/dev/null; then
node_versions=$(nvm ls --no-colors 2>/dev/null | grep -c "v[0-9]" || echo 0)
current_node=$(nvm current 2>/dev/null || echo "none")
json_finding "info" "nvm detected: ${node_versions} version(s), current: ${current_node}" "none" ""
fi
fi
if command -v npm &>/dev/null; then
json_finding "info" "npm detected" "none" "$(npm --version 2>/dev/null)"
npm_outdated=$(npm outdated -g --json 2>/dev/null || echo '{}')
npm_outdated_count=$(echo "$npm_outdated" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
if [ "$npm_outdated_count" -gt 0 ] 2>/dev/null; then
json_finding "warning" "${npm_outdated_count} global npm packages outdated" "npm update -g" ""
fi
else
json_finding "info" "npm not installed" "none" ""
fi
# --- Python / pyenv ---
if command -v pyenv &>/dev/null; then
pyenv_versions=$(pyenv versions --bare 2>/dev/null | wc -l | tr -d ' ')
current_python=$(pyenv version-name 2>/dev/null || echo "system")
json_finding "info" "pyenv detected: ${pyenv_versions} version(s), current: ${current_python}" "none" ""
# Check pip outdated for current environment
if command -v pip &>/dev/null || command -v pip3 &>/dev/null; then
pip_cmd=$(command -v pip3 || command -v pip)
pip_outdated=$($pip_cmd list --outdated --format=json 2>/dev/null || echo '[]')
pip_count=$(echo "$pip_outdated" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
if [ "$pip_count" -gt 0 ] 2>/dev/null; then
json_finding "warning" "${pip_count} pip packages outdated in current env" "pip install --upgrade" ""
fi
fi
elif command -v python3 &>/dev/null; then
py_version=$(python3 --version 2>/dev/null || echo "unknown")
json_finding "info" "System Python: ${py_version} (no pyenv)" "none" ""
fi
# --- Other package managers (presence detection only) ---
for mgr in yarn pnpm cargo go gem composer; do
if command -v "$mgr" &>/dev/null; then
version=$($mgr --version 2>/dev/null | head -1 || echo "unknown")
json_finding "info" "${mgr} detected: ${version}" "none" "Presence check only"
fi
done

View 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

View File

@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# audit_security.sh — Security posture assessment for macOS (read-only)
# 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":"security","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# --- SIP (System Integrity Protection) ---
sip_status=$(csrutil status 2>/dev/null || echo "unknown")
if echo "$sip_status" | grep -q "enabled"; then
json_finding "info" "SIP enabled" "none" ""
elif echo "$sip_status" | grep -q "disabled"; then
json_finding "critical" "SIP is disabled" "report" "System Integrity Protection should be enabled"
else
json_finding "warning" "SIP status unknown" "report" "$sip_status"
fi
# --- Gatekeeper ---
gatekeeper_status=$(spctl --status 2>/dev/null || echo "unknown")
if echo "$gatekeeper_status" | grep -q "assessments enabled"; then
json_finding "info" "Gatekeeper enabled" "none" ""
elif echo "$gatekeeper_status" | grep -q "assessments disabled"; then
json_finding "critical" "Gatekeeper is disabled" "report" "Enable via: sudo spctl --master-enable"
else
json_finding "warning" "Gatekeeper status unknown" "report" "$gatekeeper_status"
fi
# --- Firewall ---
fw_tool="/usr/libexec/ApplicationFirewall/socketfilterfw"
if [ -x "$fw_tool" ]; then
fw_status=$("$fw_tool" --getglobalstate 2>/dev/null || echo "unknown")
if echo "$fw_status" | grep -qi "enabled"; then
json_finding "info" "Firewall enabled" "none" ""
else
json_finding "critical" "Firewall is disabled" "report" "Enable in System Settings > Network > Firewall"
fi
else
# Fallback to defaults
fw_default=$(defaults read /Library/Preferences/com.apple.alf globalstate 2>/dev/null || echo "unknown")
case "$fw_default" in
0) json_finding "critical" "Firewall is disabled" "report" "Enable in System Settings > Network > Firewall" ;;
1|2) json_finding "info" "Firewall enabled" "none" "" ;;
*) json_finding "warning" "Firewall status unknown" "report" "" ;;
esac
fi
# --- FileVault ---
fv_status=$(fdesetup status 2>/dev/null || echo "unknown")
if echo "$fv_status" | grep -q "FileVault is On"; then
json_finding "info" "FileVault enabled (disk encryption)" "none" ""
elif echo "$fv_status" | grep -q "FileVault is Off"; then
json_finding "critical" "FileVault is off (disk not encrypted)" "report" "Enable in System Settings > Privacy and Security > FileVault"
else
json_finding "warning" "FileVault status unknown" "report" "$fv_status"
fi
# --- Open listening ports ---
listening_ports=$(lsof -iTCP -sTCP:LISTEN -nP 2>/dev/null | tail -n +2 || echo "")
port_count=$(echo "$listening_ports" | grep -c . 2>/dev/null || echo 0)
if [ "$port_count" -gt 0 ]; then
# Extract unique port/process combos
port_summary=$(echo "$listening_ports" | awk '{print $1":"$9}' | sort -u | head -10 | tr '\n' ', ' | sed 's/,$//')
json_finding "info" "${port_count} listening TCP connections" "review" "$port_summary"
fi
# --- SSH config ---
ssh_config="/etc/ssh/sshd_config"
if [ -f "$ssh_config" ]; then
# Check password auth
if grep -qi "^PasswordAuthentication yes" "$ssh_config" 2>/dev/null; then
json_finding "warning" "SSH password authentication enabled" "report" "Consider key-based auth only"
fi
# Check root login
if grep -qi "^PermitRootLogin yes" "$ssh_config" 2>/dev/null; then
json_finding "warning" "SSH root login permitted" "report" "Consider disabling root SSH login"
fi
fi
# --- Remote login ---
remote_login=$(systemsetup -getremotelogin 2>/dev/null || echo "unknown")
if echo "$remote_login" | grep -qi "on"; then
json_finding "info" "Remote Login (SSH) is enabled" "review" "Disable if not needed: System Settings > General > Sharing"
fi
# --- Remote management ---
remote_mgmt=$(defaults read /Library/Preferences/com.apple.RemoteManagement ARD_AllLocalUsers 2>/dev/null || echo "not configured")
if [ "$remote_mgmt" = "1" ]; then
json_finding "warning" "Remote Management enabled for all users" "review" "Check System Settings > General > Sharing"
fi
# --- Software updates ---
# Just check if updates are available (this can be slow, so we time-limit it)
# macOS lacks GNU timeout — use perl alarm as a portable alternative
update_check=$(perl -e 'alarm 30; exec @ARGV' -- softwareupdate -l 2>&1 || echo "timeout or error")
if echo "$update_check" | grep -q "No new software available"; then
json_finding "info" "macOS software is up to date" "none" ""
elif echo "$update_check" | grep -q "timeout or error"; then
json_finding "info" "Software update check timed out" "none" "Run softwareupdate -l manually"
else
update_count=$(echo "$update_check" | grep -c "^\*" 2>/dev/null || echo 0)
json_finding "warning" "${update_count} software update(s) available" "softwareupdate" "Run: softwareupdate -l for details"
fi
# --- Screen lock ---
screen_lock_delay=$(defaults -currentHost read com.apple.screensaver idleTime 2>/dev/null || echo "not set")
ask_password=$(defaults read com.apple.screensaver askForPassword 2>/dev/null || echo "unknown")
if [ "$ask_password" = "0" ]; then
json_finding "warning" "Screen lock password not required" "report" "Enable in System Settings > Lock Screen"
elif [ "$screen_lock_delay" != "not set" ] && [ "$screen_lock_delay" -gt 300 ] 2>/dev/null; then
json_finding "info" "Screen lock delay: ${screen_lock_delay}s (consider reducing)" "report" ""
fi

View File

@@ -0,0 +1,169 @@
#!/usr/bin/env bash
# cleanup_execute.sh — Execute cleanup actions on macOS
# Usage: cleanup_execute.sh [--dry-run|--execute] <target> [<target>...]
# Targets: user-logs, user-caches, brew-cache, npm-cache, pip-cache,
# xcode-derived, xcode-archives, ios-support, docker, trash
set -euo pipefail
MODE="dry-run"
TARGETS=()
BACKUP_DIR="${BACKUP_DIR:-$HOME/.config/mac-optimizer-backups}"
usage() {
echo "Usage: $0 [--dry-run|--execute] <target> [<target>...]"
echo ""
echo "Options:"
echo " --dry-run Show what would be deleted (default)"
echo " --execute Actually perform cleanup"
echo ""
echo "Targets:"
echo " user-logs ~/Library/Logs/"
echo " user-caches ~/Library/Caches/"
echo " brew-cache Homebrew download cache"
echo " npm-cache npm cache"
echo " pip-cache pip cache"
echo " xcode-derived Xcode DerivedData"
echo " xcode-archives Xcode Archives"
echo " ios-support iOS DeviceSupport files"
echo " docker Docker system prune"
echo " trash Empty Trash"
exit 1
}
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) MODE="dry-run"; shift ;;
--execute) MODE="execute"; shift ;;
--help|-h) usage ;;
*) TARGETS+=("$1"); shift ;;
esac
done
if [ ${#TARGETS[@]} -eq 0 ]; then
echo "Error: No targets specified"
usage
fi
log_action() {
local target="$1" action="$2" size="$3"
echo "[${MODE}] ${target}: ${action} (${size})"
}
clean_dir() {
local name="$1" path="$2"
if [ ! -d "$path" ]; then
echo "[skip] ${name}: directory not found (${path})"
return
fi
local size=$(du -sh "$path" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "$name" "would remove contents of ${path}" "$size"
else
log_action "$name" "removing contents of ${path}" "$size"
find "$path" -mindepth 1 -delete 2>/dev/null || {
# Fallback: remove what we can
find "$path" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true
}
echo "[done] ${name}: cleaned"
fi
}
for target in "${TARGETS[@]}"; do
case "$target" in
user-logs)
clean_dir "User Logs" "$HOME/Library/Logs"
;;
user-caches)
clean_dir "User Caches" "$HOME/Library/Caches"
;;
brew-cache)
if command -v brew &>/dev/null; then
brew_cache=$(brew --cache 2>/dev/null || echo "")
if [ -n "$brew_cache" ]; then
size=$(du -sh "$brew_cache" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "Homebrew Cache" "would run brew cleanup" "$size"
else
log_action "Homebrew Cache" "running brew cleanup" "$size"
brew cleanup --prune=0 2>/dev/null || true
echo "[done] Homebrew Cache: cleaned"
fi
fi
else
echo "[skip] brew-cache: Homebrew not installed"
fi
;;
npm-cache)
if command -v npm &>/dev/null; then
npm_cache="$HOME/.npm/_cacache"
if [ -d "$npm_cache" ]; then
size=$(du -sh "$npm_cache" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "npm Cache" "would run npm cache clean --force" "$size"
else
log_action "npm Cache" "running npm cache clean" "$size"
npm cache clean --force 2>/dev/null || true
echo "[done] npm Cache: cleaned"
fi
fi
else
echo "[skip] npm-cache: npm not installed"
fi
;;
pip-cache)
pip_cache="$HOME/Library/Caches/pip"
if [ -d "$pip_cache" ]; then
size=$(du -sh "$pip_cache" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "pip Cache" "would run pip cache purge" "$size"
else
log_action "pip Cache" "running pip cache purge" "$size"
pip3 cache purge 2>/dev/null || pip cache purge 2>/dev/null || true
echo "[done] pip Cache: cleaned"
fi
else
echo "[skip] pip-cache: no pip cache found"
fi
;;
xcode-derived)
clean_dir "Xcode DerivedData" "$HOME/Library/Developer/Xcode/DerivedData"
;;
xcode-archives)
clean_dir "Xcode Archives" "$HOME/Library/Developer/Xcode/Archives"
;;
ios-support)
clean_dir "iOS DeviceSupport" "$HOME/Library/Developer/Xcode/iOS DeviceSupport"
;;
docker)
if command -v docker &>/dev/null; then
if [ "$MODE" = "dry-run" ]; then
echo "[dry-run] Docker: would run docker system prune"
docker system df 2>/dev/null || echo " (Docker not running)"
else
echo "[execute] Docker: running docker system prune -f"
docker system prune -f 2>/dev/null || echo " Docker prune failed (is Docker running?)"
fi
else
echo "[skip] docker: Docker not installed"
fi
;;
trash)
trash_dir="$HOME/.Trash"
if [ -d "$trash_dir" ]; then
size=$(du -sh "$trash_dir" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "Trash" "would empty Trash" "$size"
else
log_action "Trash" "emptying Trash" "$size"
rm -rf "$trash_dir"/* 2>/dev/null || true
rm -rf "$trash_dir"/.[!.]* 2>/dev/null || true
echo "[done] Trash: emptied"
fi
fi
;;
*)
echo "[error] Unknown target: ${target}"
;;
esac
done