28 个快捷键,1 个能跨应用:WorkBuddy 的入口短板

别人家的 AI 桌面端是这样工作的:ChatGPT 按 ⌥Space 弹出一条浮动输入框,问完 Esc 走人;Claude 直接双击 Option 就能说话,主窗口一辈子不用打开。WorkBuddy 是另一套动作——切到它、找到输入框、点新建,三个动作做完,刚才那个念头已经凉了。

数据取自本机 WorkBuddy 5.9.3 客户端:快捷键定义从 app.asar 内 main / renderer 产物解析,内存与进程数由 ps 实测,采集时间 2026-11-03 23:00(UTC-8)。

一句话结论

我用一个 29.5 MB 的后台进程,给 WorkBuddy 补上了它缺的那块:按 ⌘⇧K,窗口到前台、落在空白新对话、光标在输入框里,直接打字就行。

但这不应该是外挂能干的事。所以我顺手把它每个快捷键的作用域数了一遍,结果比预想的更直接——28 个快捷键,只有 1 个能在 WorkBuddy 之外生效,而那唯一的一个,在我这台机器上压根没注册成功。

指标 数值 说明
快捷键总数 28 全部可自定义,覆盖导航、编辑、窗口、通用四类
能在应用外生效 1 唯一的全局项 toggle-window,默认 Shift+Alt+W
实测该全局项可用 0 两种方式触发均无响应,注册路径被跳过
补上这一步的代价 29.5 MB Hammerspoon 常驻内存,空闲 CPU 为 0

省下的不是三秒,是那个念头

先算一笔小账:假定一天要问 30 次,每次切窗口、找输入框、点新建平均 3 秒,一天 90 秒,一年不到 9 小时。按这个口径,做不做这个优化都无所谓,谁都不缺这 9 小时。

真正的成本不在秒数。我在用 ChatGPT 和 Claude 的时候有个明显习惯:想到什么立刻按一下就能问,问题倾向于当场问完;换成要切窗口的工具,同一个问题会被我记成一句备忘,攒到晚上一起问。攒起来的那批问题,十个里有七个最后没问——不是不重要,是当时的上下文已经散了。

所以入口延迟影响的不是效率,是使用频率本身。一次交互超过两秒,人就会开始给自己找理由跳过它。这也是为什么 ChatGPT 和 Claude 都把这件事做成了系统级的默认能力,而不是藏在设置里的一个可选项。

ChatGPT 桌面版的浮动输入条:一条提示词浮在桌面最上层,下方展开结果面板

ChatGPT 的入口是一条浮条。问完就走,主窗口可以一直不打开。

WorkBuddy 主窗口的新建对话页:需要先把整个窗口切到前台,才能找到输入框

WorkBuddy 的入口是整扇窗。切过来、找到输入框,才轮到打字。

要点:把 AI 当工具用的人,衡量的是「这一次省了多少」;把 AI 当同事用的人,衡量的是「我愿不愿意随时开口」。前者看功能,后者看入口。

实测:28 个快捷键的作用域分布

WorkBuddy 的快捷键设置页分四类:窗口、编辑、任务、通用。看起来挺完整,但真正决定「能不能在任何地方按下就用」的不是分类,是作用域(scope)。

我把应用包里那份快捷键定义抽出来统计了一遍:app 作用域 21 个,context 作用域 6 个,global 作用域 1 个。也就是说,只要你不在 WorkBuddy 窗口里,27 个快捷键全部失效。

快捷键按作用域分布

WorkBuddy 28 个快捷键的作用域分布横向条形图:app 作用域 21 个,context 作用域 6 个,global 作用域 1 个

28 个快捷键中只有 1 个(toggle-window)是 global 作用域,其余 27 个都要求 WorkBuddy 已在前台或输入框已聚焦。

三个关键项的实际表现

动作 默认按键 作用域 能不能在别的应用里按
新建对话 ⌘N app 不能
新建快速问答 ⌘⇧N app 不能
唤起/隐藏主窗口 ⇧⌥W global 理论上能,实测未注册

那个 global 项就是 toggle-window。它的定义写得很清楚:窗口可见且聚焦时隐藏,其余情况恢复并聚焦。语义是对的,问题出在注册时机。

我把主进程代码翻出来看了一遍:注册函数被放在「主窗口尚不存在时才创建窗口」的分支里。而正常启动流程里,窗口在那之前就已经建好了,于是这个分支被短路,注册被跳过——不报错、不提示、设置页里照常显示快捷键已启用。

我用两种方式验证过:程序合成按键、系统级脚本发送。窗口都没有任何反应。这不是我按错了,是这个键从头到尾没注册上。

WorkBuddy 主进程中 ensureMainWindow 的真实代码:主窗口已存在时直接 return false

这是 5.9.3 里的真实代码。getMainWindow() 一旦有值就 return false,后面那串注册语句永远等不到执行。

三个方案,前两个都是我自己的错

既然自带的用不了,就自己补。我先想到的是对标 Claude 的手感:双击修饰键,不占字母键,肌肉记忆成本最低。

方案迭代记录

方案 结果 失败原因
双击 Option 撞车 WorkBuddy 把单独按 Option 绑成了语音输入,双击等于连发两次语音指令
双击 Command 事件全丢 我拿按键码做过滤,但它在这类事件里恒为 0,自己把事件筛没了
⌘⇧K 可用 与 WorkBuddy 现有快捷键零冲突,走系统级热键注册

第一个坑值得展开说,因为它不是我的问题。WorkBuddy 把「单独按 Option」的默认绑定给了语音输入——这跟 Claude 双击 Option 唤起输入框的设计直接对撞。两个功能抢同一个键,表现出来就是「偶尔能生效、偶尔逻辑很奇怪」,因为一半的事件被语音吃了。

第二个坑是我的:Hammerspoon 里监听修饰键变化的事件回调,返回的按键码在任何情况下都是 0。我用它区分左右 Command,等于给所有事件加了一个永远不成立的条件,于是监听器挂着,日志一条不进。这个 bug 掩盖得很好,因为热键列表里我的绑定看起来一切正常。

最后落到 ⌘⇧K。理由很朴素:系统级热键注册不依赖事件监听,不受语音快捷键干扰,也不会被输入法的组合键吃掉。查过 WorkBuddy 现有的 28 项,⌘K 是全局搜索、⌘⇧N 是快速问答、⌘N 是新建对话,⌘⇧K 空着。

注意:想在 macOS 上做「双击修饰键」触发,先查清楚目标应用有没有把这个修饰键单独绑走。修饰键双击天然脆弱:系统可能合并事件、输入法会抢、你自己的逻辑还容易写错。能用组合键就别用双击。

为什么是 Hammerspoon,不是 Raycast

补这个功能有两类工具可选:Hammerspoon 这种配置式脚本引擎,或者 Raycast 那种带界面的启动器。功能上都能做,差别在代价。

Hammerspoon 是原生 Objective-C 外壳 + Lua 脚本,实测常驻 29.5 MB,空闲 CPU 为 0,不建索引、不同步、不联网。Raycast 第一代是原生 Swift,现在已经重写成 WebView + Node 后端,官方拆解里光 WebView 就 120–200 MB、Node 后端 150–200 MB。为了一个快捷键装一个启动器,不划算。

WorkBuddy 自身的内存分布(前 8 大进程)

WorkBuddy 前 8 大进程内存占用横向条形图,最高 457 MB,最低 76 MB

作为参照:WorkBuddy 单次运行 18 个进程、合计 1792 MB。最大的单个渲染进程 457 MB,已是 Hammerspoon 整个进程的 15 倍。

这不是说 WorkBuddy 内存占用有问题——Electron 应用多进程是这个量级,Chrome 也一样。问题是当它连「按一下就问」这种基础入口都要靠第三方补时,那 1792 MB 里有一块是白花的。

吐槽:不是没做,是没做完

把这件事归到「产品没做」并不准确。它做了:有快捷键设置页、有 global 作用域的字段、有 toggle-window 这个动作,代码里连「被其他应用占用时打警告日志」都写了。这是一个知道该做什么、并且动手做了的团队。

问题出在收尾。功能写完了,但没验证它在真实启动路径上是否生效;注册失败只写进日志,界面上照样显示「已启用」;用户按了没反应,唯一能得到的解释是自己去翻设置。这不是能力问题,是交付标准的问题。

三家 AI 桌面端的入口能力对照

产品 默认全局唤起 唤起后是什么 需要主窗口吗
ChatGPT macOS ⌥Space 浮动输入条,可直接提问/截图/传文件 不需要
Claude Desktop 双击 Option 快速输入框 不需要
WorkBuddy 5.9.3 ⇧⌥W(实测未注册) 把主窗口切到前台,停在原对话 需要

三点具体差距,按严重程度排:

一是「唤起」和「新对话」被拆成了两个动作。竞品的全局唤起进去就是一个空白输入框,WorkBuddy 唤起的是你上次那个对话,要新开还得再按 ⌘N。这两个动作在产品逻辑上是独立的,但在用户意图上是一件事——我按快捷键的那一刻,想的是「我要问个新问题」,不是「我要看看上次聊到哪」。

二是快捷键的能力边界没有暴露。设置页列了 28 项,看不出哪些只在窗口内有效。用户很容易以为是自己设置错了,实际上那 27 项本来就不该期待跨应用生效。一个 scope 标签、一句「仅应用内生效」,能省掉用户半小时的困惑。

三是状态不可见。全局热键注册失败是硅谷式静默失败:日志里有 warn,界面上一切正常。这类失败必须让用户看见——设置页那一行应该直接标红「注册失败,可能被其他应用占用」。

要点:这三个问题指向同一个设计惯性:把 WorkBuddy 当成一个「应用」,而不是「随时可用的能力」。一旦按应用来设计,入口就自然长在窗口里;按能力来设计,入口必须先于窗口存在。ChatGPT 和 Claude 的差距不在模型,在这一层。

两边的护栏

如果你在做这类产品

全局入口不是锦上添花,是这类工具的第一优先级。判断标准很简单:用户在其他应用里想到要问的时候,能不能不离开当前窗口就问出口。做不到这一条,其余功能再强,使用频率也会被入口卡死。

另外两条:全局热键注册失败必须可见,不要只写日志;快捷键列表要标出作用域,别让用户猜。这两件事的开发成本都以小时计,但对信任的伤害是长期的。

如果你只是想自己补上

别碰修饰键双击,直接上组合键。选键之前先翻一遍目标应用已有的快捷键定义,避开它的保留键——尤其注意它有没有把某个修饰键单独绑给了别的功能(语音输入是重灾区)。

启动类操作要留出足够的等待时间。冷启动时窗口画出来了不代表界面准备好接快捷键,那中间还有一两秒,早发的按键会被吞掉,表现出来就是「有时候行有时候不行」。

核心逻辑(Hammerspoon,约 20 行)

-- 唤起 WorkBuddy 并落到空白新对话
local BUNDLE = "com.tencent.workbuddy.mac"

local function mainWindow()
  local app = hs.application.get(BUNDLE)
  if not app then return nil end
  for _, w in ipairs(app:allWindows()) do
    local f = w:frame()
    -- 只认足够大的标准窗口,避开启动闪屏
    if w:isStandard() and f.w >= 600 and f.h >= 300 then return w end
  end
end

hs.hotkey.bind({ "cmd", "shift" }, "K", function()
  -- 未运行:先拉起,轮询等窗口
  if not hs.application.get(BUNDLE) then
    hs.execute('open -a "WorkBuddy"')
  end
  local tries = 0
  local timer
  timer = hs.timer.doEvery(0.5, function()
    tries = tries + 1
    local w = mainWindow()
    if w then
      timer:stop()
      w:raise(); w:focus()
      -- 等渲染器挂稳,否则 ⌘N 会被吞
      hs.timer.doAfter(2.5, function()
        hs.eventtap.keyStroke({ "cmd" }, "n", 0)
      end)
    elseif tries > 120 then
      timer:stop()
    end
  end)
end)

引用与来源

本文数据与事实出处

C code80.ai · AI 编码 API 聚合 Claude / GPT 多模型统一接入,稳定不限速,按量计费,几行配置接入 Claude Code。 了解一下 ›

抢沙发

评论前必须登录!

立即登录   注册