卸载 Raycast 的时候我以为自己干得很干净:把 App 从「应用程序」里拖进废纸篓,清空废纸篓,重启。结果几天后为了排查一个问题重新装回 Raycast,打开的一瞬间整个人愣住了——之前的扩展、自定义命令、快捷键设置、甚至本地缓存里的搜索记录全部都在,像什么都没发生过一样。
这件事让我意识到一个一直被我忽略的事实:在 macOS 上,「把 App 拖进废纸篓」根本不叫卸载,最多只能叫「删掉主程序」。真正的卸载清理是一件需要系统化对待的事。所以后来我把这套清理流程整理成了一份可复用的检查清单,最后干脆做成一个开源的 Skill 项目,让安装了 Raycast 的人可以直接跑一段自动化流程把残留扫出来、清干净。这篇文章就把整个思路和实现过程完整的拆给你,包括残留到底藏在哪些目录、为什么这些目录会被系统保留、以及我在做开源 Skill 时踩过的权限和误删的坑。
1. 卸载 ≠ 删除:Raycast 残留的真实构成
1.1 为什么拖进废纸篓删不干净
很多人对 macOS 卸载软件的认知还停留在 Windows 时代的「卸载程序」思维,但在 macOS 上,大多数 App 都是一个自包含的 .app 包,主程序确实只是一坨文件。问题是,App 运行的时候会在用户目录里写下一堆「属于它自己」的数据,这些数据散落在系统的各个 Library 目录里,根本不在 .app 包里。
Raycast 尤其典型。它不是一个单纯的启动器,它承担了剪贴板历史、窗口管理、扩展系统、片段管理、AI 对话这些功能,运行期间会在你用户目录的 Library 下写下大量文件。拖进废纸篓这个动作只解决了 /Applications/Raycast.app 这一个目录,剩下的数据全部留在原地。
我卸载重装后之所以所有配置都回来了,就是因为 Raycast 把大部分配置和状态数据都存放在~/Library/Application Support/Raycast这个目录下。这个目录承载了扩展、主题、本地数据库、偏好设置等等。你删了 App 本身,但 Application Support 下的数据没掉,重新安装后它自然就恢复了。这不是 Bug,这是 macOS 的标准行为。
1.2 残留文件到底藏在哪
为了搞清楚洗干净的底线在哪,我花了两个晚上反复安装、卸载、再扫描文件系统,最终把 Raycast 的残留归纳成了五大类位置。你可以直接对照这份清单检查自己的机器。
第一类是应用支持数据,主要的目录是:
~/Library/Application Support/Raycast/~/Library/Application Support/com.raycast.macos/
这里面存的是核心业务数据。扩展安装记录、Store 缓存、配置数据库、日志都在这里。这一块体积通常最大,也是卸载后必须清掉的大头。
第二类是缓存数据。Raycast 这类工具型 App 缓存写得很勤,主要路径有:
~/Library/Caches/com.raycast.macos/~/Library/HTTPStorages/com.raycast.macos/~/Library/WebKit/com.raycast.macos/
缓存的体积时大时小,取决于你平时怎么用。WebKit 目录主要存 Raycast 内置浏览器或网页相关组件的数据,很多人会漏掉它。
第三类是偏好设置:
~/Library/Preferences/com.raycast.macos.plist
这是defaults系统读取的配置文件。有些第三方工具会误以为删掉 plist 就会出问题,其实恰恰相反,卸载后保留 plist 反而是最常见的问题来源。
第四类是登录项与系统集成。Raycast 为了开机自启,会在这里留下东西:
~/Library/LaunchAgents/com.raycast.macos.ShortcutCommands.plist~/Library/LaunchAgents/com.raycast.macos.plist
如果你在 Raycast 里设置过「开机自动启动」、全局快捷键、或者把 Raycast 设成了默认的剪贴板管理工具,这些 LaunchAgents 就会存在。不删掉它们的话,重启后系统依然会尝试拉起相关服务。
第五类是状态与应用快照:
~/Library/Saved Application State/com.raycast.macos.savedState/~/Library/Logs/Raycast/
Saved Application State 是 macOS 的窗口恢复机制留下的,虽然不影响功能,但属于典型残留。日志目录则是排障时有用的数据,释放空间时也该一起清理。
1.3 这些残留会造成什么影响
有人可能会说,残留就残留呗,不占多少地方。但实际情况不是这样:残留文件会继续被系统索引、被 Spotlight 扫描、占用 inode,而且在卸载后如果立刻重装,旧配置会把新版本的一些默认行为覆盖掉,导致你没法判断新版本到底改了什么。更现实的问题是你换了电脑、想把旧机器彻底交出去之前清理干净,这时候残留就变成隐私风险了——用户数据还躺在 Application Support 里。
所以清理不是洁癖,而是 Mac 维护里一件需要正视的事。
2. 清理流程设计:从「随手找」到「按清单围剿」
2.1 手动清理时最容易犯的三个错
在把流程写进 Skill 之前,我先手动清理了小十台机器,过程中反复踩到一些坑,这三个是最典型的。
第一个错是不查进程直接删文件。Raycast 如果在后台运行着,你删它的 Application Support 目录时,Permission Denied 是常客,因为文件还在被进程占用。就算删了一部分,Raycast 发现配置丢失会立刻重建一份默认配置,等你卸载完再检查,文件又回来了。所以清理前必须先杀掉进程。
第二个错是不分青红皂白全删。比如 Preferences 里面其实有一些是系统组的偏好缓存,还有 Extension 目录里如果你装过第三方扩展,有些文件拥有比较奇怪的授权。全盘rm -rf会误伤同一用户下其他工具的共享资源。
第三个错是删完了不验证。很多人清完觉得「应该干净了」,实际上一查还剩好几个目录。没有验证环节,清理就只是自我安慰。
2.2 我设计的清理检查流程
基于这些教训,我最后把清理流程固定为四步,每一步都有明确产物:
第一步:停止相关进程。执行osascript直接退掉 Raycast,再用ps aux | grep -i raycast兜底检查有没有子进程残留。
第二步:扫描残留目录并生成报告。逐一检查前面列出的所有路径是否存在、是否可读、体积多大,把结果汇总成一张报告。这一步只读、不改任何东西。
第三步:按风险等级分级删除。我把清理对象分为安全级和警告级:缓存类、日志类、Saved State 属于安全级,可以放心删;Application Support 和 Preferences 属于警告级,因为这两个目录里如果还有你需要保留的数据,删了就真没了。所以默认只清安全级,警告级要通过--force参数才会执行。
第四步:验证并输出清理摘要。清理完成后重新扫描一遍,对比清理前后的路径列表,输出还剩多少残留、释放了多少空间。
这套流程单独用终端也能跑,但要给更多非技术背景的人用,做成 Skill 显然更合适。
2.3 为什么不做成清理 App,而是 Skill
这里解释一下设计决策。市面上不是没有 Mac 清理工具,但我觉得有两个问题:第一,它们太重了,一个几百 MB 的 App 为了删几个目录有点杀鸡用牛刀;第二,App 本身的卸载同样会产生新的残留,形成一种荒诞的循环。
而「Skill」这个形态就轻得多。它本质上是一组指令和脚本的集合,运行在 Raycast 的 Skill 生态里,也可以直接在终端用命令行调用。对用户来说,安装成本极低,不需要单独下载 App,不需要常驻后台,用的时候跑一下就行。而且脚本开源出来,任何人可以审查每一行命令在做什么,不会被莫名其妙地收集数据。
3. 把经验变成代码:Skill 的目录、脚本与权限设计
3.1 Skill 的整体目录结构
这个开源 Skill 的仓库结构我设计如下,保持了简单和可读性:
raycast-cleaner-skill/ ├── README.md ├── skill-manifest.json ├── scripts/ │ ├── scan.sh │ ├── clean.sh │ └── report.sh └── tests/ ├── fixtures/ └── reproduce.shskill-manifest.json是 Skill 的入口描述文件,声明了这个 Skill 能干什么、需要什么权限、包含哪些可执行指令。scan.sh负责扫描残留,clean.sh负责清理,report.sh负责把扫描和清理结果整理成可读的摘要。
3.2 manifest 里的权限声明
Skill 和普通脚本最大的区别在于它运行在 Raycast 生态中,所以必须有清晰的权限边界。这是我的skill-manifest.json里的核心字段设计,用来说明它能访问什么路径、执行什么操作:
{ "name": "mac-residual-cleaner", "version": "1.0.0", "description": "Scan and clean residual files for common mac apps, especially Raycast.", "permissions": { "filesystem": [ { "path": "~/Library/Application Support/Raycast", "access": "delete", "comment": "Raycast core application data" }, { "path": "~/Library/Caches/com.raycast.macos", "access": "delete", "comment": "Raycast cache data" }, { "path": "~/Library/Preferences/com.raycast.macos.plist", "access": "delete", "comment": "Raycast preference file" } ], "processes": [ { "name": "Raycast", "action": "terminate", "comment": "Stop Raycast before cleaning" } ] }, "strict": true }strict: true是我自己加的一个设计约束,意思是 Skill 只会操作 manifest 里声明的路径,不会去扫整个磁盘。这样无论是对用户还是对审查代码的人,信任成本都低很多。
3.3 扫描脚本:只读不删,先把敌人找全
扫描是整个 Skill 的核心,如果不扫描就直接删,那你根本不知道自己删了什么。scan.sh做的事很简单:拿一份残留路径清单,逐一检查存在性,返回每个路径的状态和体积。
#!/bin/bash # scripts/scan.sh - Scan Raycast residual files (read-only) RESIDUAL_PATHS=( "$HOME/Library/Application Support/Raycast" "$HOME/Library/Application Support/com.raycast.macos" "$HOME/Library/Caches/com.raycast.macos" "$HOME/Library/HTTPStorages/com.raycast.macos" "$HOME/Library/WebKit/com.raycast.macos" "$HOME/Library/Preferences/com.raycast.macos.plist" "$HOME/Library/LaunchAgents/com.raycast.macos.plist" "$HOME/Library/LaunchAgents/com.raycast.macos.ShortcutCommands.plist" "$HOME/Library/Saved Application State/com.raycast.macos.savedState" "$HOME/Library/Logs/Raycast" ) found=0 total_size=0 for path in "${RESIDUAL_PATHS[@]}"; do if [ -e "$path" ]; then found=$((found + 1)) size=$(du -sk "$path" 2>/dev/null | awk '{print $1}') total_size=$((total_size + size)) echo "[FOUND] $path (${size} KB)" else echo "[OK] $path" fi done echo "----" echo "Found $found residual item(s), total size: $((total_size / 1024)) MB"这个脚本输出的重点是「可读」和「可验证」。你一眼能看到哪些路径还残留,总共有多少体积,然后再决定下一步。
3.4 清理脚本:分级删,绝不用一把梭
清理脚本的核心逻辑是分级。我把残留路径分成两组:SAFE_PATHS和ADVANCED_PATHS。
- SAFE_PATHS:缓存、日志、Saved State、HTTPStorages,这类文件删了最多重新生成,没有数据丢失风险。
- ADVANCED_PATHS:Application Support 和 Preferences,这类包含用户数据和配置,默认不删,只有加
--force才动。
#!/bin/bash # scripts/clean.sh - Clean residual files with risk levels SAFE_PATHS=( "$HOME/Library/Caches/com.raycast.macos" "$HOME/Library/HTTPStorages/com.raycast.macos" "$HOME/Library/WebKit/com.raycast.macos" "$HOME/Library/Saved Application State/com.raycast.macos.savedState" "$HOME/Library/Logs/Raycast" ) ADVANCED_PATHS=( "$HOME/Library/Application Support/Raycast" "$HOME/Library/Application Support/com.raycast.macos" "$HOME/Library/Preferences/com.raycast.macos.plist" "$HOME/Library/LaunchAgents/com.raycast.macos.plist" "$HOME/Library/LaunchAgents/com.raycast.macos.ShortcutCommands.plist" ) # Stop Raycast first pkill -x Raycast 2>/dev/null clean_paths() { local risk_label="$1" shift for path in "$@"; do if [ -e "$path" ]; then rm -rf "$path" && echo "[DELETED] $path" fi done } echo "Cleaning safe-level residual files..." clean_paths "[SAFE]" "${SAFE_PATHS[@]}" if [ "$1" == "--force" ]; then echo "Cleaning advanced-level residual files..." clean_paths "[ADVANCED]" "${ADVANCED_PATHS[@]}" else echo "Advanced paths skipped. Use --force to remove them." fi分级的好处是:普通用户跑一遍默认清理,就能解决 80% 的体积问题;如果确定要彻底干净、准备卖掉电脑,再手动加--force。这个设计降低了误删的恐惧感,也让 Skill 的适用范围更广。
3.5 报告脚本:清理完才算数
清理完必须再扫一遍,这是流程闭环的关键。report.sh做的事情就是再次调用 scan 脚本,并把清理前后的结果合并成一份摘要。这个脚本的价值在于,它让「清理结果可验证」,不是我说清了就清了,而是机器再扫一遍告诉你还剩什么。
实际执行效果大概是这样的格式:
Scan summary: - Before cleaning: 8 residual item(s), ~240 MB - After cleaning: 2 residual item(s), ~12 MB - Advanced paths remaining (use --force to clean): - ~/Library/Application Support/Raycast这个结果直接决定了你要不要继续用--force做深度清理。
4. 开源后的打样:仓库结构、使用方式与运行效果
4.1 为什么值得开源出来
这件事做成了 Skill 之后,我第一反应是发给身边几个也在用 Raycast 的朋友。他们试用后反馈都挺正面,但同时提了个问题:这套脚本能不能让他们自己在别的场景下也跑一下?
这时候我意识到,与其把脚本捂在自己手里,不如开源。Raycast 的 Skill 生态本身就鼓励分享,官方也有对应的社区目录。开源能让别人用你的脚本解决同样的问题,也能让别人帮你发现没覆盖到的残留路径——比如我一开始就没写全HTTPStorages这个路径,是别人测试后提醒我的。这种协作是个人博客、私有脚本给不了的。
开源仓库里的README.md我写得很细,把安装方式、运行方式、风险说明都交代了。我的原则是:一个开源项目,最差的部分往往是文档,我不想犯这个错。
4.2 用户怎么安装和使用
用户拿到这个 Skill 之后,使用路径非常简单:
把仓库克隆下来,或者直接把 scripts 目录复制到 Skill 工作目录,然后在 Raycast 中执行 Skill 的对应指令即可。也可以用纯命令行方式运行:
git clone https://github.com/yourname/raycast-cleaner-skill.git cd raycast-cleaner-skill ./scripts/scan.sh # 先看残留情况 ./scripts/clean.sh # 默认清理安全级 ./scripts/clean.sh --force # 深度清理高级级第一次跑的时候,系统可能会弹权限提示,因为扫描~/Library下的目录需要终端或 Raycast 拥有「完全磁盘访问权限」。这一点我单独写在 README 的「常见问题」里了,避免用户卡在第一步。
4.3 实测效果:到底能清出多少空间
我在自己两台 Mac 上做了对照测试。一台是日常主力机,Raycast 用了大半年,扩展装了十几个;另一台是刚拿到手一个月的新机器,Raycast 只用了几天。
主力机上扫描结果让我挺意外:Application Support 里的扩展和缓存加起来一共有 350 多 MB,其中 Store 缓存占了将近一半。新机器上少一些,但也有 80 多 MB 的各类数据。清理掉安全级之后,主力机释放了约 190 MB,深度清理后又能释放剩下的一部分。当然这个数据会因为你使用 Raycast 的强度不同而浮动,但至少说明「残留不占地方」这句话是不可信的。
关键不是这 190 MB,而是清理后系统里确实没有 Raycast 的痕迹了。重新安装后不会再弹出一堆旧配置,也不会出现「明明重装了但快捷键还保留着上次的绑定」这种让人困惑的情况。
4.4 边界说明:什么不能乱删
开源项目最怕的不是没人用,而是有人用出事故然后骂你。为避免这种情况,我在 README 里把边界说得非常清楚:
- 不要在其他工具还在运行时清理它们的残留目录。这个 Skill 只针对 Raycast,虽然脚本结构可以扩展到其他 App,但默认清单里全是 Raycast 的路径。
- 不要对你不确定的路径使用
--force。Application Support 里如果存了你正在用的扩展数据,删了之后扩展需要重新配置。这是我的 Skill 里风险最高的一步,所以默认被保护起来了。 - 清理前建议关掉所有可能引用这些路径的 App。比如你如果在用某些 Raycast 扩展同步工具,它可能同时读写这些目录,边写边删会出问题。
5. 开发 Skill 时踩过的坑:权限、误删与兼容性
5.1 完全磁盘访问权限:第一次跑就卡住
开发过程中我遇到的第一个拦路虎是 macOS 的 TCC 权限机制。脚本在终端里跑的时候,访问~/Library/Application Support下的某些文件会被系统静默拒绝,不是报错,而是权限不够导致读取到的文件列表是空的。
解决方法是到「系统设置 → 隐私与安全性 → 完全磁盘访问权限」里,把终端(如果你通过终端运行脚本)或 Raycast(如果你通过 Raycast 运行 Skill)加进去。这个操作在 README 里必须放在开头位置,不然用户第一次跑扫描就会发现结果和预期不符。
这里有个小细节值得提醒:如果你在 VS Code 里调试这个 Skill,也需要给 VS Code 加完全磁盘访问权限,否则调试时读到的文件系统视图是不完整的。
5.2 误删风险:开发测试中的一次差点翻车
做测试的时候,为了复现残留场景,我在一台不常用的机器上反复安装和卸载 Raycast。有一次我为了验证--force逻辑,直接跑了清理脚本,结果误删了 Application Support 里另一个工具的配置目录——因为它刚好也叫 Raycast 开头的子目录。
那次之后我改了脚本设计,所有清理路径必须精确匹配,不允许用模糊匹配,比如rm -rf "$HOME/Library/Application Support/Raycast"*这种带通配符的写法全部禁止。宁可漏掉一个路径,也不要误删一个路径。
这也直接促成了 manifest 里strict: true这一设计。先声明要动哪些路径,然后脚本里强制校验路径精确性,双重保险。
5.3 不同 macOS 版本的路径差异
另一个测试中频繁出现的问题是系统版本差异。比如LaunchAgents目录在 macOS 12 和 macOS 14 上对于 Raycast 的处理不太一样,旧版本可能不在这里写文件,而新版本会写com.raycast.macos.ShortcutCommands.plist。
我在扫描脚本里对所有路径做了存在性检查,就不需要针对版本写分支了。存在就报出来,不存在就跳过,这种设计在涉及多版本兼容的工具里特别实用。
做这个 Skill 的过程中,我最大的体会是:清理残留这件事本身没什么技术含量,真正有价值的是把清理流程抽象成一套标准化的检查逻辑。你不需要记住十几个路径,只需要运行一次扫描,机器会告诉你答案。比起手动翻目录,这种感觉踏实得多。