把 Claude Code / Codex 的 hook 事件桥接到飞书(Lark),只在你离开电脑时才打扰你: 人在座,一切走 agent 本地流程;人离开,飞书远程接管审批与通知。
codelark 不追求把 Claude Code 搬进飞书里操作。飞书卡片适合选择题——允许/拒绝、选一个 问答选项、挑一个会话——不适合复杂编辑和长输出,那些场景终端本来就更好用。codelark 的 定位是用 tmux 把同一个会话接到三个入口,随时无缝切换:
- 本地终端:人在电脑前,
larkclaude起的会话本来就跑在 tmux pane 里,直接用,飞书全程静默。 - 飞书(轻量远程):人离开电脑后,飞书接管审批、通知,以及「补一句话让它接着干」 级别的轻量控制——daemon 直接向 tmux pane 注入文本,效果等同于你亲自坐回电脑前敲键盘。
- 远程 SSH + tmux attach(完整远程):飞书卡片不够用、需要复杂编辑或翻看长输出时,
SSH 回这台电脑、
tmux attach接上同一个会话,就是一个完整终端,历史输出、滚屏、 交互式操作全都在,跟本地坐着操作没有区别。
三个入口共享同一个 tmux pane,不是三套互相隔离的界面:本地开的会话,飞书能接手轻量决策, SSH 也能随时接管成完整终端,互不冲突、无需切换工具,谁方便用谁。
注意:「离开」判定只看这台电脑本身的键盘/鼠标空闲时长和锁屏状态(见下方「离开检测」), 和你是否正通过 SSH 操作无关。也就是说,即使你正 SSH 回来、在 tmux 里敲着命令,只要这台 电脑本地处于锁屏或空闲状态,codelark 依然判定你「离开」,审批和通知照常推送到飞书—— 不会因为你在远程操作就自动静默。这是保守的故障安全策略:宁可多推一条飞书消息,也不猜测 "SSH 在用等于人已经回来",避免误判导致该通知的没通知到。
flowchart LR
SSH["🔌 远程终端<br/>SSH + tmux attach"]
subgraph local["💻 本机"]
direction TB
CC["Claude Code<br/>(tmux pane)"]
Hook["hook client<br/>codelark hook <event>"]
Daemon(("daemon<br/>常驻单例"))
end
Feishu["📱 飞书<br/>卡片 / 消息"]
SSH -.->|"直接 attach<br/>同一个 pane"| CC
CC -->|"① hook 事件<br/>stdin(JSON)"| Hook
Hook -->|"② Unix socket"| Daemon
Daemon -->|"③ 决策/回执"| Hook
Hook -->|"④ stdout 决策<br/>(JSON)"| CC
Daemon ==>|"⑤ tmux inject<br/>回复/绑定,绕开 hook"| CC
Daemon <-->|"WebSocket 长连接"| Feishu
- SSH -.->:第三个入口,和 codelark 的进程完全无关。SSH 回这台电脑后
tmux attach接上同一个 pane,就是一个完整终端——历史输出、滚屏、交互式操作全都在。它不经过 daemon 也不经过 hook,只是标准的 tmux 多客户端 attach;之所以能接上,是因为会话本来就跑在 tmux 里(larkclaude启动的),不是 codelark 单独做了什么远程终端功能。 - ①→④:Claude Code 主动触发的请求/决策闭环。hook 读 stdin 判断你是否离开,在座时 全程静默交还本地;离开才经 Unix socket 找 daemon 要决策,决策再通过 stdout 传回 Claude Code(允许/拒绝/问答答案)。
- ⑤:方向相反、且不经过 hook 的另一条通道。飞书「💬 回复」按钮或
/workspace绑定后发的消息,由 daemon 直接用tmux inject写进 Claude Code 所在的 pane,模拟 粘贴 + 回车。只有跑在 tmux 里的会话才有这条通道,这也是为什么 codelark 推荐用larkclaude启动会话。 - daemon 与飞书之间是一条常驻 WebSocket 长连接,收发卡片、按钮点击和消息回调。
三个入口(本地终端、飞书、SSH+tmux attach)随时可以换着用,互不打架:本地敲的命令、 飞书注入的回复、SSH 那边看到的滚屏历史,都是同一个 pane 的同一份状态。
详细设计见 docs/design.md。
-
会话追踪:
SessionStart/SessionEnd/UserPromptSubmit/PostToolUse/SubagentStart/PreCompact事件在 Claude Code 会话生命周期中自动向 daemon 注册/更新会话信息,确保飞书的/workspace(/ws)命令始终准确反映活跃会话及其状态。不发送飞书通知,完全静默。 -
远程审批:
PermissionRequest触发时,若你离开,飞书推送带决策按钮的卡片。卡片保持精简: Bash 的命令直接展示在等宽代码块里(审批的核心信息一眼可见);其他工具的参数不再铺原始 JSON——Write/Edit 高亮「文件」并把内容放进代码块,其余工具按key = value逐行展示 (嵌套结构压成单行),全部收进默认折叠的「📝 参数」面板;三个决策按钮竖排全宽,手机上好点。✅ 允许:允许这次操作,下次遇到同样的工具调用仍需审批✅ 始终允许:允许这次操作,并自动添加权限规则到本地配置(~/.claude/settings.json),以后相同的操作将自动允许❌ 拒绝:拒绝该操作
当你点击"始终允许"时:
- 当前请求会被允许
- 对于
Bash工具,会将具体命令添加到允许列表(例如npm test) - 对于其他工具,会将整个工具添加到允许列表
- 规则会被写入项目的
.claude/settings.json文件
这样你就可以快速建立信任的命令白名单,减少重复审批。点击后直接把决策回传给 Claude Code。等待超时会自动交还本地,绝不代替你做决定。
Codex 的"始终允许"走 Codex 自己的持久化方式(Codex 的 PermissionRequest 输出不接受
updatedPermissions):点击时在本地写入 Codex 原生规则——Bash 命令写 Starlarkprefix_rule到$CODEX_HOME/rules/codelark.rules,mcp__server__tool工具写[mcp_servers.<server>.tools.<tool>] approval_mode = "approve"到$CODEX_HOME/config.toml——当前请求仍只回一次性 allow。规则写入失败时卡片会明确提示 「仅本次生效」。若 Codex 配置了自动审查(approvals_reviewer为auto_review或guardian_subagent),普通审批会交还 Codex 自己的审查流程,不再推飞书卡片。 -
权限拒绝通知:
PermissionDenied事件在 Claude Code 的自动模式(auto-mode)拒绝工具调用时触发,离开时推送飞书通知卡片告知你哪个工具被拒绝了。这是纯通知,无需回传决策(工具调用已经被拒绝)。 -
远程问答:Claude Code 调用
AskUserQuestion工具时(需要你选择选项),若你离开,飞书推送带下拉选择器的交互卡片,展示所有问题选项。你可以:- 从下拉菜单中选择一个选项,答案会自动回传给 Claude Code
- 点击"其他"按钮,触发本地终端输入自定义答案
支持单选和多选问题,完整展示 Claude Code 的问题选项,不再局限于简单的"同意/拒绝"二选一。
-
远程回复(入站):
Stop/Notification/StopFailure/PermissionDenied通知卡片上带「💬 回复」按钮。 当 Claude Code 跑在 tmux 里、你又离开电脑时,点它可以输入一句话,codelark 会通过 tmux 把内容注入回该会话的 pane(等同你在键盘上打字回车),适合「任务停下后补一句让它接着干」。- 只有跑在 tmux 中的会话才显示该按钮;非 tmux 会话不受影响。
- 只有配置里的本人
open_id点击才会生效,他人点击被忽略。 - 会话已退出 / pane 已关闭时,卡片会提示「会话已结束」,不会误发。
-
远程绑定(/workspace):在飞书对话框发
/workspace(或/ws)列出所有活跃会话,点某行的「🔗 绑定」按钮把当前对话粘性绑定到该会话, 之后每条普通消息(非斜杠命令)都会连续注入到该会话的 tmux pane,像聊天一样持续指挥 Claude Code。- 只认已知命令(
/workspace、/ws、/current、/new);其余任何文字都当作内容注入。会话详情(含终端快照)改由卡片上的「📊 详情」按钮查看。 /current查看当前绑定的是哪个会话(项目、id、路径);未绑定时会提示先发/workspace。- 绑定时会探测 pane;会话退出后再发消息会自动解绑并提示。
- 每条注入回一条「📤 已发送」回执。注入 = 模拟粘贴回车,只保证文字进 pane,不保证被处理。
- 只认已知命令(
-
远程新建会话(/new):在飞书对话框发
/new(不带参数),daemon 推一张卡片列出配置里预声明的所有工作区。每个工作区是一行全宽可点卡片(项目名 + 灰色路径,手机上同样好点),点整行即用当前视图的 agent 新建会话;卡片头部标明当前 agent 视图(默认default_agent,未配置时为 Claude Code),底部一行「🔄 用 Codex 新建」可把整卡切换到另一 agent 的视图。项目再多,列表也是一行一个、不臃肿。 点行后 daemon 在对应目录用 tmux 起一个 detached 会话并运行当前视图的 agent;新会话第一次上报 pane 时自动绑定为当前对话,随后直接发消息即可指挥它。- 只能在配置
workspaces白名单里的目录启动,按钮只带该路径在列表里的下标、路径只存在于本机 config——一条飞书消息无法把/new指向任意目录。 - 发卡(
/new)和建会话(点按钮)两处都只认本人open_id,他人点击被忽略。 - 按钮点击到自动绑定之间有几秒延迟(等 agent 启动、触发第一个 hook);卡片会先显示「⏳ 正在启动」,绑定完成后再推一张「✅ 已自动绑定」。
- launchd 精简 PATH 下 daemon 可能找不到 agent 二进制,可在配置里用
claude_bin/codex_bin显式指定其绝对路径。
- 只能在配置
-
快速启动(larkclaude / larkcodex):在任意目录敲
larkclaude,就在当前目录用 tmux 起一个会话并运行 Claude Code;敲larkcodex则运行 Codex。随后自动 attach(已在 tmux 内则switch-client)。 因为跑在 tmux 里,新会话会被 agent 的 hooks 自动上报给 daemon,天然具备上面的远程回复/绑定能力。- 会话名取自目录名 + 随机短后缀,重复启动不会撞名。
- 额外参数原样透传给 agent(如
larkclaude --resume)。 larkclaude/larkcodex是install时按所选 agent 在 codelark 同目录创建的软链; 也可直接用codelark tmux [--agent claude|codex](--agent缺省为 claude)。
-
离开提醒:检测到你从「在座」切换到「离开」(锁屏,或输入空闲超过阈值)时,推送一张 「🚶 已离开电脑」卡片,告诉你后续审批与通知将转到飞书。每段离开只推一次:你回到电脑 (任意 hook 事件带回「在座」状态)后标志复位,下次离开会再次提醒。
-
远程通知:
Stop(任务完成)、Notification(Claude 需要你注意)、StopFailure(对话异常中断)、PermissionDenied(工具调用被拒绝)四类事件,离开时推送纯通知卡片。 -
零打扰:坐在电脑前时,以上事件全部静默,不发任何飞书消息。
-
故障安全:daemon 未启动、连不上、超时、任何内部错误,一律降级为「交还本地」, 绝不阻断 Claude Code。
目前只支持 macOS(Intel / Apple Silicon)。
一键安装(下载预编译二进制到 ~/.local/bin,检测 tmux,随后自动运行 codelark install):
codelark install 会在配置完飞书凭据后询问「要接入哪些 agent?」(Claude Code / Codex /
两者,默认按已安装情况或两者都装),只给选中的 agent 写 hook;同时接入两者时会再问
「默认用哪个 agent 新建会话?」(写入配置 default_agent,决定 /new 卡片的主视图)。
Codex 的 hooks 写入 ~/.codex/hooks.json(与其他工具的条目合并,不覆盖),首次运行
Codex 时需确认 hook 信任(trust)。
curl -fsSL https://raw.githubusercontent.com/zat366/codelark/main/install.sh | bash或从源码手动构建(需要 Go 1.23+):
git clone https://github.com/zat366/codelark.git
cd codelark
go build -o bin/codelark .
mv bin/codelark ~/.local/bin/ # 或放到任意 PATH 目录远程回复、远程绑定(/workspace)、larkclaude 快捷启动都依赖 tmux,建议提前装好:
brew install tmux- 在飞书开放平台创建一个自建应用,记录
App ID/App Secret。 - 「事件与回调」→ 启用长连接(WebSocket)模式。
- 订阅事件:
card.action.trigger(卡片按钮点击回调)。 - 「权限管理」中开通:
im:message、im:message:send_as_bot(发送消息 / 卡片)。 - 发布应用版本,并把接收通知的账号加为应用的可用范围。
- 获取接收人的
open_id(可通过飞书开放平台的 API 调试台,用手机号/邮箱查询)。
也可以跳过手动创建,直接用下面的扫码授权。
codelark install这是唯一需要运行的安装命令,交互式完成配置 + 接入 Claude Code 两件事:
- 飞书凭据:如果
~/.config/codelark/config.json已存在会询问是否重新配置; 否则(或选择重新配置)会让你二选一:- 扫码授权(推荐):终端里直接显示二维码,用飞书 App 扫码授权后自动创建/关联应用,
无需手动打开开发者后台,
app_id/app_secret/open_id全部自动获取 - 手动输入:适合已经有现成应用、或网络受限访问不到
accounts.feishu.cn的场景, 直接输入App ID/App Secret/Owner Open ID。
- 扫码授权(推荐):终端里直接显示二维码,用飞书 App 扫码授权后自动创建/关联应用,
无需手动打开开发者后台,
- 可选参数:空闲阈值、审批超时秒数,直接回车即用默认值。
- 写入配置:保存到
~/.config/codelark/config.json(权限 0600)。 - 接入 Claude Code:把十二个 hook 条目合并进
~/.claude/settings.json(原文件会先备份为settings.json.bak-<timestamp>),效果等价于手动写入:
{
"hooks": {
"SessionStart": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook SessionStart" }
]}
],
"SessionEnd": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook SessionEnd" }
]}
],
"UserPromptSubmit": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook UserPromptSubmit" }
]}
],
"PermissionRequest": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook PermissionRequest", "timeout": 600 }
]}
],
"PermissionDenied": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook PermissionDenied" }
]}
],
"PreToolUse": [
{ "matcher": "AskUserQuestion", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook PreToolUse", "timeout": 600 }
]}
],
"PostToolUse": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook PostToolUse" }
]}
],
"Stop": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook Stop" }
]}
],
"Notification": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook Notification" }
]}
],
"StopFailure": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook StopFailure" }
]}
],
"SubagentStart": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook SubagentStart" }
]}
],
"PreCompact": [
{ "matcher": "*", "hooks": [
{ "type": "command", "command": "/usr/local/bin/codelark hook PreCompact" }
]}
]
}
}- larkclaude 快捷方式:询问是否在 codelark 同目录创建
larkclaude软链(用于一键在 tmux 中启动 Claude Code);跳过也不影响功能,之后可用codelark tmux代替,或重新运行codelark install补建。 - 启动 daemon:询问是否立即在后台启动(也可以之后随时手动
codelark start)。
install 会修改全局 Claude Code 配置,请留意备份提示;如需回退,用备份文件覆盖即可。
如果要卸载,运行 codelark uninstall:会先停止 daemon,再从 ~/.claude/settings.json
移除十二个 hook 条目(同样先备份)。config.json 不会被删除,方便下次重新 install 时
沿用已有的飞书凭据;如需彻底清理,手动删除 ~/.config/codelark 目录即可。
生成的配置文件字段:
| 字段 | 含义 | 默认 |
|---|---|---|
app_id / app_secret |
飞书自建应用凭据 | 扫码或手动输入 |
open_id |
接收消息的用户 open_id | 扫码或手动输入 |
idle_threshold_sec |
空闲多少秒判定为「离开」 | 300 |
approval_timeout_sec |
审批卡片等待点击的超时秒数 | 120 |
disable_idle_check |
跳过「离开」检测,所有事件都无条件推送飞书 | false |
domain |
飞书 API 域名(扫码时自动判断,国际版为 open.larksuite.com) | open.feishu.cn |
workspaces |
/new 可新建会话的工作区白名单:绝对路径列表。为空则关闭 /new |
无 |
claude_bin |
claude 可执行文件的绝对路径,为空时自动解析(PATH → 常见安装位置) |
自动解析 |
workspaces / claude_bin 样例(其余字段照常):
{
"workspaces": [
"/Users/you/projects/codelark",
"/Users/you/projects/blog"
],
"claude_bin": "/Users/you/.claude/local/claude"
}也可以直接用 codelark add [路径] 往列表里追加(省略路径则用当前目录),无需手动编辑 JSON。
app_secret 是敏感凭据,配置文件强制 0600,切勿提交到版本控制。若要修改已保存的配置,
重新运行 codelark install 并选择重新配置,或直接编辑 ~/.config/codelark/config.json。
hook 子命令在需要时会自动拉起 daemon。日常有两种管理方式,按需选择:
codelark start # 后台启动(已在运行则提示 pid,不会重复启动)
codelark stop # 停止重启电脑或注销后不会自动恢复,daemon 崩溃了也不会自动重启。适合临时测试。
# 列出所有活跃的 agent 会话
codelark list飞书里发 /workspace(或 /ws)后,每个会话卡片带「📊 详情」按钮,点开即可看该会话的详细状态和 tmux pane 实时快照。
daemon 会自动追踪所有 Claude Code agent 会话的状态:
- idle - 空闲,agent 已停止
- processing - 处理中,agent 正在思考
- running - 运行中,agent 正在执行工具
- waiting - 等待中,等待用户审批或回答
会话信息包括:
- 会话 ID
- 项目名称和工作目录
- 当前状态和正在使用的工具
- 开始时间和最后活动时间
- 相关消息(如错误信息)
示例输出:
$ codelark list
活跃的 agent 会话 (2):
1. 会话 ID: abc123
项目: my-project
路径: /Users/user/projects/my-project
状态: running
当前工具: Bash
开始时间: 2026-08-02 19:30:00
最后活动: 2026-08-02 19:35:12
2. 会话 ID: def456
项目: another-project
路径: /Users/user/projects/another-project
状态: waiting
消息: 等待审批
开始时间: 2026-08-02 19:32:00
最后活动: 2026-08-02 19:34:00空闲超过 30 分钟的会话会自动清理。
用 macOS launchd 托管,命令全部由 codelark 封装,不需要手写 plist:
codelark daemon install # 安装并加载 LaunchAgent(同时立即启动一次)
codelark daemon status # 查看是否已安装、当前是否在运行、pid
codelark daemon stop # 停止(launchd 不会自动重启它,直到下次 start/重新登录)
codelark daemon start # 启动
codelark daemon restart # 重启
codelark daemon uninstall # 卸载 LaunchAgent(移除 plist)codelark daemon install 会写入并加载
~/Library/LaunchAgents/com.codelark.daemon.plist,日志输出到 /tmp/codelark.log。
注意:如果已经装了 launchd 常驻服务,就不要再用 codelark start/stop ——
codelark stop 只是临时终止进程,几秒内会被 launchd 的 KeepAlive 自动拉起来;
codelark stop 检测到这种情况会提示你改用 codelark daemon stop。
codelark不带任何参数直接运行即为前台模式,Ctrl+C 退出。这也是 codelark start 和
launchd LaunchAgent 内部实际执行的命令。
go build ./...
go vet ./...
go test ./...- 离开检测目前只在 macOS 上有实现:屏幕锁定(
ioreg的CGSSessionScreenIsLocked) 或输入空闲超过阈值(HIDIdleTime)任一满足即判定为「离开」,锁屏可立即触发、无需等空闲计时。 其他平台会保守地始终按「离开」处理,功能可用但无法区分你是否真的在电脑前。 - 远程点击「允许/始终允许/拒绝」只能用于
PermissionRequest事件;Notification里的权限提示 子类型只是提醒,不能回传决策(飞书 SDK / Claude Code hook 本身的限制)。 AskUserQuestion的"其他"选项目前会降级到本地终端输入;未来可能支持通过飞书输入框直接输入自定义答案。- 远程回复依赖 Claude Code 跑在 tmux 中,且注入时会话正停在等待输入的状态;若此刻正卡在 本地权限弹窗或正在跑工具,注入的文字可能不被接收。会话空闲超过 30 分钟被清理后也无法再回复。
- 远程绑定的注入依赖会话正停在等待输入的状态;若此刻正卡在本地权限弹窗或正在跑工具,注入的
文字可能不被接收。绑定不持久化,daemon 重启后需重发
/workspace(或/ws)重新绑定;会话空闲超过 30 分钟被清理后 再发消息会自动解绑。