Skip to content

Repository files navigation

codelark

把 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 &lt;event&gt;"]
        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
Loading
  • 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),以后相同的操作将自动允许
    • ❌ 拒绝:拒绝该操作

    当你点击"始终允许"时:

    1. 当前请求会被允许
    2. 对于 Bash 工具,会将具体命令添加到允许列表(例如 npm test
    3. 对于其他工具,会将整个工具添加到允许列表
    4. 规则会被写入项目的 .claude/settings.json 文件

    这样你就可以快速建立信任的命令白名单,减少重复审批。点击后直接把决策回传给 Claude Code。等待超时会自动交还本地,绝不代替你做决定。

    Codex 的"始终允许"走 Codex 自己的持久化方式(Codex 的 PermissionRequest 输出不接受 updatedPermissions):点击时在本地写入 Codex 原生规则——Bash 命令写 Starlark prefix_rule$CODEX_HOME/rules/codelark.rulesmcp__server__tool 工具写 [mcp_servers.<server>.tools.<tool>] approval_mode = "approve"$CODEX_HOME/config.toml——当前请求仍只回一次性 allow。规则写入失败时卡片会明确提示 「仅本次生效」。若 Codex 配置了自动审查(approvals_reviewerauto_reviewguardian_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 / larkcodexinstall 时按所选 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

准备飞书应用

  1. 飞书开放平台创建一个自建应用,记录 App ID / App Secret
  2. 「事件与回调」→ 启用长连接(WebSocket)模式。
  3. 订阅事件:card.action.trigger(卡片按钮点击回调)。
  4. 「权限管理」中开通:im:messageim:message:send_as_bot(发送消息 / 卡片)。
  5. 发布应用版本,并把接收通知的账号加为应用的可用范围。
  6. 获取接收人的 open_id(可通过飞书开放平台的 API 调试台,用手机号/邮箱查询)。

也可以跳过手动创建,直接用下面的扫码授权。

一键安装

codelark install

这是唯一需要运行的安装命令,交互式完成配置 + 接入 Claude Code 两件事:

  1. 飞书凭据:如果 ~/.config/codelark/config.json 已存在会询问是否重新配置; 否则(或选择重新配置)会让你二选一:
    • 扫码授权(推荐):终端里直接显示二维码,用飞书 App 扫码授权后自动创建/关联应用, 无需手动打开开发者后台,app_id/app_secret/open_id 全部自动获取
    • 手动输入:适合已经有现成应用、或网络受限访问不到 accounts.feishu.cn 的场景, 直接输入 App ID/App Secret/Owner Open ID
  2. 可选参数:空闲阈值、审批超时秒数,直接回车即用默认值。
  3. 写入配置:保存到 ~/.config/codelark/config.json(权限 0600)。
  4. 接入 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" }
      ]}
    ]
  }
}
  1. larkclaude 快捷方式:询问是否在 codelark 同目录创建 larkclaude 软链(用于一键在 tmux 中启动 Claude Code);跳过也不影响功能,之后可用 codelark tmux 代替,或重新运行 codelark install 补建。
  2. 启动 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

管理 daemon

hook 子命令在需要时会自动拉起 daemon。日常有两种管理方式,按需选择:

临时启停(本次登录会话内有效)

codelark start    # 后台启动(已在运行则提示 pid,不会重复启动)
codelark stop     # 停止

重启电脑或注销后不会自动恢复,daemon 崩溃了也不会自动重启。适合临时测试。

查看活跃会话(NEW! 🎉)

# 列出所有活跃的 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

手动运行 daemon(调试用,前台阻塞)

codelark

不带任何参数直接运行即为前台模式,Ctrl+C 退出。这也是 codelark start 和 launchd LaunchAgent 内部实际执行的命令。

开发

go build ./...
go vet ./...
go test ./...

已知限制

  • 离开检测目前只在 macOS 上有实现:屏幕锁定(ioregCGSSessionScreenIsLocked) 或输入空闲超过阈值(HIDIdleTime)任一满足即判定为「离开」,锁屏可立即触发、无需等空闲计时。 其他平台会保守地始终按「离开」处理,功能可用但无法区分你是否真的在电脑前。
  • 远程点击「允许/始终允许/拒绝」只能用于 PermissionRequest 事件;Notification 里的权限提示 子类型只是提醒,不能回传决策(飞书 SDK / Claude Code hook 本身的限制)。
  • AskUserQuestion 的"其他"选项目前会降级到本地终端输入;未来可能支持通过飞书输入框直接输入自定义答案。
  • 远程回复依赖 Claude Code 跑在 tmux 中,且注入时会话正停在等待输入的状态;若此刻正卡在 本地权限弹窗或正在跑工具,注入的文字可能不被接收。会话空闲超过 30 分钟被清理后也无法再回复。
  • 远程绑定的注入依赖会话正停在等待输入的状态;若此刻正卡在本地权限弹窗或正在跑工具,注入的 文字可能不被接收。绑定不持久化,daemon 重启后需重发 /workspace(或 /ws)重新绑定;会话空闲超过 30 分钟被清理后 再发消息会自动解绑。

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages