remnote-bridge 0.1.13 → 0.1.15

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 (42) hide show
  1. package/README.md +147 -28
  2. package/README.zh-CN.md +374 -0
  3. package/dist/cli/commands/health.js +231 -112
  4. package/dist/cli/commands/read-rem-in-tree.js +84 -0
  5. package/dist/cli/config.js +2 -0
  6. package/dist/cli/daemon/registry.js +8 -0
  7. package/dist/cli/handlers/edit-handler.js +14 -0
  8. package/dist/cli/handlers/patch-engine.js +347 -0
  9. package/dist/cli/handlers/read-handler.js +2 -53
  10. package/dist/cli/handlers/rem-field-filter.js +102 -0
  11. package/dist/cli/handlers/tree-edit-handler.js +67 -7
  12. package/dist/cli/handlers/tree-read-handler.js +4 -1
  13. package/dist/cli/handlers/tree-rem-read-handler.js +73 -0
  14. package/dist/cli/main.js +53 -2
  15. package/dist/cli/server/ws-server.js +9 -1
  16. package/dist/mcp/daemon-client.js +22 -2
  17. package/dist/mcp/instructions.js +99 -58
  18. package/dist/mcp/tools/edit-tools.js +7 -2
  19. package/dist/mcp/tools/infra-tools.js +20 -11
  20. package/dist/mcp/tools/read-tools.js +88 -2
  21. package/package.json +1 -1
  22. package/remnote-plugin/dist/index-sandbox.js +24 -24
  23. package/remnote-plugin/dist/index.js +24 -24
  24. package/remnote-plugin/dist/manifest.json +1 -1
  25. package/remnote-plugin/package.json +1 -1
  26. package/remnote-plugin/public/manifest.json +1 -1
  27. package/remnote-plugin/src/bridge/message-router.ts +3 -0
  28. package/remnote-plugin/src/services/read-rem-in-tree.ts +43 -0
  29. package/remnote-plugin/src/services/read-rem.ts +31 -16
  30. package/remnote-plugin/src/services/read-tree.ts +5 -0
  31. package/remnote-plugin/src/settings.ts +1 -1
  32. package/skills/remnote-bridge/SKILL.md +50 -8
  33. package/skills/remnote-bridge/instructions/connect.md +31 -8
  34. package/skills/remnote-bridge/instructions/disconnect.md +5 -0
  35. package/skills/remnote-bridge/instructions/edit-tree.md +117 -51
  36. package/skills/remnote-bridge/instructions/health.md +81 -53
  37. package/skills/remnote-bridge/instructions/overall.md +39 -8
  38. package/skills/remnote-bridge/instructions/read-rem-in-tree.md +100 -0
  39. package/skills/remnote-bridge/instructions/read-rem.md +30 -11
  40. package/skills/remnote-bridge-test/SKILL.md +847 -0
  41. package/skills/remnote-bridge-test/references/regression-suite.md +960 -0
  42. package/skills/remnote-bridge-test/references/verification-guide.md +161 -0
@@ -7,7 +7,7 @@
7
7
  "version": {
8
8
  "major": 0,
9
9
  "minor": 2,
10
- "patch": 0
10
+ "patch": 1
11
11
  },
12
12
  "theme": [],
13
13
  "enableOnMobile": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": true,
3
3
  "name": "unofficial-remnote-bridge-plugin",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "license": "MIT",
6
6
  "description": "RemNote 桥接层:嵌入 RemNote 的 WebSocket 桥接插件",
7
7
  "scripts": {
@@ -7,7 +7,7 @@
7
7
  "version": {
8
8
  "major": 0,
9
9
  "minor": 2,
10
- "patch": 0
10
+ "patch": 1
11
11
  },
12
12
  "theme": [],
13
13
  "enableOnMobile": false,
@@ -11,6 +11,7 @@ import type { ReactRNPlugin } from '@remnote/plugin-sdk';
11
11
  import type { BridgeRequest } from './websocket-client';
12
12
  import { readRem } from '../services/read-rem';
13
13
  import { readTree } from '../services/read-tree';
14
+ import { readRemInTree } from '../services/read-rem-in-tree';
14
15
  import { readGlobe } from '../services/read-globe';
15
16
  import { readContext } from '../services/read-context';
16
17
  import { writeRemFields } from '../services/write-rem-fields';
@@ -36,6 +37,8 @@ export function createMessageRouter(plugin: ReactRNPlugin): (request: BridgeRequ
36
37
  return readRem(plugin, request.payload as { remId: string; includePowerup?: boolean });
37
38
  case 'read_tree':
38
39
  return readTree(plugin, request.payload as { remId: string; depth?: number; maxNodes?: number; maxSiblings?: number; ancestorLevels?: number; includePowerup?: boolean });
40
+ case 'read_rem_in_tree':
41
+ return readRemInTree(plugin, request.payload as { remId: string; depth?: number; maxNodes?: number; maxSiblings?: number; ancestorLevels?: number; includePowerup?: boolean });
39
42
  case 'write_rem_fields':
40
43
  return writeRemFields(plugin, request.payload as { remId: string; changes: Record<string, unknown> });
41
44
  case 'create_rem':
@@ -0,0 +1,43 @@
1
+ /**
2
+ * read-rem-in-tree service — read_tree + 对每个节点 buildRemObject
3
+ *
4
+ * 同态命名:read_rem_in_tree (action) → read-rem-in-tree.ts (文件) → readRemInTree (函数)
5
+ */
6
+
7
+ import type { ReactRNPlugin } from '@remnote/plugin-sdk';
8
+ import type { RemObject } from '../types';
9
+ import { readTree, type ReadTreePayload, type ReadTreeResult } from './read-tree';
10
+ import { buildRemObject } from './read-rem';
11
+
12
+ export interface ReadRemInTreePayload extends ReadTreePayload {}
13
+
14
+ export interface ReadRemInTreeResult extends ReadTreeResult {
15
+ /** remId → RemObject 的扁平映射 */
16
+ remObjects: Record<string, RemObject & { powerupFiltered?: { tags: number; children: number } }>;
17
+ }
18
+
19
+ /**
20
+ * 读取 Rem 子树大纲 + 对树中每个节点构建完整 RemObject。
21
+ *
22
+ * @throws Error — Rem 不存在、节点数超限
23
+ */
24
+ export async function readRemInTree(
25
+ plugin: ReactRNPlugin,
26
+ payload: ReadRemInTreePayload,
27
+ ): Promise<ReadRemInTreeResult> {
28
+ // 1. 获取 outline + nodeRemIds(复用 readTree)
29
+ const treeResult = await readTree(plugin, payload);
30
+
31
+ // 2. 用 readTree 遍历时收集的 nodeRemIds,直接并行 buildRemObject
32
+ const includePowerup = payload.includePowerup ?? false;
33
+ const remObjects: ReadRemInTreeResult['remObjects'] = {};
34
+
35
+ await Promise.all(treeResult.nodeRemIds.map(async (id) => {
36
+ const rem = await plugin.rem.findOne(id);
37
+ if (rem) {
38
+ remObjects[id] = await buildRemObject(plugin, rem, { includePowerup });
39
+ }
40
+ }));
41
+
42
+ return { ...treeResult, remObjects };
43
+ }
@@ -33,6 +33,21 @@ export async function readRem(
33
33
  throw new Error(`Rem not found: ${payload.remId}`);
34
34
  }
35
35
 
36
+ return buildRemObject(plugin, rem, { includePowerup });
37
+ }
38
+
39
+ /**
40
+ * 从已有 Rem 对象组装完整 RemObject(跳过 findOne)。
41
+ *
42
+ * 供 readRem 和 readRemInTree 共享。
43
+ */
44
+ export async function buildRemObject(
45
+ plugin: ReactRNPlugin,
46
+ rem: Rem,
47
+ options: { includePowerup?: boolean },
48
+ ): Promise<RemObject & { powerupFiltered?: { tags: number; children: number } }> {
49
+ const { includePowerup = false } = options;
50
+
36
51
  // 并行获取所有异步字段
37
52
  const [
38
53
  isDocument,
@@ -196,7 +211,7 @@ export async function readRem(
196
211
  portalType: remTypeToString(rem.type as number) === 'portal'
197
212
  ? portalTypeToString(portalType as number)
198
213
  : null,
199
- portalDirectlyIncludedRem: portalDirectlyIncludedRems.map(r => r._id),
214
+ portalDirectlyIncludedRem: portalDirectlyIncludedRems.map(r => r._id).sort(),
200
215
 
201
216
  // 属性类型
202
217
  propertyType: (propertyType as PropertyTypeValue | undefined) ?? null,
@@ -205,27 +220,27 @@ export async function readRem(
205
220
  enablePractice,
206
221
  practiceDirection: practiceDirection as RemObject['practiceDirection'],
207
222
 
208
- // 关联 — 直接关系
209
- tags: filteredTagRems.map(r => r._id),
210
- sources: sourceRems.map(r => r._id),
211
- aliases: aliasRems.map(r => r._id),
223
+ // 关联 — 直接关系(排序保证确定性序列化)
224
+ tags: filteredTagRems.map(r => r._id).sort(),
225
+ sources: sourceRems.map(r => r._id).sort(),
226
+ aliases: aliasRems.map(r => r._id).sort(),
212
227
 
213
228
  // 关联 — 引用关系
214
- remsBeingReferenced: refsBeingReferenced.map(r => r._id),
215
- deepRemsBeingReferenced: deepRefsBeingReferenced.map(r => r._id),
216
- remsReferencingThis: refsReferencingThis.map(r => r._id),
229
+ remsBeingReferenced: refsBeingReferenced.map(r => r._id).sort(),
230
+ deepRemsBeingReferenced: deepRefsBeingReferenced.map(r => r._id).sort(),
231
+ remsReferencingThis: refsReferencingThis.map(r => r._id).sort(),
217
232
 
218
233
  // 关联 — 标签体系
219
- taggedRem: taggedRems.map(r => r._id),
220
- ancestorTagRem: ancestorTagRems.map(r => r._id),
221
- descendantTagRem: descendantTagRems.map(r => r._id),
234
+ taggedRem: taggedRems.map(r => r._id).sort(),
235
+ ancestorTagRem: ancestorTagRems.map(r => r._id).sort(),
236
+ descendantTagRem: descendantTagRems.map(r => r._id).sort(),
222
237
 
223
238
  // 关联 — 层级遍历
224
- descendants: descendantRems.map(r => r._id),
225
- siblingRem: siblingRems.map(r => r._id),
226
- portalsAndDocumentsIn: portalsAndDocsIn.map(r => r._id),
227
- allRemInDocumentOrPortal: allRemInDocOrPortal.map(r => r._id),
228
- allRemInFolderQueue: allRemInFolderQ.map(r => r._id),
239
+ descendants: descendantRems.map(r => r._id).sort(),
240
+ siblingRem: siblingRems.map(r => r._id).sort(),
241
+ portalsAndDocumentsIn: portalsAndDocsIn.map(r => r._id).sort(),
242
+ allRemInDocumentOrPortal: allRemInDocOrPortal.map(r => r._id).sort(),
243
+ allRemInFolderQueue: allRemInFolderQ.map(r => r._id).sort(),
229
244
 
230
245
  // 位置 / 统计
231
246
  positionAmongstSiblings: position ?? null,
@@ -43,6 +43,8 @@ export interface ReadTreeResult {
43
43
  depth: number;
44
44
  nodeCount: number;
45
45
  outline: string;
46
+ /** 树中所有节点的 remId 列表(遍历顺序) */
47
+ nodeRemIds: string[];
46
48
  /** 祖先链(从直接父亲到最远祖先,由近及远) */
47
49
  ancestors?: AncestorInfo[];
48
50
  powerupFiltered?: { tags: number; children: number };
@@ -75,6 +77,7 @@ export async function readTree(
75
77
  let totalFilteredTags = 0;
76
78
  let totalFilteredChildren = 0;
77
79
  const budget = { remaining: maxNodes };
80
+ const nodeRemIds: string[] = [];
78
81
 
79
82
  /**
80
83
  * 递归构建 OutlineNode 树。
@@ -86,6 +89,7 @@ export async function readTree(
86
89
  async function buildNode(rem: Rem, currentDepth: number, maxDepth: number): Promise<OutlineNode> {
87
90
  nodeCount++;
88
91
  budget.remaining--;
92
+ nodeRemIds.push(rem._id);
89
93
 
90
94
  const allChildren = await rem.getChildrenRem();
91
95
  const children = includePowerup ? allChildren : await filterNoisyChildren(allChildren);
@@ -179,6 +183,7 @@ export async function readTree(
179
183
  depth,
180
184
  nodeCount,
181
185
  outline,
186
+ nodeRemIds,
182
187
  };
183
188
 
184
189
  // 祖先链构建
@@ -5,7 +5,7 @@
5
5
  * 多 daemon 连接:Plugin 同时连接 ALL_WS_PORTS 对应的 4 个槽位。
6
6
  */
7
7
 
8
- export const DEFAULT_PLUGIN_VERSION = '0.2.0';
8
+ export const DEFAULT_PLUGIN_VERSION = '0.2.1';
9
9
 
10
10
  /** 4 个固定 WS 端口,对应 4 个 daemon 槽位 */
11
11
  export const ALL_WS_PORTS = [29100, 29110, 29120, 29130] as const;
@@ -18,6 +18,7 @@ description: "RemNote 知识库操作指南。通过 remnote-bridge 命令行工
18
18
  | read-rem | `instructions/read-rem.md` |
19
19
  | edit-rem | `instructions/edit-rem.md` |
20
20
  | read-tree | `instructions/read-tree.md` |
21
+ | read-rem-in-tree | `instructions/read-rem-in-tree.md` |
21
22
  | edit-tree | `instructions/edit-tree.md` |
22
23
  | read-globe | `instructions/read-globe.md` |
23
24
  | read-context | `instructions/read-context.md` |
@@ -222,6 +223,7 @@ RemNote 格式设置(fontSize、highlightColor 等)底层通过 Powerup 机
222
223
  用户当前在看什么("当前页面") → read-context
223
224
  某个 Rem 的子树("展开这个主题") → read-tree <remId>
224
225
  某个 Rem 的详细属性("详细信息") → read-rem <remId>
226
+ 子树 + 每个节点属性("批量标注") → read-rem-in-tree <remId>
225
227
  按关键词搜索("搜索 X") → search <query>(中文搜索有限制,见下方说明)
226
228
  ```
227
229
 
@@ -233,8 +235,9 @@ RemNote 格式设置(fontSize、highlightColor 等)底层通过 Powerup 机
233
235
  | 我在编辑什么 | `read-context --mode focus` | 鱼眼视图(焦点 depth=3,siblings depth=1,叔伯 depth=0),**无缓存** |
234
236
  | 当前页面内容 | `read-context --mode page` | 均匀展开,**无缓存** |
235
237
  | 展开某主题细节 | `read-tree <id>` | 完整子树,**有缓存**供 edit-tree |
238
+ | 展开子树 + 每个节点属性 | `read-rem-in-tree <id>` | 大纲 + RemObject,**双重缓存**供 edit-tree 和 edit-rem |
236
239
 
237
- **重要**:read-globe、read-context、search 都**不写入缓存**,不能替代 read-tree/read-rem 作为 edit 的前置条件。read-context 需要用户在 RemNote 中有焦点(focus 模式)或打开页面(page 模式)。
240
+ **重要**:read-globe、read-context、search 都**不写入缓存**,不能替代 read-tree/read-rem/read-rem-in-tree 作为 edit 的前置条件。read-context 需要用户在 RemNote 中有焦点(focus 模式)或打开页面(page 模式)。
238
241
 
239
242
  ### 修改:用户想改什么?
240
243
 
@@ -246,11 +249,43 @@ Rem 的属性(文本、类型、格式、标签) → edit-rem (前置
246
249
 
247
250
  **关键区分**:`edit-rem` 修改 Rem 的**属性**,`edit-tree` 修改 Rem 之间的**结构关系**。`edit-tree` **禁止修改行内容**。
248
251
 
252
+ ### read-rem-in-tree:何时用、怎么用
253
+
254
+ `read-rem-in-tree` = `read-tree` + 批量 `read-rem` 的合体。一次调用同时获取子树大纲和每个节点的 RemObject(含完整 RichText),同时建立 `tree:` 和 `rem:` 双重缓存。
255
+
256
+ **何时用**:当你需要对一棵子树中的**多个节点**做属性修改(edit-rem)时。典型场景:
257
+
258
+ - 批量标注重点(给多个节点加高亮/粗体)
259
+ - 批量修改类型(把多个普通 Rem 改为 concept)
260
+ - 读取子树后需要检查多个节点的 RichText 再决定怎么改
261
+
262
+ **何时不用**:只需要看大纲结构(用 `read-tree`),或只改 1-2 个节点属性(用 `read-tree` + `read-rem`)。
263
+
264
+ **用法示例**(课本划重点场景):
265
+
266
+ ```bash
267
+ # 1. 一次获取大纲 + 所有节点属性(默认 maxNodes=50)
268
+ read-rem-in-tree --json '{"remId":"kLr...","depth":3,"maxNodes":30}'
269
+
270
+ # 返回:outline(大纲文本)+ remObjects(每个节点的 RemObject)
271
+
272
+ # 2. 从 remObjects 中找到要标注的节点,直接 edit-rem(rem 缓存已就绪)
273
+ edit-rem --json '{"remId":"ABC","changes":{"highlightColor":"Yellow"}}'
274
+ edit-rem --json '{"remId":"DEF","changes":{"text":["关键词",{"b":true,"h":1,"i":"m","text":"重点"}]}}'
275
+
276
+ # 3. 如需结构变更,直接 edit-tree(tree 缓存已就绪)
277
+ edit-tree --json '{"remId":"kLr...","oldStr":"...","newStr":"..."}'
278
+ ```
279
+
280
+ **对比效率**:修改 10 个节点时,`read-rem-in-tree` = 1 次调用;`read-tree` + 10×`read-rem` = 11 次调用。
281
+
249
282
  ---
250
283
 
251
284
  ## 3. 标准工作流
252
285
 
253
- ### ⚠️ 标准模式:connect 后需要用户配合
286
+ ### ⚠️ 标准模式(推荐):connect 后需要用户配合
287
+
288
+ **标准模式是推荐的日常使用方式**。用户在自己的浏览器中操作 RemNote,Agent 可以通过 `read-context` 感知用户正在浏览的页面和焦点位置,实现真正的协作。
254
289
 
255
290
  `connect` 成功只意味着 daemon 和 Plugin 服务已启动,**Plugin 并未自动连接**。用户必须在 RemNote 中完成操作,Plugin 才能连接到 daemon:
256
291
 
@@ -266,11 +301,18 @@ Rem 的属性(文本、类型、格式、标签) → edit-rem (前置
266
301
 
267
302
  **你必须**:执行 `connect` 后,**立即告知用户需要完成上述操作**,不要直接调用业务命令。引导用户完成后,用 `health` 确认三层就绪再继续。
268
303
 
269
- ### Headless 模式:自动连接
304
+ ### Headless 模式(特殊场景,不推荐日常使用)
305
+
306
+ 通过 setup(一次性)+ headless Chrome 实现自动连接,后续 connect 无需用户介入。
307
+
308
+ **⚠️ 不推荐日常使用**。Headless Chrome 是后台独立实例,**会丢失用户上下文**——`read-context` 返回的是 headless Chrome 的上下文,不是用户浏览器的。Agent 无法感知用户正在浏览和操作的页面,协作体验大打折扣。
270
309
 
271
- 标准模式每次 connect 后都需要用户手动操作 RemNote。Headless 模式通过 setup(一次性)+ headless Chrome 实现自动连接,后续 connect 无需用户介入。
310
+ **仅在以下场景使用 headless**:
311
+ - 用户明确要求在**服务器/无 GUI 环境**中运行
312
+ - 用户明确表示**不想参与操作**,希望全自动化(CI/CD、定时任务、批量处理等)
313
+ - 用户自己不在 RemNote 前面,不需要与 Agent 协作浏览
272
314
 
273
- **⚠️ 模式选择建议**:日常使用推荐**标准模式**。Headless 模式下 Chrome 在后台运行,**无法感知用户正在 RemNote 中浏览和操作的界面**(`read-context` 返回的是 headless Chrome 的上下文,而非用户的浏览器)。只有在全自动化场景(CI/CD、定时任务、批量操作等无需与用户界面交互的场景)才建议使用 Headless 模式。
315
+ **默认始终使用标准模式**,除非用户主动要求 headless
274
316
 
275
317
  #### 首次使用(setup)
276
318
 
@@ -315,9 +357,9 @@ setup 只需执行一次。之后每次连接直接用 `connect --headless`。
315
357
  4. read-globe -- 了解知识库结构(首次探索)
316
358
  或 read-context -- 了解用户当前上下文
317
359
  5. search "关键词" -- 定位目标 Rem(结果不进缓存!)
318
- 6. read-tree <id> -- 展开子树 → 写入缓存(edit-tree 的前置)
319
- 7. read-rem <id> -- 读取属性 写入缓存(edit-rem 的前置)
320
- 8. edit-rem / edit-tree -- 执行修改
360
+ 6a. [单节点] read-tree <id> + read-rem <id> -- 各自建立缓存
361
+ 6b. [多节点] read-rem-in-tree <id> -- 一次建立双重缓存(推荐 ≥3 个节点需修改时)
362
+ 7. edit-rem / edit-tree -- 执行修改
321
363
  9. disconnect -- 结束会话(缓存全部清空,幂等)
322
364
  ```
323
365
 
@@ -43,7 +43,9 @@ remnote-bridge --json connect --instance work
43
43
  | 2 | 29120 | 29121 | 29122 |
44
44
  | 3 | 29130 | 29131 | 29132 |
45
45
 
46
- **实例名解析优先级**:CLI `--instance` 参数 > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default`。Headless 模式下固定为 `headless`。
46
+ **实例名解析优先级**:CLI `--instance` 参数 > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default`。
47
+
48
+ **⚠️ `headless` 是保留实例名**:`--instance headless` 会直接报错。headless 模式必须使用专用的 `--headless` 全局选项(见下方 Headless 模式章节)。
47
49
 
48
50
  **首次使用多实例时**,用户需在 RemNote 中为每个实例分别配置 dev plugin URL(对应各自的 Plugin 服务端口)。
49
51
 
@@ -51,13 +53,20 @@ remnote-bridge --json connect --instance work
51
53
 
52
54
  ## 两种模式
53
55
 
54
- ### 标准模式(默认)
56
+ ### 标准模式(默认,推荐)
57
+
58
+ **标准模式是推荐的日常使用方式**。启动 daemon 后用户在自己的浏览器中加载 Plugin。优势:Agent 可以通过 `read-context` 感知用户正在浏览的页面和焦点位置,实现真正的协作。
55
59
 
56
- 启动 daemon 后需要用户手动在 RemNote 中加载 Plugin。适用于用户已打开 RemNote 的场景。
60
+ ### Headless 模式(`--headless`,特殊场景)
57
61
 
58
- ### Headless 模式(`--headless`)
62
+ 自动启动 headless Chrome 加载 Plugin,无需用户操作。
59
63
 
60
- 自动启动 headless Chrome 加载 Plugin,无需用户操作。适用于无 GUI 环境或全自动连接场景。
64
+ **⚠️ 不推荐日常使用**。Headless Chrome 是后台独立实例,**会丢失用户上下文**——`read-context` 返回的是 headless Chrome 的上下文,不是用户浏览器的。
65
+
66
+ **仅在以下场景使用 headless**:
67
+ - 用户明确要求在**服务器/无 GUI 环境**中运行
68
+ - 用户明确表示**不想参与操作**,希望全自动化(CI/CD、定时任务、批量处理等)
69
+ - 用户自己不在 RemNote 前面,不需要与 Agent 协作浏览
61
70
 
62
71
  **前置条件**:必须先执行 `setup` 完成 RemNote 登录。
63
72
 
@@ -74,6 +83,15 @@ remnote-bridge --json connect --headless
74
83
 
75
84
  Headless 模式下 Plugin 可能需要 10-30 秒才能连接到 daemon,使用 `health` 确认就绪。
76
85
 
86
+ `--headless` 是全局选项,headless 会话中**所有命令都需要带上**:
87
+
88
+ ```bash
89
+ remnote-bridge --headless connect # 启动
90
+ remnote-bridge --headless health # 检查
91
+ remnote-bridge --headless read-rem --json '{"remId":"..."}' # 业务命令
92
+ remnote-bridge --headless disconnect # 结束
93
+ ```
94
+
77
95
  排查工具:`health --diagnose`(截图+状态+console 错误)、`health --reload`(重载 Chrome 页面)。
78
96
 
79
97
  ---
@@ -82,12 +100,17 @@ Headless 模式下 Plugin 可能需要 10-30 秒才能连接到 daemon,使用
82
100
 
83
101
  `connect`(不传 `--headless`)成功只意味着 daemon 和 Plugin 服务已启动,**Plugin 并未自动连接**。用户必须在 RemNote 中完成以下操作:
84
102
 
103
+ > **⚠️ 防幻觉红线**:本插件是**开发者插件**,通过「开发你的插件」功能加载本地 URL。
104
+ > - **禁止**告诉用户"去插件市场/商店搜索安装"——本插件**不在 RemNote 插件市场中**
105
+ > - **禁止**告诉用户"Settings → Plugins"——这个路径不存在
106
+ > - **禁止**编造不存在的安装流程——严格按照下方步骤引导用户
107
+
85
108
  ### 首次使用(RemNote 从未加载过此插件)
86
109
 
87
110
  1. 打开 RemNote 桌面端或网页端
88
- 2. 点击左侧边栏底部的插件图标(拼图形状)
89
- 3. 点击「开发你的插件」(Develop Your Plugin)
90
- 4. 在输入框中填入 connect 输出的 Plugin 服务地址(如 `http://localhost:29101`)
111
+ 2. 点击左侧边栏底部的**插件图标**(拼图形状)
112
+ 3. 点击「**开发你的插件**」(Develop Your Plugin)
113
+ 4. 在输入框中填入 connect 输出的 **Plugin 服务地址**(如 `http://localhost:29101`)
91
114
  5. 等待插件加载完成
92
115
 
93
116
  ### 非首次使用(之前已加载过此插件)
@@ -18,12 +18,17 @@
18
18
 
19
19
  通过 `--instance <name>` 指定要停止的实例。不指定时停止 `default` 实例。
20
20
 
21
+ `--headless` 是全局选项,用于停止 headless 模式启动的实例。
22
+
21
23
  ```bash
22
24
  # 停止默认实例
23
25
  remnote-bridge disconnect
24
26
 
25
27
  # 停止指定实例
26
28
  remnote-bridge disconnect --instance work
29
+
30
+ # 停止 headless 实例
31
+ remnote-bridge --headless disconnect
27
32
  ```
28
33
 
29
34
  ---