remnote-bridge 0.1.10 → 0.1.12

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 (68) hide show
  1. package/dist/cli/addon/addon-manager.js +163 -0
  2. package/dist/cli/addon/registry.js +24 -0
  3. package/dist/cli/commands/addon.js +149 -0
  4. package/dist/cli/commands/clean.js +121 -52
  5. package/dist/cli/commands/connect.js +72 -33
  6. package/dist/cli/commands/disconnect.js +19 -19
  7. package/dist/cli/commands/edit-rem.js +3 -31
  8. package/dist/cli/commands/edit-tree.js +3 -20
  9. package/dist/cli/commands/health.js +19 -18
  10. package/dist/cli/commands/read-context.js +8 -23
  11. package/dist/cli/commands/read-globe.js +3 -20
  12. package/dist/cli/commands/read-rem.js +3 -31
  13. package/dist/cli/commands/read-tree.js +3 -20
  14. package/dist/cli/commands/search.js +97 -21
  15. package/dist/cli/config.js +148 -72
  16. package/dist/cli/daemon/daemon.js +104 -24
  17. package/dist/cli/daemon/dev-server.js +9 -1
  18. package/dist/cli/daemon/pid.js +36 -22
  19. package/dist/cli/daemon/registry.js +160 -0
  20. package/dist/cli/daemon/send-request.js +11 -11
  21. package/dist/cli/daemon/static-server.js +97 -34
  22. package/dist/cli/handlers/context-read-handler.js +5 -3
  23. package/dist/cli/handlers/read-handler.js +4 -3
  24. package/dist/cli/handlers/tree-parser.js +16 -9
  25. package/dist/cli/main.js +51 -9
  26. package/dist/cli/protocol.js +18 -4
  27. package/dist/cli/server/config-server.js +280 -14
  28. package/dist/cli/server/ws-server.js +93 -44
  29. package/dist/cli/utils/output.js +29 -0
  30. package/dist/mcp/instructions.js +103 -10
  31. package/dist/mcp/resources/edit-rem-guide.js +3 -4
  32. package/dist/mcp/resources/error-reference.js +5 -3
  33. package/dist/mcp/resources/rem-object-fields.js +3 -3
  34. package/dist/mcp/tools/infra-tools.js +54 -6
  35. package/dist/mcp/tools/read-tools.js +16 -3
  36. package/package.json +2 -2
  37. package/remnote-plugin/dist/bridge_widget-sandbox.js +17 -17
  38. package/remnote-plugin/dist/bridge_widget.js +17 -17
  39. package/remnote-plugin/dist/index-sandbox.js +31 -31
  40. package/remnote-plugin/dist/index.js +31 -31
  41. package/remnote-plugin/dist/manifest.json +1 -1
  42. package/remnote-plugin/package.json +1 -1
  43. package/remnote-plugin/public/manifest.json +1 -1
  44. package/remnote-plugin/src/bridge/message-router.ts +1 -1
  45. package/remnote-plugin/src/bridge/multi-connection-manager.ts +151 -0
  46. package/remnote-plugin/src/bridge/websocket-client.ts +62 -16
  47. package/remnote-plugin/src/services/index.ts +0 -8
  48. package/remnote-plugin/src/services/read-context.ts +13 -4
  49. package/remnote-plugin/src/services/read-rem.ts +1 -9
  50. package/remnote-plugin/src/services/search.ts +13 -10
  51. package/remnote-plugin/src/settings.ts +9 -7
  52. package/remnote-plugin/src/utils/index.ts +0 -5
  53. package/remnote-plugin/src/widgets/bridge_widget.tsx +105 -20
  54. package/remnote-plugin/src/widgets/index.tsx +41 -44
  55. package/remnote-plugin/webpack.config.js +35 -0
  56. package/skills/remnote-bridge/SKILL.md +14 -9
  57. package/skills/remnote-bridge/instructions/addon.md +134 -0
  58. package/skills/remnote-bridge/instructions/clean.md +110 -0
  59. package/skills/remnote-bridge/instructions/connect.md +80 -37
  60. package/skills/remnote-bridge/instructions/disconnect.md +22 -9
  61. package/skills/remnote-bridge/instructions/edit-rem.md +37 -9
  62. package/skills/remnote-bridge/instructions/health.md +23 -13
  63. package/skills/remnote-bridge/instructions/install-skill.md +58 -0
  64. package/skills/remnote-bridge/instructions/overall.md +76 -21
  65. package/skills/remnote-bridge/instructions/read-context.md +34 -8
  66. package/skills/remnote-bridge/instructions/read-rem.md +10 -10
  67. package/skills/remnote-bridge/instructions/search.md +73 -14
  68. package/skills/remnote-bridge/instructions/setup.md +1 -1
@@ -73,7 +73,7 @@ Rem 有两个独立维度:**type**(闪卡语义)和 **isDocument**(页
73
73
  | 删除 Portal | \\\`edit_tree\\\` | 从大纲中移除 Portal 行(与删除普通行相同) |
74
74
  | 修改引用列表(增删引用的 Rem) | \\\`edit_rem\\\` | str_replace 简化 JSON 中的 \\\`portalDirectlyIncludedRem\\\` 数组 |
75
75
  | 移动 Portal(换父节点/位置) | \\\`edit_tree\\\` | 与移动普通行相同 |
76
- | 读取 Portal | \\\`read_rem\\\` | 自动输出 9 字段简化 JSON |
76
+ | 读取 Portal | \\\`read_rem\\\` | 自动输出 8 字段简化 JSON |
77
77
 
78
78
  ### Powerup 过滤
79
79
 
@@ -85,6 +85,16 @@ RemNote 的格式设置(标题、高亮、代码等)会注入隐藏的系统
85
85
 
86
86
  所有操作都依赖一个活跃的会话。会话 = 守护进程的生命周期。
87
87
 
88
+ ### 多实例支持
89
+
90
+ 系统支持最多 **4 个并发实例**,每个实例连接不同的 RemNote 知识库。通过 \\\`instance\\\` 参数指定实例名:
91
+
92
+ - \\\`connect(instance="work")\\\` — 启动 "work" 实例
93
+ - \\\`health(instance="work")\\\` — 检查 "work" 实例状态
94
+ - \\\`disconnect(instance="work")\\\` — 停止 "work" 实例
95
+
96
+ 不指定 instance 时默认为 \\\`"default"\\\`。每个实例独占一个槽位(固定端口组),槽位自动分配。各实例的缓存相互独立。
97
+
88
98
  ### 标准模式(需要用户配合)
89
99
 
90
100
  \\\`\\\`\\\`
@@ -109,7 +119,7 @@ disconnect → 关闭 daemon,清空所有缓存
109
119
 
110
120
  \\\`setup\\\` 会弹出 Chrome 窗口,用户需要完成两件事:
111
121
  1. **登录 RemNote**
112
- 2. **配置 dev plugin**:插件图标 → 开发你的插件 → 填入 \\\`http://localhost:8080\\\`
122
+ 2. **配置 dev plugin**:插件图标 → 开发你的插件 → 填入 connect 输出的 Plugin 服务地址(如 \\\`http://localhost:29101\\\`)
113
123
 
114
124
  完成后**彻底退出 Chrome**(macOS 必须 Cmd+Q,仅关窗口不够)。
115
125
 
@@ -118,7 +128,7 @@ disconnect → 关闭 daemon,清空所有缓存
118
128
  2. 立即告知用户:
119
129
  "已打开 Chrome 浏览器。请完成以下操作:
120
130
  1. 登录 RemNote
121
- 2. 在 RemNote 中配置开发插件:点击左下角插件图标 → 开发你的插件 → 输入 http://localhost:8080
131
+ 2. 在 RemNote 中配置开发插件:点击左下角插件图标 → 开发你的插件 → 输入 connect 输出的 Plugin 服务地址
122
132
  3. 完成后彻底退出 Chrome(macOS 请按 Cmd+Q)"
123
133
  3. 等待 \\\`setup\\\` 返回(阻塞,最长 10 分钟)
124
134
  4. 成功 → 进入下一步 \\\`connect(headless=true)\\\`
@@ -166,7 +176,7 @@ disconnect → 关闭 daemon + headless Chrome,清空所有缓存
166
176
  1. 打开 RemNote 桌面端或网页端
167
177
  2. 点击左侧边栏底部的插件图标(拼图形状)
168
178
  3. 点击「开发你的插件」(Develop Your Plugin)
169
- 4. 在输入框中填入 \\\`http://localhost:8080\\\`(即 connect 输出的 Plugin 服务地址)
179
+ 4. 在输入框中填入 connect 输出的 Plugin 服务地址(如 \\\`http://localhost:29101\\\`)
170
180
  5. 等待插件加载完成
171
181
 
172
182
  **非首次使用**(之前已加载过此插件):
@@ -191,11 +201,11 @@ disconnect → 关闭 daemon + headless Chrome,清空所有缓存
191
201
 
192
202
  > 用户说:"搜一下关于 X 的笔记"、"找找 X 在哪里"
193
203
 
194
- 先用 \\\`search\\\` 搜索关键词,获得匹配的 Rem 列表(含 remId)。然后根据需要:
204
+ 先用 \\\`search\\\` 搜索关键词,获得匹配的 Rem 列表(含 remId)。返回结果中的 \\\`source\\\` 字段标识搜索来源:\\\`"rag"\\\` 为语义搜索(配置中启用 remnote-rag addon 时),\\\`"sdk"\\\` 为 SDK 全文搜索。RAG 模式额外返回 backText、ancestorPath、type、tags、score。然后根据需要:
195
205
  - 用 \\\`read_rem\\\` 查看某条结果的详细属性
196
206
  - 用 \\\`read_tree\\\` 展开某条结果的子树结构
197
207
 
198
- **注意**:中文等无空格语言搜索效果较差。如果完整词搜索无结果,尝试用单个最具区分度的字搜索;如果仍然无果,改用 \\\`read_globe\\\` → \\\`read_tree\\\` 手动定位。
208
+ **注意**:SDK 模式下中文等无空格语言搜索效果较差。如果完整词搜索无结果,尝试用单个最具区分度的字搜索;如果仍然无果,改用 \\\`read_globe\\\` → \\\`read_tree\\\` 手动定位。RAG 模式对中文支持更好。
199
209
 
200
210
  ### 场景 C:了解当前上下文(⚠️ 高优先级)
201
211
 
@@ -210,7 +220,7 @@ disconnect → 关闭 daemon + headless Chrome,清空所有缓存
210
220
  先用 \\\`read_context\\\` 看到用户所看到的,再做决策,沟通才能顺畅。
211
221
 
212
222
  使用 \\\`read_context\\\`:
213
- - **focus 模式**(默认):以用户当前光标所在的 Rem 为中心,构建鱼眼视图——焦点处完全展开,周围递减。焦点行以 \\\`* \\\` 前缀标记。需要用户在 RemNote 中已点击某个 Rem
223
+ - **focus 模式**(默认):以用户当前光标所在的 Rem 为中心,构建鱼眼视图——焦点处完全展开,周围递减。焦点行以 \\\`* \\\` 前缀标记。需要用户在 RemNote 中已点击某个 Rem。可通过 \\\`focusRemId\\\` 指定任意 Rem 作为鱼眼中心,此时不依赖用户焦点。
214
224
  - **page 模式**:以当前打开的页面为根,均匀展开子树。
215
225
 
216
226
  两种模式都会返回面包屑路径,帮助你理解当前位置在知识库中的层级。
@@ -245,7 +255,72 @@ oldStr: "text": [\\n "点击访问官网"\\n ]
245
255
  newStr: "text": [\\n "点击",\\n {\\n "i": "m",\\n "iUrl": "https://remnote.com",\\n "text": "访问官网"\\n }\\n ]
246
256
  \\\`\\\`\\\`
247
257
 
248
- 注意:\\\`highlightColor\\\`(Rem 顶层字段,字符串如 \\\`"Red"\\\`)和 RichText 的 \\\`h\\\`(行内格式标记,数字 0-9)是两个独立属性。详见 \\\`resource://rem-object-fields\\\`。
258
+ ### ⚠️ highlightColor vs h 两种完全不同的高亮
259
+
260
+ 这是最常见的混淆点,必须区分:
261
+
262
+ | 属性 | 位置 | 值类型 | 效果 | 修改方式 |
263
+ |:-----|:-----|:-------|:-----|:---------|
264
+ | \\\`highlightColor\\\` | RemObject 顶层字段 | 字符串 \\\`"Red"\\\`/\\\`"Yellow"\\\` 等,或 \\\`null\\\` | 整行背景色(左侧彩色竖条) | str_replace 顶层字段 |
265
+ | \\\`h\\\` | RichText 元素内部 | 数字 0-9 | 文字片段的荧光底色 | str_replace text 数组内的对象 |
266
+
267
+ **RichText \\\`h\\\` 颜色值对照表**(必须用数字,不是字符串):
268
+
269
+ | 值 | 颜色 | 值 | 颜色 | 值 | 颜色 |
270
+ |:---|:-----|:---|:-----|:---|:-----|
271
+ | 0 | 无(默认) | 4 | Green | 7 | Gray |
272
+ | 1 | Red | 5 | Purple | 8 | Brown |
273
+ | 2 | Orange | 6 | Blue | 9 | Pink |
274
+ | 3 | Yellow | — | — | — | — |
275
+
276
+ ### 常见属性修改速查(edit_rem str_replace 模板)
277
+
278
+ 以下模板基于 \\\`JSON.stringify(obj, null, 2)\\\` 的缩进格式。**oldStr/newStr 必须包含精确的缩进空格**。
279
+
280
+ **① 设置/清除整行背景色(highlightColor)**
281
+
282
+ \\\`\\\`\\\`
283
+ // 设置为黄色背景
284
+ oldStr: "highlightColor": null
285
+ newStr: "highlightColor": "Yellow"
286
+
287
+ // 清除背景色
288
+ oldStr: "highlightColor": "Yellow"
289
+ newStr: "highlightColor": null
290
+ \\\`\\\`\\\`
291
+
292
+ **② 给文字加荧光底色(RichText h 字段)**
293
+
294
+ 先 read_rem 找到 text 数组中目标文字对象的精确格式,然后替换 h 值。
295
+
296
+ \\\`\\\`\\\`
297
+ // "Todo List" 文字加黄色荧光(h: 0 → 3)
298
+ // ⚠️ 只替换包含目标文字的那一行 h 值,用上下文确保唯一匹配
299
+ oldStr: "h": 0,\\n "i": "m",\\n "text": "Todo List "
300
+ newStr: "h": 3,\\n "i": "m",\\n "text": "Todo List "
301
+
302
+ // 去掉荧光(h: 3 → 0)
303
+ oldStr: "h": 3,\\n "i": "m",\\n "text": "Todo List "
304
+ newStr: "h": 0,\\n "i": "m",\\n "text": "Todo List "
305
+ \\\`\\\`\\\`
306
+
307
+ **⚠️ 关键要点**:oldStr 中必须包含足够的上下文(如 \\\`"i": "m"\\\` 和 \\\`"text": "..."\\\`)来唯一定位。不能只写 \\\`"h": 0\\\` — 可能匹配到多处。
308
+
309
+ **③ 修改 Rem 类型**
310
+
311
+ \\\`\\\`\\\`
312
+ // 普通 → 概念
313
+ oldStr: "type": "default"
314
+ newStr: "type": "concept"
315
+ \\\`\\\`\\\`
316
+
317
+ **④ 修改文字颜色(RichText tc 字段)**
318
+
319
+ \\\`\\\`\\\`
320
+ // 加红色文字颜色(与 h 类似,tc 也是数字 0-9)
321
+ oldStr: "i": "m",\\n "text": "重要内容"
322
+ newStr: "i": "m",\\n "tc": 1,\\n "text": "重要内容"
323
+ \\\`\\\`\\\`
249
324
 
250
325
  ### 缓存行为速查
251
326
 
@@ -312,11 +387,28 @@ newStr: 最后一个兄弟 <!--idZ-->
312
387
 
313
388
  > 用户说:"连不上"、"命令报错了"
314
389
 
315
- 使用 \\\`health\\\` 检查三层状态,然后对症处理:
390
+ 使用 \\\`health\\\` 检查三层状态(多实例时指定 instance),然后对症处理:
316
391
  - daemon 未运行 → 执行 \\\`connect\\\`,然后引导用户在 RemNote 中加载插件
317
- - Plugin 未连接 → 提醒用户:首次使用需在 RemNote 的「开发你的插件」中填入 \\\`http://localhost:8080\\\`;非首次使用只需刷新 RemNote 页面
392
+ - Plugin 未连接 → 提醒用户:首次使用需在 RemNote 的「开发你的插件」中填入对应 Plugin 服务地址(如 \\\`http://localhost:29101\\\`);非首次使用只需刷新 RemNote 页面
318
393
  - SDK 未就绪 → 等待几秒后重试 health
319
394
 
395
+ ### 场景 H:管理增强项目
396
+
397
+ > 用户说:"安装语义搜索"、"配置 RAG"
398
+
399
+ 使用 \\\`addon\\\` 工具管理增强项目:
400
+ - \\\`addon(action="list")\\\` — 查看所有可用增强项目及状态
401
+ - \\\`addon(action="install", name="remnote-rag")\\\` — 安装指定增强项目
402
+ - \\\`addon(action="uninstall", name="remnote-rag")\\\` — 卸载指定增强项目
403
+
404
+ 安装后需在配置页面中填写必需的配置项(如 API Key)。
405
+
406
+ ### 场景 I:重置环境
407
+
408
+ > 用户说:"全部清理"、"重新开始"
409
+
410
+ 使用 \\\`clean\\\` 工具彻底清理所有 daemon 进程、PID 文件、注册表和 addon 数据。清理后需重新 \\\`connect\\\`。
411
+
320
412
  ---
321
413
 
322
414
  ## 4. Safety Rules
@@ -404,6 +496,7 @@ newStr: 最后一个兄弟 <!--idZ-->
404
496
  | orphan_detected | 删了父行但保留了子行 | 同时删除所有子行 |
405
497
  | folded_delete | 删除有隐藏子节点的行 | 用更大 depth 重新 read_tree |
406
498
  | children_captured | 新行插在父 Rem 和它的 children 之间,劫持了已有子节点 | 把新行插到所有兄弟的**末尾**而非紧跟父 Rem 之后(见下方说明) |
499
+ | focusRemId 仅在 focus 模式下有效 | page 模式下不应传 focusRemId | 去掉 focusRemId 参数,或改用 focus 模式 |
407
500
 
408
501
  完整错误参考见 \\\`resource://error-reference\\\`。
409
502
  `;
@@ -176,7 +176,7 @@ newStr: "practiceDirection": "both"
176
176
  | 混淆 highlightColor 和 h | 前者字符串 \\\`"Red"\\\`,后者数字 \\\`1\\\` | 参考上方对比表 |
177
177
  | 漏 onlyAudio | \\\`i:"a"\\\` 的 \\\`onlyAudio\\\` 是必填 | true=音频,false=视频 |
178
178
  | JSON 语法错 | 引号、逗号、括号不完整 | 检查替换边界 |
179
- | Portal oldStr 不匹配 | Portal 编辑在简化 JSON 上匹配,不是完整 JSON | 检查 oldStr 是否匹配 9 字段简化 JSON |
179
+ | Portal oldStr 不匹配 | Portal 编辑在简化 JSON 上匹配,不是完整 JSON | 检查 oldStr 是否匹配 8 字段简化 JSON |
180
180
 
181
181
  ---
182
182
 
@@ -188,7 +188,7 @@ newStr: "practiceDirection": "both"
188
188
 
189
189
  ### 操作目标:简化 JSON
190
190
 
191
- Portal 的 str_replace 在 **9 字段简化 JSON** 上执行(而非完整 51 字段):
191
+ Portal 的 str_replace 在 **8 字段简化 JSON** 上执行(而非完整 51 字段):
192
192
 
193
193
  \\\`\\\`\\\`json
194
194
  {
@@ -198,7 +198,6 @@ Portal 的 str_replace 在 **9 字段简化 JSON** 上执行(而非完整 51
198
198
  "portalDirectlyIncludedRem": ["remId1", "remId2"],
199
199
  "parent": "parentId",
200
200
  "positionAmongstSiblings": 3,
201
- "children": ["remId1", "remId2"],
202
201
  "createdAt": 1709000000000,
203
202
  "updatedAt": 1709000000000
204
203
  }
@@ -212,7 +211,7 @@ Portal 的 str_replace 在 **9 字段简化 JSON** 上执行(而非完整 51
212
211
  | \\\`parent\\\` | setParent() |
213
212
  | \\\`positionAmongstSiblings\\\` | setParent(parent, position) |
214
213
 
215
- 其余字段(id、type、portalType、children、createdAt、updatedAt)为只读,修改只产生警告。
214
+ 其余字段(id、type、portalType、createdAt、updatedAt)为只读,修改只产生警告。
216
215
 
217
216
  ### 示例
218
217
 
@@ -54,7 +54,7 @@ export const ERROR_REFERENCE_CONTENT = `
54
54
  | Failed to update field '{field}': ... | SDK setter 调用失败 | 检查字段值是否在允许范围内 |
55
55
  | Field '{fieldName}' is read-only and was ignored | 修改了只读字段 | **警告**(非阻断),该字段不可修改 |
56
56
  | Setting 'todoStatus' without 'isTodo: true' may have no effect | todoStatus 非 null 但 isTodo=false | 先将 isTodo 设为 true |
57
- | old_str not found in the simplified Portal JSON of rem {remId} | Portal 编辑时 oldStr 在 9 字段简化 JSON 中不匹配 | 检查 oldStr 是否匹配 Portal 简化 JSON 格式(9 字段:id、type、portalType、portalDirectlyIncludedRem、parent、positionAmongstSiblings、children、createdAt、updatedAt) |
57
+ | old_str not found in the simplified Portal JSON of rem {remId} | Portal 编辑时 oldStr 在 8 字段简化 JSON 中不匹配 | 检查 oldStr 是否匹配 Portal 简化 JSON 格式(8 字段:id、type、portalType、portalDirectlyIncludedRem、parent、positionAmongstSiblings、createdAt、updatedAt) |
58
58
  | old_str matches {N} locations in Portal rem {remId}. Make old_str more specific to match exactly once. | Portal 编辑时 oldStr 在简化 JSON 中匹配多处 | 扩大 oldStr 范围以唯一定位 |
59
59
 
60
60
  ---
@@ -78,7 +78,9 @@ export const ERROR_REFERENCE_CONTENT = `
78
78
  | 错误信息 | 触发命令 | 原因 | 恢复操作 |
79
79
  |:---------|:---------|:-----|:---------|
80
80
  | Rem not found: {remId} | read_rem / read_tree | remId 无效或 Rem 已被删除 | 使用 \\\`search\\\` 重新定位 |
81
- | 无法获取当前聚焦的 Rem | read_context (focus) | 用户未在 RemNote 中点击任何 Rem | 提醒用户在 RemNote 中点击一个 Rem |
81
+ | 指定的 Rem 不存在: {remId} | read_context (focus + focusRemId) | focusRemId 无效或 Rem 已被删除 | 使用 \\\`search\\\` 重新定位 |
82
+ | focusRemId 仅在 focus 模式下有效 | read_context (page + focusRemId) | page 模式下不应传 focusRemId | 去掉 focusRemId 参数,或改用 focus 模式 |
83
+ | 无法获取当前聚焦的 Rem | read_context (focus) | 用户未在 RemNote 中点击任何 Rem(且未指定 focusRemId) | 提醒用户在 RemNote 中点击一个 Rem,或传入 focusRemId |
82
84
  | 无法获取当前页面 | read_context (page) | RemNote 未打开文档页面 | 提醒用户打开一个文档页面 |
83
85
 
84
86
  ---
@@ -109,7 +111,7 @@ export const ERROR_REFERENCE_CONTENT = `
109
111
 
110
112
  ├─ "old_str not found"
111
113
  │ ├─ 检查空格、换行、引号是否精确匹配
112
- │ ├─ Portal Rem?检查是否匹配 9 字段简化 JSON(非完整 JSON)
114
+ │ ├─ Portal Rem?检查是否匹配 8 字段简化 JSON(非完整 JSON)
113
115
  │ └─ 重新 read 确认当前内容
114
116
 
115
117
  ├─ "old_str matches N locations"
@@ -11,8 +11,8 @@ RemObject 是本项目对 RemNote Rem 的标准化表示,包含 51 个字段
11
11
  |:-----|:-----|:-----|:---------|
12
12
  | RW | 可读可写 | 20 | 默认输出 |
13
13
  | Portal-W | Portal 专用可写 | 1 | 默认输出(Portal 简化模式) |
14
- | R | 只读 | 14 | 默认输出 |
15
- | R-F | 只读低频 | 17 | 仅 \\\`--full\\\` 输出 |
14
+ | R | 只读 | 13 | 默认输出 |
15
+ | R-F | 只读低频 | 18 | 仅 \\\`--full\\\` 输出 |
16
16
 
17
17
  ---
18
18
 
@@ -41,7 +41,7 @@ RemObject 是本项目对 RemNote Rem 的标准化表示,包含 51 个字段
41
41
  | 字段 | 类型 | 权限 | 说明 |
42
42
  |------|------|:----:|------|
43
43
  | \\\`parent\\\` | \\\`string \\| null\\\` | RW | 父 Rem ID。null=顶级。UI:Rem 从原位置消失,出现在新父级下 |
44
- | \\\`children\\\` | \\\`string[]\\\` | R | 子 Rem ID 有序数组 |
44
+ | \\\`children\\\` | \\\`string[]\\\` | R-F | 子 Rem ID 有序数组 |
45
45
 
46
46
  ## 格式 / 显示
47
47
 
@@ -21,14 +21,17 @@ export function registerInfraTools(server) {
21
21
  // -------------------------------------------------------------------------
22
22
  server.addTool({
23
23
  name: 'connect',
24
- description: '启动守护进程(daemon),建立 CLI 与 RemNote Plugin 之间的通信通道。这是所有业务命令(read_rem、edit_rem、search 等)的前置步骤。\n\n适用场景:\n- 开始一次 RemNote 操作会话前,必须先调用此工具\n- 不确定 daemon 是否在运行时,也可安全调用(幂等)\n\n两种模式:\n- 标准模式(默认):启动 daemon 后需要用户在 RemNote 中手动加载 Plugin\n- Headless 模式(headless=true):自动启动 headless Chrome 加载 Plugin,无需用户操作。需先完成 setup(保存登录凭证)\n\n输出:返回 JSON,关键字段 ok、alreadyRunning(是否已运行)、pid、wsPort、headless。\n幂等:重复调用不会启动多个 daemon。daemon 默认 30 分钟无活动自动关闭。\n关联工具:setup(headless 前置)、disconnect(结束会话)、health(检查状态)',
24
+ description: '启动守护进程(daemon),建立 CLI 与 RemNote Plugin 之间的通信通道。这是所有业务命令(read_rem、edit_rem、search 等)的前置步骤。\n\n适用场景:\n- 开始一次 RemNote 操作会话前,必须先调用此工具\n- 不确定 daemon 是否在运行时,也可安全调用(幂等)\n\n两种模式:\n- 标准模式(默认):启动 daemon 后需要用户在 RemNote 中手动加载 Plugin\n- Headless 模式(headless=true):自动启动 headless Chrome 加载 Plugin,无需用户操作。需先完成 setup(保存登录凭证)\n\n多实例支持:通过 instance 参数启动多个独立实例(最多 4 个),每个实例连接不同的 RemNote 知识库。不指定时默认为 "default"。\n\n输出:返回 JSON,关键字段 ok、alreadyRunning、instance、slotIndex、pid、wsPort、headless。\n幂等:重复调用不会启动多个 daemon。daemon 默认 30 分钟无活动自动关闭。\n关联工具:setup(headless 前置)、disconnect(结束会话)、health(检查状态)',
25
25
  parameters: z.object({
26
26
  headless: z.boolean().optional().describe('启用 headless 模式:自动启动 Chrome 加载 Plugin(需先 setup)'),
27
+ instance: z.string().optional().describe('实例名称(默认 "default"),用于多实例并发连接不同知识库'),
27
28
  }),
28
29
  execute: async (args) => {
29
30
  const flags = [];
30
31
  if (args.headless)
31
32
  flags.push('--headless');
33
+ if (args.instance)
34
+ flags.push('--instance', args.instance);
32
35
  const response = await callCli('connect', undefined, {
33
36
  timeoutMs: 90_000,
34
37
  flags: flags.length > 0 ? flags : undefined,
@@ -41,10 +44,17 @@ export function registerInfraTools(server) {
41
44
  // -------------------------------------------------------------------------
42
45
  server.addTool({
43
46
  name: 'disconnect',
44
- description: '停止守护进程,释放所有端口、清空内存缓存、结束当前会话。\n\n适用场景:\n- 操作完成后主动释放资源\n- 需要重置连接状态(例如排查问题时先 disconnect 再 connect)\n\n输出:返回 JSON,关键字段 ok、wasRunning(之前是否在运行)、pid。\n幂等:daemon 未运行时调用也返回 ok: true。\n所有缓存随 daemon 退出清空,下次 connect 后需重新 read。\n关联工具:connect(启动会话)、health(检查状态)',
45
- parameters: z.object({}),
46
- execute: async () => {
47
- const response = await callCli('disconnect');
47
+ description: '停止守护进程,释放所有端口、清空内存缓存、结束当前会话。\n\n适用场景:\n- 操作完成后主动释放资源\n- 需要重置连接状态(例如排查问题时先 disconnect 再 connect)\n\n多实例支持:通过 instance 参数指定要停止的实例。不指定时停止 "default" 实例。\n\n输出:返回 JSON,关键字段 ok、wasRunning(之前是否在运行)、instance、pid。\n幂等:daemon 未运行时调用也返回 ok: true。\n所有缓存随 daemon 退出清空,下次 connect 后需重新 read。\n关联工具:connect(启动会话)、health(检查状态)',
48
+ parameters: z.object({
49
+ instance: z.string().optional().describe('实例名称(默认 "default"),指定要停止的实例'),
50
+ }),
51
+ execute: async (args) => {
52
+ const flags = [];
53
+ if (args.instance)
54
+ flags.push('--instance', args.instance);
55
+ const response = await callCli('disconnect', undefined, {
56
+ flags: flags.length > 0 ? flags : undefined,
57
+ });
48
58
  return JSON.stringify(response, null, 2);
49
59
  },
50
60
  });
@@ -53,10 +63,11 @@ export function registerInfraTools(server) {
53
63
  // -------------------------------------------------------------------------
54
64
  server.addTool({
55
65
  name: 'health',
56
- description: '检查系统三层状态(daemon 守护进程 / Plugin 连接 / SDK 就绪),用于诊断连接问题。\n\n适用场景:\n- 业务命令失败时,首先调用 health 定位故障层级\n- 执行 connect 后确认通道是否完全就绪\n- 长时间未操作后,检查 daemon 是否仍在运行\n\n输出:返回 JSON,关键字段 ok(三层是否全部健康)、daemon.running、plugin.connected、sdk.ready、timeoutRemaining。headless 模式下额外包含 headless 对象。\n三层有严格依赖:daemon 运行 → Plugin 连接 → SDK 就绪。\nok 为 false 时:daemon 未运行则 connect;Plugin 未连接则确认 RemNote 已打开(或使用 headless 模式);SDK 未就绪则等待重试。\n\n--diagnose 模式(headless 专用):截图 + 详细状态 + console 错误 + 排查建议。\n--reload 模式(headless 专用):重载 headless Chrome 页面。\n\n只读不写,不改变任何状态(--reload 除外)。\n关联工具:connect(启动)、disconnect(结束)',
66
+ description: '检查系统三层状态(daemon 守护进程 / Plugin 连接 / SDK 就绪),用于诊断连接问题。\n\n适用场景:\n- 业务命令失败时,首先调用 health 定位故障层级\n- 执行 connect 后确认通道是否完全就绪\n- 长时间未操作后,检查 daemon 是否仍在运行\n\n多实例支持:通过 instance 参数指定要检查的实例。不指定时检查 "default" 实例。\n\n输出:返回 JSON,关键字段 ok(三层是否全部健康)、instance、slotIndex、daemon.running、plugin.connected、sdk.ready、timeoutRemaining。headless 模式下额外包含 headless 对象。\n三层有严格依赖:daemon 运行 → Plugin 连接 → SDK 就绪。\nok 为 false 时:daemon 未运行则 connect;Plugin 未连接则确认 RemNote 已打开(或使用 headless 模式);SDK 未就绪则等待重试。\n\n--diagnose 模式(headless 专用):截图 + 详细状态 + console 错误 + 排查建议。\n--reload 模式(headless 专用):重载 headless Chrome 页面。\n\n只读不写,不改变任何状态(--reload 除外)。\n关联工具:connect(启动)、disconnect(结束)',
57
67
  parameters: z.object({
58
68
  diagnose: z.boolean().optional().describe('诊断 headless Chrome(截图 + 状态 + console 错误)'),
59
69
  reload: z.boolean().optional().describe('重载 headless Chrome 页面'),
70
+ instance: z.string().optional().describe('实例名称(默认 "default"),指定要检查的实例'),
60
71
  }),
61
72
  execute: async (args) => {
62
73
  const flags = [];
@@ -64,10 +75,47 @@ export function registerInfraTools(server) {
64
75
  flags.push('--diagnose');
65
76
  if (args.reload)
66
77
  flags.push('--reload');
78
+ if (args.instance)
79
+ flags.push('--instance', args.instance);
67
80
  const response = await callCli('health', undefined, {
68
81
  flags: flags.length > 0 ? flags : undefined,
69
82
  });
70
83
  return JSON.stringify(response, null, 2);
71
84
  },
72
85
  });
86
+ // -------------------------------------------------------------------------
87
+ // addon
88
+ // -------------------------------------------------------------------------
89
+ server.addTool({
90
+ name: 'addon',
91
+ description: '管理增强项目(addon):查看状态、安装、卸载。\n\n增强项目是独立安装的可选组件,扩展核心功能(如 remnote-rag 语义搜索)。\n\n操作:\n- action="list":查看所有可用增强项目的状态(已安装/未安装、已启用/已禁用、缺失配置)\n- action="install":安装并启用指定增强项目(name 必填)\n- action="uninstall":卸载指定增强项目(name 必填,可选 purge 清理数据目录)\n\n安装后需在配置页面中填写必需的配置项(如 API Key)。\n不需要 daemon 运行。',
92
+ parameters: z.object({
93
+ action: z.enum(['list', 'install', 'uninstall']).describe('操作类型'),
94
+ name: z.string().optional().describe('增强项目名称(install/uninstall 时必填)'),
95
+ purge: z.boolean().optional().describe('卸载时清理数据目录(仅 uninstall 有效)'),
96
+ }),
97
+ execute: async (args) => {
98
+ const flags = [args.action];
99
+ if (args.name)
100
+ flags.push(args.name);
101
+ if (args.purge)
102
+ flags.push('--purge');
103
+ const response = await callCli('addon', undefined, {
104
+ flags,
105
+ });
106
+ return JSON.stringify(response, null, 2);
107
+ },
108
+ });
109
+ // -------------------------------------------------------------------------
110
+ // clean
111
+ // -------------------------------------------------------------------------
112
+ server.addTool({
113
+ name: 'clean',
114
+ description: '清理所有 daemon 进程、PID 文件、日志、注册表和 addon 数据。\n\n适用场景:\n- daemon 进程残留,disconnect 无法正常停止时\n- 端口被占用,多次启动失败后\n- 注册表损坏需要重置\n- 完整重装,清除所有状态从零开始\n\n⚠️ 破坏性操作——会停止所有 daemon、清空所有缓存和注册表。执行后需重新 connect。\n不需要 daemon 运行。',
115
+ parameters: z.object({}),
116
+ execute: async () => {
117
+ const response = await callCli('clean');
118
+ return JSON.stringify(response, null, 2);
119
+ },
120
+ });
73
121
  }
@@ -9,7 +9,7 @@ export function registerReadTools(server) {
9
9
  // -------------------------------------------------------------------------
10
10
  server.addTool({
11
11
  name: 'search',
12
- description: '在 RemNote 知识库中按关键词全文搜索 Rem,返回匹配结果的摘要列表。\n\n适用场景:知道关键词但不知道位置时使用;按结构浏览应使用 read_globe。\n输出:results 数组,每项包含 remId、text(Markdown 单行)、isDocument,以及 totalFound。\n搜索结果不写入缓存——需要详情请拿 remId 调用 read_rem 或 read_tree。\n中文搜索限制:SDK 分词基于空格,中文多字词可能搜不到,建议用单字重试或改用 read_globe + read_tree 按结构定位。\n常见工作流:search 定位 → read_rem 获取详情 → read_tree 展开子树。\n关联工具:read_rem(详情)、read_tree(子树)、read_globe(按结构浏览)',
12
+ description: '在 RemNote 知识库中搜索 Rem,返回匹配结果列表。\n\n搜索来源(配置驱动):在 ~/.remnote-bridge/config.json 中启用 addons.remnote-rag 后,优先使用语义向量搜索(source:"rag",中文支持更好);未启用或不可用时降级到 SDK 全文搜索(source:"sdk")。\nRAG 模式额外返回:backText、ancestorPath、type、tags、score。\n\n适用场景:知道关键词但不知道位置时使用;按结构浏览应使用 read_globe。\n输出:results 数组,每项包含 remId、textisDocument,以及 totalFound 和 source。\n搜索结果不写入缓存——需要详情请拿 remId 调用 read_rem 或 read_tree。\n常见工作流:search 定位 → read_rem 获取详情 → read_tree 展开子树。\n关联工具:read_rem(详情)、read_tree(子树)、read_globe(按结构浏览)',
13
13
  parameters: z.object({
14
14
  query: z.string().describe('搜索关键词'),
15
15
  numResults: z
@@ -30,7 +30,7 @@ export function registerReadTools(server) {
30
30
  // -------------------------------------------------------------------------
31
31
  server.addTool({
32
32
  name: 'read_rem',
33
- description: '通过 Rem ID 读取单个 Rem 的完整属性,返回标准化的 RemObject(JSON 格式)。\n\n适用场景:\n- 查看 Rem 的详细属性(文本、类型、标签、父子关系、练习方向等)\n- 作为 edit_rem 的前置步骤——必须先 read_rem 建立缓存\n- 不适合查看子树结构(那是 read_tree)\n\n输出:RemObject JSON,默认 34 个常用字段,full=true 返回 51 个,fields 可指定子集。\n关键字段:id, text, backText, type, parent, children, tags, isDocument, practiceDirection。\n结果自动缓存供 edit_rem 使用。默认过滤 Powerup 噪音。\n完整字段列表见 resource://rem-object-fields。\n关联工具:search(定位 remId)→ read_rem → edit_rem(编辑属性)\n区别:read_tree 返回子树大纲,read_rem 返回单个 Rem 的 JSON',
33
+ description: '通过 Rem ID 读取单个 Rem 的完整属性,返回标准化的 RemObject(JSON 格式)。\n\n适用场景:\n- 查看 Rem 的详细属性(文本、类型、标签、父子关系、练习方向等)\n- 作为 edit_rem 的前置步骤——必须先 read_rem 建立缓存\n- 不适合查看子树结构(那是 read_tree)\n\n输出:RemObject JSON,默认 33 个常用字段,full=true 返回 51 个,fields 可指定子集。\n关键字段:id, text, backText, type, parent, children, tags, isDocument, practiceDirection。\n结果自动缓存供 edit_rem 使用。默认过滤 Powerup 噪音。\n完整字段列表见 resource://rem-object-fields。\n关联工具:search(定位 remId)→ read_rem → edit_rem(编辑属性)\n区别:read_tree 返回子树大纲,read_rem 返回单个 Rem 的 JSON',
34
34
  parameters: z.object({
35
35
  remId: z.string().describe('目标 Rem 的 ID'),
36
36
  fields: z
@@ -55,6 +55,13 @@ export function registerReadTools(server) {
55
55
  if (args.includePowerup !== undefined)
56
56
  payload.includePowerup = args.includePowerup;
57
57
  const response = await callCli('read-rem', payload);
58
+ // 直接返回 data(RemObject)——缩进与 edit_rem 的 str_replace 目标格式一致。
59
+ // 附加提示让模型直接复制而非重新构造 oldStr。
60
+ if (response.ok && response.data) {
61
+ const json = JSON.stringify(response.data, null, 2);
62
+ return ('以下是 edit_rem 的 str_replace 操作对象,构造 oldStr 时直接从中复制(含缩进空格):\n\n' +
63
+ json);
64
+ }
58
65
  return JSON.stringify(response, null, 2);
59
66
  },
60
67
  });
@@ -149,7 +156,7 @@ export function registerReadTools(server) {
149
156
  // -------------------------------------------------------------------------
150
157
  server.addTool({
151
158
  name: 'read_context',
152
- description: '读取用户在 RemNote 中的当前上下文视图,生成带面包屑路径的 Markdown 大纲。\n无需指定 remId,自动获取用户当前焦点位置或打开的页面。\n\n适用场景:\n- 用户说"我现在在看什么"、"当前页面是什么"\n- 需要了解用户焦点位置以提供上下文帮助\n- 不适合查看特定 Rem(已知 remId 用 read_tree)\n\n两种模式:\n- focus(默认):以焦点 Rem 为中心的鱼眼视图。焦点完全展开(depth=3),siblings 浅层预览(depth=1),叔伯不展开。焦点行以 * 前缀标记。\n- page:以当前页面为根均匀展开子树。\n\n输出:Markdown 大纲 + 面包屑路径。不缓存。\n前提:focus 模式需用户有焦点 Rem,page 模式需有打开的页面。\n典型工作流:read_context 了解位置 → read_tree/read_rem 深入。',
159
+ description: '读取用户在 RemNote 中的当前上下文视图,生成带面包屑路径的 Markdown 大纲。\n无需指定 remId,自动获取用户当前焦点位置或打开的页面。\n\n适用场景:\n- 用户说"我现在在看什么"、"当前页面是什么"\n- 需要了解用户焦点位置以提供上下文帮助\n- 不适合查看特定 Rem(已知 remId 用 read_tree)\n\n两种模式:\n- focus(默认):以焦点 Rem 为中心的鱼眼视图。焦点完全展开(depth=3),siblings 浅层预览(depth=1),叔伯不展开。焦点行以 * 前缀标记。可通过 focusRemId 指定任意 Rem 作为鱼眼中心,此时不依赖用户焦点。\n- page:以当前页面为根均匀展开子树。\n\n输出:Markdown 大纲 + 面包屑路径。不缓存。\n前提:focus 模式需用户有焦点 Rem 或指定 focusRemId,page 模式需有打开的页面。\n典型工作流:read_context 了解位置 → read_tree/read_rem 深入。',
153
160
  parameters: z.object({
154
161
  mode: z
155
162
  .enum(['focus', 'page'])
@@ -171,6 +178,10 @@ export function registerReadTools(server) {
171
178
  .number()
172
179
  .optional()
173
180
  .describe('同级节点显示上限'),
181
+ focusRemId: z
182
+ .string()
183
+ .optional()
184
+ .describe('指定鱼眼中心 Rem ID(仅 focus 模式,默认使用当前焦点)'),
174
185
  }),
175
186
  execute: async (args) => {
176
187
  const payload = {};
@@ -184,6 +195,8 @@ export function registerReadTools(server) {
184
195
  payload.maxNodes = args.maxNodes;
185
196
  if (args.maxSiblings !== undefined)
186
197
  payload.maxSiblings = args.maxSiblings;
198
+ if (args.focusRemId !== undefined)
199
+ payload.focusRemId = args.focusRemId;
187
200
  const response = await callCli('read-context', payload);
188
201
  const data = response.data;
189
202
  if (data?.outline && typeof data.outline === 'string') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remnote-bridge",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "RemNote 自动化桥接工具集:CLI + MCP Server + Plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  "commander": "^12.1.0",
39
39
  "puppeteer-core": "^24.0.0",
40
40
  "ws": "^8.16.0",
41
- "fastmcp": "latest",
41
+ "fastmcp": "^3.34.0",
42
42
  "zod": "^3.23.0"
43
43
  },
44
44
  "license": "MIT",