mac-declutter 是做什麼的?
一套經實戰驗證的 Mac 開發者深度清理指南:AI Agent 殘留、僵屍背景服務、應用殘餘、代理陷阱,以及效能審計。
核心原則:
- 先盤點再刪除。 絕不盲目解除安裝——先測量大小、上次使用日期與執行狀態,再讓使用者選擇。
- 除非使用者明確要求,否則絕不觸碰工作階段記錄或使用者資料。 快取可以重新產生,但工作階段不行。
- 每次變更後都要驗證 —— 程序已消失、連線埠已關閉、登入項目已移除。
- 系統完整性保護 (SIP) 是硬性限制。 根使用者擁有的應用程式、沙盒容器與 DriverKit 擴充功能各有允許的移除路徑;不要與作業系統對抗。
第 0 階段 —— 網路檢測(優先執行)
症狀「所有東西都斷線」幾乎永遠是本機代理程式掛掉,而不是應用程式壞掉:
scutil --proxy # 系統代理指向哪裡
nc -z -w 2 127.0.0.1 <port> # 真的有人在監聽嗎?
lsof -nP -iTCP:<port> -sTCP:LISTEN # 誰佔用了這個埠
經典陷阱:Clash / V2Ray 類工具將 macOS 系統代理設為 127.0.0.1:PORT,然後核心程式死掉——規則模式無關緊要,所有流量都在門口死亡。 會遵循系統代理的應用程式(大多數 GUI 應用與部分 CLI)都會失敗;curl 仍能工作,因為它繞過了系統代理,從而誤導診斷。 解決方法:重新啟動代理核心,或關閉代理(networksetup -setwebproxystate Wi-Fi off + -setsecurewebproxystate + -setsocksfirewallproxystate + 關閉 WPAD),或將應用程式自身的代理設定指向空值。 切記:curl 忽略系統代理——請使用 curl -x http://127.0.0.1:PORT 來測試應用程式風格的連線。
第 1 階段 —— 盤點(唯讀)
執行 scripts/inventory.sh,或手動涵蓋以下面向:
- Agent 設定目錄:
~/.claude、~/.codex、~/.pi、~/.jcode、~/.config/opencode等。 對每個目錄:大小(du -sh)、最後活動時間(最新檔案的 mtime)、CLI 二進位檔是否存在(which)。 - 登入 / 驗證狀態:
auth.json、openai-auth.json、憑證檔案——列出金鑰名稱,絕不輸出機密。 - 背景服務:
launchctl list | grep -v com.apple以及~/Library/LaunchAgents/*.plist——讀取每個 plist 的ProgramArguments以了解其實際執行的內容。 - 應用程式:對
/Applications和~/Applications中每個非 Apple 的.app,收集大小、透過mdls取得的kMDItemLastUsedDate,以及_MASReceipt是否存在(App Store 應用程式)。 「從未開啟」+ 體積龐大 = 主要清理對象。 - 快取:
~/Library/Caches、~/.npm、pip / bun 快取——體積龐大且永遠可以安全清除。
將結果呈現為保留/移除表格,讓使用者選擇。 標記付費 / 訂閱應用程式(例如 _MASReceipt、已知的訂閱產品),以便使用者在刪除前取消帳單。
第 2 階段 —— 解除安裝手冊
針對每個目標,依序操作:
- 停止服務:對每個相關的 LaunchAgent/Daemon 執行
launchctl remove <label>。 - 刪除 plist 檔案:
~/Library/LaunchAgents/<label>.plist。 - 刪除應用程式 / CLI:
rm -rf應用程式.app/npm uninstall -g <pkg>/ 移除二進位檔。Permission denied→ 根使用者擁有(.pkg 安裝)。 使用osascript -e 'do shell script "rm -rf ..." with administrator privileges'(會彈出一次 GUI 密碼提示)。 絕不盲目使用sudo。
- Library 殘留:檢查並移除
~/Library/{Application Support,Caches,Containers,HTTPStorages,Preferences}/<app-or-bundle-id>。- 沙盒
Containers/*可能連 root 都無法處理(容器管理器保護)。 如果很小,就留著——作業系統會回收孤兒容器;禁用 SIP 永遠不值得。
- 沙盒
- 系統擴充功能:
systemextensionsctl list。 SIP 會阻止直接解除安裝——唯一乾淨的路徑是重新安裝供應商應用程式,並執行其官方解除安裝程式(在/Library/Application Support/<vendor>/下尋找uninstall.sh)。 DriverKit.dext驅動程式僅在下次重新啟動時才會卸載——告知使用者重新啟動後該項目就會消失。 - 僵屍登入項目:刪除應用程式後,其「開機時登入」項目通常會以僵屍形式殘留。 使用
osascript -e 'tell application "System Events" to delete login item "<name>"'移除。 - 執行中的殘留:已刪除的應用程式可能會留下從更新程式的暫存目錄(例如 Squirrel 的
ShipIt暫存區)啟動的程序。ps aux | grep -i <name>,殺死它們,並檢查$HOME中是否有資料目錄(~/.<appname>)——更新程式產生的 daemon 通常會在那裡隱藏數百 MB 的資料。
第 3 階段 —— 快取清除(零風險)
npm cache clean --force
rm -rf ~/Library/Caches/pip ~/.bun/install/cache
rm -rf ~/Library/Caches/<deleted-app>/*
快取會重新產生。 工作階段歷史記錄(sessions/、projects/、*.jsonl 對話記錄)不會——除非使用者明確要求,否則不要動它們。
第 4 階段 —— 效能審計
memory_pressure | tail -2 # 剩餘 % 與壓力
sysctl -n vm.swapusage # 健康機器上 swap 使用量應接近 0
sysctl -n vm.loadavg
ps -axm -o rss,comm | awk 'NR>1 {rss=$1; $1=""; sub(/^ /,""); a[$0]+=rss} END {for (k in a) printf "%d\t%s\n", a[k]/1024, k}' | sort -rn | head -15
- 活動監視器中的「已使用記憶體」包含檔案快取——應根據記憶體壓力顏色和 swap 來判斷,而不是看 GB 數字。
- 大量刪除後,Spotlight(
mds_stores、corespotlightd)會重新索引幾個小時——這是暫時的,不要試圖「修復」它。 - 按應用程式家族(瀏覽器與聊天應用程式會產生許多助手程序)進行 RSS 分組;報告每個家族的總和,而不是每個程序的雜訊。
- 不要最佳化不存在的問題:在 swap 為零且壓力綠色的機器上,為了節省幾百 MB 而犧牲每日主力應用的使用體驗(例如智慧型輸入法)是不划算的——請明確告知。
第 5 階段 —— 最終驗證
- 已刪除的標籤不在
launchctl list中;plist 檔案已消失;沒有與已刪除名稱相符的程序(除非是需要重新啟動才能卸載的驅動程式)。 - 代理連線埠監聽狀態與
scutil --proxy相符;主要的 API 端點可達(401= 可達但需要驗證,視為通過)。 - 每個存活的 Agent CLI 都能通過真正的端到端測試(發送一行提示,預期收到回覆)。
df -h /前後的差異作為清理成績單。