3 月 12 日晚上,我在 VS Code 里跟 Claude Code 连续聊了好几个小时。

那天我一边看 DeerFlow,一边跑多模型审计,一边和上下文窗口拉扯。到后面,我最先罢工的不是脑子,是手。

我当时问了一个非常直接的问题:

能不能别让我一直打字?

这就是 /Users/hjjk/voice-input/ 这个项目的起点。

后来我做出来的,是一个很具体的 Mac 工具:按住 Fn 开始说话,松开 Fn 结束录音,语音自动转成文字,再直接粘贴到当前光标位置。

但这个小项目真正有意思的地方,不只是我多了一个语音输入工具,而是它把一件事验证得越来越清楚:

agent 最该先接管的,往往不是“你到底要做什么”,也不是最后的验收,而是开发流程中间那段最细碎、最高频、最容易反复验证的翻译和审计工作。

从打字疲劳,到按住 Fn 说话输入

图 1:这个项目表面上是在做一个语音输入工具,实际上它更像一次很具体的开发工作流实验。

一、这个项目不是从“我要做产品”开始的,而是从“我真的不想再打字了”开始的

项目最早的起点其实很朴素。

我先问 Claude,有没有办法直接对着它说话,而不是一直打字。它先给了我两个常规答案:macOS 自带听写,以及一些现成第三方工具。那都不是我想要的。系统自带的听写功能(语音转文字)在语音识别上不够智能,对中文,尤其是中英文夹杂识别准确率很低。尤其是对我这种普通话不标准,英文更是带着湖北口音的用户,更是无所适从。

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

而且我真正想要的,不是“电脑能听懂我说话”这么抽象的一件事,而是一种很具体的交互:

  • 按住键盘上的某一个键开始录音
  • 松开键盘上的键结束录音
  • 系统自动转写
  • 自动粘贴到当前输入框

这一步后来回头看特别重要。

因为它意味着,在这个项目里,我一开始没有让 agent 决定“要做什么”,而是先把交互标准锁死了。

也就是说,最早由我自己牢牢握住的,是这些东西:

  • 我的工作场景到底是什么
  • 什么交互才算顺手
  • 什么虽然能跑,但对我的场景没用
  • 我到底愿不愿意每天真的打开它

比如最初的原型很快就能做出来。Claude 几分钟内就给了我一个能录音、能转写、能把结果塞回剪贴板的最小版本。

但我一试就知道:这个版本不对。

它能工作,却不适合我真正的工作流。

这也是我现在越来越相信的一件事:

agent 一开始不该先接管“你到底想要什么”。

那一段最好还是人来定。因为如果这一步交出去,后面再快,也只是朝错误方向加速。

二、这个工具不是一下子做出来的,而是沿着 V0 到 V3 被一点点推成了日用工具

这个项目的开发过程其实非常典型,不是一口气做完,而是被一连串真实使用里的问题推着往前走。

V0:十分钟原型

第一版非常简单:

  • sox 录音
  • Enter 结束
  • 调本地 Whisper
  • 把文字复制到剪贴板,然后我再手动粘贴到需要用到的地方

它证明了一件事:这条链路能跑通。

但问题也立刻暴露出来了。我不是偶尔说一句话,我是在一个长时间和 Claude 协作的工作场景里频繁说话。每次都重新启动一个命令,根本不现实。

V1:常驻循环,但交互错了

于是我把它改成了常驻版本:按一次 Enter 开始录,按一次 Enter 停止,再继续等待下一轮。

这版看起来更像产品了,但我立刻发现致命问题:Enter 是整个系统里最常用的键,这种设计一定会和别的任务冲突。

也就是在这里,我把真正的交互标准说清楚了:

要像微信那样,按住说话,松开发送。

不是 Enter,而是 Fn

V1.5:Fn 守护进程,核心路径第一次成型

接下来这个项目第一次长得像一个真正能用的工具。

我让它在后台常驻,用 QuartzCGEventTap 监听 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 最适合先介入的地方。

这个项目里,人和 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 监听 Fn
  • sox 录音
  • DashScope 做云端 STT
  • Whisper 做本地 fallback
  • pbcopyosascript 完成回填
  • LaunchAgent 负责自动启动

这些都没错。

但如果只按这个角度理解,这个项目就会被误解成一个“工具堆叠”的故事。

真正决定它是不是一个我愿意每天用的工具的,其实是另外一批问题:

  • 为什么一定要 Fn,而不是 Enter
  • 为什么它必须像微信,而不是像一个技术 demo
  • bilingual 输入为什么必须支持
  • 标点为什么不是小优化,而是日常可用性的关键
  • 自动粘贴值不值得做
  • “说到 screenshot 就截图”这种功能到底是惊喜还是打扰

这些判断,agent 可以参与,但不能替我拍板。

因为这些不是“会不会写”的问题,而是“我到底愿不愿意真的每天用”的问题。

这也是我现在越来越确定的一件事:

产品感觉、交互判断、优先级取舍,最好还是人自己牢牢握住。

六、这个小项目让我更确定:agent 应该先站在开发流程中间那个工位上

如果回头看整个 voice-input 项目,我现在会把开发流程拆成 4 段:

第一段:场景和判断

我要解决什么问题?
什么交互才算对?
什么虽然能跑,但对我没用?

这段,应该主要由人来掌握。

第二段:从目标到实现路径

监听什么键?
用什么录音?
状态机怎么收?
菜单栏怎么做?
后台常驻怎么接?

这段,非常适合先交给 agent。

第三段:方案审计和风险排查

语言模式理解对了吗?
线程安全吗?
权限模型对吗?
系统兼容性有没有坑?

这段,甚至比写代码更适合先交给 agent。

第四段:真实使用和最后拍板

我会不会真的天天用?
它在我的桌面环境里顺不顺?
自动化到底是惊喜还是打扰?

这段,还是得人来测,来烦,来拍板。

所以如果今天有人问我:

开发流程里,哪一段最该先被 agent 接管?

我现在的答案会比以前更明确:

不是最前面的判断,
也不是最后的验收,
而是中间那一大段高频、细碎、可验证、容易返工的实现和审计工作。

这也是为什么我觉得,这个小项目比它看起来更重要。

表面上看,我只是做了一个按住 Fn 就能说话输入的 Mac 工具。

但对我来说,它其实像一次缩小版的实验。

我用一个足够小、足够具体、足够容易测试的项目,重新验证了一遍自己现在对 agent 的判断:

  • agent 不是魔法 coder
  • agent 不是用来替代产品判断
  • agent 最该先接管的,是开发流程里最容易被反复验证的中间段
  • 一旦这段接好了,整条开发速度会明显不一样

我最后得到的,不只是一个按住 Fn 就能输入的工具。

我还顺手确认了一件对后面很多项目都更重要的事:

当我真的想把 agent 用进开发里,我应该先把哪一段工作流交给它。

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

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