起因

事情最早发生在今年我在家 remote 的那段时间。

我每天都在 Mac 上工作,也每天都在锁屏、解锁、离开、回来。

但有一个问题我一直不知道答案:我一天到底锁屏了多久?

你可能会觉得这不重要,但我后来越来越觉得,这其实是一个很有意思的数据。

锁屏,意味着中断。 解锁到下一次锁屏之间,意味着一段连续工作的时间。

如果这些东西能被记录下来,它至少能帮助我更诚实地看待自己的工作节奏,而不是只靠感觉。

Raycast 是我每天都会打开很多次的工具。所以当我想到这个需求的时候,第一反应不是“去 App Store 找找有没有现成产品”,而是:

要不,我自己做一个 Raycast Extension 试试看。

这个想法当时就种下了。

真正把它做出来,是在过年期间。

那几天我刚好有一段比较完整的时间,也想试试看:借助 AI,我能不能把这样一个自己真会用到的小工具,尽快做成一个可用的版本。

这就是 Lock Time 的起点。

也是我第一次认真写一个 Raycast Extension 的起点。

先把问题想清楚

这次我没有一上来就写代码。

我先写了一份很小的 PRD。原因也不复杂:越是这种“小工具”,越容易边做边歪。你本来只是想解决一个很具体的问题,写着写着就想加统计、加分析、加建议、加同步,最后把自己拖进泥潭。

我当时只想清楚了三件事:

  1. 我要记录什么?
  2. 我怎么知道系统现在是不是锁屏?
  3. 数据应该存在哪里?

最后收敛下来的答案也很朴素:

  1. 记录今日累计锁屏时长
  2. 记录上一次锁屏持续了多久
  3. 记录上一次连续工作了多久

检测方式先用轮询,数据存本地,用 Raycast 的 LocalStorage,完全 local-first,不碰网络。

边界一旦清楚,事情就会轻很多。

我不做复杂分析。 我不做跨设备同步。 我也不做那种“根据你的锁屏习惯给你人生建议”的功能。

我只想把时间事实记录清楚。

第一个版本,比我想得快

Raycast 给新手的第一印象其实非常好。

我用 npx create-raycast-extension 把项目拉起来之后,基本没怎么折腾环境。npm run dev 跑起来,改完代码,Raycast 里就能实时看到效果。

这种反馈速度很重要。

你会明显感觉到,它不是那种“先研究半天平台规则,才能开始写功能”的扩展生态。相反,它更像是在说:来,先做出来,我们后面再慢慢把细节做好。

而且 Raycast 的 API 设计很克制。

ListDetailActionMenuBarExtra 这些组件不算多,但够用了。对于我这种第一次接触的人来说,这种“够用但不过度复杂”的感觉非常舒服。

加上那段时间我也一直在比较重度地用 AI 协助开发,所以第一个 MVP 出来的速度,比我预想中快很多。

不到一天,我就已经能在本地看到一个真正能工作的版本了。

说实话,那一刻是很上头的。

因为你会突然意识到:

原来一个自己每天都想用的小工具,真的可以在一个周末里长出来。

真正难的,不是写出来,而是把它做对

真正的难点,出现在第一个版本跑起来之后。

这也是我后来越来越喜欢这种“小产品”的原因。它们表面上很小,但只要你想把它做得靠谱一点,问题就会一个接一个冒出来。

MVP 做出来很快,但后面这些坑,确实来来回回花了我不少时间。

第一个坑:macOS 26 上锁屏检测完全失效

在一开始就发现一个严重问题:

在 macOS 26 上,所有时间指标几乎都是 0。

这不是“有点不准”,而是“完全不能用”。

后面一路排查下来,我才慢慢意识到,问题不在业务逻辑,而在我最初采用的那套检测方案上。更准确地说,这套方案本身也不是我自己拍脑袋定的,很多实现思路和排查方向其实都是 AI 给出来的。

但问题就在这里。

因为我自己对这部分偏系统层的能力并没有那么熟,AI 给的方案虽然能快速帮我起步,也能不断给出新的尝试方向,但一旦踩进坑里,我其实很难第一时间判断它到底哪里不对。

所以后面基本就是靠 Cursor debug,再加上多个 AI 模型来回试、来回查,才一点点定位到问题。最后确认是 JXA 的 ObjC bridge 在这个系统版本上并没有稳定工作,桥接到 CFDictionary 这一步就已经不可靠了。

最后改成 Swift 原生调用 CGSessionCopyCurrentDictionary(),问题才真正解决。

第二个坑:首屏加载慢得离谱

锁屏检测修好之后,又来了第二个问题。

每次打开 Lock Stats,都要等大概 3 秒。

3 秒看起来不长,但放在 Raycast 这种“我按下快捷键就想立刻看到结果”的场景里,已经很难接受了。

继续拆之后发现,瓶颈不在 React,也不在数据处理,而在 Swift 解释器的启动开销。

也就是说,功能没问题,但用户感知就是慢。

后来我换了一个思路:

不要让用户每次都等“最新结果”出来。 先把缓存展示出来,再在后台异步刷新。

也就是一个很朴素的 stale-while-revalidate 策略。

改完之后,首屏体验立刻顺滑很多。

这件事让我再次确认了一点:

很多时候,性能优化不是让程序更快,而是先让用户别等。

功能做完之后,上架 Ryacast Store 又是另一道坎

等功能能用了、体验也顺了之后,我开始准备把它真正送去 Raycast Store。

一开始我其实有点犯怵。

因为“上架”这两个字,天然会给人一种很重的感觉。你会下意识以为,审核流程很长、规范很多、仓库很大、门槛很高。

但等我真的做起来,才发现它也不是那种“照着文档过一遍就结束”的事情。

虽然有 AI 的帮助,也有官方文档可以参考,但实际跑下来也不是一次就能过。第一次准备 Store 的时候,你还是会不断去确认:这里到底是不是硬性要求,那个文件要不要补,这种格式写法会不会不过,截图和图标是不是还得重新弄。

不过,换个角度看,相比 Apple 那种更偏黑盒的审核,Raycast 这套已经算清楚、友好多了。

我自己最先撞上的,就是 lint 和构建。

这听起来像废话,但真不是。

我就碰到了 @raycast/eslint-config 升级之后的兼容问题,直接报:

1
TypeError: Unexpected array.

最后是手动 flatten 配置才过掉。

这种问题如果你不在本地把 npm run buildnpm run lint 跑干净,等 CI 再告诉你,节奏就会一下子断掉。

接下来就是那些看起来不难、实际上很花时间的东西:CHANGELOG、README、截图、图标、命名、描述。

你平时本地自己用的时候,不太会在意这些细节。真要上 Store 了,它们就一个都绕不过去。

尤其是 README 要英文、CHANGELOG 要按它的格式、截图和图标也都有讲究。这些地方你很难说哪一项最难,但它们会把你一点点拉进“这已经不是我自己本地用的小工具了”的状态里。

提交 PR 这一步,我也研究了挺久

等前面的东西都准备好之后,真正提交到 raycast/extensions 仓库,还是比我最初想的顺了一些。

我原本以为,面对一个那么大的 monorepo,我得先完整 clone 下来,再研究半天目录结构,最后才能开始提 PR。

后面才知道,没必要把自己吓住。

通过官方文档里的 sparse checkout 方式,你其实只需要拿到必要的部分就够了。

如果你也准备提自己的第一个 PR,我非常建议先看这两篇:

  1. Prepare an Extension for Store
  2. Review an Extension in a Pull Request

我自己实际走下来,大概就是 Fork、clone、把扩展目录整理进去、本地确认能过、然后提 PR。

说起来不复杂,但我前前后后其实也研究了不少时间。

而且最后我能顺利把 PR 提上去,社区里的帮助也非常关键。比如 @alexi_build,他是 100daysofraycast 的作者,现在也在做 Raycast weekly newsletter。他提醒我不用傻乎乎地把整个仓库完整拉下来,这种信息对于第一次提 PR 的人来说,真的很有用。

所以回头看,这一步不是“很轻松就搞定了”,而是我自己先摸索了挺久,最后在社区帮助下把路走通了。

提交 PR 之后,才发现真正的 review 才刚开始

我原来以为,PR 提上去,CI 过了,接下来就是等人工审核。

后来才发现,事情没有那么简单。

Raycast 那边的 review 里还有 AI Code Review,Greptile 这一类自动化审查也会参与进来。于是整个过程并不是“一次提交,然后等结果”,而是来来回回改了好几轮。

这种 review 体验其实有点折腾。

你会感觉要求确实不低,很多细节并不会因为“这是你第一次提扩展”就被轻轻放过。

但换个角度看,也正是这些来回修改,才让它最后不像一个仓促拼出来的版本。

社区这件事,这次我是真切感受到了

以前我也知道文档重要,知道开源生态重要。

但这次自己从头走完一遍之后,感受会更具体一些。

比如文档确实能帮你少走很多弯路,已有扩展也能给你很多参考。再比如有人一句话提醒你 sparse checkout 怎么弄,可能就直接帮你省掉半天时间。

这种帮助不一定有多宏大,但对第一次做的人来说特别重要。因为很多时候,卡住你的不是代码本身,而是你不知道下一步该往哪里走。

“Raycast is a lifestyle”

2026 年 1 月 11 日,我在 Raycast Meetup Shenzhen 现场听到 Thomas Paul Mann 说了一句话:

Raycast is a lifestyle.

那一刻我其实挺有共鸣的。

以前我听到这句话,更多是觉得它说得挺好。

但这次自己真的从一个小念头开始,把 Lock Time 做出来、提上去、改 review、最后上架之后,我会更能理解那种感觉。

因为 Raycast 对我来说,已经不只是一个启动器了。它确实变成了一个“我想到一个小需求,就愿意顺手把它做出来”的地方。

最后

回头看,从一个很小的念头,到真正成为一个 Raycast Extension Author,中间其实没有什么特别戏剧化的故事。

如果只看 MVP,本质上就是过年期间很快做出来的一个版本。

但如果看完整旅程,它绝不是一个“周末项目”那么简单。

大概是这样一个节奏:

  1. 在家 remote 时想到这个需求
  2. 过年期间借助 AI 很快做出 MVP
  3. 来来回回修掉锁屏检测和首屏性能这些坑
  4. 花不少时间研究 Raycast Store 提交规范和准备材料
  5. 在社区帮助下成功提交 PR
  6. 经过多轮 review 和等待,最终真正上架

在这个过程中,没有 AI 就没有这个 Extension 的成功上架, AI 正在切实的改变我们的构建过程。

未来,人人都是 Builder。

至少对我来说,它最大的价值不是“替我写完”,而是让我在很多本来会犹豫、会卡住的地方,能继续往前走。

正式成为 Raycast Extension Author 的时间,是 2026.03.03。

当时我还发了一条推特动态: I became a Raycast author

现在回头看,Lock Time 是我的第一个 Raycast Extension,但大概率不会是最后一个。

因为当你真的把一个脑海里很小的需求,做成一个自己每天都能用到的东西之后,那种满足感还挺直接的。

如果你也一直想做自己的第一个 Raycast Extension,我的建议其实只有一句:

不要上来就想做一个很大、很完整、很厉害的东西。

先找一个你自己每天都会遇到、但还没人替你解决好的小问题。

从那里开始,就够了。