3 月 12 日晚上,我在 VS Code 里跟 Claude Code 连续聊了好几个小时。
那天我一边看 DeerFlow,一边跑多模型审计,一边和上下文窗口拉扯。到后面,我最先罢工的不是脑子,是手。
我当时问了一个非常直接的问题:
能不能别让我一直打字?

这就是 /Users/hjjk/voice-input/ 这个项目的起点。
后来我做出来的,是一个很具体的 Mac 工具:按住 Fn 开始说话,松开 Fn 结束录音,语音自动转成文字,再直接粘贴到当前光标位置。
但这个小项目真正有意思的地方,不只是我多了一个语音输入工具,而是它把一件事验证得越来越清楚:
agent 最该先接管的,往往不是“你到底要做什么”,也不是最后的验收,而是开发流程中间那段最细碎、最高频、最容易反复验证的翻译和审计工作。

图 1:这个项目表面上是在做一个语音输入工具,实际上它更像一次很具体的开发工作流实验。
一、这个项目不是从“我要做产品”开始的,而是从“我真的不想再打字了”开始的
项目最早的起点其实很朴素。
我先问 Claude,有没有办法直接对着它说话,而不是一直打字。它先给了我两个常规答案:macOS 自带听写,以及一些现成第三方工具。那都不是我想要的。系统自带的听写功能(语音转文字)在语音识别上不够智能,对中文,尤其是中英文夹杂识别准确率很低。尤其是对我这种普通话不标准,英文更是带着湖北口音的用户,更是无所适从。

后来我把方向说得更明确:我要的不是泛化听写,而是更接近微信语音输入和 Whisper 的那种交互手感。

而且我真正想要的,不是“电脑能听懂我说话”这么抽象的一件事,而是一种很具体的交互:
- 按住键盘上的某一个键开始录音
- 松开键盘上的键结束录音
- 系统自动转写
- 自动粘贴到当前输入框
这一步后来回头看特别重要。
因为它意味着,在这个项目里,我一开始没有让 agent 决定“要做什么”,而是先把交互标准锁死了。
也就是说,最早由我自己牢牢握住的,是这些东西:
- 我的工作场景到底是什么
- 什么交互才算顺手
- 什么虽然能跑,但对我的场景没用
- 我到底愿不愿意每天真的打开它
比如最初的原型很快就能做出来。Claude 几分钟内就给了我一个能录音、能转写、能把结果塞回剪贴板的最小版本。
但我一试就知道:这个版本不对。

它能工作,却不适合我真正的工作流。
这也是我现在越来越相信的一件事:
agent 一开始不该先接管“你到底想要什么”。
那一段最好还是人来定。因为如果这一步交出去,后面再快,也只是朝错误方向加速。
二、这个工具不是一下子做出来的,而是沿着 V0 到 V3 被一点点推成了日用工具
这个项目的开发过程其实非常典型,不是一口气做完,而是被一连串真实使用里的问题推着往前走。
V0:十分钟原型
第一版非常简单:
- 用
sox录音 - 按
Enter结束 - 调本地
Whisper - 把文字复制到剪贴板,然后我再手动粘贴到需要用到的地方
它证明了一件事:这条链路能跑通。
但问题也立刻暴露出来了。我不是偶尔说一句话,我是在一个长时间和 Claude 协作的工作场景里频繁说话。每次都重新启动一个命令,根本不现实。
V1:常驻循环,但交互错了
于是我把它改成了常驻版本:按一次 Enter 开始录,按一次 Enter 停止,再继续等待下一轮。
这版看起来更像产品了,但我立刻发现致命问题:Enter 是整个系统里最常用的键,这种设计一定会和别的任务冲突。
也就是在这里,我把真正的交互标准说清楚了:


要像微信那样,按住说话,松开发送。
不是 Enter,而是 Fn。
V1.5:Fn 守护进程,核心路径第一次成型
接下来这个项目第一次长得像一个真正能用的工具。
我让它在后台常驻,用 Quartz 的 CGEventTap 监听 Fn 的按下和释放,用 sox 录音,用 Whisper 转写,再把结果送回剪贴板。
我还加了一个非常现实的要求:我说话经常中英混着来,所以它必须能处理 bilingual 输入。

这一轮跑通以后,我马上把它当成一个阶段性 milestone 记了下来。

这一版把核心路径真正打通了:
按住 Fn -> 说话 -> 松开 -> 转写 -> 回到输入框
但它还只是一个 rough daemon,离“我愿意每天用”还有距离。
V2:从 CLI hack 推成真正的 menu bar app
再往后,我开始补那些只有在真实使用里才会不断冒出来的问题:
- 没标点,读起来很难受
- 没有菜单栏控制,不知道它现在到底活着没有
- 每次重启都要手动启动
- 有时我想先看文本,有时我想直接粘贴
于是它开始从一个 CLI hack 往真正的 macOS 工具走:
- 用
rumps做菜单栏应用 - 用
--initial_prompt去改善标点 - 用
LaunchAgent做开机启动 - 用
osascript做自动粘贴 - 再往前推一步,增加“说到 screenshot 就触发截图”的能力


也是在这一阶段,最能说明开发真实感的那批问题全都来了:
- 短中文片段被 Whisper 错听成瑞典语
- 系统重启后 PID 文件指向了别的系统进程
- LaunchAgent 拉起后菜单栏出现双图标
- 屏幕截图权限和辅助功能权限并不像想象中那样自动就对

如果不是亲手在自己的桌面环境里来回用,这些问题其实都不会在纸面方案里长出来。
V3:云端 STT + 本地 fallback
这个项目最后一个很关键的跃迁,不是“能不能用”,而是“是不是足够快”。
本地 Whisper 可以工作,但 3 到 5 秒的等待还是让我觉得慢。于是我又把它推了一步:先走 DashScope 的 qwen3-asr-flash 云端识别,失败时再回落到本地 Whisper。

结果非常直接:
按住 Fn -> 说话 -> 松开 -> 大约 1 秒 -> 文字出现在光标位置

到这时,这个工具才真正从“一个能演示的原型”变成“一个我愿意天天用的东西”。
现在实际使用的情况来看,当我在电脑上需要输入的时候,我只需要用食指按住 Fn 键,对着电脑说话,内容直接被转录到剪贴板。我松开按键,内容就会自动粘贴到光标目前所在的地方,准确率接近 100%。
当然,最近我发现微信语音也实现了这个功能,系统层调用不仅限于微信界面上的输入。所以有的时候,我这个工具也可以暂停,直接使用微信语音来做同样的运用。当涉及到保密,或者电脑没有联网的情况,我就激活这个工具来使用。
三、在这个项目里,agent 最先接管的,不是创意,而是“翻译层”
如果把整个过程抽象一下,我现在会把它分成两层。
第一层,是我要什么。
我要按住 Fn 才开始录。
我要它像微信,不像命令行。
我要 mixed Chinese and English 都能过。
我要它不是“先复制再手动粘贴”的半成品,而是尽量贴近真实输入体验。
这一层,我没有交给 agent。
第二层,是一旦我已经知道自己要什么,怎么把它翻译成可以跑、可以迭代、可以部署的实现路径。
这层工作包括:
- 监听哪个键
- 录音怎么挂
- 事件循环怎么跑
- 菜单栏状态怎么切
- 线程怎么收口
- 后台常驻怎么做
- 自动启动怎么接
- fallback 怎么留
这才是这个项目里 agent 最先大规模接过去的部分。
如果用今天更抽象的话说,agent 在这里接管的,不是“产品判断”,而是“产品意图和工程实现之间的翻译层”。
这层工作有几个非常鲜明的特征:
- 细碎
- 高频
- 可拆分
- 反馈快
- 对错特别容易验证
而这恰恰是 agent 最适合先介入的地方。

图 2:这个项目里,我一直自己握着交互和判断,把“从目标到实现”的翻译层先交给了 agent。
四、真正让我服气的,不是它写了多少代码,而是它先替我拆掉了多少雷
如果只看表面,这个项目很容易被理解成:Claude 帮我写了一个五百多行的 Python 工具。
但如果只看到这一层,其实会完全低估这个项目里 agent 最有价值的部分。
真正让我更确定这套方法有效的,不是代码生成,而是 V2 落地前那轮多模型审计。
我把方案同时丢给了 Claude CLI 和 Kimi CLI,让它们独立 review。
结果两个审计者都抓到了同一个关键错误:
我当时以为 Whisper 的 --language en 可以把中文翻成英文,实际上它只是强制把所有语音都按英语处理。真正的翻译模式应该是 --task translate。而且 Whisper 根本没有“翻译成中文”这个选项。
这不是一个小瑕疵,而是一个如果直接实现,就会把整个功能做歪的错误。
更重要的是,这个错误不是在写代码的时候暴露出来的,而是在“实现前的方案审计”里暴露出来的。
除了这个核心误解,审计还帮我提前挖出了很多系统级问题:
- 后台线程更新 UI 的方式并不安全
- 新 macOS 上通知方案有兼容坑
CGEventTap可能被系统静默禁用- 状态机如果不收紧,会出现录音和转写竞态
- LaunchAgent 在 GUI 会话里的权限模型不能想当然
这件事对我的提醒非常大。
因为它说明了一件事:
agent 最有价值的时候,不一定是在替你写,而是在替你提前拆雷。
如果没有这一轮审计,我大概率也能把功能做出来,但最后很可能得到的是一个“看起来能跑,细节一堆坑”的版本。

图 3:这个项目真正的提效,不在于 agent 写了多少行,而在于它先替我把一批昂贵返工挡在了实现前面。
五、我一直没交出去的那部分,反而决定了这个工具最后有没有灵魂
如果只按技术名词来描述,这个项目最后无非就是:
Quartz监听Fnsox录音- DashScope 做云端 STT
Whisper做本地 fallbackpbcopy和osascript完成回填LaunchAgent负责自动启动
这些都没错。
但如果只按这个角度理解,这个项目就会被误解成一个“工具堆叠”的故事。
真正决定它是不是一个我愿意每天用的工具的,其实是另外一批问题:
- 为什么一定要
Fn,而不是Enter - 为什么它必须像微信,而不是像一个技术 demo
- bilingual 输入为什么必须支持
- 标点为什么不是小优化,而是日常可用性的关键
- 自动粘贴值不值得做
- “说到 screenshot 就截图”这种功能到底是惊喜还是打扰
这些判断,agent 可以参与,但不能替我拍板。
因为这些不是“会不会写”的问题,而是“我到底愿不愿意真的每天用”的问题。
这也是我现在越来越确定的一件事:
产品感觉、交互判断、优先级取舍,最好还是人自己牢牢握住。
六、这个小项目让我更确定:agent 应该先站在开发流程中间那个工位上
如果回头看整个 voice-input 项目,我现在会把开发流程拆成 4 段:
第一段:场景和判断
我要解决什么问题?
什么交互才算对?
什么虽然能跑,但对我没用?
这段,应该主要由人来掌握。
第二段:从目标到实现路径
监听什么键?
用什么录音?
状态机怎么收?
菜单栏怎么做?
后台常驻怎么接?
这段,非常适合先交给 agent。
第三段:方案审计和风险排查
语言模式理解对了吗?
线程安全吗?
权限模型对吗?
系统兼容性有没有坑?
这段,甚至比写代码更适合先交给 agent。
第四段:真实使用和最后拍板
我会不会真的天天用?
它在我的桌面环境里顺不顺?
自动化到底是惊喜还是打扰?
这段,还是得人来测,来烦,来拍板。
所以如果今天有人问我:
开发流程里,哪一段最该先被 agent 接管?
我现在的答案会比以前更明确:
不是最前面的判断,
也不是最后的验收,
而是中间那一大段高频、细碎、可验证、容易返工的实现和审计工作。
这也是为什么我觉得,这个小项目比它看起来更重要。
表面上看,我只是做了一个按住 Fn 就能说话输入的 Mac 工具。
但对我来说,它其实像一次缩小版的实验。
我用一个足够小、足够具体、足够容易测试的项目,重新验证了一遍自己现在对 agent 的判断:
- agent 不是魔法 coder
- agent 不是用来替代产品判断
- agent 最该先接管的,是开发流程里最容易被反复验证的中间段
- 一旦这段接好了,整条开发速度会明显不一样
我最后得到的,不只是一个按住 Fn 就能输入的工具。
我还顺手确认了一件对后面很多项目都更重要的事:
当我真的想把 agent 用进开发里,我应该先把哪一段工作流交给它。

图 4:这个项目真正验证的,不只是一个工具,而是一种 agent 介入开发的顺序。