给 Ghostty 写了个 Alfred workflow:检索所有 Tab 和分屏

给 Ghostty 写了个 Alfred workflow:检索所有 Tab 和分屏

TL;DR

gt 搜 Ghostty 全部分屏、回车直达;go 直接开目录。附选型、踩坑与测试全记录。

背景

Alfred 的 Mega Powerpack 是我十多年前买的,很可能也是我在 macOS 上付费的第一个 App。买断制是我很喜欢的模式——尤其是在订阅制比比皆是的今天。

Ghostty 是 AI coding agent 出现之后,接替了用了十多年的 iTerm2——iTerm2 的 TUI 在 agent 长时间运行时会卡顿掉帧,实在扛不住。

Ghostty 占着我日常很长的时间,其中一块最近被 DeepSeek Harness 分走了。但工作中照样开着 N 个 Tab 和分屏,同时跑着多个 task。task 一多,切换、查找就成了麻烦事:上下文切换本来就已经很耗脑力了,再搭上一个“找 terminal”,实在不划算。

其实之前也考虑过给 Ghostty 写个 Alfred workflow,但受 Ghostty 本身的限制,实现起来太麻烦,一直搁着。正好 Ghostty 1.4.0 的发布把门槛降下来不少,于是就有了今天的 alfred-ghostty。

顺带一提,用 Raycast 的用户不用着急——我也向上游提了 PR,把 gt 的检索能力(分屏、按进程/tty 匹配)搬了过去,go 的目录打开暂时没有。

Ghostty 1.4 藏了份字典

翻开 Ghostty 1.4.0 才发现,它自带了一份完整的 AppleScript 字典,只是没怎么被宣传。一行 sdef 命令能把字典整个 dump 出来,层级正好对应我的使用方式:application → windows → tabs → terminals,窗口、Tab、分屏三层全在。

属性也对得上需求:分屏(Ghostty 叫 surface)有 working directory——完整路径,比 Tab 标题里截断的省略形式好用得多;有 pid,能反查前台正在跑的命令;还有 tty 做唯一标识。跳转要的命令也是现成的:activate window、select tab、focus。alfred-browser-tabs 的三段式,每一段都有了着落。

设计和实现的思路

先抄作业

浏览器 Tab 之所以能进 Alfred,是因为官方提供了脚本接口。alfred-browser-tabs 的实现分三段:脚本枚举“窗口 × Tab”拼成 Alfred 要的 JSON(title 给人看、match 给搜索、arg 记坐标),过滤交给 Alfred 本地做(敲字零延迟),回车时按 arg 激活对应窗口。照方抓药,原料齐了。

设计

列表的单位是第一个决策:按分屏而不是 Tab 枚举——分屏里跑的都是独立任务,检索粒度就应该是分屏;普通 Tab 只有一个分屏,自然退化成“Tab 项”。跳转是三连:窗口置前、选中 Tab、聚焦分屏,切 Tab 和切分屏从此是同一个动作。

检索拼五个维度:终端标题、Tab 标题、完整工作目录、正在运行的进程、tty,合进一个 match 字段交给 Alfred 模糊匹配,多关键词取交集——“这个项目里跑着 node 的那个窗格”,一次就能筛出来。

Alfred gt 关键词的检索结果列表

其中进程是拿分屏的 pid 反查 ps 得到的,空闲分屏显示 zsh,跑着 vim 或 htop 的一眼可辨。

交互上 gt 一个入口管全部:回车跳转,⌃回车复制完整路径。go 管“往里开”:不带参数,默认打开 Finder 当前正看着的目录(没开 Finder 就退回个人目录);带路径就目录补全、Tab 逐级下钻,回车开新 Tab,⌘回车开新窗口,Ghostty 没跑就直接冷启动。

三个坑

敲个空格,列表没了。 本地一模一样地模拟调用,脚本输出全对——问题不在脚本,在 plist 配置:Alfred 的键叫 argumenttreatemptyqueryasnil,我凭印象写成了 asnone,等于没设置,可选参数模式在空格过渡时列表直接清空。教训:不确定的配置键名,抄成熟项目的形状,别猜。

静默忽略比报错更费时间。 go 要在指定目录开新 Tab,按字典文档传工作目录,AppleScript 报类型错误,换 JXA 更阴——不报错,但配置被静默忽略,新 Tab 还是继承原目录。翻字典的 XML 才明白,record 属性真正认的是四字符代码,得传 {«class GScD»:"/path"}。

胶水层的管道要自己验。 JXA 的 doShellScript 会把输出里的换行转成 \r(旧 Mac 风格),find、ps 的多行解析全废,之前没炸纯属运气;另外进程死后,脚本字典还会返回 pid=0 的僵尸分屏。出口统一归一换行,枚举后按 ps 判活过滤。

测试和发布

Alfred 没有官方测试设施,就把它的调用契约模拟出来,做成一条 make test,分三层:plist 连线契约、脚本输出的 JSON 规范、真连 Ghostty 的动作集成。集成测试全部在临时目录自建的 Tab 里跑,不碰真实分屏;清理时 lsof 按目录反查 shell 进程再 SIGKILL——交互式 zsh 无视 SIGTERM,脚本字典的 close 在某些窗口类型上还会静默失效,清理逻辑比测试本身还曲折。12 项测试一条命令跑完。

发布用 tag 驱动的 GitHub Actions:推个 v* 标签,版本互验、测试、打包、挂 Release 自动完成。

最后

东西放在 GitHub:alfred-ghostty,Release 里下载 .alfredworkflow 双击导入,首次使用允许 Alfred 控制 Ghostty 即可。

Alfred 用了十多年,越来越觉得它的价值不在自身,而在能把任何新工具接进同一套肌肉记忆。Ghostty 换掉了 iTerm2,肌肉记忆不用换——缺的拼图,自己补一块就好。

乱世浮生微信公众号二维码

(转载本站文章请注明作者和出处乱世浮生,请勿用于任何商业用途)