remnote-bridge 0.1.11 → 0.1.13

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 (70) 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 +8 -36
  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 +3 -20
  11. package/dist/cli/commands/read-globe.js +3 -20
  12. package/dist/cli/commands/read-rem.js +6 -32
  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/edit-handler.js +49 -140
  23. package/dist/cli/handlers/read-handler.js +9 -9
  24. package/dist/cli/handlers/rem-cache.js +10 -5
  25. package/dist/cli/handlers/tree-parser.js +16 -9
  26. package/dist/cli/main.js +67 -19
  27. package/dist/cli/protocol.js +18 -4
  28. package/dist/cli/server/config-server.js +280 -14
  29. package/dist/cli/server/ws-server.js +93 -44
  30. package/dist/cli/utils/output.js +29 -0
  31. package/dist/mcp/format.js +43 -0
  32. package/dist/mcp/index.js +0 -55
  33. package/dist/mcp/instructions.js +424 -216
  34. package/dist/mcp/resources/edit-rem-guide.js +37 -158
  35. package/dist/mcp/resources/edit-tree-guide.js +1 -1
  36. package/dist/mcp/resources/error-reference.js +9 -13
  37. package/dist/mcp/resources/rem-object-fields.js +6 -6
  38. package/dist/mcp/tools/edit-tools.js +69 -8
  39. package/dist/mcp/tools/infra-tools.js +44 -8
  40. package/dist/mcp/tools/read-tools.js +136 -20
  41. package/package.json +2 -2
  42. package/remnote-plugin/dist/bridge_widget-sandbox.js +17 -17
  43. package/remnote-plugin/dist/bridge_widget.js +17 -17
  44. package/remnote-plugin/dist/index-sandbox.js +31 -31
  45. package/remnote-plugin/dist/index.js +31 -31
  46. package/remnote-plugin/dist/manifest.json +1 -1
  47. package/remnote-plugin/package.json +1 -1
  48. package/remnote-plugin/public/manifest.json +1 -1
  49. package/remnote-plugin/src/bridge/multi-connection-manager.ts +151 -0
  50. package/remnote-plugin/src/bridge/websocket-client.ts +62 -16
  51. package/remnote-plugin/src/services/index.ts +0 -8
  52. package/remnote-plugin/src/services/read-rem.ts +1 -9
  53. package/remnote-plugin/src/services/search.ts +13 -10
  54. package/remnote-plugin/src/settings.ts +9 -7
  55. package/remnote-plugin/src/utils/index.ts +0 -5
  56. package/remnote-plugin/src/widgets/bridge_widget.tsx +105 -20
  57. package/remnote-plugin/src/widgets/index.tsx +41 -44
  58. package/remnote-plugin/webpack.config.js +35 -0
  59. package/skills/remnote-bridge/SKILL.md +45 -40
  60. package/skills/remnote-bridge/instructions/addon.md +134 -0
  61. package/skills/remnote-bridge/instructions/clean.md +110 -0
  62. package/skills/remnote-bridge/instructions/connect.md +80 -37
  63. package/skills/remnote-bridge/instructions/disconnect.md +22 -9
  64. package/skills/remnote-bridge/instructions/edit-rem.md +113 -327
  65. package/skills/remnote-bridge/instructions/health.md +23 -13
  66. package/skills/remnote-bridge/instructions/install-skill.md +58 -0
  67. package/skills/remnote-bridge/instructions/overall.md +99 -35
  68. package/skills/remnote-bridge/instructions/read-rem.md +15 -15
  69. package/skills/remnote-bridge/instructions/search.md +77 -18
  70. package/skills/remnote-bridge/instructions/setup.md +5 -6
@@ -6,11 +6,19 @@
6
6
 
7
7
  ## 功能
8
8
 
9
- `health` 分两步检查系统状态:
9
+ `health` 分两步检查指定实例的系统状态:
10
10
 
11
- 1. **本地检查**:读取 PID 文件,确认 daemon 进程是否存活
11
+ 1. **本地检查**:通过注册表查找实例,确认 daemon 进程是否存活
12
12
  2. **远程检查**:通过 WS 连接 daemon,获取 Plugin 连接状态和 SDK 就绪状态
13
13
 
14
+ ### 多实例支持
15
+
16
+ 通过 `--instance <name>` 指定要检查的实例。不指定时检查 `default` 实例。
17
+
18
+ ```bash
19
+ remnote-bridge health --instance work
20
+ ```
21
+
14
22
  ---
15
23
 
16
24
  ## 用法
@@ -24,7 +32,7 @@ remnote-bridge health
24
32
  输出示例(全部健康):
25
33
 
26
34
  ```
27
- ✅ 守护进程 运行中(PID: 12345,已运行 5 分钟)
35
+ ✅ 守护进程 运行中(PID: 12345,实例: default,槽位: 0,已运行 5 分钟)
28
36
  ✅ Plugin 已连接
29
37
  ✅ SDK 就绪
30
38
 
@@ -34,7 +42,7 @@ remnote-bridge health
34
42
  输出示例(部分不健康):
35
43
 
36
44
  ```
37
- ✅ 守护进程 运行中(PID: 12345,已运行 2 分钟)
45
+ ✅ 守护进程 运行中(PID: 12345,实例: work,槽位: 1,已运行 2 分钟)
38
46
  ❌ Plugin 未连接
39
47
  ❌ SDK 未就绪
40
48
 
@@ -80,11 +88,12 @@ remnote-bridge health --reload
80
88
  "ok": true,
81
89
  "command": "health",
82
90
  "exitCode": 0,
91
+ "instance": "default",
92
+ "slotIndex": 0,
83
93
  "daemon": { "running": true, "pid": 12345, "reachable": true, "uptime": 300 },
84
94
  "plugin": { "connected": true },
85
95
  "sdk": { "ready": true },
86
- "timeoutRemaining": 1500,
87
- "timestamp": "2026-03-06T10:00:00.000Z"
96
+ "timeoutRemaining": 1500
88
97
  }
89
98
  ```
90
99
 
@@ -95,11 +104,12 @@ remnote-bridge health --reload
95
104
  "ok": false,
96
105
  "command": "health",
97
106
  "exitCode": 1,
107
+ "instance": "work",
108
+ "slotIndex": 1,
98
109
  "daemon": { "running": true, "pid": 12345, "reachable": true, "uptime": 120 },
99
110
  "plugin": { "connected": false },
100
111
  "sdk": { "ready": false },
101
- "timeoutRemaining": 1680,
102
- "timestamp": "2026-03-06T10:00:00.000Z"
112
+ "timeoutRemaining": 1680
103
113
  }
104
114
  ```
105
115
 
@@ -110,10 +120,10 @@ remnote-bridge health --reload
110
120
  "ok": false,
111
121
  "command": "health",
112
122
  "exitCode": 2,
123
+ "instance": "default",
113
124
  "daemon": { "running": false },
114
125
  "plugin": { "connected": false },
115
- "sdk": { "ready": false },
116
- "timestamp": "2026-03-06T10:00:00.000Z"
126
+ "sdk": { "ready": false }
117
127
  }
118
128
  ```
119
129
 
@@ -123,7 +133,7 @@ remnote-bridge health --reload
123
133
 
124
134
  | 检查项 | 检查方式 | 含义 |
125
135
  |--------|----------|------|
126
- | **daemon** | PID 文件 + `kill(pid, 0)` 探活 | 守护进程是否在运行且可达 |
136
+ | **daemon** | 注册表查找 + `kill(pid, 0)` 探活 | 守护进程是否在运行且可达 |
127
137
  | **plugin** | daemon 内部的 `pluginConnected` 状态 | RemNote Plugin 是否已通过 WS 连接到 daemon |
128
138
  | **sdk** | Plugin 的 hello 握手中的 `sdkReady` 字段 | RemNote SDK 是否就绪(知识库已加载,可调用 API) |
129
139
 
@@ -179,7 +189,7 @@ headless 模式下 `health` 基础输出额外包含 `headless` 对象:
179
189
  "headless": {
180
190
  "status": "running",
181
191
  "chromeConnected": true,
182
- "pageUrl": "http://localhost:8080",
192
+ "pageUrl": "http://localhost:29101",
183
193
  "reloadCount": 0,
184
194
  "lastError": null,
185
195
  "recentConsoleErrors": []
@@ -218,7 +228,7 @@ headless 模式下 `health` 基础输出额外包含 `headless` 对象:
218
228
  | 症状 | 可能原因 | 解决方案 |
219
229
  |------|----------|----------|
220
230
  | daemon 未运行 | 未执行 connect / 已超时关闭 | 执行 `connect` |
221
- | daemon 运行但不可达 | WS 端口被占用或配置不匹配 | 检查 `.remnote-bridge.json` 中的 `wsPort` |
231
+ | daemon 运行但不可达 | WS 端口被占用或配置不匹配 | 检查 `~/.remnote-bridge/slots.json` 中的端口配置 |
222
232
  | Plugin 未连接(标准模式) | RemNote 未打开 / Plugin 未安装 / URL 不匹配 | 打开 RemNote,确认 Plugin 中的 WS URL 设置 |
223
233
  | Plugin 未连接(headless 模式) | Chrome 页面加载异常 | `health --diagnose` 查看截图和状态,`health --reload` 重载页面 |
224
234
  | SDK 未就绪 | 知识库加载中 / Plugin 异常 | 等待几秒后重试,或刷新 RemNote 页面 |
@@ -0,0 +1,58 @@
1
+ # install-skill
2
+
3
+ > 将 remnote-bridge Skill 安装到 AI Agent 环境中。
4
+
5
+ ---
6
+
7
+ ## 功能
8
+
9
+ `install-skill` 将 Skill 文档(SKILL.md + instructions/*.md)安装到 AI Agent 可发现的位置。支持两种安装方式:
10
+
11
+ | 方式 | 说明 |
12
+ |:-----|:-----|
13
+ | Vercel Skills CLI(默认) | 通过 `npx skills add` 安装,适用于支持 Vercel Skills 生态的 Agent |
14
+ | Claude Code 直接复制(fallback) | 将文件复制到 `~/.claude/skills/remnote-bridge/`,仅适用于 Claude Code |
15
+
16
+ ---
17
+
18
+ ## 用法
19
+
20
+ ```bash
21
+ # 通过 Vercel Skills CLI 安装(推荐)
22
+ remnote-bridge install-skill
23
+
24
+ # 直接复制到 Claude Code skills 目录
25
+ remnote-bridge install-skill-copy
26
+ ```
27
+
28
+ ---
29
+
30
+ ## 安装逻辑
31
+
32
+ ### install-skill
33
+
34
+ 1. 检测 `npx` 是否可用
35
+ 2. 可用 → 执行 `npx skills add baobao700508/unofficial-remnote-bridge-cli -s remnote-bridge`
36
+ 3. 不可用或执行失败 → 自动 fallback 到文件复制模式
37
+
38
+ ### install-skill-copy
39
+
40
+ 直接将 Skill 文件从 npm 包内复制到 `~/.claude/skills/remnote-bridge/`:
41
+ - `SKILL.md`
42
+ - `instructions/*.md`(所有 Markdown 文件)
43
+
44
+ ---
45
+
46
+ ## 退出码
47
+
48
+ | 退出码 | 含义 |
49
+ |:-------|:-----|
50
+ | 0 | 安装成功 |
51
+ | 1 | 安装失败(找不到源文件、权限不足等) |
52
+
53
+ ---
54
+
55
+ ## AI Agent 注意事项
56
+
57
+ - 此命令通常由用户在初始设置时手动执行,Agent 一般不需要调用
58
+ - 安装完成后,Agent 即可通过 Skill 接口发现和使用 remnote-bridge 功能
@@ -128,9 +128,9 @@ Rem 有两个**独立维度**的类型:
128
128
  |:-----|:-----|:-----|
129
129
  | 创建 Portal | `edit-tree` | 新增行 `<!--portal refs:id1,id2-->` |
130
130
  | 删除 Portal | `edit-tree` | 从大纲中移除 Portal 行(与删除普通行相同) |
131
- | 修改引用列表(增删引用的 Rem) | `edit-rem` | str_replace 简化 JSON 中的 `portalDirectlyIncludedRem` 数组 |
131
+ | 修改引用列表(增删引用的 Rem) | `edit-rem` | 直接修改 changes 中的 `portalDirectlyIncludedRem` 数组 |
132
132
  | 移动 Portal(换父节点/位置) | `edit-tree` | 与移动普通行相同 |
133
- | 读取 Portal | `read-rem` | 自动输出 9 字段简化 JSON |
133
+ | 读取 Portal | `read-rem` | 自动输出 8 字段简化 JSON |
134
134
 
135
135
  ### 2.6 Powerup 机制简述
136
136
 
@@ -199,19 +199,53 @@ health → 确认三层就绪 → 会话可用
199
199
  disconnect → daemon 关闭 → 会话结束,缓存清空
200
200
  ```
201
201
 
202
- > **重要**:`connect` 成功只意味着 daemon 已启动,Plugin 并未自动连接。首次使用需用户在 RemNote「开发你的插件」中填入 `http://localhost:8080`;非首次只需刷新 RemNote 页面。必须引导用户完成此步后再用 `health` 确认就绪。
202
+ > **重要**:`connect` 成功只意味着 daemon 已启动,Plugin 并未自动连接。首次使用需用户在 RemNote「开发你的插件」中填入对应的 Plugin 服务地址;非首次只需刷新 RemNote 页面。必须引导用户完成此步后再用 `health` 确认就绪。
203
203
 
204
- `connect` 启动三个服务:
204
+ `connect` 启动三个服务,端口由槽位自动分配:
205
205
 
206
- | 服务 | 默认端口 | 用途 |
207
- |:-----|:---------|:-----|
208
- | WS Server | 3002 | CLI ↔ daemon ↔ Plugin 通信 |
209
- | Plugin 服务 | 8080 | 加载 Plugin 到 RemNote(默认静态服务器,`--dev` 时为 webpack-dev-server) |
210
- | ConfigServer | 3003 | HTTP 配置界面 |
206
+ | 服务 | 槽位 0 端口 | 用途 |
207
+ |:-----|:-----------|:-----|
208
+ | WS Server | 29100 | CLI ↔ daemon ↔ Plugin 通信 |
209
+ | Plugin 服务 | 29101 | 加载 Plugin 到 RemNote(默认静态服务器,`--dev` 时为 webpack-dev-server) |
210
+ | ConfigServer | 29102 | HTTP 配置界面 |
211
211
 
212
212
  超时机制:daemon 默认 **30 分钟无活动**自动关闭。每次收到 CLI 请求时重置计时器。
213
213
 
214
- ### 3.3 健康状态
214
+ ### 3.3 多实例支持
215
+
216
+ 系统支持最多 **4 个并发实例**,每个实例连接不同的 RemNote 知识库。
217
+
218
+ ```bash
219
+ # 启动默认实例
220
+ remnote-bridge connect
221
+
222
+ # 启动额外实例
223
+ remnote-bridge connect --instance work
224
+ remnote-bridge connect --instance personal
225
+
226
+ # 查看指定实例状态
227
+ remnote-bridge health --instance work
228
+
229
+ # 停止指定实例
230
+ remnote-bridge disconnect --instance work
231
+ ```
232
+
233
+ **槽位分配**:每个实例启动时自动占用第一个空闲槽位,端口固定分配:
234
+
235
+ | 槽位 | WS 端口 | Plugin 服务端口 | 配置端口 |
236
+ |:-----|:--------|:---------------|:---------|
237
+ | 0 | 29100 | 29101 | 29102 |
238
+ | 1 | 29110 | 29111 | 29112 |
239
+ | 2 | 29120 | 29121 | 29122 |
240
+ | 3 | 29130 | 29131 | 29132 |
241
+
242
+ **实例名解析优先级**:CLI `--instance` > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default`。
243
+
244
+ **Plugin 自动发现**:Plugin 启动后通过 `/api/discovery` 获取其孪生 daemon 的连接信息(WS 端口、槽位索引等),自动建立连接。一个 Plugin 可同时连接最多 4 个 daemon。
245
+
246
+ **业务命令的实例选择**:`read-rem`、`edit-rem` 等业务命令自动路由到 `--instance` 指定的实例(默认 `default`)。各实例的缓存相互独立。
247
+
248
+ ### 3.4 健康状态
215
249
 
216
250
  `health` 命令检查三层状态:
217
251
 
@@ -233,9 +267,12 @@ disconnect → daemon 关闭 → 会话结束,缓存清空
233
267
 
234
268
  | 命令 | 功能 | 需要 Plugin | 详细文档 |
235
269
  |:-----|:-----|:------------|:---------|
236
- | `connect` | 启动守护进程 | 否 | `connect.md` |
237
- | `health` | 检查系统状态 | 否 | `health.md` |
238
- | `disconnect` | 停止守护进程 | 否 | `disconnect.md` |
270
+ | `connect` | 启动守护进程(支持 `--instance`) | 否 | `connect.md` |
271
+ | `health` | 检查系统状态(支持 `--instance`) | 否 | `health.md` |
272
+ | `disconnect` | 停止守护进程(支持 `--instance`) | 否 | `disconnect.md` |
273
+ | `addon` | 管理增强项目(list/install/uninstall) | 否 | `addon.md` |
274
+ | `clean` | 清理所有进程、缓存和注册表 | 否 | `clean.md` |
275
+ | `install-skill` | 安装 Skill 到 Agent 环境 | 否 | `install-skill.md` |
239
276
 
240
277
  #### 读取命令
241
278
 
@@ -251,7 +288,7 @@ disconnect → daemon 关闭 → 会话结束,缓存清空
251
288
 
252
289
  | 命令 | 功能 | 前置条件 | 安全机制 | 详细文档 |
253
290
  |:-----|:-----|:---------|:---------|:---------|
254
- | `edit-rem` | str_replace 编辑 Rem JSON 字段 | 先 `read-rem` | 三道防线 | `edit-rem.md` |
291
+ | `edit-rem` | 直接修改 Rem 属性字段 | 先 `read-rem` | 两道防线 + 字段白名单 | `edit-rem.md` |
255
292
  | `edit-tree` | str_replace 编辑树结构 | 先 `read-tree` | 三道防线 + diff | `edit-tree.md` |
256
293
 
257
294
  ### 4.2 探索决策指南
@@ -373,7 +410,7 @@ Agent 需要根据用户意图选择正确的读取命令:
373
410
  ```bash
374
411
  read-rem kLrIOHJLyMd8Y2lyA --fields text,type
375
412
  read-tree kLrIOHJLyMd8Y2lyA --depth 2
376
- edit-rem kLrIOHJLyMd8Y2lyA --old-str '"concept"' --new-str '"descriptor"'
413
+ edit-rem kLrIOHJLyMd8Y2lyA --changes '{"type":"descriptor"}'
377
414
  ```
378
415
 
379
416
  - 位置参数 = remId 或关键词
@@ -385,7 +422,7 @@ edit-rem kLrIOHJLyMd8Y2lyA --old-str '"concept"' --new-str '"descriptor"'
385
422
  ```bash
386
423
  read-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","fields":["text","type"]}'
387
424
  read-tree --json '{"remId":"kLrIOHJLyMd8Y2lyA","depth":2}'
388
- edit-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","oldStr":"\"concept\"","newStr":"\"descriptor\""}'
425
+ edit-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","changes":{"type":"descriptor"}}'
389
426
  ```
390
427
 
391
428
  - **位置参数 = JSON 字符串**,所有参数打包在 JSON 对象中
@@ -454,8 +491,8 @@ RemObject 是本项目对 RemNote Rem 的标准化表示,包含 51 个字段
454
491
  | 标记 | 含义 | 数量 | 输出条件 |
455
492
  |:-----|:-----|:-----|:---------|
456
493
  | RW | 可读可写 | 20 | 默认输出 |
457
- | R | 只读 | 14 | 默认输出 |
458
- | R-F | 只读低频 | 17 | 仅 `--full` 输出 |
494
+ | R | 只读 | 13 | 默认输出 |
495
+ | R-F | 只读低频 | 18 | 仅 `--full` 输出 |
459
496
 
460
497
  ### 7.2 核心字段速览
461
498
 
@@ -463,7 +500,7 @@ RemObject 是本项目对 RemNote Rem 的标准化表示,包含 51 个字段
463
500
  标识: id
464
501
  内容: text [RW], backText [RW]
465
502
  类型: type [RW], isDocument [RW]
466
- 结构: parent [RW], children [R]
503
+ 结构: parent [RW], children [R-F]
467
504
  格式: fontSize [RW], highlightColor [RW]
468
505
  状态: isTodo [RW], todoStatus [RW], isCode [RW], isQuote [RW],
469
506
  isListItem [RW], isCardItem [RW], isSlot [RW], isProperty [RW]
@@ -534,11 +571,18 @@ Portal:portalType [R], portalDirectlyIncludedRem [Portal-W]
534
571
  { "i": "a", "onlyAudio": true, "url": "..." } // 音频
535
572
  ```
536
573
 
537
- > 在 RemObject 格式化 JSON 中,数组内对象展开为多行。构造 edit-rem 的 oldStr/newStr 必须用多行格式。
574
+ > 在 RemObject 格式化 JSON 中,数组内对象展开为多行。构造 edit-tree 的 oldStr/newStr 或 edit-rem 的 changes 中 RichText 值时,需注意多行格式。
575
+
576
+ **⚠️ highlightColor vs h — 两种完全不同的高亮**:
577
+
578
+ | 属性 | 位置 | 值类型 | 效果 |
579
+ |:-----|:-----|:-------|:-----|
580
+ | `highlightColor` | RemObject 顶层字段 | 字符串 `"Yellow"`/`"Red"` 等或 `null` | 整行背景色(左侧彩色竖条) |
581
+ | `h` | RichText 元素内部 | 数字 0-9 | 文字片段荧光底色 |
538
582
 
539
- **highlightColor vs h**:`highlightColor` RemObject 顶层字段(字符串如 `"Red"`),`h` RichText 行内格式标记(数字 0-9)。两者独立。
583
+ `h` 颜色值:0=无, 1=Red, 2=Orange, 3=Yellow, 4=Green, 5=Purple, 6=Blue, 7=Gray, 8=Brown, 9=Pink。
540
584
 
541
- **序列化确定性**:RichText 对象内部按 key 字母序排列(`sortRichTextKeys()`)。`_id` 的 `_`(U+005F)排在所有小写字母之前。这对 edit-rem 的 str_replace 和并发检测至关重要。
585
+ **序列化确定性**:RichText 对象内部按 key 字母序排列(`sortRichTextKeys()`)。`_id` 的 `_`(U+005F)排在所有小写字母之前。这对乐观并发检测和 edit-tree 的 str_replace 至关重要。
542
586
 
543
587
  ---
544
588
 
@@ -646,7 +690,7 @@ read-tree / read-globe / read-context 的输出核心是 Markdown 大纲文本
646
690
 
647
691
  | 前缀 | 用途 | 写入命令 |
648
692
  |:-----|:-----|:---------|
649
- | `rem:{remId}` | RemObject JSON | read-rem |
693
+ | `rem:{remId}` | RemObject 对象 | read-rem |
650
694
  | `tree:{remId}` | Markdown 大纲 | read-tree |
651
695
  | `tree-depth:{remId}` 等 | read-tree 参数 | read-tree |
652
696
 
@@ -654,9 +698,9 @@ read-tree / read-globe / read-context 的输出核心是 Markdown 大纲文本
654
698
  - disconnect 关闭 daemon 时缓存自动消失
655
699
  - **没有 TTL**——三道防线的并发检测已能捕获所有陈旧数据
656
700
 
657
- ### 9.2 三道防线
701
+ ### 9.2 安全防线
658
702
 
659
- `edit-rem` 和 `edit-tree` 使用 str_replace 语义编辑数据。为防止数据损坏,实施三道防线:
703
+ `edit-rem` 和 `edit-tree` 编辑数据时,实施多道防线防止数据损坏:
660
704
 
661
705
  #### 防线 1:缓存存在性检查
662
706
 
@@ -674,7 +718,16 @@ edit 时重新从 SDK 读取最新数据 → 与缓存严格比较
674
718
 
675
719
  如果 Rem 在 read 之后被外部修改(用户在 RemNote UI 中编辑、其他 Agent 修改等),数据不一致时拒绝编辑,**且不更新缓存**——迫使 Agent 重新 read。
676
720
 
677
- #### 防线 3:str_replace 精确匹配
721
+ #### edit-rem 防线 3:字段白名单校验
722
+
723
+ ```
724
+ changes 中的字段必须在 RW 白名单内,枚举值必须合法
725
+ ```
726
+
727
+ - 只读字段(R / R-F)写入时**警告跳过**,不阻断其他字段
728
+ - 枚举值非法时**报错拒绝**(如 `type: "invalid"`)
729
+
730
+ #### edit-tree 防线 3:str_replace 精确匹配
678
731
 
679
732
  ```
680
733
  oldStr 必须在目标文本中恰好匹配 1 次
@@ -689,7 +742,8 @@ oldStr 必须在目标文本中恰好匹配 1 次
689
742
  | 场景 | 缓存行为 |
690
743
  |:-----|:---------|
691
744
  | 写入全部成功 | 从 SDK 重新读取最新状态 → **更新缓存** |
692
- | 防线拒绝 | **不更新缓存**(迫使 Agent 重新 read) |
745
+ | 防线拒绝(缓存缺失 / 并发冲突) | **不更新缓存**(迫使 Agent 重新 read) |
746
+ | 枚举值非法 | **报错拒绝**,不更新缓存 |
693
747
  | 部分写入失败 | **不更新缓存** |
694
748
 
695
749
  ---
@@ -751,13 +805,10 @@ RemNote 的格式设置通过 Powerup 机制实现,会向 Rem 注入隐藏的
751
805
 
752
806
  ## 12. 配置系统
753
807
 
754
- 配置文件位于项目根目录:`.remnote-bridge.json`
808
+ 配置文件位于全局目录:`~/.remnote-bridge/config.json`(所有实例共享)。
755
809
 
756
810
  ```json
757
811
  {
758
- "wsPort": 3002,
759
- "devServerPort": 8080,
760
- "configPort": 3003,
761
812
  "daemonTimeoutMinutes": 30,
762
813
  "defaults": {
763
814
  "maxNodes": 200,
@@ -776,7 +827,21 @@ RemNote 的格式设置通过 Powerup 机制实现,会向 Rem 注入隐藏的
776
827
  ```
777
828
 
778
829
  - 文件不存在时使用全部默认值
779
- - 三个端口不允许冲突
830
+ - 端口由槽位自动分配(`~/.remnote-bridge/slots.json`),通常无需手动配置
831
+
832
+ ### 多实例文件布局
833
+
834
+ ```
835
+ ~/.remnote-bridge/
836
+ ├── config.json — 全局配置(所有实例共享)
837
+ ├── slots.json — 槽位端口定义(自动生成)
838
+ ├── registry.json — 实例→槽位映射(运行时维护)
839
+ └── instances/
840
+ ├── 0.pid / 0.log — 槽位 0 的 PID 和日志
841
+ ├── 1.pid / 1.log — 槽位 1
842
+ ├── 2.pid / 2.log — 槽位 2
843
+ └── 3.pid / 3.log — 槽位 3
844
+ ```
780
845
 
781
846
  ---
782
847
 
@@ -799,13 +864,12 @@ Agent 遇到错误时的诊断和恢复指南:
799
864
  | has not been read yet | 未先执行 read-rem / read-tree | 执行对应 read 命令后重试 |
800
865
  | has been modified since last read | Rem 在 read 和 edit 之间被外部修改 | 重新执行 read 获取最新状态后重试 |
801
866
 
802
- ### str_replace 错误
867
+ ### edit-tree str_replace 错误
803
868
 
804
869
  | 错误 | 原因 | 恢复 |
805
870
  |:-----|:-----|:-----|
806
- | old_str not found | oldStr 在目标文本中不存在 | 检查 oldStr 是否精确匹配(含引号、空格、换行) |
871
+ | old_str not found | oldStr 在目标文本中不存在 | 检查 oldStr 是否精确匹配(含空格、换行、缩进) |
807
872
  | old_str matches N locations | oldStr 匹配到多个位置 | 扩大 oldStr 范围,包含更多上下文以唯一定位 |
808
- | invalid JSON | 替换后的文本不是合法 JSON | 检查 newStr 的引号、逗号、括号完整性 |
809
873
 
810
874
  ### edit-tree 专用错误
811
875
 
@@ -9,7 +9,7 @@
9
9
  `read-rem` 通过 Rem ID 读取一个 Rem 的所有可获取属性,返回标准化的 RemObject。读取结果会被缓存在 daemon 内存中,供后续 `edit-rem` 使用。
10
10
 
11
11
  核心能力:
12
- - 返回 51 个字段的完整 Rem 数据(默认 34 个,Portal 简化 9 个,`--full` 时 51 个)
12
+ - 返回 51 个字段的完整 Rem 数据(默认 33 个,Portal 简化 8 个,`--full` 时 51 个)
13
13
  - 支持 `--fields` 指定字段子集
14
14
  - 支持 Powerup 噪音过滤(默认过滤)
15
15
  - 自动缓存,为 `edit-rem` 建立编辑基础
@@ -76,12 +76,11 @@ remnote-bridge read-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA"}'
76
76
  "type": "concept",
77
77
  "isDocument": false,
78
78
  "parent": "parentRemId",
79
- "children": ["childId1", "childId2"],
80
79
  "fontSize": null,
81
80
  "highlightColor": null,
82
81
  "isTodo": false,
83
82
  "todoStatus": null,
84
- "...": "(共 34 个字段,--full 时 51 个)"
83
+ "...": "(共 33 个字段,--full 时 51 个)"
85
84
  },
86
85
  "timestamp": "2026-03-06T10:00:00.000Z"
87
86
  }
@@ -159,8 +158,8 @@ remnote-bridge read-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA"}'
159
158
  ├─ 字段过滤:
160
159
  │ ├─ --full → 返回全部 51 字段
161
160
  │ ├─ --fields → 返回指定字段 + id
162
- │ ├─ type=portal → Portal 简化模式(返回 9 个关键字段)
163
- │ └─ 默认 → 排除 R-F 字段(返回 34 字段)
161
+ │ ├─ type=portal → Portal 简化模式(返回 8 个关键字段)
162
+ │ └─ 默认 → 排除 R-F 字段(返回 33 字段)
164
163
  └─ 附加 _cacheOverridden 元数据(若之前有缓存)
165
164
  4. CLI 格式化输出(人类模式 pretty-print / JSON 模式单行)
166
165
  ```
@@ -172,8 +171,8 @@ remnote-bridge read-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA"}'
172
171
  RemObject 共 51 个字段,按读写权限分为三类:
173
172
 
174
173
  - **RW**(20 个):可读可写,SDK 有对应的 setter
175
- - **R**(14 个):只读,默认输出
176
- - **R-F**(17 个):只读,仅 `--full` 模式输出(低频 / 可由其他字段推导)
174
+ - **R**(13 个):只读,默认输出
175
+ - **R-F**(18 个):只读,仅 `--full` 模式输出(低频 / 可由其他字段推导)
177
176
 
178
177
  ### 核心标识
179
178
 
@@ -200,7 +199,7 @@ RemObject 共 51 个字段,按读写权限分为三类:
200
199
  | 字段 | 类型 | 权限 | 说明 |
201
200
  |------|------|:----:|------|
202
201
  | `parent` | `string \| null` | RW | 父 Rem ID。null=顶级。UI:Rem 从原位置消失,出现在新父级下 |
203
- | `children` | `string[]` | R | 子 Rem ID 有序数组 |
202
+ | `children` | `string[]` | R-F | 子 Rem ID 有序数组 |
204
203
 
205
204
  ### 格式 / 显示
206
205
 
@@ -377,14 +376,15 @@ text | number | date | checkbox | single_select | multi_select | url | image | t
377
376
 
378
377
  | 模式 | 输出字段数 | 说明 |
379
378
  |------|:----------:|------|
380
- | 默认 | 34 | RW + R 字段,覆盖常用场景 |
381
- | Portal 简化 | 9 | type=portal 时自动使用(id、type、portalType、portalDirectlyIncludedRem、parent、positionAmongstSiblings、children、createdAt、updatedAt)。`--full` / `--fields` 可覆盖 |
379
+ | 默认 | 33 | RW + R 字段,覆盖常用场景 |
380
+ | Portal 简化 | 8 | type=portal 时自动使用(id、type、portalType、portalDirectlyIncludedRem、parent、positionAmongstSiblings、createdAt、updatedAt)。`--full` / `--fields` 可覆盖 |
382
381
  | `--full` | 51 | 全部字段(含 R-F 低频字段) |
383
382
  | `--fields` | 自选 + id | 仅返回指定字段(始终包含 id) |
384
383
 
385
384
  ### R-F 字段列表(默认不输出,`--full` 时输出)
386
385
 
387
386
  ```
387
+ children,
388
388
  isPowerup, isPowerupEnum, isPowerupProperty, isPowerupPropertyListItem, isPowerupSlot,
389
389
  deepRemsBeingReferenced,
390
390
  ancestorTagRem, descendantTagRem,
@@ -422,7 +422,7 @@ localUpdatedAt, lastPracticed
422
422
  | `tags` | `rem.addTag()` / `rem.removeTag()` | **Diff 机制**:对比当前 vs 目标,增删差异项。必须列出完整目标数组,缺少的会被删除 |
423
423
  | `sources` | `rem.addSource()` / `rem.removeSource()` | **Diff 机制**:同 tags |
424
424
  | `positionAmongstSiblings` | `rem.setParent(parent, position)` | 与 `parent` 联动(见下方说明) |
425
- | `portalDirectlyIncludedRem` | `rem.addToPortal()` / `rem.removeFromPortal()` | string[] | **Portal-W Diff 机制**:仅 type=portal 时可修改。对比当前 vs 目标数组,逐项增删。调用方向:在被引用 Rem 上调用,参数是 Portal Rem |
425
+ | `portalDirectlyIncludedRem` | `rem.addToPortal()` / `rem.removeFromPortal()` | **Portal-W Diff 机制**:仅 type=portal 时可修改。对比当前 vs 目标数组,逐项增删 |
426
426
 
427
427
  ### parent + positionAmongstSiblings 联动
428
428
 
@@ -434,7 +434,7 @@ localUpdatedAt, lastPracticed
434
434
  | 只有 `parent` 变更 | `setParent(newParent)` 不带 position(保持末尾) |
435
435
  | 只有 `positionAmongstSiblings` 变更 | 获取当前 parent → `setParent(currentParent, newPosition)` |
436
436
 
437
- **应在同一次 str_replace 中同时修改这两个字段。**
437
+ **应在同一次 edit-rem 的 changes 中同时传入这两个字段。**
438
438
 
439
439
  ---
440
440
 
@@ -561,7 +561,7 @@ RemObject 中的 `text` 和 `backText` 字段使用 RichText 格式——一个
561
561
 
562
562
  ### 序列化确定性
563
563
 
564
- RichText 对象元素内部按 **key 字母序排列**(Plugin 端 `sortRichTextKeys()` 处理),确保同一内容的序列化 JSON 始终一致。这对 `edit-rem` 的 str_replace 和乐观并发检测至关重要。
564
+ RichText 对象元素内部按 **key 字母序排列**(Plugin 端 `sortRichTextKeys()` 处理),确保同一内容的序列化 JSON 始终一致。这对乐观并发检测至关重要。
565
565
 
566
566
  - `_`(下划线)在 Unicode 中排在所有小写字母之前(`_` = U+005F,`a` = U+0061),所以 `_id` 总是排在第一位
567
567
  - 排序由 `Object.keys().sort()` 决定,即 JavaScript 默认的 Unicode 字典序
@@ -589,11 +589,11 @@ RichText 对象元素内部按 **key 字母序排列**(Plugin 端 `sortRichTex
589
589
  |------|------|
590
590
  | 读取成功 | 完整 JSON 写入缓存 `cache.set('rem:' + remId, fullJson)` |
591
591
  | 已有缓存 | 覆盖旧缓存,返回 `cacheOverridden` 元数据 |
592
- | 缓存用途 | 供 `edit-rem` 的三道防线使用(存在性检查 + 乐观并发检测 + str_replace) |
592
+ | 缓存用途 | 供 `edit-rem` 的防线检查使用(存在性检查 + 乐观并发检测) |
593
593
  | 缓存存储 | daemon 内存中的 LRU 缓存(最大 200 条目) |
594
594
  | 缓存清空 | daemon 关闭时自动消失 |
595
595
 
596
- **重要**:缓存存储的是 **完整 RemObject**(含 R-F 字段),不受 `--fields` / `--full` 选项影响。字段过滤仅作用于返回给 CLI 的输出。
596
+ **重要**:缓存存储的是 **完整 RemObject 对象**(含 R-F 字段),不受 `--fields` / `--full` 选项影响。字段过滤仅作用于返回给 CLI 的输出。
597
597
 
598
598
  ---
599
599