Alfred 玩到深处几乎所有人都会遇到同一个尴尬Workflow 越加越多真正每天用的反而就那几条绝大多数时间都耗在维护关键字、调参数格式、试各种x-callback写法上。我最近一段时间折腾的 Protocol Launcher 系列就是想把散落的“打开某个 App”“执行某个 CLI 命令”“给某个工具传一段参数”全部收拢到同一个协议入口里。这套思路的核心是Alfred 只是前台真正干活的是一个按“协议 参数”统一路由的轻量脚本层。适合已经能写简单 workflow 但想深挖自动化的人也适合刚入门、想找一条不太绕路的上手路径的朋友。下面把我自己的搭建过程和踩坑记录整理出来。1. 为什么我最后选了“协议层”而不是一堆独立 Workflow先说动机。早期我的 Alfred 里躺着十几个 workflow一个负责开 Bear 笔记一个负责往 Things 里加任务一个负责调某个终端工具还有一个专门处理编码转换。每个 workflow 都有独立的 Script Filter、独立的 Python/Shell 脚本、独立的配置项。表面看互不干扰实际用起来很痛苦想加一个新动作得先复制一个旧 workflow改关键字、改脚本、改参数步骤重复且容易错。想在某个流程里把选中文本传进去要么到处配 “Argument Selection”要么在每处都写一遍相似代码。关键字本身也会撞车。比如todo可能同时被三个 workflow 占用排错时非常难受。后来我把思路换了一下与其一个动作一个 workflow不如把所有动作都看成“协议调用”。open bear://x-callback-url/open-note?idxxx是一次调用claude --thinking xhigh也是一次调用它们的本质都是“给某个目标传一段参数”。如果我统一在一个入口里做“参数解析 路由”那新加一个动作就只是往注册表里加一行。这里有一个值得先说明的对比。Alfred 自带 URL Action 其实已经能打开自定义 URL但它只解决了“把 URL 发出去”这件事解决不了更常见的三个问题方案适合场景主要限制每个动作一个独立 Workflow只需要一两个固定动作一多就乱关键字冲突维护成本指数上升Alfred 自带 URL Action临时打开某个不太变动的 URL无法动态拼接参数无法读取选中文本出错只能自己吞Protocol Launcher 协议层动作数量多、需要动态传参、还要混入 CLI 命令前期需要搭一个注册表和路由脚本所以“深集成”在我这里不是指某个 workflow 写得多炫而是指把 Alfred 的输入能力关键字、选中文本、剪贴板都变成协议参数再喂给统一后端。这个后端不关心你是要唤起 App 还是要跑 Shell 命令它只认注册表里的条目。2. 把 Protocol Launcher 跑通的最小骨架2.1 先搞清楚Alfred 里“打开 URL”到底调用了什么在 macOS 上URL Scheme 本质上是一张全局路由表。你在浏览器地址栏输入bear://...系统会把这段字符串交给注册过bearscheme 的那个 App。终端里也一样的道理open -g bear://x-callback-url/...就是在不抢当前焦点的情况下发一条路由请求。Alfred 的工作流里可以直接用 “Run Script” 块跑open ...也可以用 “URL Action” 块。但 URL Action 块的参数拼接能力很弱尤其是要往 URL 里塞中文、空格、特殊符号时它不太容易控制编码。我更倾向于用 Shell Script 做路由把编码、转义和类型判断都攥在自己手里。Protocol Launcher 的第一版不需要真的注册一个launcher://自定义协议。只要 Alfred 的 Script Filter 能拿到查询词脚本能根据查询词查注册表再用open把参数转发给目标 App链路就已经通了。真正的自定义协议是后话等你需要“从外部反过来唤起这套 workflow”时再补也不迟。2.2 注册表长什么样我建议用一个registry.json作为唯一配置源。每条配置代表一个动作字段只有两类URL 型和 CLI 型。先看示例{ bear: { type: url, prefix: bear://x-callback-url/open-note, template: ?id{query} }, things: { type: url, prefix: things:///add, template: ?title{query}due明天 }, cc: { type: cli, action: cc.sh } }url类型负责唤 Appprefix是协议头加固定路径template是参数模板{query}是 Alfred 传过来的查询词占位符。cli类型负责执行本地脚本action指向和launcher.sh同目录下的脚本文件。用 JSON 而不是直接在代码里if ... then ...好处是可以让整个入口对“新动作”零代码开放。你新增一个 App 集成时不需要碰逻辑脚本只改一遍注册表就能立刻在 Script Filter 里搜到。2.3 Script Filter 与执行脚本的配合Script Filter 的作用是把你的输入变成结构化列表。下面是最小可用的 Alfred JSON 输出{ items: [ { uid: bear, title: Bear 打开笔记, arg: bear {query}, autocomplete: bear }, { uid: cc, title: Claude Code 执行, arg: cc {query}, autocomplete: cc } ] }这里的关键是arg。Alfred 会把用户选中的 item 的arg作为输入传给下一个环节。所以我把arg设计成“入口关键字 原始查询词”的组合比如bear {query}。路由脚本接收这串组合先拆分出关键字和后置参数再去注册表里查#!/bin/bash # launcher.sh input$1 read -r key arg $input entry$(jq -c --arg key $key .[$key] registry.json) if [[ -z $entry || $entry null ]]; then echo Protocol Launcher: 没有这个入口 $key exit 1 fi type$(jq -r .type $entry) if [[ $type url ]]; then prefix$(jq -r .prefix $entry) template$(jq -r .template $entry) url$prefix${template//\{query\}/$arg} open -g $url elif [[ $type cli ]]; then action$(jq -r .action $entry) bash $(dirname $0)/$action $arg fi脚本本身不长但它把“查表、拼 URL、调 CLI”三件事固定成了同一套流程。之后不管你是想深夜从 Alfred 唤起一本笔记还是想一键跑某个命令行工具都用同一个入口、同一套参数规则。这里有个前提Mac 上不一定预装jq。没有的话先brew install jq或者用python3 -c import json,sys; print(json.load(open(registry.json))[sys.argv[1]])替换。我的习惯是主流程尽量少依赖额外工具但jq在路由脚本里真的省事值得装一次。3. 热词接进来Claude Code 的 xhigh 思考等级入网3.1 为什么这件事适合放进 Workflow最近社区里聊 Alfred 和 workflows 时出现频率很高的一个热词组合是“claude code调整思考等级命令xhigh workflows”。背后的需求其实很具体用 Claude Code 处理复杂任务时用户希望把模型的思考强度调到最高档也就是 xhigh让它在动手前多推演几轮如果手动去终端敲命令每次都要回忆参数、切路径、补环境变量非常琐碎。这类 CLI 型工具和普通 URL Scheme 有很大差别。URL 型调用通常只需要系统帮你转发CLI 型调用则需要你理解 PATH、TTY、环境变量和当前目录。而 Protocol Launcher 天生适合接这类东西只要把它注册成cli类型剩下的都由脚本去处理。我把cc注册为入口关键字使用方式是cc xhigh、cc mid、cc high甚至cc 某个项目路径。对 Alfred 来说这不过是一次普通参数传递对维护者来说真正的逻辑全在cc.sh一个文件里。3.2 以 xhigh 为例的 CLI 型入口配置注册表里只需要一行{ cc: { type: cli, action: cc.sh } }cc.sh里我做了三件事补环境变量、判断参数、执行最终命令。下面是一个可以直接抄的骨架#!/bin/bash # cc.sh - Claude Code 入口 # 参数由 launcher.sh 传入例如 xhigh 或 ~/Projects/demo export PATH$HOME/.local/bin:/opt/homebrew/bin:$PATH query$1 # 如果传进来的是目录先进目录再开会话 if [[ -d $query ]]; then cd $query || exit 1 fi # Claude Code 调整思考等级的命令不同版本写法不完全一样。 # 社区里常看到的是 --thinking xhigh也有版本通过配置文件指定。 # 建议先用 claude --help 或 claude config --help 核对本机语法。 thinking_levelhigh case $query in xhigh|high|medium|low) thinking_level$query ;; esac exec claude --thinking $thinking_level之所以不把xhigh写死在注册表里是因为“思考等级”是会变的。今天你可能只想用默认档明天接一个特别复杂的重构任务才想到 xhigh。让它变成 workflow 参数意味着你不用打开 Alfred 改配置直接cc xhigh就能覆盖本次会话。这里有一个容易踩的分歧点有的人把“xhigh”理解成命令行 flag有的人则把它写进 Claude Code 的配置文件里让每次启动都生效。两种思路不一样。我用--thinking $thinking_level这种形态是因为它在大多数版本里都能通过参数覆盖默认值如果你本机不支持换成配置文件写法改动只发生在cc.sh不会影响 Alfred 入口本身。3.3 面对会变化的 CLI 参数别硬编码CLI 工具和 URL Scheme 最大的不同是它的参数语法会随版本迭代而变化。今天热传的xhigh下个版本可能改成thinkingextreme或者干脆要求claude --set thinking-level xhigh。所以我把所有版本相关的东西都收进一个脚本不让 Alfred 界面感知到变化。这一点是我用过很多 workflow 后最深的体会一个称手的 Protocol Launcher入口是稳定的后端是容易换的。你维护的应该是“cc Claude Code 相关操作”这个语义而不是某个具体 flag 长什么样。语义稳定接口就稳定命令怎么变都影响不到肌肉记忆。4. 实测两天后撞见的坑和完整排查链路4.1 坑一claude 找不着第一次跑cc xhighAlfred 的调试器里只有一个冷冰冰的提示/bin/zsh: command not found: claude。第一反应是 Claude Code 没装好但在终端里which claude明明能找着。问题出在 Alfred 的脚本环境不是登录 Shell$PATH被压缩成系统最小集。你日常终端里的~/.local/bin、/opt/homebrew/bin根本没有加载进来。修复方式就是在cc.sh开头手动补环境变量export PATH$HOME/.local/bin:/opt/homebrew/bin:$PATH如果 Claude Code 是通过nvm、asdf这类版本管理器安装的PATH 可能更复杂。我的建议是先把which claude的完整路径打出来把那个目录加到cc.sh的 PATH 里比用万能export更可控。4.2 坑二URL 参数被截断URL 型入口里最容易翻车的是参数里的中文和空格。比如bear://x-callback-url/open-note?id今天要读的书如果原样拼进 URL系统路由时很可能会把非 ASCII 字符解析得乱七八糟轻则打开空笔记重则整个请求不被响应。正确做法是先对{query}做百分号编码。我在launcher.sh里加了一个小函数encode_url() { python3 -c import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1])) $1 }然后在拼 URL 前先跑一遍arg$(encode_url $arg)这样中文、空格、、?这些特殊字符就不会干扰协议路由。像things:///add?title明天 9 点开会这种带空格的查询编码后才会被正确解析成一个参数而不是被系统当成多段文本。4.3 坑三open -g与 x-callback 回跳的冲突open -g的作用是不抢当前焦点这通常很友好。但有些 App 走 x-callback 协议时需要在前台运行才能正确显示结果甚至要求用户授权。比如你想从 Alfred 唤起某个笔记 App让它执行完动作后回调回 Alfred这时候-g会导致回调流程没法正常衔接。我的处理方式是区分两种情况单纯“后台打开”的动作保留-g需要用户立刻看到结果或者需要二次交互的动作去掉-g。在注册表里加一个可选的background字段就能解决{ bear: { type: url, prefix: bear://x-callback-url/open-note, template: ?id{query}, background: true } }launcher.sh里判断一下这个字段再决定要不要加-g。别为了省事统一加体验会差很多。4.4 调试 Workflow 的正确姿势Protocol Launcher 这类“一条入口、多条出口”的结构调试时最忌讳直接看最终效果。我的排查链路固定是四步先在终端手动跑一次路由脚本bash launcher.sh cc xhigh。这一步能确认注册表、脚本、参数拆分都没问题。如果终端正常但 Alfred 里没反应打开 Alfred 的 Workflow 调试器看 Run Script 块接收到的arg到底是什么。很多问题出在 Script Filter 的 JSON 输出上尤其是arg字段没拼对。再看执行脚本有没有输出错误。Shell 脚本里我会把中间变量写到日志文件echo $(date) input$input url$url /tmp/protocol-launcher.log这样即使 Alfred 界面不弹出提示日志里也能看到链路走没走通。最后才回到真实操作场景测试选中文本、剪贴板这类外部输入。这套链路能解决九成问题。很多时候不是协议不响应而是某个环节的变量被 Alfred 的缓存吃掉了或者 JSON 里少了一个逗号。脚本类 workflow 最怕的就是藏在脚本里的静默失败日志是最快定位方式。5. 扩展方向把零散动作收敛成一个协议生态跑通之后你已经拥有一个“一个入口匹配无数动作”的基础设施。接下来值得做的扩展有三个方向。第一个方向是把注册表资产化。我自己的registry.json和所有action脚本都放进同一个 Git 仓库换新电脑或者重装系统后只需要 pull 下来再往 Alfred 里挂载同一个 workflow 目录所有动作原样恢复。由于配置全是文本diff 起来也很直观改错了很容易回滚。第二个方向是支持更多的 action 类型。当前只有url和cli两种实际上还可以加file类型直接打开某个固定文件或者目录加app类型用osascript控制某个 App 执行 AppleScript甚至加callback类型让外部程序通过 URL 回调向 Alfred 通知任务结果。每种类型只需要在launcher.sh里加一个分支注册表结构完全不需要大改。第三个方向是安全问题。自定义协议和 CLI 脚本组合起来等于给系统开了一扇“远程控制”窗口。如果某个网页知道了你的launcher://协议并且协议后面接的是可以传参的脚本入口那么恶意参数就有机会进到你的 Shell 环境里。我的底线是注册表只做白名单匹配绝不把用户输入直接拼接进eval或bash -c。任何不确定来源的输入都先做编码或参数化处理再做下一步。这套 Protocol Launcher 系列发展到今天我最满意的不是某一个脚本多高效而是它让我彻底改变了加新集成的习惯。现在遇到一个新工具我的第一反应不再是“要不要新建一个 Alfred workflow”而是先问一句它能不能映射成一个协议如果能注册表里多一行就结束了。省下来的时间恰好都花在了真正需要思考的事情上。如果你也想做类似的统一入口最小骨架大概一小时就能搭完剩下的都是在使用过程里慢慢长出来的。