@jesonliu/lark-claudecode-bridge 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -0
  3. package/config.example.yaml +42 -0
  4. package/deploy/com.lark-claudecode-bridge.plist +9 -0
  5. package/deploy/lark-claudecode-bridge.service +11 -0
  6. package/deploy/windows-start.bat +2 -0
  7. package/dist/access/access-control.d.ts +43 -0
  8. package/dist/access/access-control.js +88 -0
  9. package/dist/access/access-control.js.map +1 -0
  10. package/dist/bin/lcb.d.ts +2 -0
  11. package/dist/bin/lcb.js +191 -0
  12. package/dist/bin/lcb.js.map +1 -0
  13. package/dist/cli/app-manager.d.ts +28 -0
  14. package/dist/cli/app-manager.js +99 -0
  15. package/dist/cli/app-manager.js.map +1 -0
  16. package/dist/cli/asker.d.ts +7 -0
  17. package/dist/cli/asker.js +41 -0
  18. package/dist/cli/asker.js.map +1 -0
  19. package/dist/cli/setup-wizard.d.ts +28 -0
  20. package/dist/cli/setup-wizard.js +114 -0
  21. package/dist/cli/setup-wizard.js.map +1 -0
  22. package/dist/cli/stdin-pairing.d.ts +7 -0
  23. package/dist/cli/stdin-pairing.js +19 -0
  24. package/dist/cli/stdin-pairing.js.map +1 -0
  25. package/dist/cli/ws-manager.d.ts +11 -0
  26. package/dist/cli/ws-manager.js +67 -0
  27. package/dist/cli/ws-manager.js.map +1 -0
  28. package/dist/config.d.ts +7 -0
  29. package/dist/config.js +169 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/executor/claude-executor.d.ts +28 -0
  32. package/dist/executor/claude-executor.js +84 -0
  33. package/dist/executor/claude-executor.js.map +1 -0
  34. package/dist/executor/notify-server.d.ts +34 -0
  35. package/dist/executor/notify-server.js +193 -0
  36. package/dist/executor/notify-server.js.map +1 -0
  37. package/dist/executor/output-collector.d.ts +5 -0
  38. package/dist/executor/output-collector.js +17 -0
  39. package/dist/executor/output-collector.js.map +1 -0
  40. package/dist/executor/permission-gate.d.ts +20 -0
  41. package/dist/executor/permission-gate.js +66 -0
  42. package/dist/executor/permission-gate.js.map +1 -0
  43. package/dist/gateway/card-builder.d.ts +15 -0
  44. package/dist/gateway/card-builder.js +60 -0
  45. package/dist/gateway/card-builder.js.map +1 -0
  46. package/dist/gateway/feishu-gateway.d.ts +164 -0
  47. package/dist/gateway/feishu-gateway.js +249 -0
  48. package/dist/gateway/feishu-gateway.js.map +1 -0
  49. package/dist/gateway/progress-card.d.ts +34 -0
  50. package/dist/gateway/progress-card.js +106 -0
  51. package/dist/gateway/progress-card.js.map +1 -0
  52. package/dist/index.d.ts +38 -0
  53. package/dist/index.js +433 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/session/channel.d.ts +18 -0
  56. package/dist/session/channel.js +44 -0
  57. package/dist/session/channel.js.map +1 -0
  58. package/dist/session/commands.d.ts +24 -0
  59. package/dist/session/commands.js +87 -0
  60. package/dist/session/commands.js.map +1 -0
  61. package/dist/session/session-store.d.ts +42 -0
  62. package/dist/session/session-store.js +85 -0
  63. package/dist/session/session-store.js.map +1 -0
  64. package/dist/session/triggers.d.ts +12 -0
  65. package/dist/session/triggers.js +34 -0
  66. package/dist/session/triggers.js.map +1 -0
  67. package/dist/transcript/transcript-writer.d.ts +85 -0
  68. package/dist/transcript/transcript-writer.js +64 -0
  69. package/dist/transcript/transcript-writer.js.map +1 -0
  70. package/dist/types.d.ts +105 -0
  71. package/dist/types.js +2 -0
  72. package/dist/types.js.map +1 -0
  73. package/dist/version.d.ts +1 -0
  74. package/dist/version.js +3 -0
  75. package/dist/version.js.map +1 -0
  76. package/package.json +41 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jesonliu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # lark-claudecode-bridge
2
+
3
+ 飞书 ↔ Claude Code 桥接器:在飞书里遥控本机 Claude Code。
4
+ 写操作以卡片按钮确认(长连接回调),结果文本与产出文件回传飞书。
5
+ **无需公网 IP、无需内网穿透;模型自由(订阅 / API Key / 第三方端点均可)。**
6
+
7
+ ## 特性
8
+
9
+ - 私聊 / 群聊 @机器人 触发;群聊多人可用(配对 + 白名单访问控制)
10
+ - **多机器人**:一个进程同时跑 N 个飞书机器人,各自独立会话池、独立并发、独立 Claude Code 实例(会话存储/设置/插件/模型凭证互不共享)
11
+ - 写操作确认卡片:允许 / 拒绝 / 本次会话不再询问(仅任务发起人可点)
12
+ - 流式进度卡片(打字机效果 + 工具调用 + 运行心跳,静默不等于卡死)
13
+ - 结果文本 + 产出文件回传(图片预览、>10 文件自动 zip)
14
+ - 多工作区切换(/ws)、会话管理(/new /resume)、/stop 打断
15
+ - **对话内容落盘**:用户消息与 Claude 回复全文存为 JSONL(`transcripts/`,为后续知识库挖掘打底;可选保留期)
16
+ - 通道并发(默认 3),通道内串行
17
+
18
+ ## 前置条件
19
+
20
+ 1. Node.js ≥ 20
21
+ 2. Claude Code CLI 可用:终端 `claude "hi"` 能正常回复(任意鉴权方式:
22
+ 订阅 `claude login` / `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL` 第三方端点如 GLM——桥接器透传环境变量,不介入鉴权)
23
+
24
+ ## 安装
25
+
26
+ ```bash
27
+ npm install -g @jesonliu/lark-claudecode-bridge
28
+ lcb setup
29
+ lcb start
30
+ ```
31
+
32
+ ## 飞书应用配置(图文)
33
+
34
+ 1. https://open.feishu.cn → 创建企业自建应用 → 添加「机器人」能力
35
+ 2. 权限管理开通:`im:message`、`im:message:send_as_bot`、`im:resource`、`contact:user.base:readonly`
36
+ 3. 事件与回调 → 事件配置 → 订阅方式选「使用长连接接收事件」→ 添加 `im.message.receive_v1`
37
+ 4. 事件与回调 → 回调配置 → 订阅方式选「使用长连接接收回调」→「已订阅的回调」点「添加回调」,添加「卡片回传交互」(`card.action.trigger`)
38
+ 5. 凭证与基础信息 → 复制 App ID / App Secret
39
+ 6. 版本管理与发布 → 创建版本并发布,管理员审核通过
40
+ 7. 运行 `lcb setup` 填入凭证 → `lcb start` → 私聊机器人发「/help」
41
+
42
+ ## 首次配对
43
+
44
+ 第一个发消息的用户会收到 6 位配对码(15 分钟内有效),在 `lcb start` 的终端里输入该码回车即批准(首个批准者自动成为 admin)。或另开终端 `lcb pair <code>`——批准写盘后,运行中的桥在下一条消息到达时自动重读白名单,无需重启。
45
+
46
+ ## lcb 命令
47
+
48
+ | 命令 | 说明 |
49
+ |---|---|
50
+ | `lcb setup` | 引导式配置(写入 `~/.lark-claudecode-bridge/config.yaml`,支持一次配多个机器人) |
51
+ | `lcb start` | 启动桥接器(前台,为每个机器人各建一条长连接;终端可直接输入配对码批准) |
52
+ | `lcb pair <code>` | 另开终端批准 6 位配对码 |
53
+ | `lcb app list` | 列出机器人应用 |
54
+ | `lcb app add` | 添加机器人应用(交互式;旧单应用配置会自动升级为多应用格式,重启后生效) |
55
+ | `lcb app remove <名字\|app_id>` | 删除机器人应用(最后一个不可删;其会话分片与落盘目录保留待人工清理) |
56
+ | `lcb ws add <名字> <路径>` | 添加工作区(路径需已存在;增量写回,保留 config.yaml 注释) |
57
+ | `lcb ws remove <名字>` | 删除工作区(默认工作区被删时自动回退;apps 里的引用联动清理) |
58
+ | `lcb ws list` | 列出工作区(`*` 为默认) |
59
+ | `lcb version` | 查看版本 |
60
+
61
+ > **热生效**:桥接器运行中执行 `lcb ws add / remove`,下一条消息到达时自动重读配置,无需重启(apps 应用列表、凭证与 `concurrency` 改动除外,需重启)。
62
+
63
+ ## 命令速查(飞书里发给机器人)
64
+
65
+ | 命令 | 说明 |
66
+ |---|---|
67
+ | /new | 开新会话 |
68
+ | /resume | 列出/恢复历史会话(`/resume <编号>` 恢复指定会话) |
69
+ | /stop | 停止当前任务 |
70
+ | /status | 当前状态 |
71
+ | /ws list / /ws use \<名字\> | 工作区(切换仅 admin 可用) |
72
+ | /help | 帮助 |
73
+
74
+ ## 配置文件 ~/.lark-claudecode-bridge/config.yaml
75
+
76
+ `lcb setup` 会引导生成,也可手动编辑(或用 `lcb app add / lcb ws add / remove` 增量维护,保留注释)。字段说明见仓库内 `config.example.yaml`:
77
+
78
+ ```yaml
79
+ apps: # 多机器人:每个应用一条长连接
80
+ - name: 主力助手 # 显示名,缺省取 app_id
81
+ app_id: cli_xxxx # 飞书开放平台 → 凭证与基础信息
82
+ app_secret: xxxx
83
+ # domain: lark # 国际版 Lark 才需要
84
+ # default_workspace: demo # 该机器人的默认工作区
85
+ # concurrency: 2 # 该机器人的并发上限
86
+ # append_system_prompt: '你是我的素材收集助手' # 人格补充
87
+ # env: # per-app 环境变量(不同机器人用不同模型端点/密钥)
88
+ # ANTHROPIC_BASE_URL: https://your-endpoint/
89
+ workspaces: # 工作区白名单(列表全局共享;「当前用哪个」per-app 隔离)
90
+ - name: demo
91
+ path: F:\workspace\demo
92
+ defaults:
93
+ workspace: demo
94
+ concurrency: 3 # 通道间并发上限(未单独配置的 app 沿用)
95
+ # transcripts: # 对话落盘清理策略;缺省 = 永久保留
96
+ # retention_days: 90
97
+ ```
98
+
99
+ ### 多机器人隔离语义(重要)
100
+
101
+ - **会话池**:每个机器人一份 `sessions.<app_id>.json`,历史会话绝不共享,`/resume` 只见自己的
102
+ - **Claude Code 实例**:每个机器人一个独立的 `claude/<app_id>/` 数据目录(会话存储、用户设置、插件、登录态各自一套);也可用 `claude_config_dir` 显式指定
103
+ - **模型凭证**:`env` 可按机器人覆盖 `ANTHROPIC_BASE_URL` / `ANTHROPIC_API_KEY` / `ANTHROPIC_MODEL` 等(如对话机器人用高端模型、素材收集用便宜端点)
104
+ - **并发**:按机器人独立限额(缺省沿用全局 `concurrency`);同时与多个机器人对话互不排队
105
+ - **升级兼容**:旧版 `feishu:` 单应用配置无需改动即可启动(自动归一化,旧会话原样可 `/resume`);首次 `lcb app add` 会把旧配置原地转为 `apps:` 格式,**转换后新增的机器人请追加在列表后面**(旧数据归属第一个应用)
106
+
107
+ ### 对话落盘
108
+
109
+ 每轮任务的完整对话(你的消息、Claude 回复、工具调用、最终结果)以 JSONL 追加到 `~/.lark-claudecode-bridge/transcripts/<app_id>/<chat_id>/<日期>.jsonl`,按天分文件,为后续知识库挖掘(如写入 Notion)打底。不想要可设 `transcripts.retention_days` 定期清理;写失败只打警告,不影响任务执行。
110
+
111
+ ## ⚠️ 安全须知(必读)
112
+
113
+ 本机 Claude Code 是共用资源:白名单用户可通过确认卡片让它在你电脑执行任意命令。
114
+ 请只批准信任的人;工作区白名单、用户白名单、写操作确认三道闸不要关闭。
115
+
116
+ **隐私提醒**:对话全文(含代码、文件路径)明文落盘于 `~/.lark-claudecode-bridge/transcripts/`;`config.yaml` 中的 `app_secret` 与 `apps[].env` 值同样为明文。请自行控制该目录与文件的访问权限,并按需配置 `transcripts.retention_days` 保留期。
117
+
118
+ ## 常驻运行
119
+
120
+ - **Windows**:任务计划程序建「开机时启动」任务,程序指向下面的 `windows-start.bat`(先放到固定位置,如 `C:\tools\lcb\windows-start.bat`)
121
+ - **macOS**:launchd——把 `com.lark-claudecode-bridge.plist` 放到 `~/Library/LaunchAgents/`,然后 `launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.lark-claudecode-bridge.plist`
122
+ - **Linux**:systemd user 单元——把 `lark-claudecode-bridge.service` 放到 `~/.config/systemd/user/`,然后 `systemctl --user daemon-reload && systemctl --user enable --now lark-claudecode-bridge`
123
+
124
+ 模板全文(npm 包内 `deploy/` 目录,或仓库 [deploy/](deploy/)):
125
+
126
+ **deploy/windows-start.bat**
127
+
128
+ ```bat
129
+ @echo off
130
+ lcb start >> "%USERPROFILE%\.lark-claudecode-bridge\bridge.log" 2>&1
131
+ ```
132
+
133
+ **deploy/com.lark-claudecode-bridge.plist**
134
+
135
+ ```xml
136
+ <?xml version="1.0" encoding="UTF-8"?>
137
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
138
+ <plist version="1.0"><dict>
139
+ <key>Label</key><string>com.lark-claudecode-bridge</string>
140
+ <key>ProgramArguments</key><array><string>/usr/local/bin/lcb</string><string>start</string></array>
141
+ <key>RunAtLoad</key><true/>
142
+ <key>KeepAlive</key><true/>
143
+ <key>StandardOutPath</key><string>/tmp/lark-claudecode-bridge.log</string>
144
+ </dict></plist>
145
+ ```
146
+
147
+ **deploy/lark-claudecode-bridge.service**
148
+
149
+ ```ini
150
+ [Unit]
151
+ Description=lark-claudecode-bridge
152
+ After=network-online.target
153
+
154
+ [Service]
155
+ ExecStart=/usr/bin/env lcb start
156
+ Restart=always
157
+ RestartSec=10
158
+
159
+ [Install]
160
+ WantedBy=default.target
161
+ ```
162
+
163
+ ## 已知限制
164
+
165
+ 1. **Linux 上 >10 文件不打 zip**:文件打包用 `tar -a`(按扩展名选容器),Windows 10+ / macOS 自带的 bsdtar 支持 zip 容器;Linux 的 GNU tar 不支持创建 zip,会自动退化为逐个上传文件(功能不丢,只是消息条数多)。
166
+ 2. **access.json 多进程并发写为 last-writer-wins**:`lcb pair` 独立进程批准后,运行中的桥会在下一条消息到达时重读 `~/.lark-claudecode-bridge/access.json`(无需重启、不会重复索要配对码)。剩余风险仅在极短窗口:两个进程**恰好同时**写盘时后写者覆盖先写者(如一侧刚批准的用户在另一侧写回后丢失),正常使用几乎不会触发;如遇丢失,重新配对即可。
167
+ 3. **多机器人总并发 = 各应用并发之和**:每个机器人独立限额(互不排队),N 个机器人同时满载时本机会同时跑 Σ(concurrency) 个 Claude Code 子进程,机器吃紧可按 app 调低。
168
+ 4. **多机器人同群的 @ 识别**:依赖每个机器人各自的 open_id 精确匹配;若某机器人的 open_id 拉取失败,多应用部署下该机器人的**群聊消息会被丢弃**(宁丢不猜,防止同群消息触发两个机器人重复执行)——私聊不受影响。
169
+ 5. **旧数据迁移归属**:升级多应用后,旧 `sessions.json` 归属 `apps` 列表的第一个应用;新增机器人请追加在列表末尾,否则历史会话会挂错机器人。
170
+ 6. **飞书 SDK 对非法 app_id 静默失败**:`ws.start()` 对形状不合法的 app_id 只打日志不报错,启动后请确认每条「✅ <应用名> 长连接已启动」状态行都出现了。
171
+
172
+ ## 开发
173
+
174
+ ```bash
175
+ npm install
176
+ npm test # vitest 全量单测
177
+ npm run build # tsc → dist/
178
+ node dist/bin/lcb.js version
179
+ ```
180
+
181
+ ## License
182
+
183
+ MIT
@@ -0,0 +1,42 @@
1
+ # 多机器人:apps 数组,每个应用一条独立长连接 + 独立会话池 + 独立 Claude Code 实例。
2
+ # 升级提示:旧版单应用 feishu: 格式仍可直接使用(启动时自动归一化,首次 lcb app add 时原地转格式)。
3
+ apps:
4
+ - name: 主力助手 # 显示名(日志前缀 / /status / lcb app list),缺省取 app_id
5
+ app_id: cli_xxxx # 飞书开放平台 → 凭证与基础信息
6
+ app_secret: xxxx
7
+ # domain: lark # 国际版 Lark 才需要
8
+ # default_workspace: demo # 该机器人的默认工作区(须在 workspaces 列表内),缺省用全局 defaults.workspace
9
+ # concurrency: 2 # 该机器人的并发上限(1-100),缺省用全局 concurrency
10
+ # claude_config_dir: 'C:\Users\you\.claude' # Claude Code 数据目录(会话存储/设置/插件)
11
+ # 缺省为 ~/.lark-claudecode-bridge/claude/<app_id>/——每个机器人一套;
12
+ # 旧 feishu 格式迁移的应用自动沿用系统默认 ~/.claude(保存量会话)
13
+ # append_system_prompt: '你是我的素材收集助手' # 可选人格补充(直通 Claude Code)
14
+ # env: # per-app 环境变量:不同机器人可用不同模型凭证
15
+ # ANTHROPIC_BASE_URL: https://your-endpoint/
16
+ # ANTHROPIC_MODEL: glm-4.6
17
+ # triggers: # 触发词映射:命中即把消息改写为 rewrite 再入队(按序首个命中生效)。
18
+ # # SDK 单次会话不展开斜杠命令,飞书里触发技能必须经此改写;
19
+ # # match 以 / 开头 = 首 token 精确匹配(/produce 命中、/produced 不命中),
20
+ # # 否则 = 整条消息包含该关键词即命中;{text}=原文全文,{args}=去掉首 token 的剩余参数。
21
+ # # /stop /new /help 等本地命令永远优先,不会被劫持。改动写盘后下一条消息即热生效。
22
+ # - match: /produce
23
+ # rewrite: '调用 content-producer 插件的主技能(skills/content-producer/SKILL.md)执行内容生产流程,严格遵循其编排。任务参数:{args}'
24
+ # - match: 写文章
25
+ # rewrite: '调用 content-producer 插件的主技能(skills/content-producer/SKILL.md)执行内容生产流程,严格遵循其编排。任务描述:{text}'
26
+ # plugins: # 显式加载的本地插件(path 指含 .claude-plugin/plugin.json 的目录)。
27
+ # # 新机器人的 claude_config_dir 独立于 ~/.claude,已安装插件对其不可见——
28
+ # # 直指源码目录改 SKILL.md 即生效,无需重装;插件的 MCP/hooks 随之加载,
29
+ # # MCP 凭证(IMAGE_GEN_API_KEY 等)经上方 env 传入。
30
+ # - name: content-producer
31
+ # path: F:/workspace/plugins/content-producer/plugin
32
+ # - name: 素材收集 # 再添一个机器人:复制上面的结构即可
33
+ # app_id: cli_yyyy
34
+ # app_secret: yyyy
35
+ workspaces: # 工作区白名单:名字 + 路径(所有机器人共享列表,当前使用哪个 per-app 隔离)
36
+ - name: demo
37
+ path: F:\workspace\demo
38
+ defaults:
39
+ workspace: demo
40
+ concurrency: 3 # 全局并发上限(未单独配置的 app 沿用此值)
41
+ # transcripts: # 对话落盘清理策略;缺省 = 永久保留
42
+ # retention_days: 90
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0"><dict>
4
+ <key>Label</key><string>com.lark-claudecode-bridge</string>
5
+ <key>ProgramArguments</key><array><string>/usr/local/bin/lcb</string><string>start</string></array>
6
+ <key>RunAtLoad</key><true/>
7
+ <key>KeepAlive</key><true/>
8
+ <key>StandardOutPath</key><string>/tmp/lark-claudecode-bridge.log</string>
9
+ </dict></plist>
@@ -0,0 +1,11 @@
1
+ [Unit]
2
+ Description=lark-claudecode-bridge
3
+ After=network-online.target
4
+
5
+ [Service]
6
+ ExecStart=/usr/bin/env lcb start
7
+ Restart=always
8
+ RestartSec=10
9
+
10
+ [Install]
11
+ WantedBy=default.target
@@ -0,0 +1,2 @@
1
+ @echo off
2
+ lcb start >> "%USERPROFILE%\.lark-claudecode-bridge\bridge.log" 2>&1
@@ -0,0 +1,43 @@
1
+ export interface AccessStoreData {
2
+ users: Record<string, {
3
+ name: string;
4
+ role: 'admin' | 'member';
5
+ pairedAt: string;
6
+ }>;
7
+ pending: Record<string, {
8
+ userId: string;
9
+ name: string;
10
+ code: string;
11
+ expiresAt: number;
12
+ }>;
13
+ }
14
+ export declare class AccessControl {
15
+ private storePath;
16
+ private data;
17
+ constructor(storePath: string, data?: AccessStoreData);
18
+ static load(storePath: string): AccessControl;
19
+ /**
20
+ * 重读 store 文件并整体替换内存态:`lcb pair` / 运行终端等独立路径批准写盘后,
21
+ * 桥内长存实例经 reload 即可感知(否则 isAllowed 查旧内存 → 反复发配对码 →
22
+ * beginPairing 的整盘覆写把刚批准的 users 抹掉,形成死循环)。
23
+ * 读盘失败(文件不存在/损坏)保持内存不动——首次配对前 store 尚未落盘属正常。
24
+ */
25
+ reload(): void;
26
+ private save;
27
+ isAllowed(userId: string): boolean;
28
+ isAdmin(userId: string): boolean;
29
+ listPending(): Array<{
30
+ userId: string;
31
+ name: string;
32
+ code: string;
33
+ }>;
34
+ beginPairing(userId: string, name: string): string;
35
+ approvePairing(code: string): {
36
+ ok: boolean;
37
+ userId?: string;
38
+ isFirstAdmin?: boolean;
39
+ error?: string;
40
+ };
41
+ rejectPairing(code: string): boolean;
42
+ private evict;
43
+ }
@@ -0,0 +1,88 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { randomInt } from 'node:crypto';
4
+ const TTL_MS = 15 * 60 * 1000;
5
+ export class AccessControl {
6
+ storePath;
7
+ data;
8
+ constructor(storePath, data) {
9
+ this.storePath = storePath;
10
+ this.data = data ?? { users: {}, pending: {} };
11
+ }
12
+ static load(storePath) {
13
+ try {
14
+ const raw = JSON.parse(readFileSync(storePath, 'utf8'));
15
+ return new AccessControl(storePath, { users: raw.users ?? {}, pending: raw.pending ?? {} });
16
+ }
17
+ catch {
18
+ return new AccessControl(storePath);
19
+ }
20
+ }
21
+ /**
22
+ * 重读 store 文件并整体替换内存态:`lcb pair` / 运行终端等独立路径批准写盘后,
23
+ * 桥内长存实例经 reload 即可感知(否则 isAllowed 查旧内存 → 反复发配对码 →
24
+ * beginPairing 的整盘覆写把刚批准的 users 抹掉,形成死循环)。
25
+ * 读盘失败(文件不存在/损坏)保持内存不动——首次配对前 store 尚未落盘属正常。
26
+ */
27
+ reload() {
28
+ try {
29
+ const raw = JSON.parse(readFileSync(this.storePath, 'utf8'));
30
+ this.data = { users: raw.users ?? {}, pending: raw.pending ?? {} };
31
+ }
32
+ catch {
33
+ // 忽略:以上一次内存态继续服务
34
+ }
35
+ }
36
+ save() {
37
+ mkdirSync(dirname(this.storePath), { recursive: true });
38
+ writeFileSync(this.storePath, JSON.stringify(this.data, null, 2), 'utf8');
39
+ }
40
+ isAllowed(userId) {
41
+ return Boolean(this.data.users[userId]);
42
+ }
43
+ isAdmin(userId) {
44
+ return this.data.users[userId]?.role === 'admin';
45
+ }
46
+ listPending() {
47
+ this.evict();
48
+ return Object.values(this.data.pending).map(({ userId, name, code }) => ({ userId, name, code }));
49
+ }
50
+ beginPairing(userId, name) {
51
+ this.evict();
52
+ const code = String(randomInt(0, 1_000_000)).padStart(6, '0');
53
+ this.data.pending[userId] = { userId, name, code, expiresAt: Date.now() + TTL_MS };
54
+ this.save();
55
+ return code;
56
+ }
57
+ approvePairing(code) {
58
+ this.evict();
59
+ const entry = Object.values(this.data.pending).find((p) => p.code === code);
60
+ if (!entry)
61
+ return { ok: false, error: '配对码无效或已过期' };
62
+ const isFirstAdmin = Object.keys(this.data.users).length === 0;
63
+ this.data.users[entry.userId] = {
64
+ name: entry.name,
65
+ role: isFirstAdmin ? 'admin' : 'member',
66
+ pairedAt: new Date().toISOString(),
67
+ };
68
+ delete this.data.pending[entry.userId];
69
+ this.save();
70
+ return { ok: true, userId: entry.userId, isFirstAdmin };
71
+ }
72
+ rejectPairing(code) {
73
+ const entry = Object.values(this.data.pending).find((p) => p.code === code);
74
+ if (!entry)
75
+ return false;
76
+ delete this.data.pending[entry.userId];
77
+ this.save();
78
+ return true;
79
+ }
80
+ evict() {
81
+ const now = Date.now();
82
+ for (const [uid, p] of Object.entries(this.data.pending)) {
83
+ if (p.expiresAt < now)
84
+ delete this.data.pending[uid];
85
+ }
86
+ }
87
+ }
88
+ //# sourceMappingURL=access-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-control.js","sourceRoot":"","sources":["../../src/access/access-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAO9B,MAAM,OAAO,aAAa;IAEJ,SAAS;IADrB,IAAI,CAAkB;IAC9B,YAAoB,SAAiB,EAAE,IAAsB;yBAAzC,SAAS;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACjD,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,SAAiB;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAoB,CAAC;YAC3E,OAAO,IAAI,aAAa,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9F,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,aAAa,CAAC,SAAS,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IACD;;;;;OAKG;IACH,MAAM;QACJ,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAoB,CAAC;YAChF,IAAI,CAAC,IAAI,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;QACnB,CAAC;IACH,CAAC;IACO,IAAI;QACV,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5E,CAAC;IACD,SAAS,CAAC,MAAc;QACtB,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,CAAC,MAAc;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,KAAK,OAAO,CAAC;IACnD,CAAC;IACD,WAAW;QACT,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACpG,CAAC;IACD,YAAY,CAAC,MAAc,EAAE,IAAY;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC9D,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;QACnF,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IACD,cAAc,CAAC,IAAY;QACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC5E,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QACrD,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG;YAC9B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;YACvC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACnC,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC;IAC1D,CAAC;IACD,aAAa,CAAC,IAAY;QACxB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC5E,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IACO,KAAK;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ // lcb 命令行入口:setup / start / pair / app / ws / version
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { readFileSync, writeFileSync } from 'node:fs';
5
+ import { runSetup, APP_ID_RE } from '../cli/setup-wizard.js';
6
+ import { addWorkspace, removeWorkspace } from '../cli/ws-manager.js';
7
+ import { addApp, removeApp, listApps } from '../cli/app-manager.js';
8
+ import { asker } from '../cli/asker.js';
9
+ import { approvePairingLine } from '../cli/stdin-pairing.js';
10
+ import { startBridge } from '../index.js';
11
+ import { loadConfig, CONFIG_PATH, CONFIG_DIR } from '../config.js';
12
+ import { AccessControl } from '../access/access-control.js';
13
+ import { VERSION } from '../version.js';
14
+ import { join } from 'node:path';
15
+ function showPending(access) {
16
+ const pending = access.listPending();
17
+ if (pending.length)
18
+ console.log('待配对:', pending.map((p) => `${p.code}(${p.userId})`).join('、'));
19
+ }
20
+ async function main() {
21
+ const [cmd, ...args] = process.argv.slice(2);
22
+ switch (cmd) {
23
+ case 'setup':
24
+ await runSetup(CONFIG_PATH);
25
+ break;
26
+ case 'start': {
27
+ const config = loadConfig(CONFIG_PATH); // 启动前校验:文件缺失/字段非法立即报错退出
28
+ // Task 6 遗留建议:App ID 形状不符仅打警告,不阻止启动(形状非强约束,避免误杀合法 ID)
29
+ for (const app of config.apps) {
30
+ if (!APP_ID_RE.test(app.appId)) {
31
+ console.warn(`⚠️ 应用 ${app.name} 的 App ID "${app.appId}" 不符合常见形状 cli_ + 16 位十六进制,启动继续;若后续连接失败请先核对`);
32
+ }
33
+ }
34
+ await startBridge(CONFIG_PATH);
35
+ // 前台监听 stdin:管理员可直接在运行终端输入配对码批准。
36
+ // 现读现批(每次 load 新实例再 approve):桥运行中 beginPairing 写入的新 pending
37
+ // 不在启动快照里——复用启动实例必然「配对码无效」,且其 save() 整盘覆写会
38
+ // 抹掉桥内新 pending。与独立进程 lcb pair 走同一条现读路径
39
+ const rl = createInterface({ input: process.stdin });
40
+ rl.on('line', (line) => approvePairingLine(line, join(CONFIG_DIR, 'access.json')));
41
+ break;
42
+ }
43
+ case 'pair': {
44
+ const access = AccessControl.load(join(CONFIG_DIR, 'access.json'));
45
+ const code = args[0];
46
+ if (!/^\d{6}$/.test(code ?? '')) {
47
+ console.log('用法:lcb pair <6位配对码>');
48
+ showPending(access);
49
+ process.exit(1);
50
+ }
51
+ const r = access.approvePairing(code);
52
+ console.log(r.ok ? `✅ 已批准 ${r.userId}${r.isFirstAdmin ? '(admin)' : ''}` : `❌ ${r.error}`);
53
+ if (!r.ok)
54
+ process.exit(1);
55
+ console.log('注:若桥接器正在运行,需重启或改在运行终端输入配对码');
56
+ break;
57
+ }
58
+ case 'ws': {
59
+ // 先整体校验再增量修改:带着非法配置做 add/remove 会把错误掩埋进写回结果
60
+ let config;
61
+ try {
62
+ config = loadConfig(CONFIG_PATH);
63
+ }
64
+ catch (e) {
65
+ console.error(`❌ ${e instanceof Error ? e.message : String(e)}`);
66
+ process.exit(1);
67
+ }
68
+ const sub = args[0];
69
+ if (sub === 'list') {
70
+ console.log(config.workspaces
71
+ .map((w) => `${w.name === config.defaults.workspace ? '*' : ' '} ${w.name} ${w.path}`)
72
+ .join('\n'));
73
+ break;
74
+ }
75
+ const raw = readFileSync(CONFIG_PATH, 'utf8'); // 保留注释的增量改写基于原文,而非重序列化内存对象
76
+ if (sub === 'add') {
77
+ const [name, path] = args.slice(1);
78
+ if (!name || !path) {
79
+ console.log('用法:lcb ws add <名字> <路径>(路径需已存在,含空格需加引号)');
80
+ process.exit(1);
81
+ }
82
+ const r = addWorkspace(raw, name, path);
83
+ if (!r.ok) {
84
+ console.error(`❌ ${r.error}`);
85
+ process.exit(1);
86
+ }
87
+ writeFileSync(CONFIG_PATH, r.yaml, 'utf8');
88
+ console.log(`✅ 已添加工作区 ${name}(${path})`);
89
+ console.log('桥接器若正在运行,下一条消息时自动生效,无需重启');
90
+ }
91
+ else if (sub === 'remove') {
92
+ const name = args[1];
93
+ if (!name) {
94
+ console.log('用法:lcb ws remove <名字>');
95
+ process.exit(1);
96
+ }
97
+ const r = removeWorkspace(raw, name);
98
+ if (!r.ok) {
99
+ console.error(`❌ ${r.error}`);
100
+ process.exit(1);
101
+ }
102
+ writeFileSync(CONFIG_PATH, r.yaml, 'utf8');
103
+ console.log(`✅ 已删除工作区 ${name}`);
104
+ }
105
+ else {
106
+ console.log('用法:lcb ws add <名字> <路径> | lcb ws remove <名字> | lcb ws list');
107
+ }
108
+ break;
109
+ }
110
+ case 'version':
111
+ console.log(VERSION);
112
+ break;
113
+ case 'app': {
114
+ const sub = args[0];
115
+ const raw = readFileSync(CONFIG_PATH, 'utf8'); // 增量改写基于原文,保留注释
116
+ if (sub === 'list') {
117
+ const list = listApps(raw);
118
+ if (!list.length) {
119
+ console.log('暂无应用配置,请先运行 lcb setup');
120
+ break;
121
+ }
122
+ console.log(list.map((a) => `- ${a.name}${a.name !== a.appId ? ` (${a.appId})` : ''}${a.domain === 'lark' ? ' [lark]' : ''}${a.defaultWorkspace ? ` 默认工作区:${a.defaultWorkspace}` : ''}`).join('\n'));
123
+ break;
124
+ }
125
+ if (sub === 'add') {
126
+ const { ask, close } = asker(process.stdin, process.stdout);
127
+ const appId = await ask('App ID (cli_ 开头,形如 cli_aabbccddeeff0011)');
128
+ if (!appId) {
129
+ close();
130
+ console.log('App ID 不能为空,已取消');
131
+ break;
132
+ }
133
+ if (!APP_ID_RE.test(appId))
134
+ console.warn(`⚠️ App ID ${appId} 不符合常见形状 cli_ + 16 位十六进制,请确认(不阻止继续)`);
135
+ const name = await ask('应用名字(显示用,如「素材收集」,回车默认取 App ID)');
136
+ const appSecret = await ask('App Secret');
137
+ close();
138
+ if (!appSecret) {
139
+ console.log('App Secret 不能为空,已取消');
140
+ break;
141
+ }
142
+ const r = addApp(raw, { appId, appSecret, ...(name ? { name } : {}) });
143
+ if (!r.ok) {
144
+ console.error(`❌ ${r.error}`);
145
+ process.exit(1);
146
+ }
147
+ writeFileSync(CONFIG_PATH, r.yaml, 'utf8');
148
+ console.log(`✅ 已添加应用 ${name || appId}(重启桥接器后生效)`);
149
+ break;
150
+ }
151
+ if (sub === 'remove') {
152
+ const target = args[1];
153
+ if (!target) {
154
+ console.log('用法:lcb app remove <名字|app_id>');
155
+ process.exit(1);
156
+ }
157
+ const r = removeApp(raw, target);
158
+ if (!r.ok) {
159
+ console.error(`❌ ${r.error}`);
160
+ process.exit(1);
161
+ }
162
+ writeFileSync(CONFIG_PATH, r.yaml, 'utf8');
163
+ console.log(`✅ 已删除应用 ${target}(重启桥接器后生效)`);
164
+ console.log('注:该应用的会话分片(sessions.<appId>.json)与对话落盘(transcripts/<appId>/)保留未动,确认不再需要时可手工删除');
165
+ break;
166
+ }
167
+ console.log('用法:lcb app list | lcb app add | lcb app remove <名字|app_id>');
168
+ break;
169
+ }
170
+ default:
171
+ console.log(`lcb — 飞书 ↔ Claude Code 桥接器 v${VERSION}
172
+
173
+ 用法:
174
+ lcb setup 引导式配置(支持多个机器人应用)
175
+ lcb start 启动桥接器(前台,所有应用各建一条长连接)
176
+ lcb pair <code> 批准配对码
177
+ lcb app list 列出机器人应用
178
+ lcb app add 添加机器人应用(重启后生效)
179
+ lcb app remove <名> 删除机器人应用
180
+ lcb ws add <名> <路径> 添加工作区(运行中热生效)
181
+ lcb ws remove <名> 删除工作区
182
+ lcb ws list 列出工作区(* 为默认)
183
+ lcb version 版本`);
184
+ break;
185
+ }
186
+ }
187
+ main().catch((e) => {
188
+ console.error(`❌ ${e instanceof Error ? e.message : String(e)}`);
189
+ process.exit(1);
190
+ });
191
+ //# sourceMappingURL=lcb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lcb.js","sourceRoot":"","sources":["../../src/bin/lcb.ts"],"names":[],"mappings":"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"}