ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用Raycast Skill彻底清理macOS卸载残留,释放磁盘空间

用Raycast Skill彻底清理macOS卸载残留,释放磁盘空间 前几天我彻底卸载 Raycast 时发现一个挺讽刺的事调用效率工具的人反而没效率地处理一堆残留文件。~/Library/Application Support/Raycast还在偏好设置还在日志、崩溃报告、本地缓存全在它把我 Mac 的磁盘空间当成了永久居住地。更尴尬的是我当时手里正好有几个清理脚本但每一个都是临时用一下的水平有的只扫缓存有的需要手动改路径有的删完连自己都不敢确定有没有漏。折腾过几次之后我决定把这些年积累的 Mac 卸载残留清理经验整理成一个可复用的开源 Skill挂在 Raycast 里用自然语言直接调度。这篇文章就把整个项目的来龙去脉、实现思路、风险设计和实测数据完整写出来给同样被磁盘系统数据折磨的人一个可抄作业的方案。1. 卸载后的残留到底藏在哪一次手工排查记录1.1 第一轮排查先弄清哪些目录是重灾区动手清理之前我先用文本编辑器和终端做了地毯式排查而不是直接盲目扫盘。因为残留是个很宽泛的词不搞清楚它们分布在哪些层级、什么权限、什么生命周期写出来的清理脚本一定会出事故。我先从用户资源库开始查。macOS 把单用户级配置和缓存按照 XDG 类似的约定放在~/Library下典型目录包括 Application Support、Caches、Preferences、Containers 等。我用一行命令列出了同一款软件在各处的踪迹ls -ld ~/Library/Application\ Support/* 2/dev/null | grep -i raycast ls -ld ~/Library/Caches/* 2/dev/null | grep -i raycast ls -ld ~/Library/Preferences/* 2/dev/null | grep -i raycast ls -ld ~/Library/HTTPStorages/* 2/dev/null | grep -i raycast ls -ld ~/Library/WebKit/* 2/dev/null | grep -i raycast ls -ld ~/Library/Containers/* 2/dev/null | grep -i raycastGrep 出来的结果将近二十项。这还只是按前缀匹配。有一部分残留文件并不带应用名而是在公共组件、URL scheme、辅助权限数据库里登记了引用靠找同名文件根本发现不了。比如登录项列表、通知配置、辅助功能权限、自动填充数据这些不是普通文件而是以目录项或系统数据库条目形式存在的人工检查要打开系统设置逐项看。1.2 残留文件分类表把两轮扫描结果汇总后我将残留目标分成六类这也是 Skill 后面扫描规则的基础模型。类别路径典型残留删除风险应用支持数据~/Library/Application Support/App配置、插件、下载缓存中包含用户自定义数据偏好设置文件~/Library/Preferences/bundle-id.plist快捷键、窗口状态、登录信息低但可能影响同套件应用缓存与临时文件~/Library/Caches/App压缩包、图片缓存、崩溃日志缓冲低可再生成保存的应用状态~/Library/Saved Application State/bundle-id.savedState上次打开的窗口与文档记录低系统级支持文件/Library/Application Support/App全局代理、Log daemon、驱动附加文件高普通用户权限不足误删会影响系统登录项与后台服务~/Library/LaunchAgents/*.plist开机自启项、KeepAlive 进程高可能隐藏恶意服务或破坏其他软件正常启动最麻烦的不是前四类而是最后一类。很多软件卸载后LaunchAgent 或 LaunchDaemon 仍然在原位进程还会在下次登录或定时触发时重新拉起来。表面上卸载干净了实际上后台重启后还能蹦出来。1.3 为什么单靠右键移入废纸篓永远清不干净macOS 的拖入废纸篓只删了/Applications和主程序包自身。应用程序是一个 Dock 和 Launch Services 认识的 bundle它不会负责清理自己在你系统里写过的其他位置这几乎是所有 macOS 应用的通用行为。类似地用 App Cleaner、CleanMyMac 这类工具虽然能多扫一些但大多数也依赖自己的预置规则库规则库更新慢、识别不全遇到改了 bundle id或把配置写在公共目录的软件同样抓瞎。更隐蔽的一类是 sandbox 容器数据。如果应用使用 App Sandbox系统会把它的所有可写内容重定向到~/Library/Containers/bundle-id主目录表面是干净的数据全在容器里。卸载 App 时如果不手动删容器几十 GB 的沙盒数据比如聊天记录附件、音视频素材会原封不动留在磁盘里。2. 为什么做成 Raycast Skill而不是写个 Shell 脚本2.1 我有脚本了但每次都输命令很反人性在做成 Skill 之前我已经有一个相对完整的检测脚本。它能扫六类残留目录输出 CSV 报告附带 dry-run 模式。但实际用下来有两个痛点。第一入口太重。每次要清理我得打开终端输入一段很长的路径再追加不同的参数。几个月没打开终端的人光想~/Library/Application Support/里哪个是哪个就消耗掉大半耐心。第二脚本没有记忆。它不理解用户意图不知道我刚刚卸载了就只能找那个名字所以每次都要把应用名作为参数传进去漏传一个就扫错范围。Raycast Skill 解决了这两个问题。Skill 作为 Raycast AI 的可复用能力模块允许我用 Markdown 文件定义一套完整的工作流再用语言模型自动匹配该调用哪些脚本、按什么顺序调用、如何解释输出结果。对使用者来说只需要在 Raycast 中输入一句大白话比如清理一下刚卸载的应用残留剩下的流程交给 AI Agent 去拆解并驱动脚本完成。2.2 Skill 与普通脚本、第三方卸载工具的本质区别Skill 是一个介于自然语言指令和确定性脚本之间的编排层。GitHub 上很多人把 Skill 理解成一组提示词其实不准确。我理解的 Skill 是一份定义行为的文档加上若干可执行工具语言模型根据文档决定是否以及如何调用工具。它有点像给 Agent 提供了一份 SOPAgent 再按 SOP 执行具体动作。对比能看得更清楚方案交互方式可扩展性风险控制适用人群手写 Shell 脚本终端命令加参数中等需要手动维护取决于脚本质量一般偏弱熟悉命令行的开发者App Cleaner 等图形工具拖拽、点选低依赖工具规则库中有风险提示但不够透明普通用户Raycast Skill自然语言即用高改 Markdown 和脚本即可高可强制 dry-run、备份、确认机制愿意接受新工具效率用户对比完就明白Skill 最大的增量不是删除能力本身而是把用户意图和删除动作之间的沟通成本降到最低同时保留了脚本的确定性和可控性。它始终是人在做决策Agent 负责跑腿和整理信息。2.3 Skill 带来的交互闭环人在回路很多人担心自动清理会误删这个担心很正常。Raycast Skill 里我可以定义必须先 dry-run再逐项向用户确认最后执行清理的流程把人在回路human-in-the-loop做成硬规则而不是靠用户自觉。实际体验是我和 Raycast 说一句扫描 Raycast 的残留它会先列出候选文件清单显示每条路径、大小、风险等级然后问我是全部删除、只删缓存还是跳过某一类。对这种场景人是最可靠的判断器。Skill 的价值不是替代人做判断而是让人做判断的每一个步骤都更快、更清晰。3. 开源 Skill 的实现拆解一份 SKILL.md 怎么变成清理流程3.1 项目结构与我为什么这么分文件开源仓库我按职责分成了四块不搞花活清理工具最重要的是可审计。任何人都能打开每个文件看出某个路径为什么被判定为残留、为什么被列入白名单、删除前会备份到哪里。mac-cleanup-skill/ ├── SKILL.md # 技能定义Agent 首先读取它 ├── scripts/ │ ├── scan_residue.sh # 扫描六类残留目录 │ ├── backup_residue.sh # 备份待删除文件打时间戳 │ ├── remove_residue.sh # 按清单执行删除 │ └── analyze_report.py # 把扫描结果转成人类可读报告 ├── rules/ │ ├── system_whitelist.txt # 系统关键目录永不删除 │ ├── app_whitelist.txt # 用户主动保护的 App │ └── suspicious_keywords.txt # 可疑进程名与路径特征 └── backup/ └── README.md # 备份目录说明这样拆的好处是扫描、备份、删除三个动作解耦。Agent 可以先扫描生成报告备份脚本拿到报告后做压缩拷贝最后删除脚本只处理已经备份成功且未在白名单里的条目。如果哪一步出错日志会非常明确地告诉你停顿在哪一环。SKILL.md 放在根目录文件名对 Agent 的发现机制最友好。Raycast AI 加载 Skill 时优先看这个文件里的 name 和 description用来判断在什么场景下唤起这个技能。description 我写得很直白扫描并清除 macOS 应用卸载后残留文件。避免被无关问题误触发。3.2 核心SKILL.md 里的规则与伪代码SKILL.md 的写法决定了 Agent 怎么理解任务。我参考了社区里常见的 Markdown 技能格式把正文分成 Goal、Constraints、Workflow、Tools 四部分让模型每一步都有依据。--- name: mac-residue-cleanup description: 扫描并清理 macOS 应用卸载后遗留的缓存、偏好设置、容器、启动代理等残留文件。 --- # Goal 当用户描述某个应用卸载后没清理干净或想清理系统残留时 按照给定 Workflow 执行扫描、备份、确认、删除。 # Constraints 1. 禁止扫描或删除任何挂载在 /System、/Library/Extensions、/usr/bin 下的路径。 2. 未经过 dry-run 和用户确认禁止执行 remove_residue.sh。 3. 删除任何非 Cache 类文件前必须先执行 backup_residue.sh。 4. 若目标保留时间不足 7 天的文件提示用户确认后跳过。 5. 不处理 /Users/Shared 之外的跨用户共享数据。 # Workflow ## Step 1: 扫描 调用 scripts/scan_residue.sh传入用户给出的应用名称。 输出为 JSON 文件存放到临时目录并展示摘要给用户。 ## Step 2: 备份 如果用户确认要删除非缓存文件调用 scripts/backup_residue.sh。 备份默认放入 ./backup 目录按应用名和时间戳分目录。 ## Step 3: 删除 调用 scripts/remove_residue.sh --list report.json --whitelist ./rules/app_whitelist.txt 只允许处理已备份文件或 Size 小于 10MB 的缓存文件。 ## Step 4: 汇报 返回删除结果表路径、类型、释放空间、是否成功。这里面最关键的约束在第 3、4 条。缓存文件被允许在无备份情况下直接删但非缓存文件一定先备份。判断逻辑由 analyze_report.py 根据类别字段完成不依赖语言模型的判断保证每一次删除行为都有代码层面的兜底。3.3 脚本背后的扫描逻辑白名单优先删除兜底扫描脚本的核心是一个 while 循环逐类读取目标目录。对所有匹配项先做白名单过滤再判断是否存在正在运行的进程占用最后才进入候选清单。#!/bin/zsh # scan_residue.sh app_name APP_NAME${1:?需要提供应用名称} REPORT/tmp/raycast_skill_scan_$$.json WHITELIST$(cd $(dirname $0)/../rules pwd)/system_whitelist.txt find $HOME/Library/Application Support $HOME/Library/Caches \ $HOME/Library/Preferences $HOME/Library/HTTPStorages \ $HOME/Library/WebKit $HOME/Library/Containers \ $HOME/Library/Saved Application State \ -maxdepth 2 -iname *${APP_NAME}* -print0 2/dev/null | while IFS read -r -d path; do # 跳过白名单 grep -Fq $path $WHITELIST continue # 跳过仍在运行的进程占用文件 lsof D $path 2/dev/null | grep -q . continue size$(du -sk $path 2/dev/null | awk {print $1}) ctime$(stat -f %SB -t %Y-%m-%d $path 2/dev/null) echo {\path\:\$path\,\size_kb\:$size,\ctime\:\$ctime\} $REPORT done python3 scripts/analyze_report.py --report $REPORT --app $APP_NAME这个脚本的设计重点是保守只要文件被lsof判定为被占用就跳过。宁可少删一个可再生的缓存也不要删掉一个正在写入数据的文件。有些人会觉得lsof误判多比如渲染引擎可能只读取某个资源文件并不代表删除会出问题。但清理工具本来就不是为省那几 MB 空间冒险的稳定比激进更重要所以我保留了这条硬规则。4. 风险控制清理工具的命门在别删错4.1 删之前先备份备份目录结构设计在真正删除非缓存文件之前备份脚本会把文件复制进一个带时间戳的目录并保留完整相对路径。#!/bin/zsh # backup_residue.sh report.json REPORT$1 BACKUP_ROOT$(dirname $0)/../backup/$(date %Y%m%d_%H%M%S) python3 - $REPORT $BACKUP_ROOT PY import json, os, sys, shutil report, backup_root sys.argv[1], sys.argv[2] os.makedirs(backup_root, exist_okTrue) with open(report) as f: items json.load(f) for it in items: if it[category] in (cache, temporary): continue # 缓存类不做备份节省空间 src it[path] rel os.path.relpath(src, os.path.expanduser(~)) dst os.path.join(backup_root, rel) os.makedirs(os.path.dirname(dst), exist_okTrue) try: shutil.copytree(src, dst) except shutil.Error: shutil.copy2(src, dst) print(fbackup {src} - {dst}) PY备份路径直接用relpath可以保证恢复时按原位置放回。用户如果想回滚直接把 backup 目录下的内容复制回对应根目录即可。这套逻辑不复杂但在实际运行中特别可靠因为它的失败模式只有一个磁盘空间不足。如果备份目录所在卷空间不够脚本会中断不会出现删了但又没完全删掉的状态。4.2 dry-run 机制与删除阈值删除脚本会对候选清单再做一次二次校验。它不直接读扫描结果而是要求扫描报告里每个条目都带一个backed_up字段或risklow标记否则拒绝删除。这个逻辑最大的作用不是技术防护而是心理防线用户知道所有删除动作都经过了一道独立校验才敢放心让 AI Agent 连续执行高危操作。另外我设置了两个阈值参数参数默认值作用MAX_CACHE_SIZE_MB10低于该大小的缓存文件允许直接删不生成追加备份MIN_RESIDENCE_DAYS7创建时间不足该天数且不在缓存目录内的文件请求确认这两条都出自实际教训。缓存类文件虽然可再生但如果是热更新内容删掉会影响下次启动速度超过 10MB 再备份又太占空间所以只备份大头。MIN_RESIDENCE_DAYS 是为了防止误伤刚安装的软件——如果你才升级完应用目录里生成了一堆新的支持文件它们并不是残留而是应用正在使用的数据应当跳过。4.3 白名单与可疑清单的维护思路rules 目录下的白名单分两类。system_whitelist.txt 是全局性的任何人都不能删。app_whitelist.txt 是面向用户预设的默认收录了一些看似残留但实际是共享数据的路径比如 iTerm2 的配置目录、1Password 的~/Library/Containers/2BUA8C4S2C.com.1password.desktop、Homebrew 的 Cellar 符号链接等。应用匹配到一个在 whitelist 下的路径时扫描脚本直接跳过不会出现在候选报告里。这比删除时再过滤更安全因为报告一旦生成用户可能直接全选删除根本不会留意中间夹杂的敏感项。suspicious_keywords.txt 则是另一套逻辑。它不用于删除而是用来告警。我录入了keymonitor、launchctl wrapper之类的高风险特征词。如果某个待清理路径和这些特征词匹配脚本会输出红色级别的警告并建议用户先用lsof检查。这套清单我不指望能覆盖所有恶意软件但能挡住很常见的一类伪装成应用残留的后台服务。4.4 实测踩坑我自己差点删掉的三个东西开发过程中我踩过三次大坑每个都差点造成不可逆问题。第一个是 1Password 的容器目录。~/Library/Containers/2BUA8C4S2C.com.1password.desktop前缀既包含com.1password从名字看疑似是某个 app 的残留 container。实际上这是 App Store 沙盒版的完整数据目录里面是用户密码库的自动填充缓存。我最初扫描 Raycast 时因为 Raycast 也做过浏览器扩展把几个类似目录都列了进来差点一键删除。第二个是 Homebrew 的 Caskroom 与 Cellar。卸载某些 GUI 应用后/opt/homebrew/Caskroom/app和/opt/homebrew/Cellar/app可能还会保留旧版本。从残留角度看它们属于可清理但它们是 Homebrew 管理的文件正确做法是brew uninstall --cask或brew cleanup直接用文件删除会把 Homebrew 的元数据弄乱。第三个是 LaunchAgent 里名字相近但功能不同的 plist。比如com.raycast.menubar.plist和com.raycast.updater.plist一个负责菜单栏组件一个负责自动更新。只卸载应用主体时这两个代理都活下来自动更新那个会反复拉起 Raycast 更新进程。手动删除时如果只删名字包含raycast的还好但如果某个软件的名称为另一软件的缩写前缀就会误伤别的应用。最后我加了按完整 bundle id 匹配的规则要求所有 plist 候选必须包含应用名字段且不包含其他 Whitelist 中的 bundle id避免因名称重叠导致误判。5. 完整使用过程从安装 Skill 到磁盘瘦身5.1 安装方式克隆仓库 导入 Raycast安装流程分三步。先克隆仓库到你希望保存的位置比如~/Developer/skills然后在 Raycast AI 的设置中把该目录加入 Skills 搜索路径。Raycast 会在后台扫描所有 SKILL.md 文件使技能在 AI 对话中可被调用。git clone https://github.com/你的用户名/mac-cleanup-skill.git ~/Developer/skills/mac-cleanup-skill导入后验证方式很简单在 Raycast AI 对话框里输入列出当前可用的技能。 如果返回结果包含mac-residue-cleanup说明加载成功。我遇到过最常见的导入失败原因是目录权限不够Raycast 跑在沙盒或完整磁盘访问受限状态读不到~/Library下的扫描路径。解决方法是到系统设置里给 Raycast 开启完全磁盘访问权限否则脚本无法遍历需要清理的用户资源库和其他受保护目录。5.2 一次真实清理演示提示词、输出与删除确认我挑了一个典型场景做演示系统里曾安装过一款叫 MockFlow 的原型设计工具后来转移到 FigmaMockFlow 卸载掉了但系统设置里储存空间一直显示系统数据占了很多。我在 Raycast AI 中输入的提示词是我之前卸载了 MockFlow帮我扫一下这个应用还有没有残留。Agent 看到 description 与清理残留相关先加载 SKILL.md然后调用scripts/scan_residue.sh MockFlow生成一份 JSON 报告转成人类可读表格候选残留清单来自扫描 MockFlow [1] ~/Library/Application Support/MockFlow 93.2 MB 2023-11-05 [2] ~/Library/Caches/com.mockflow.app 18.7 MB 2025-01-12 [3] ~/Library/Preferences/com.mockflow.app.plist 1.2 MB [4] ~/Library/Saved Application State/com.mockflow.app.savedState 0.8 MB [5] ~/Library/LaunchAgents/com.mockflow.helper.plist 0.1 MB接着 Raycast 询问我有 5 个条目共约 114 MB其中第 5 个是 LaunchAgent已确认无相关进程运行。是否全部删除我选择了第 1、2、4 项删除第 3 项先备份第 5 项保留。这是典型的用户判断时刻我不确定 LaunchAgent 是否和旧版 MockFlow 之外的某个组件绑定所以保留它转向查看进程列表后再决定。5.3 清理效果与磁盘空间变化整个清理过程执行时长不到 1 分钟。结果如下待清理项操作方式释放空间Application Support/MockFlow删除93.2 MBCaches/com.mockflow.app删除18.7 MBPreferences/com.mockflow.app.plist先备份后删除1.2 MBSaved Application State删除0.8 MBLaunchAgent plist保留并生成备注0.1 MB清理后我用df -h /对比根卷自由空间增加了大约 110 MB。量不大但把系统数据里那些最碍眼的碎文件清掉了磁盘信息里的隐藏占位不再是脏乱状态。在另一台长期不清理的测试 Mac 上用同样流程清一款已卸载 5 个月的 Qt 开发工具释放了 2.3GB 空间其中大头是 Application Support 里的离线运行库和容器里的开发缓存。这个项目真正吃下的空间往往比用户以为的大得多。6. 开源后的维护心得与扩展方向6.1 开源收获Issue 里最有价值的意见仓库发到 GitHub 后第一个星期就收到了二十多个 Issue。最有价值的反馈来自一位做 macOS 安全研究的读者他指出了一个我此前没充分考虑到的点某些软件的残留文件可能以隐藏文件形式存在于~/Library/WebKit的WebsiteData子目录中而且这类目录往往被 Spotlight 索引删除后会导致系统后台重新建立索引大量占用 CPU 和电量。他在自己的分支里把 WebKit 数据按域名进行了白名单细分只删除与目标应用关联的域名条目。我随即将这个逻辑合并进主仓库在扫描脚本中新增了--webkit-domain参数可以精准匹配com.raycast或mockflow域名。另一个有价值的反馈是多版本残留问题。比如你在不同时间装了同一个应用的不同版本有些 Preferences 文件是旧版本生成的新版本不再使用。扫描时按照 bundle id 会全部命中但直接删除可能会丢失新版本还需要的迁移数据。最终方案是在analyze_report.py中优先列出最后修改时间早于当前版本安装时间的文件只有这种文件才标记为recommended_delete其他保留为manual_decision。这样的分级让普通用户可以一键处理进阶用户可以人工复核。6.2 下一步从卸载残留清理扩展到系统数据体检这个 Skill 目前的定位很窄就是卸载后的残留清理。但不少 Issue 用户要求扩展到系统数据整体体检。因为很多人打开关于本机 - 存储空间时看到最大的占位是系统数据根本不知道它是什么。这个需求比残留清理更普遍也更难做。我计划在下一版本里增加两个能力模块。第一扫描/private/var/folders下的临时文件展示大小并按时间排序。第二检查~/Library/Developer/Xcode/DerivedData和~/Library/Developer/CoreSimulator这类开发者缓存目录它们经常占据数十 GB。这些目录不应该默认删除但可以做成一键汇总、分级确认的模式让 Skill 先给出一份磁盘申请人清单用户再决定处理哪些而不是直接扮演磁盘清理大师。同时我也在调研如何把每款应用正常卸载时推荐的官方清理命令映射进规则库。比如 Homebrew 应用应调用brew remove原生 pkg 应用应调用pkgutil --forget这些命令比文件删除更规范但会给 Skill 引入更多权限和交互复杂度。优先级排在后面。6.3 如果你也想做自己的 Raycast Skill基于这次经验想自己开发 Raycast Skill 的话最核心的建议只有一条把约束写彻底把工具拆干净。很多人第一次写 Skill 时喜欢把大量逻辑塞进 SKILL.md 的文字描述里期待模型会正确理解每一步。但语言模型的正确率再高也不是 100%。尽量把什么能做、什么不能做落实到独立脚本里用代码的确定性兜住模型的不确定性。比如判断某个文件是否应该跳过让脚本做白名单过滤而不是让模型去读一个很长的白名单文件后自己判断。这点在清理类工具中尤其重要因为错误删除的后果无法用重新生成缓存来弥补。我的习惯是能在 shell 里解决的问题不交回给模型需要模型发挥的场景只让它解释报告、决定优先级。如果要做类似清理工具还建议从一开始就把日志体系建好。扫描时打日志备份时打日志删除时打日志而且日志行要包含每条路径、原因、动作和结果。将来排查问题会少掉很多头痛时刻。不要偷懒清理工具的日志不是给别人看的是给三天后的自己看的。写到这里我回想起第一次在终端用一行命令清掉 10GB 缓存时的那种畅快。现在它已经变成一套可以被自然语言调用的 Skill开源在仓库里别人可以安装、改造、反馈。距离我最初卸载 Raycast 后发现一堆残留的场景已经过去三个月但这个项目还在持续迭代。对普通人来说最直接的收益是以后真需要清理时不用再点开一堆系统设置目录人工找半天。对开发者来说这个项目也是一个很好的 Raycast Skill 参考案例——把复杂流程封装成 SKILL.md 加脚本的组合让 AI Agent 帮你执行而不是帮你想象。
返回列表