@jesonliu/lark-claudecode-bridge 0.18.1 → 0.19.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 (47) hide show
  1. package/README.md +5 -3
  2. package/assets/web/css/main.css +2 -0
  3. package/assets/web/js/main.js +3 -1
  4. package/assets/web/js/pages/mcp.js +229 -0
  5. package/assets/web/js/pages/skills.js +169 -0
  6. package/assets/web/js/pages/workspaces.js +23 -2
  7. package/dist/bin/lcb.js +5 -0
  8. package/dist/bin/lcb.js.map +1 -1
  9. package/dist/config.js +33 -7
  10. package/dist/config.js.map +1 -1
  11. package/dist/executor/notify-server.d.ts +13 -0
  12. package/dist/executor/notify-server.js +60 -4
  13. package/dist/executor/notify-server.js.map +1 -1
  14. package/dist/executor/plugin-discovery.d.ts +6 -0
  15. package/dist/executor/plugin-discovery.js +14 -0
  16. package/dist/executor/plugin-discovery.js.map +1 -1
  17. package/dist/gateway/card-builder.d.ts +24 -26
  18. package/dist/gateway/card-builder.js +79 -109
  19. package/dist/gateway/card-builder.js.map +1 -1
  20. package/dist/gateway/feishu-gateway.d.ts +10 -0
  21. package/dist/gateway/feishu-gateway.js +48 -0
  22. package/dist/gateway/feishu-gateway.js.map +1 -1
  23. package/dist/gateway/progress-card.d.ts +26 -0
  24. package/dist/gateway/progress-card.js +68 -4
  25. package/dist/gateway/progress-card.js.map +1 -1
  26. package/dist/index.d.ts +7 -1
  27. package/dist/index.js +174 -71
  28. package/dist/index.js.map +1 -1
  29. package/dist/notify-sop.d.ts +1 -0
  30. package/dist/notify-sop.js +30 -0
  31. package/dist/notify-sop.js.map +1 -0
  32. package/dist/types.d.ts +14 -2
  33. package/dist/util/log-tee.d.ts +4 -0
  34. package/dist/util/log-tee.js +99 -0
  35. package/dist/util/log-tee.js.map +1 -0
  36. package/dist/util/runtime-dirs.d.ts +4 -0
  37. package/dist/util/runtime-dirs.js +27 -0
  38. package/dist/util/runtime-dirs.js.map +1 -0
  39. package/dist/web/lifecycle.d.ts +4 -1
  40. package/dist/web/lifecycle.js +11 -25
  41. package/dist/web/lifecycle.js.map +1 -1
  42. package/dist/web/server.js +311 -11
  43. package/dist/web/server.js.map +1 -1
  44. package/dist/web/skills-mcp-api.d.ts +113 -0
  45. package/dist/web/skills-mcp-api.js +312 -0
  46. package/dist/web/skills-mcp-api.js.map +1 -0
  47. package/package.json +3 -1
package/README.md CHANGED
@@ -23,6 +23,8 @@
23
23
  - 多工作区切换(/ws)、会话管理(/new /resume)、/stop 打断、模型切换(/model)、厂商档案切换(/model-profile)、加载清单查看(/skills /plugins /mcp)、插件管理(/plugin)
24
24
  - **后台子代理续跑**:主 Agent 派发的后台子代理在主回复结束后继续执行,完成后自动唤醒主循环汇总结果(进度卡可见「等待后台任务」与子代理输出)
25
25
  - **对话内容落盘**:用户消息与 Claude 回复全文存为 JSONL(`transcripts/`,为后续知识库挖掘打底;可选保留期)
26
+ - **回复链上游自动拼入 prompt**:在飞书里「回复」某条消息再发新指令,上游最多 3 层消息文本会作为引用附在新消息前部一起发给 Claude(需 `im:message` 读取权限;失败降级只发当前消息)
27
+ - **Skills / MCP 可视化管理页**:配置页新增「Skills」「MCP」两页——Skills 三来源(用户级·本机 / 用户级·bridge / 项目级·工作区) + zip 导入;MCP 三来源 + 命令方式(`claude mcp add`)/ JSON 配置 + 状态探测 + 抽屉查看 env 引用展开当前值。页面添加的 MCP 存 `~/.lark-claudecode-bridge/mcp/servers.json`,任务级热生效(无须重启)
26
28
  - 通道并发(默认 3),通道内串行
27
29
 
28
30
  ## 前置条件
@@ -46,7 +48,7 @@ lcb start
46
48
  ## 飞书应用配置(图文)
47
49
 
48
50
  1. https://open.feishu.cn → 创建企业自建应用 → 添加「机器人」能力
49
- 2. 权限管理开通:`im:message`、`im:message:send_as_bot`、`im:resource`(**接收用户图片时下载消息资源用,不开则图片任务会提示下载失败**)、`contact:user.base:readonly`、`application:app_slash_command:write` / `application:app_slash_command:read`(斜杠命令一键同步用,见下)
51
+ 2. 权限管理开通:`im:message`(**含读取单条消息:回复链上游内容拼接用,不开则回复消息时降级为只发当前消息**)、`im:message:send_as_bot`、`im:resource`(**接收用户图片时下载消息资源用,不开则图片任务会提示下载失败**)、`contact:user.base:readonly`、`application:app_slash_command:write` / `application:app_slash_command:read`(斜杠命令一键同步用,见下)
50
52
  3. 事件与回调 → 事件配置 → 订阅方式选「使用长连接接收事件」→ 添加 `im.message.receive_v1`
51
53
  4. 事件与回调 → 回调配置 → 订阅方式选「使用长连接接收回调」→「已订阅的回调」点「添加回调」,添加「卡片回传交互」(`card.action.trigger`)
52
54
  5. 凭证与基础信息 → 复制 App ID / App Secret
@@ -88,7 +90,7 @@ lcb start
88
90
  配置页「概览」支持托管桥接器进程与自更新(源码 tsx 运行模式下自动降级为手动指引):
89
91
 
90
92
  - **启停/重启**:概览「运行状态」卡显示桥接器进程状态(PID),可一键启动(后台守护进程)/ 停止 / 重启。`lcb start` 内嵌页面停止/重启时页面随进程短暂失联后自动恢复;`lcb ui` 独立页面则跨进程操作(Windows 下停止为硬终止,会话逐消息落盘不受影响)。
91
- - **后台运行日志**:经页面启动/重启的桥接器,输出落 `~/.lark-claudecode-bridge/bridge.log`(超 5MB 自动截断);进程 PID 记录于同目录 `bridge.pid`(进程消亡后自动清理)。
93
+ - **后台运行日志**:桥接器输出按天落 `~/.lark-claudecode-bridge/logs/bridge-YYYY-MM-DD.log`(自动跨天切换,保留 14 天);进程 PID 记录于 `~/.lark-claudecode-bridge/bridge.pid`(进程消亡后自动清理)。
92
94
  - **版本更新**:概览「版本与更新」卡自动对比 npm registry(跟随本机 `.npmrc` 镜像配置)与当前版本;有新版时一键更新(`npm install -g`)并自动重启生效。
93
95
 
94
96
  ## 命令速查(飞书里发给机器人)
@@ -291,7 +293,7 @@ WantedBy=default.target
291
293
  8. **plan 卡片的「按意见修改」依赖飞书卡片输入框回传**:修改意见经卡片 input 组件随按钮回调传回;若个别客户端版本不回传输入值,点「按意见修改」会提示先填写意见——此时可改用「放弃计划」后在会话里直接发修改要求重新起任务。
292
294
  9. **code-dev 工作区的收尾 diff 基于 git**:`type: code-dev` 的工作区需要是 git 仓库(含未提交改动即可,无需 commit);非 git 仓库自动回退为旧的整文件上传行为。untracked 新文件按全新增 diff 展示(目录级 untracked 与超过 20 个的 untracked 文件不展开)。
293
295
  10. **入站图片不清理**:用户发送的图片落盘 `~/.lark-claudecode-bridge/inbox/` 后不会自动删除(供会话内多次查看),长期使用可手动清理;Claude 是否能「看懂」图片取决于当前模型是否多模态(非多模态模型可配置识图 MCP 兜底)。富文本(post)中的超链接以 `[文字](链接)` 形式拍平进文本,@用户 被移除。
294
- 11. **短回复不再单独发结果消息**:回复不超过进度卡正文上限(1200 字)时,结果就展示在进度卡终态里(避免同内容两条消息);更长回复仍会单独发一条结果消息(进度卡只保留尾部)。
296
+ 11. **短回复不再单独发结果消息**:回复不超过进度卡终态上限(400 字)时,结果就展示在进度卡终态里(避免同内容两条消息);更长回复仍会单独发一条结果消息(进度卡只保留尾部)。运行中的进度卡**不展示**过程文本与思考内容(主卡只留状态 / 当前工具 / 子代理 / 确认区 / 计时等关键信息)。
295
297
  12. **Web 配置页改 apps/workspaces 段会丢段内手写注释**:页面按整段替换写回(值未变的段落跳过重写、注释保留;`lcb ws add` 等增量命令不受影响)。手工注释建议写在段外或段头。
296
298
  13. **config.yaml 并发写**:配置页写盘为原子替换,但与 `lcb ws add` / `lcb app add` 等独立进程命令同时操作存在读-改-写窗口,请避免同时修改。
297
299
  14. **配置页默认仅本机可访问**(127.0.0.1);改 `server.host` 放开到局域网意味着页面可读写全部凭证,请仅在可信网络使用。
@@ -31,6 +31,8 @@
31
31
  input:focus, select:focus, textarea:focus { outline: 2px solid #c9d8ff; border-color: var(--primary); }
32
32
  textarea { font-family: Consolas, monospace; font-size: 12.5px; min-height: 90px; resize: vertical; }
33
33
  .row { display: flex; gap: 12px; } .row > * { flex: 1; min-width: 0; }
34
+ /* 两列布局:配置项多的区块(如全局默认配置)一行 2 个,避免 4 列挤压(.row 的 flex 平分在 4 项时过窄) */
35
+ .row-2col { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; }
34
36
  /* .row 内按钮按内容宽:内联 flex:0 展开为 0 1 0% 会把按钮压到单字宽(一字一行),一律走类规则 */
35
37
  .row > .btn, .row > .btn-wrap { flex: 0 0 auto; align-self: end; }
36
38
  .btn-wrap { display: flex; }
@@ -11,8 +11,10 @@ import { page as pClaude } from './pages/claude.js';
11
11
  import { page as pPermissions } from './pages/permissions.js';
12
12
  import { page as pSlash } from './pages/slash.js';
13
13
  import { page as pPlugins } from './pages/plugins.js';
14
+ import { page as pSkills } from './pages/skills.js';
15
+ import { page as pMcp } from './pages/mcp.js';
14
16
 
15
- const PAGES = [pOverview, pApps, pWorkspaces, pClaude, pPermissions, pSlash, pPlugins];
17
+ const PAGES = [pOverview, pApps, pWorkspaces, pClaude, pPermissions, pSlash, pPlugins, pSkills, pMcp];
16
18
  const pageById = new Map(PAGES.map((p) => [p.id, p]));
17
19
  const DEFAULT_TAB = 'overview';
18
20
 
@@ -0,0 +1,229 @@
1
+ // ============ MCP 管理页(#12)============
2
+ // 后端:GET /api/mcp · POST /api/mcp/action(add/update/remove)· POST /api/mcp/check(状态探测)
3
+ // 四来源:bridge 自管(mcp/servers.json,页面管理 + 注入生效)/ 用户级·本机(~/.claude.json,展示+可删)/
4
+ // 项目级(工作区 .mcp.json,只读)/ 已装插件内(<plugin-installPath>/.mcp.json,只读;未启用时标「(未启用)」)。
5
+ import { $, esc, toast, api } from '../core.js';
6
+ import { openDrawer, closeDrawer } from '../ui.js';
7
+
8
+ const SOURCE_TAG = {
9
+ bridge: 'bridge · 可管理',
10
+ 'machine-user': '用户级 · 本机',
11
+ project: '项目级',
12
+ };
13
+ const sourceTag = (s) => {
14
+ if (s.source === 'project') return `项目级 · ${esc(s.workspaceName || '?')}`;
15
+ if (s.source === 'plugin') return `插件 · ${esc(s.pluginName || '?')}${s.pluginEnabled ? '' : '(未启用)'}`;
16
+ return SOURCE_TAG[s.source] || s.source;
17
+ };
18
+ /** 来源是否可删(project / plugin 只读) */
19
+ const isDeletable = (s) => s.source !== 'project' && s.source !== 'plugin';
20
+
21
+ function renderMcp(el) {
22
+ el.innerHTML = `
23
+ <div class="card">
24
+ <h3>MCP Servers</h3>
25
+ <div class="desc">stdio 走 command + args(本地进程),http/sse 走 url(远程服务)。页面添加的配置存 bridge 自管目录(见下方路径),运行中的任务即时生效;本机 ~/.claude.json 与工作区 .mcp.json 为只读来源(前者可删);已装插件内 .mcp.json 或 plugin.json 内 mcpServers 字段只读展示。</div>
26
+ <div style="display:flex;gap:8px;justify-content:flex-end;margin-bottom:10px">
27
+ <button class="btn sm" id="mcpRefresh">↻ 刷新</button>
28
+ <button class="btn sm" id="mcpAdd">+ 添加 MCP</button>
29
+ </div>
30
+ <input type="search" id="mcpSearch" placeholder="🔍 按名字 / 来源 / 类型 / URL 过滤…" style="width:100%;padding:7px 10px;margin-bottom:8px;border:1px solid var(--border);border-radius:7px">
31
+ <table><thead><tr>
32
+ <th style="width:150px">名字</th><th>类型 / 连接</th><th style="width:130px">来源</th><th style="width:130px">状态</th><th style="width:130px"></th>
33
+ </tr></thead>
34
+ <tbody id="mcpBody"><tr><td colspan="5" class="desc">加载中…</td></tr></tbody>
35
+ </table>
36
+ <div class="desc" id="mcpBridgePath" style="margin-top:10px"></div>
37
+ </div>`;
38
+ $('#mcpRefresh').onclick = () => void loadMcp();
39
+ $('#mcpAdd').onclick = addMcp;
40
+ $('#mcpSearch').oninput = filterMcpRows;
41
+ void loadMcp();
42
+ }
43
+
44
+ /** 搜索过滤:遍历 tr.dataset.searchText 切换 hidden */
45
+ function filterMcpRows() {
46
+ const q = ($('#mcpSearch').value || '').trim().toLowerCase();
47
+ const body = $('#mcpBody');
48
+ if (!body) return;
49
+ let shown = 0;
50
+ for (const tr of body.querySelectorAll('tr[data-search-text]')) {
51
+ const hit = !q || (tr.dataset.searchText || '').includes(q);
52
+ tr.hidden = !hit;
53
+ if (hit) shown++;
54
+ }
55
+ const total = body.querySelectorAll('tr[data-search-text]').length;
56
+ const bp = $('#mcpBridgePath');
57
+ if (bp && bp.dataset.base) bp.textContent = `${bp.dataset.base} · 搜索结果 ${shown}/${total}`;
58
+ }
59
+
60
+ async function loadMcp() {
61
+ const body = $('#mcpBody');
62
+ let data;
63
+ try {
64
+ data = await api('GET', '/api/mcp');
65
+ } catch (e) {
66
+ body.innerHTML = `<tr><td colspan="5" class="desc">加载失败:${esc(e.message)}</td></tr>`;
67
+ return;
68
+ }
69
+ const bp = $('#mcpBridgePath');
70
+ // 诊断行:插件目录扫到 N 个已装插件,其中 M 个含 MCP 定义(plugin.json 内嵌或 .mcp.json 独立)
71
+ const stats = data.pluginStats;
72
+ const diag = stats && stats.total > 0 ? ` · 插件目录扫到 ${stats.total} 个已装插件,其中 ${stats.withMcp} 个含 MCP server 定义` : '';
73
+ if (bp) {
74
+ bp.dataset.base = `bridge 自管配置文件(页面添加落这里):${data.bridgePath || ''}${diag}`;
75
+ bp.textContent = bp.dataset.base;
76
+ }
77
+ const servers = data.servers || [];
78
+ if (servers.length === 0) {
79
+ body.innerHTML = `<tr class="empty-row"><td colspan="5" class="desc">暂无 MCP server。可点「+ 添加 MCP」或在终端用 claude mcp add 添加(本机用户级会出现在此列表)。</td></tr>`;
80
+ return;
81
+ }
82
+ body.innerHTML = '';
83
+ for (const s of servers) {
84
+ const conn = s.config.type === 'http' || s.config.type === 'sse'
85
+ ? `${esc(String(s.config.type))} · ${esc(String(s.config.url ?? ''))}`
86
+ : `${esc(String(s.config.type ?? 'stdio'))} · ${esc(String(s.config.command ?? ''))}${s.config.args ? ' ' + esc(JSON.stringify(s.config.args)) : ''}`;
87
+ const tag = sourceTag(s);
88
+ const tr = document.createElement('tr');
89
+ tr.dataset.searchText = `${s.name} ${sourceTag(s)} ${conn} ${s.config.url ?? ''} ${s.config.command ?? ''}`.toLowerCase();
90
+ tr.innerHTML = `
91
+ <td><strong>${esc(s.name)}</strong></td>
92
+ <td>${conn}</td>
93
+ <td><span class="chip">${tag}</span></td>
94
+ <td class="mcp-status" data-status-for="${esc(s.name)}"><span class="desc">未检测</span></td>
95
+ <td>
96
+ <button class="btn sm" data-view="${esc(s.name)}">查看</button>
97
+ ${isDeletable(s) ? `<button class="btn sm danger" data-del="${esc(s.name)}">删</button>` : '<span class="desc">只读</span>'}
98
+ </td>`;
99
+ tr.querySelector('[data-view]').onclick = () => viewMcp(s);
100
+ const delBtn = tr.querySelector('[data-del]');
101
+ if (delBtn) delBtn.onclick = () => delMcp(s);
102
+ // 状态检测按钮放状态列内(点谁检谁,不自动批量探测)
103
+ const statusCell = tr.querySelector('[data-status-for]');
104
+ statusCell.onclick = () => void checkMcp(s, statusCell);
105
+ statusCell.style.cursor = 'pointer';
106
+ statusCell.title = '点击检测';
107
+ body.appendChild(tr);
108
+ }
109
+ // 保留搜索框当前过滤(如刷新列表后保留关键字)
110
+ filterMcpRows();
111
+ }
112
+
113
+ async function checkMcp(s, cell) {
114
+ cell.innerHTML = '<span class="desc">检测中…</span>';
115
+ try {
116
+ const r = await api('POST', '/api/mcp/check', { name: s.name, source: s.source, workspaceName: s.workspaceName });
117
+ const map = {
118
+ ok: `<span style="color:#16a34a">✔ 正常</span>`,
119
+ failed: `<span style="color:#dc2626">✘ 异常</span>`,
120
+ unreachable: `<span style="color:#d97706">⚠ 不可达</span>`,
121
+ };
122
+ cell.innerHTML = `${map[r.status] || esc(r.status)}<div class="desc" style="font-size:11px;white-space:normal">${esc(r.detail || '')}</div>`;
123
+ } catch (e) {
124
+ cell.innerHTML = `<span style="color:#dc2626">检测失败</span>`;
125
+ toast(e.message, true);
126
+ }
127
+ }
128
+
129
+ function delMcp(s) {
130
+ if (!confirm(`删除 MCP server "${s.name}"(${sourceTag(s)})?`)) return;
131
+ api('POST', '/api/mcp/action', { op: 'remove', name: s.name, source: s.source })
132
+ .then(() => { toast('已删除'); void loadMcp(); })
133
+ .catch((e) => toast(e.message, true));
134
+ }
135
+
136
+ /** 抽屉查看:完整配置 JSON + env 表(${VAR} 引用已展开为当前值;未设置的变量标红) */
137
+ function viewMcp(s) {
138
+ const envKeys = Object.keys(s.config.env || {});
139
+ const envRows = envKeys.length
140
+ ? envKeys.map((k) => {
141
+ const missing = (s.missingEnv || []).includes(k) || ((s.resolvedEnv || {})[k] || '').includes('${');
142
+ const raw = String((s.config.env || {})[k] ?? '');
143
+ const cur = (s.resolvedEnv || {})[k] ?? raw;
144
+ return `<tr><td><code>${esc(k)}</code></td><td><code class="desc">${esc(raw)}</code></td>
145
+ <td>${missing ? `<span style="color:#dc2626">未设置(${esc(cur)})</span>` : `<code>${esc(cur)}</code>`}</td></tr>`;
146
+ }).join('')
147
+ : `<tr><td colspan="3" class="desc">(无 env 配置)</td></tr>`;
148
+ const onlyRead = !isDeletable(s);
149
+ openDrawer({
150
+ title: `MCP · ${s.name}`,
151
+ bodyHtml: `
152
+ <div class="hint" style="margin:0 0 8px">来源:${sourceTag(s)} · 配置文件:<code class="desc">${esc(s.path)}</code></div>
153
+ ${onlyRead ? `<div class="hint" style="margin:0 0 8px;color:#d97706">只读来源——此 MCP 由 ${s.source === 'plugin' ? '插件' : '项目'}自带,编辑请前往 ${s.source === 'plugin' ? '插件目录' : '对应工作区目录'}手动修改。</div>` : ''}
154
+ <label>配置(JSON 原文)</label>
155
+ <textarea readonly rows="10" style="min-height:140px">${esc(JSON.stringify(s.config, null, 2))}</textarea>
156
+ <label>环境变量(引用值 → 当前值)</label>
157
+ <table><thead><tr><th style="width:130px">变量</th><th>配置值</th><th>当前值</th></tr></thead><tbody>${envRows}</tbody></table>`,
158
+ footHtml: `<button class="btn" data-act="close">关闭</button>`,
159
+ onMount({ foot }) {
160
+ foot.querySelector('[data-act="close"]').onclick = () => closeDrawer();
161
+ },
162
+ });
163
+ }
164
+
165
+ /** 添加 MCP:两个 tab——原生命令(claude mcp add …)粘贴即加 / JSON 配置 */
166
+ function addMcp() {
167
+ openDrawer({
168
+ title: '添加 MCP(存 bridge 自管目录,任务即时生效)',
169
+ bodyHtml: `
170
+ <div class="subtabs" style="margin-bottom:10px">
171
+ <button class="active" data-tab="cmd">原生命令</button>
172
+ <button data-tab="json">JSON 配置</button>
173
+ </div>
174
+ <div data-panel="cmd">
175
+ <label>粘贴 claude mcp add 命令(自动解析 command / args / env / header / url)</label>
176
+ <textarea id="amCmd" rows="4" placeholder='claude mcp add my-mcp -e API_KEY=xxx -- npx -y @scope/mcp-server&#10;claude mcp add my-mcp -t http -H "Authorization: Bearer xxx" https://example.com/mcp&#10;claude mcp add-json my-mcp {"type":"http","url":"https://…"}'></textarea>
177
+ <div class="hint" style="margin-top:8px">名字取命令第 4 个参数;scope(-s)忽略——统一存 bridge 目录。</div>
178
+ </div>
179
+ <div data-panel="json" style="display:none">
180
+ <label>名字</label>
181
+ <input type="text" id="amName" placeholder="my-mcp">
182
+ <label>配置 JSON</label>
183
+ <textarea id="amJson" rows="6" placeholder='stdio:{"type":"stdio","command":"npx","args":["-y","mcp-server-foo"],"env":{"KEY":"val"}}&#10;http:{"type":"http","url":"https://example.com/mcp"}'></textarea>
184
+ </div>`,
185
+ footHtml: `<button class="btn" data-act="cancel">取消</button><button class="btn primary" data-act="save">添加</button>`,
186
+ onMount({ body, foot }) {
187
+ body.querySelectorAll('.subtabs [data-tab]').forEach((b) => {
188
+ b.onclick = () => {
189
+ body.querySelectorAll('.subtabs [data-tab]').forEach((x) => x.classList.toggle('active', x === b));
190
+ const t = b.dataset.tab;
191
+ body.querySelector('[data-panel="cmd"]').style.display = t === 'cmd' ? '' : 'none';
192
+ body.querySelector('[data-panel="json"]').style.display = t === 'json' ? '' : 'none';
193
+ };
194
+ });
195
+ foot.querySelector('[data-act="cancel"]').onclick = () => closeDrawer();
196
+ foot.querySelector('[data-act="save"]').onclick = async () => {
197
+ const cmdTab = body.querySelector('[data-panel="cmd"]').style.display !== 'none';
198
+ try {
199
+ if (cmdTab) {
200
+ const command = $('#amCmd').value.trim();
201
+ if (!command) return toast('请粘贴 claude mcp add 命令', true);
202
+ // 名字由后端从命令解析校验(表单不重复输入);先取第 4 段预校验存在性
203
+ const nameGuess = command.split(/\s+/)[3] || '';
204
+ if (!nameGuess) return toast('命令中缺少名字(claude mcp add <名字> …)', true);
205
+ await api('POST', '/api/mcp/action', { op: 'add', name: nameGuess, command });
206
+ } else {
207
+ const name = $('#amName').value.trim();
208
+ const raw = $('#amJson').value.trim();
209
+ if (!name) return toast('请填写名字', true);
210
+ if (!raw) return toast('请填写配置 JSON', true);
211
+ let config;
212
+ try { config = JSON.parse(raw); } catch (e) { return toast(`JSON 解析失败:${e.message}`, true); }
213
+ await api('POST', '/api/mcp/action', { op: 'add', name, config });
214
+ }
215
+ toast('已添加(任务级热生效,无须重启)');
216
+ closeDrawer();
217
+ void loadMcp();
218
+ } catch (e) { toast(e.message, true); }
219
+ };
220
+ },
221
+ });
222
+ }
223
+
224
+ export const page = {
225
+ id: 'mcp',
226
+ title: 'MCP',
227
+ icon: '<circle cx="12" cy="12" r="3"/><path d="M12 2v4m0 12v4M2 12h4m12 0h4M5 5l2.8 2.8m8.4 8.4L19 19M19 5l-2.8 2.8M7.8 16.2L5 19"/>',
228
+ render: renderMcp,
229
+ };
@@ -0,0 +1,169 @@
1
+ // ============ Skills 管理页(#12)============
2
+ // 后端:GET /api/skills · POST /api/skills/action(create/delete)· POST /api/skills/import(zip)
3
+ // 四来源:用户级·本机(~/.claude/skills)/ 用户级·bridge(managed 生效目录)/ 项目级(工作区 .claude/skills)/
4
+ // 已装插件内(<plugin-installPath>/skills;plugin 只读,未启用时标「(未启用)」)。
5
+ // 远端 CRUD:改动即时落盘,无页内保存条
6
+ import { $, esc, toast, api } from '../core.js';
7
+ import { openDrawer, closeDrawer } from '../ui.js';
8
+
9
+ /** 来源标签(function:plugin 来源需要拼插件名与启用状态) */
10
+ const SOURCE_TAG = {
11
+ 'machine-user': '用户级 · 本机',
12
+ bridge: '用户级 · bridge',
13
+ project: '项目级',
14
+ };
15
+ const sourceTag = (s) => {
16
+ if (s.source === 'project') return `项目级 · ${esc(s.workspaceName || '?')}`;
17
+ if (s.source === 'plugin') return `插件 · ${esc(s.pluginName || '?')}${s.pluginEnabled ? '' : '(未启用)'}`;
18
+ return SOURCE_TAG[s.source] || s.source;
19
+ };
20
+ /** 来源是否可删(project / plugin 只读;其他用户级可删) */
21
+ const isDeletable = (s) => s.source !== 'project' && s.source !== 'plugin';
22
+
23
+ function renderSkills(el) {
24
+ el.innerHTML = `
25
+ <div class="card">
26
+ <h3>Skills</h3>
27
+ <div class="desc">Claude Code 技能(每项一个目录,含 SKILL.md)。列表聚合四个来源:本机用户级、bridge 自管用户级、各工作区项目级、已装插件内的 skills(只读展示)。新增(含 zip 导入)统一进用户级生效目录。</div>
28
+ <div style="display:flex;gap:8px;justify-content:flex-end;margin-bottom:10px">
29
+ <button class="btn sm" id="skillImport">📦 导入 zip</button>
30
+ <button class="btn sm" id="skillAdd">+ 新建</button>
31
+ </div>
32
+ <input type="file" id="skillZipFile" accept=".zip" style="display:none">
33
+ <input type="search" id="skillSearch" placeholder="🔍 按名字 / 来源 / 路径过滤…" style="width:100%;padding:7px 10px;margin-bottom:8px;border:1px solid var(--border);border-radius:7px">
34
+ <table><thead><tr>
35
+ <th style="width:170px">名字</th><th>说明</th><th style="width:130px">来源</th><th style="width:240px">路径</th><th style="width:50px"></th>
36
+ </tr></thead>
37
+ <tbody id="skillBody"><tr><td colspan="5" class="desc">加载中…</td></tr></tbody>
38
+ </table>
39
+ <div class="desc" id="skillEffective" style="margin-top:10px"></div>
40
+ </div>`;
41
+ $('#skillAdd').onclick = addSkill;
42
+ $('#skillSearch').oninput = filterSkillRows;
43
+ bindZipImport();
44
+ void loadSkills();
45
+ }
46
+
47
+ /** 搜索过滤:遍历 tr.dataset.searchText 切换 hidden */
48
+ function filterSkillRows() {
49
+ const q = ($('#skillSearch').value || '').trim().toLowerCase();
50
+ const body = $('#skillBody');
51
+ if (!body) return;
52
+ let shown = 0;
53
+ for (const tr of body.querySelectorAll('tr[data-search-text]')) {
54
+ const hit = !q || (tr.dataset.searchText || '').includes(q);
55
+ tr.hidden = !hit;
56
+ if (hit) shown++;
57
+ }
58
+ // 空状态行(无 data-search-text)展示原始文案
59
+ const total = body.querySelectorAll('tr[data-search-text]').length;
60
+ const empty = body.querySelector('tr.empty-row');
61
+ if (empty) empty.hidden = q && shown > 0 ? true : false;
62
+ // 实时同步计数到有效目录那行末尾
63
+ const eff = $('#skillEffective');
64
+ if (eff && eff.dataset.base) eff.textContent = `${eff.dataset.base} · 搜索结果 ${shown}/${total}`;
65
+ }
66
+
67
+ async function loadSkills() {
68
+ const body = $('#skillBody');
69
+ let data;
70
+ try {
71
+ data = await api('GET', '/api/skills');
72
+ } catch (e) {
73
+ body.innerHTML = `<tr><td colspan="5" class="desc">加载失败:${esc(e.message)}</td></tr>`;
74
+ return;
75
+ }
76
+ const eff = $('#skillEffective');
77
+ // 诊断行:插件目录扫到 N 个已装插件,其中 M 个含 skill 定义(含 plugin 未启用)
78
+ const stats = data.pluginStats;
79
+ const diag = stats && stats.total > 0 ? ` · 插件目录扫到 ${stats.total} 个已装插件,其中 ${stats.withSkills} 个含 skill 定义` : '';
80
+ if (eff) {
81
+ eff.dataset.base = `新增 / 导入落盘目录(用户级生效目录):${data.effectiveDir || ''}${diag}`;
82
+ eff.textContent = eff.dataset.base;
83
+ }
84
+ const skills = data.skills || [];
85
+ if (skills.length === 0) {
86
+ body.innerHTML = `<tr class="empty-row"><td colspan="5" class="desc">暂无 skill(四个来源均未发现 SKILL.md)。可点上方「+ 新建」或「📦 导入 zip」。</td></tr>`;
87
+ return;
88
+ }
89
+ body.innerHTML = '';
90
+ for (const s of skills) {
91
+ const tag = sourceTag(s);
92
+ const tr = document.createElement('tr');
93
+ tr.dataset.searchText = `${s.name} ${sourceTag(s)} ${s.path} ${s.description || ''}`.toLowerCase();
94
+ tr.innerHTML = `
95
+ <td><strong>${esc(s.name)}</strong></td>
96
+ <td>${esc(s.description) || '<span class="desc">(无说明)</span>'}</td>
97
+ <td><span class="chip">${tag}</span></td>
98
+ <td><code class="desc">${esc(s.path)}</code></td>
99
+ <td>${isDeletable(s) ? `<button class="btn sm danger" data-del="${esc(s.name)}">删</button>` : '<span class="desc">只读</span>'}</td>`;
100
+ const delBtn = tr.querySelector('[data-del]');
101
+ if (delBtn) delBtn.onclick = () => delSkill(s);
102
+ body.appendChild(tr);
103
+ }
104
+ // 保留搜索框当前过滤(如刷新列表后保留关键字)
105
+ filterSkillRows();
106
+ }
107
+
108
+ function delSkill(s) {
109
+ if (!confirm(`删除 skill "${s.name}"(${sourceTag(s)})?该目录会被整体移除(不可恢复)。`)) return;
110
+ api('POST', '/api/skills/action', { op: 'delete', name: s.name, source: s.source, workspaceName: s.workspaceName })
111
+ .then(() => { toast('已删除'); void loadSkills(); })
112
+ .catch((e) => toast(e.message, true));
113
+ }
114
+
115
+ function addSkill() {
116
+ openDrawer({
117
+ title: '新建 skill(用户级)',
118
+ bodyHtml: `
119
+ <label>名字(字母/数字/下划线/连字符,1-64 位)</label>
120
+ <input type="text" id="nskName" placeholder="my-skill">
121
+ <label>说明(frontmatter description)</label>
122
+ <input type="text" id="nskDesc" placeholder="这个 skill 做什么、什么时候用">
123
+ <div class="hint" style="margin-top:10px">创建后生成目录与 SKILL.md 模板,请到目录中补充正文与脚本。</div>`,
124
+ footHtml: `<button class="btn" data-act="cancel">取消</button><button class="btn primary" data-act="save">创建</button>`,
125
+ onMount({ foot }) {
126
+ foot.querySelector('[data-act="cancel"]').onclick = () => closeDrawer();
127
+ foot.querySelector('[data-act="save"]').onclick = async () => {
128
+ const name = $('#nskName').value.trim();
129
+ const description = $('#nskDesc').value.trim();
130
+ if (!name) return toast('请填写名字', true);
131
+ try {
132
+ await api('POST', '/api/skills/action', { op: 'create', name, description });
133
+ toast('已创建(含默认 SKILL.md 模板)');
134
+ closeDrawer();
135
+ void loadSkills();
136
+ } catch (e) { toast(e.message, true); }
137
+ };
138
+ },
139
+ });
140
+ }
141
+
142
+ /** 导入 zip:兼容「根即 skill 目录」与「单层子目录」两种打包结构;根打包时弹名字输入 */
143
+ function bindZipImport() {
144
+ const fileInput = $('#skillZipFile');
145
+ $('#skillImport').onclick = () => fileInput.click();
146
+ fileInput.onchange = async () => {
147
+ const file = fileInput.files?.[0];
148
+ fileInput.value = ''; // 允许连续导入同名文件
149
+ if (!file) return;
150
+ if (file.size > 10 * 1024 * 1024) return toast('zip 超过 10MB 上限', true);
151
+ const buf = await file.arrayBuffer();
152
+ const zipBase64 = btoa(String.fromCharCode(...new Uint8Array(buf)));
153
+ // 根打包(无子目录层级)时后端拿不到名字,先问一次;目录打包可留空由后端取名
154
+ const guess = file.name.replace(/\.zip$/i, '');
155
+ const name = prompt(`skill 名(zip 内含目录结构时可留空,将取目录名;根打包时必填。建议:${guess})`, '') ?? '';
156
+ try {
157
+ const r = await api('POST', '/api/skills/import', { zipBase64, name: name.trim() || undefined });
158
+ toast(`已导入 "${r.name}"(${r.files} 个文件)`);
159
+ void loadSkills();
160
+ } catch (e) { toast(`导入失败:${e.message}`, true); }
161
+ };
162
+ }
163
+
164
+ export const page = {
165
+ id: 'skills',
166
+ title: 'Skills',
167
+ icon: '<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>',
168
+ render: renderSkills,
169
+ };
@@ -19,9 +19,16 @@ function renderWorkspaces(el) {
19
19
  </div>
20
20
  <div class="card">
21
21
  <h3>全局默认${dirtyDotHtml('def')}</h3>
22
- <div class="row">
22
+ <div class="row-2col">
23
23
  <div><label>默认工作区(defaults.workspace)</label><select id="defWs"></select></div>
24
24
  <div><label>全局并发(concurrency)</label><input type="number" id="globalCc" min="1" max="100"></div>
25
+ <div><label>上下文提醒阈值(session.context_remind_tokens;0=关闭)</label><input type="number" id="defCtxRemind" min="0" max="10000000"></div>
26
+ <div><label>飞书卡片宽度(card.width)</label>
27
+ <select id="cardWidth">
28
+ <option value="default">默认(飞书默认)</option>
29
+ <option value="fill">撑满聊天窗口</option>
30
+ </select>
31
+ </div>
25
32
  </div>
26
33
  ${saveBarHtml('def')}
27
34
  </div>`;
@@ -67,7 +74,8 @@ function renderWorkspaces(el) {
67
74
  cardDirty('ws', true);
68
75
  };
69
76
  });
70
- tr.querySelector('.btn').onclick = () => {
77
+ // 「删」按钮精确选择(class="btn sm danger");原 tr.querySelector('.btn') 命中「浏览」并被浏览逻辑覆盖,故点「删」无反应
78
+ tr.querySelector('.danger').onclick = () => {
71
79
  if (doc.workspaces.length <= 1) return toast('至少保留一个工作区', true);
72
80
  doc.workspaces.splice(idx, 1);
73
81
  cardDirty('ws', true);
@@ -89,6 +97,19 @@ function renderWorkspaces(el) {
89
97
  $('#defWs').onchange = () => { doc.defaults = { ...(doc.defaults || {}), workspace: $('#defWs').value }; cardDirty('def', true); };
90
98
  $('#globalCc').value = doc.concurrency ?? 3;
91
99
  $('#globalCc').oninput = () => { doc.concurrency = Number($('#globalCc').value) || 3; cardDirty('def', true); };
100
+ // 上下文提醒阈值:空值 = 移除 session 段(PUT 跳过 = 磁盘原值未变,等价回滚);合法数字 = 写 session 段
101
+ const ctxInput = $('#defCtxRemind');
102
+ ctxInput.value = doc.session?.context_remind_tokens ?? 150000;
103
+ ctxInput.oninput = () => {
104
+ const v = ctxInput.value.trim();
105
+ if (v === '') delete doc.session;
106
+ else doc.session = { context_remind_tokens: Number(v) };
107
+ cardDirty('def', true);
108
+ };
109
+ // 卡片宽度:写 card.width;空/未设回 'default'
110
+ const cw = $('#cardWidth');
111
+ cw.value = doc.card?.width ?? 'default';
112
+ cw.onchange = () => { doc.card = { width: cw.value }; cardDirty('def', true); };
92
113
  applyDirty(); // 增删行等操作先 cardDirty 再整卡重渲染:回放防止保存条/圆点随重建丢失
93
114
  }
94
115
 
package/dist/bin/lcb.js CHANGED
@@ -14,12 +14,17 @@ import { startWebServer } from '../web/server.js';
14
14
  import { AccessControl } from '../access/access-control.js';
15
15
  import { VERSION } from '../version.js';
16
16
  import { join } from 'node:path';
17
+ import { ensureRuntimeDirs } from '../util/runtime-dirs.js';
18
+ import { installLogTee, cleanupOldLogs } from '../util/log-tee.js';
17
19
  function showPending(access) {
18
20
  const pending = access.listPending();
19
21
  if (pending.length)
20
22
  console.log('待配对:', pending.map((p) => `${p.code}(${p.userId})`).join('、'));
21
23
  }
22
24
  async function main() {
25
+ ensureRuntimeDirs(); // 首装即建目录:后续所有命令的写盘(含配置页 tmp 文件)不再因目录缺失 ENOENT
26
+ installLogTee(); // console 双写到 logs/bridge-YYYY-MM-DD.log(#7:按天拆分,替代旧 bridge.log)
27
+ cleanupOldLogs(); // 启动时清 14 天前的日志
23
28
  const [cmd, ...args] = process.argv.slice(2);
24
29
  switch (cmd) {
25
30
  case 'setup':
@@ -1 +1 @@
1
- {"version":3,"file":"lcb.js","sourceRoot":"","sources":["../../src/bin/lcb.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lcb.js","sourceRoot":"","sources":["../../src/bin/lcb.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -7,21 +7,46 @@ export const CONFIG_DIR = join(process.env.LCB_CONFIG_DIR ?? homedir(), '.lark-c
7
7
  export const CONFIG_PATH = join(CONFIG_DIR, 'config.yaml');
8
8
  /** 会话上下文超长提醒默认阈值(tokens):约 200k 窗口的 75%,估算口径见 SessionConfig 注释 */
9
9
  export const DEFAULT_CONTEXT_REMIND_TOKENS = 150000;
10
- /** 会话行为段(整体可选):context_remind_tokens 硬校验(非法值会让提醒永久误报/永不触发) */
10
+ /** 会话行为段(整体可选):context_remind_tokens 硬校验(非法值会让提醒永久误报/永不触发);
11
+ * notify_sop 类型校验(非法值会让 SOP 注入/硬兜底配对错位) */
11
12
  function normalizeSession(doc) {
12
13
  const raw = doc.session;
13
14
  if (raw === undefined || raw === null)
14
15
  return undefined;
15
16
  if (typeof raw !== 'object')
16
- throw new Error('session 必须为对象(含 context_remind_tokens),请检查 config.yaml');
17
+ throw new Error('session 必须为对象(含 context_remind_tokens / notify_sop),请检查 config.yaml');
17
18
  const s = raw;
18
- if (s.context_remind_tokens === undefined || s.context_remind_tokens === null)
19
+ const out = {};
20
+ if (s.context_remind_tokens !== undefined && s.context_remind_tokens !== null) {
21
+ const n = Number(s.context_remind_tokens);
22
+ if (!Number.isInteger(n) || n < 0 || n > 10_000_000) {
23
+ throw new Error(`session.context_remind_tokens 必须为 0-10000000 的整数(0 = 关闭提醒;当前值:${String(s.context_remind_tokens)}),请检查 config.yaml`);
24
+ }
25
+ out.contextRemindTokens = n;
26
+ }
27
+ // notify_sop:缺省=true(开启 SOP 软约束 + 硬兜底),false = 完全关闭。type 校验保证不传字符串/数字过来配对错位
28
+ if (s.notify_sop !== undefined && s.notify_sop !== null) {
29
+ if (typeof s.notify_sop !== 'boolean') {
30
+ throw new Error(`session.notify_sop 必须为 boolean(true = 启用,false = 关闭;当前值:${String(s.notify_sop)}),请检查 config.yaml`);
31
+ }
32
+ out.notifySop = s.notify_sop;
33
+ }
34
+ return out;
35
+ }
36
+ /** 卡片展示段(整体可选):width 严格枚举(default = 飞书默认宽度;fill = 撑满聊天窗口) */
37
+ function normalizeCard(doc) {
38
+ const raw = doc.card;
39
+ if (raw === undefined || raw === null)
40
+ return undefined;
41
+ if (typeof raw !== 'object')
42
+ throw new Error('card 必须为对象(含 width),请检查 config.yaml');
43
+ const c = raw;
44
+ if (c.width === undefined || c.width === null)
19
45
  return {};
20
- const n = Number(s.context_remind_tokens);
21
- if (!Number.isInteger(n) || n < 0 || n > 10_000_000) {
22
- throw new Error(`session.context_remind_tokens 必须为 0-10000000 的整数(0 = 关闭提醒;当前值:${String(s.context_remind_tokens)}),请检查 config.yaml`);
46
+ if (c.width !== 'default' && c.width !== 'fill') {
47
+ throw new Error(`card.width 必须为 default / fill(当前值:${String(c.width)}),请检查 config.yaml`);
23
48
  }
24
- return { contextRemindTokens: n };
49
+ return { width: c.width };
25
50
  }
26
51
  /** 触发词规则校验:match/rewrite 必须非空(错误消息带定位),顺序保持(按序首个命中生效) */
27
52
  function normalizeTriggers(raw, where) {
@@ -435,6 +460,7 @@ export function parseConfigText(raw, pathForError = CONFIG_PATH) {
435
460
  ...section(claude, 'claude'),
436
461
  ...section(slashCommands, 'slashCommands'),
437
462
  ...section(session, 'session'),
463
+ ...section(normalizeCard(doc), 'card'),
438
464
  };
439
465
  }
440
466
  /** 热重载比较:任一 app 的凭证/名称/工作区/并发/配置目录/人格变化即视为需重启的变更(顺序敏感) */