remnote-bridge 0.1.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 (135) hide show
  1. package/dist/cli/commands/connect.d.ts +12 -0
  2. package/dist/cli/commands/connect.js +124 -0
  3. package/dist/cli/commands/disconnect.d.ts +11 -0
  4. package/dist/cli/commands/disconnect.js +100 -0
  5. package/dist/cli/commands/edit-rem.d.ts +13 -0
  6. package/dist/cli/commands/edit-rem.js +83 -0
  7. package/dist/cli/commands/edit-tree.d.ts +14 -0
  8. package/dist/cli/commands/edit-tree.js +67 -0
  9. package/dist/cli/commands/health.d.ts +12 -0
  10. package/dist/cli/commands/health.js +100 -0
  11. package/dist/cli/commands/install-skill.d.ts +6 -0
  12. package/dist/cli/commands/install-skill.js +39 -0
  13. package/dist/cli/commands/read-context.d.ts +20 -0
  14. package/dist/cli/commands/read-context.js +77 -0
  15. package/dist/cli/commands/read-globe.d.ts +16 -0
  16. package/dist/cli/commands/read-globe.js +60 -0
  17. package/dist/cli/commands/read-rem.d.ts +16 -0
  18. package/dist/cli/commands/read-rem.js +80 -0
  19. package/dist/cli/commands/read-tree.d.ts +17 -0
  20. package/dist/cli/commands/read-tree.js +85 -0
  21. package/dist/cli/commands/search.d.ts +12 -0
  22. package/dist/cli/commands/search.js +65 -0
  23. package/dist/cli/config.d.ts +55 -0
  24. package/dist/cli/config.js +139 -0
  25. package/dist/cli/daemon/daemon.d.ts +11 -0
  26. package/dist/cli/daemon/daemon.js +186 -0
  27. package/dist/cli/daemon/dev-server.d.ts +26 -0
  28. package/dist/cli/daemon/dev-server.js +81 -0
  29. package/dist/cli/daemon/pid.d.ts +34 -0
  30. package/dist/cli/daemon/pid.js +67 -0
  31. package/dist/cli/daemon/send-request.d.ts +24 -0
  32. package/dist/cli/daemon/send-request.js +92 -0
  33. package/dist/cli/handlers/context-read-handler.d.ts +18 -0
  34. package/dist/cli/handlers/context-read-handler.js +24 -0
  35. package/dist/cli/handlers/edit-handler.d.ts +30 -0
  36. package/dist/cli/handlers/edit-handler.js +133 -0
  37. package/dist/cli/handlers/globe-read-handler.d.ts +17 -0
  38. package/dist/cli/handlers/globe-read-handler.js +23 -0
  39. package/dist/cli/handlers/read-handler.d.ts +16 -0
  40. package/dist/cli/handlers/read-handler.js +78 -0
  41. package/dist/cli/handlers/rem-cache.d.ts +19 -0
  42. package/dist/cli/handlers/rem-cache.js +63 -0
  43. package/dist/cli/handlers/tree-edit-handler.d.ts +30 -0
  44. package/dist/cli/handlers/tree-edit-handler.js +188 -0
  45. package/dist/cli/handlers/tree-parser.d.ts +95 -0
  46. package/dist/cli/handlers/tree-parser.js +506 -0
  47. package/dist/cli/handlers/tree-read-handler.d.ts +28 -0
  48. package/dist/cli/handlers/tree-read-handler.js +53 -0
  49. package/dist/cli/main.d.ts +7 -0
  50. package/dist/cli/main.js +300 -0
  51. package/dist/cli/protocol.d.ts +39 -0
  52. package/dist/cli/protocol.js +35 -0
  53. package/dist/cli/server/config-server.d.ts +26 -0
  54. package/dist/cli/server/config-server.js +363 -0
  55. package/dist/cli/server/ws-server.d.ts +68 -0
  56. package/dist/cli/server/ws-server.js +335 -0
  57. package/dist/cli/utils/output.d.ts +11 -0
  58. package/dist/cli/utils/output.js +13 -0
  59. package/dist/mcp/daemon-client.d.ts +31 -0
  60. package/dist/mcp/daemon-client.js +99 -0
  61. package/dist/mcp/index.d.ts +7 -0
  62. package/dist/mcp/index.js +68 -0
  63. package/dist/mcp/instructions.d.ts +1 -0
  64. package/dist/mcp/instructions.js +249 -0
  65. package/dist/mcp/resources/edit-tree-guide.d.ts +1 -0
  66. package/dist/mcp/resources/edit-tree-guide.js +197 -0
  67. package/dist/mcp/resources/error-reference.d.ts +1 -0
  68. package/dist/mcp/resources/error-reference.js +132 -0
  69. package/dist/mcp/resources/outline-format.d.ts +1 -0
  70. package/dist/mcp/resources/outline-format.js +104 -0
  71. package/dist/mcp/resources/rem-object-fields.d.ts +1 -0
  72. package/dist/mcp/resources/rem-object-fields.js +331 -0
  73. package/dist/mcp/resources/separator-flashcard.d.ts +1 -0
  74. package/dist/mcp/resources/separator-flashcard.js +120 -0
  75. package/dist/mcp/tools/edit-tools.d.ts +5 -0
  76. package/dist/mcp/tools/edit-tools.js +47 -0
  77. package/dist/mcp/tools/infra-tools.d.ts +5 -0
  78. package/dist/mcp/tools/infra-tools.js +43 -0
  79. package/dist/mcp/tools/read-tools.d.ts +5 -0
  80. package/dist/mcp/tools/read-tools.js +195 -0
  81. package/dist/mcp/types.d.ts +12 -0
  82. package/dist/mcp/types.js +4 -0
  83. package/docs/instruction/connect.md +158 -0
  84. package/docs/instruction/disconnect.md +146 -0
  85. package/docs/instruction/edit-rem.md +509 -0
  86. package/docs/instruction/edit-tree.md +419 -0
  87. package/docs/instruction/health.md +159 -0
  88. package/docs/instruction/overall.md +751 -0
  89. package/docs/instruction/read-context.md +353 -0
  90. package/docs/instruction/read-globe.md +206 -0
  91. package/docs/instruction/read-rem.md +476 -0
  92. package/docs/instruction/read-tree.md +428 -0
  93. package/docs/instruction/search.md +196 -0
  94. package/package.json +41 -0
  95. package/remnote-plugin/package.json +48 -0
  96. package/remnote-plugin/postcss.config.js +5 -0
  97. package/remnote-plugin/public/bridge-icon.svg +8 -0
  98. package/remnote-plugin/public/manifest.json +22 -0
  99. package/remnote-plugin/src/bridge/message-router.ts +57 -0
  100. package/remnote-plugin/src/bridge/websocket-client.ts +245 -0
  101. package/remnote-plugin/src/index.css +1 -0
  102. package/remnote-plugin/src/services/breadcrumb.ts +26 -0
  103. package/remnote-plugin/src/services/create-rem.ts +59 -0
  104. package/remnote-plugin/src/services/delete-rem.ts +29 -0
  105. package/remnote-plugin/src/services/index.ts +16 -0
  106. package/remnote-plugin/src/services/move-rem.ts +39 -0
  107. package/remnote-plugin/src/services/powerup-filter.ts +31 -0
  108. package/remnote-plugin/src/services/read-context.ts +368 -0
  109. package/remnote-plugin/src/services/read-globe.ts +197 -0
  110. package/remnote-plugin/src/services/read-rem.ts +284 -0
  111. package/remnote-plugin/src/services/read-tree.ts +222 -0
  112. package/remnote-plugin/src/services/rem-builder.ts +124 -0
  113. package/remnote-plugin/src/services/reorder-children.ts +61 -0
  114. package/remnote-plugin/src/services/search.ts +56 -0
  115. package/remnote-plugin/src/services/write-rem-fields.ts +254 -0
  116. package/remnote-plugin/src/settings.ts +12 -0
  117. package/remnote-plugin/src/style.css +45 -0
  118. package/remnote-plugin/src/test-scripts/AGENTS.md +46 -0
  119. package/remnote-plugin/src/test-scripts/test-actions.ts +230 -0
  120. package/remnote-plugin/src/test-scripts/test-powerup-rendering.ts +722 -0
  121. package/remnote-plugin/src/test-scripts/test-rem-type-mapping.ts +283 -0
  122. package/remnote-plugin/src/test-scripts/test-richtext-builder.ts +207 -0
  123. package/remnote-plugin/src/test-scripts/test-richtext-matrix.ts +332 -0
  124. package/remnote-plugin/src/test-scripts/test-richtext-remaining.ts +245 -0
  125. package/remnote-plugin/src/test-scripts/test-rw-fields.ts +399 -0
  126. package/remnote-plugin/src/types.ts +419 -0
  127. package/remnote-plugin/src/utils/elision.ts +45 -0
  128. package/remnote-plugin/src/utils/index.ts +10 -0
  129. package/remnote-plugin/src/utils/tree-serializer.ts +269 -0
  130. package/remnote-plugin/src/widgets/bridge_widget.tsx +170 -0
  131. package/remnote-plugin/src/widgets/index.tsx +82 -0
  132. package/remnote-plugin/tailwind.config.js +7 -0
  133. package/remnote-plugin/tsconfig.json +21 -0
  134. package/remnote-plugin/webpack.config.js +125 -0
  135. package/skill/SKILL.md +428 -0
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 基础设施工具:connect、disconnect、health
3
+ */
4
+ import type { FastMCP } from 'fastmcp';
5
+ export declare function registerInfraTools(server: FastMCP): void;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 基础设施工具:connect、disconnect、health
3
+ */
4
+ import { z } from 'zod';
5
+ import { callCli } from '../daemon-client.js';
6
+ export function registerInfraTools(server) {
7
+ // -------------------------------------------------------------------------
8
+ // connect
9
+ // -------------------------------------------------------------------------
10
+ server.addTool({
11
+ name: 'connect',
12
+ description: '启动守护进程(daemon),建立 CLI 与 RemNote Plugin 之间的通信通道。这是所有业务命令(read_rem、edit_rem、search 等)的前置步骤。\n\n适用场景:\n- 开始一次 RemNote 操作会话前,必须先调用此工具\n- 不确定 daemon 是否在运行时,也可安全调用(幂等)\n\n输出:返回 JSON,关键字段 ok、alreadyRunning(是否已运行)、pid、wsPort。\n幂等:重复调用不会启动多个 daemon。daemon 默认 30 分钟无活动自动关闭。\n关联工具:disconnect(结束会话)、health(检查状态)',
13
+ parameters: z.object({}),
14
+ execute: async () => {
15
+ const response = await callCli('connect');
16
+ return JSON.stringify(response, null, 2);
17
+ },
18
+ });
19
+ // -------------------------------------------------------------------------
20
+ // disconnect
21
+ // -------------------------------------------------------------------------
22
+ server.addTool({
23
+ name: 'disconnect',
24
+ description: '停止守护进程,释放所有端口、清空内存缓存、结束当前会话。\n\n适用场景:\n- 操作完成后主动释放资源\n- 需要重置连接状态(例如排查问题时先 disconnect 再 connect)\n\n输出:返回 JSON,关键字段 ok、wasRunning(之前是否在运行)、pid。\n幂等:daemon 未运行时调用也返回 ok: true。\n所有缓存随 daemon 退出清空,下次 connect 后需重新 read。\n关联工具:connect(启动会话)、health(检查状态)',
25
+ parameters: z.object({}),
26
+ execute: async () => {
27
+ const response = await callCli('disconnect');
28
+ return JSON.stringify(response, null, 2);
29
+ },
30
+ });
31
+ // -------------------------------------------------------------------------
32
+ // health
33
+ // -------------------------------------------------------------------------
34
+ server.addTool({
35
+ name: 'health',
36
+ description: '检查系统三层状态(daemon 守护进程 / Plugin 连接 / SDK 就绪),用于诊断连接问题。\n\n适用场景:\n- 业务命令失败时,首先调用 health 定位故障层级\n- 执行 connect 后确认通道是否完全就绪\n- 长时间未操作后,检查 daemon 是否仍在运行\n\n输出:返回 JSON,关键字段 ok(三层是否全部健康)、daemon.running、plugin.connected、sdk.ready、timeoutRemaining。\n三层有严格依赖:daemon 运行 → Plugin 连接 → SDK 就绪。\nok 为 false 时:daemon 未运行则 connect;Plugin 未连接则确认 RemNote 已打开;SDK 未就绪则等待重试。\n只读不写,不改变任何状态。\n关联工具:connect(启动)、disconnect(结束)',
37
+ parameters: z.object({}),
38
+ execute: async () => {
39
+ const response = await callCli('health');
40
+ return JSON.stringify(response, null, 2);
41
+ },
42
+ });
43
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 读取类工具:search、read_rem、read_tree、read_globe、read_context
3
+ */
4
+ import type { FastMCP } from 'fastmcp';
5
+ export declare function registerReadTools(server: FastMCP): void;
@@ -0,0 +1,195 @@
1
+ /**
2
+ * 读取类工具:search、read_rem、read_tree、read_globe、read_context
3
+ */
4
+ import { z } from 'zod';
5
+ import { callCli } from '../daemon-client.js';
6
+ export function registerReadTools(server) {
7
+ // -------------------------------------------------------------------------
8
+ // search
9
+ // -------------------------------------------------------------------------
10
+ server.addTool({
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(按结构浏览)',
13
+ parameters: z.object({
14
+ query: z.string().describe('搜索关键词'),
15
+ numResults: z
16
+ .number()
17
+ .optional()
18
+ .describe('结果数量上限,默认 20'),
19
+ }),
20
+ execute: async (args) => {
21
+ const payload = { query: args.query };
22
+ if (args.numResults !== undefined)
23
+ payload.numResults = args.numResults;
24
+ const response = await callCli('search', payload);
25
+ return JSON.stringify(response, null, 2);
26
+ },
27
+ });
28
+ // -------------------------------------------------------------------------
29
+ // read_rem
30
+ // -------------------------------------------------------------------------
31
+ server.addTool({
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',
34
+ parameters: z.object({
35
+ remId: z.string().describe('目标 Rem 的 ID'),
36
+ fields: z
37
+ .array(z.string())
38
+ .optional()
39
+ .describe('只返回指定字段(默认返回全部)'),
40
+ full: z
41
+ .boolean()
42
+ .optional()
43
+ .describe('是否返回完整 RichText 原始结构'),
44
+ includePowerup: z
45
+ .boolean()
46
+ .optional()
47
+ .describe('是否包含 Powerup 元信息'),
48
+ }),
49
+ execute: async (args) => {
50
+ const payload = { remId: args.remId };
51
+ if (args.fields !== undefined)
52
+ payload.fields = args.fields;
53
+ if (args.full !== undefined)
54
+ payload.full = args.full;
55
+ if (args.includePowerup !== undefined)
56
+ payload.includePowerup = args.includePowerup;
57
+ const response = await callCli('read-rem', payload);
58
+ return JSON.stringify(response, null, 2);
59
+ },
60
+ });
61
+ // -------------------------------------------------------------------------
62
+ // read_tree
63
+ // -------------------------------------------------------------------------
64
+ server.addTool({
65
+ name: 'read_tree',
66
+ description: '将指定 Rem 的子树序列化为 Markdown 大纲,支持深度/节点预算控制和祖先路径追溯。\n\n适用场景:\n- 查看 Rem 的子树结构(而非单个 Rem 属性,那是 read_rem)\n- 作为 edit_tree 的强制前置步骤——必须先 read_tree 建立缓存\n- 不适合全局鸟瞰(那是 read_globe)\n\n输出:Markdown 大纲文本,每行格式 {缩进}{内容} <!-- {remId} {标记} -->。\n该大纲同时也是 edit_tree 的操作对象。\n\n预算控制三维度:\n- depth(默认 3,-1 无限):递归深度,超限节点标记 children:N\n- maxSiblings(默认 20):单层上限,超限时 70%头+30%尾 省略\n- maxNodes(默认 200):全局上限\n\n结果自动缓存供 edit_tree 使用。ancestorLevels 可追溯祖先提供面包屑上下文。\n详细大纲格式见 resource://outline-format。\n关联工具:search(定位 remId)→ read_tree → edit_tree(结构编辑)',
67
+ parameters: z.object({
68
+ remId: z.string().describe('根 Rem 的 ID'),
69
+ depth: z
70
+ .number()
71
+ .optional()
72
+ .describe('展开深度(默认由服务端决定)'),
73
+ maxNodes: z
74
+ .number()
75
+ .optional()
76
+ .describe('节点总数上限'),
77
+ maxSiblings: z
78
+ .number()
79
+ .optional()
80
+ .describe('同级节点显示上限(超出省略)'),
81
+ ancestorLevels: z
82
+ .number()
83
+ .optional()
84
+ .describe('向上展示的祖先层级数'),
85
+ includePowerup: z
86
+ .boolean()
87
+ .optional()
88
+ .describe('是否包含 Powerup 元信息'),
89
+ }),
90
+ execute: async (args) => {
91
+ const payload = { remId: args.remId };
92
+ if (args.depth !== undefined)
93
+ payload.depth = args.depth;
94
+ if (args.maxNodes !== undefined)
95
+ payload.maxNodes = args.maxNodes;
96
+ if (args.maxSiblings !== undefined)
97
+ payload.maxSiblings = args.maxSiblings;
98
+ if (args.ancestorLevels !== undefined)
99
+ payload.ancestorLevels = args.ancestorLevels;
100
+ if (args.includePowerup !== undefined)
101
+ payload.includePowerup = args.includePowerup;
102
+ const response = await callCli('read-tree', payload);
103
+ // read-tree 返回的大纲在 response.data.outline 中
104
+ const data = response.data;
105
+ if (data?.outline && typeof data.outline === 'string') {
106
+ return data.outline;
107
+ }
108
+ return JSON.stringify(response, null, 2);
109
+ },
110
+ });
111
+ // -------------------------------------------------------------------------
112
+ // read_globe
113
+ // -------------------------------------------------------------------------
114
+ server.addTool({
115
+ name: 'read_globe',
116
+ description: '读取知识库全局 Document 层级概览,生成 Markdown 大纲。\n无需指定 remId,自动扫描整个知识库的顶层 Rem,仅递归展开 Document 类型节点;\n非 Document 子节点不展开,仅标注 children:N。\n\n适用场景:\n- 首次探索知识库,了解有哪些文档及层级关系\n- 用户说"有哪些文档"、"知识库里有什么"\n- 不适合搜索特定内容(用 search)\n- 不适合查看某 Rem 内部子节点(用 read_tree)\n\n输出:Markdown 大纲,每行包含 Document 名称和元数据。\n不缓存结果,每次调用获取最新数据。Powerup 节点自动过滤。Portal 感知。\n预算参数:depth(默认 -1 无限)、maxNodes(200)、maxSiblings(20)。\n典型工作流:read_globe 获取宏观结构 → search 或 read_tree 深入。',
117
+ parameters: z.object({
118
+ depth: z
119
+ .number()
120
+ .optional()
121
+ .describe('展开深度'),
122
+ maxNodes: z
123
+ .number()
124
+ .optional()
125
+ .describe('节点总数上限'),
126
+ maxSiblings: z
127
+ .number()
128
+ .optional()
129
+ .describe('同级节点显示上限'),
130
+ }),
131
+ execute: async (args) => {
132
+ const payload = {};
133
+ if (args.depth !== undefined)
134
+ payload.depth = args.depth;
135
+ if (args.maxNodes !== undefined)
136
+ payload.maxNodes = args.maxNodes;
137
+ if (args.maxSiblings !== undefined)
138
+ payload.maxSiblings = args.maxSiblings;
139
+ const response = await callCli('read-globe', payload);
140
+ const data = response.data;
141
+ if (data?.outline && typeof data.outline === 'string') {
142
+ return data.outline;
143
+ }
144
+ return JSON.stringify(response, null, 2);
145
+ },
146
+ });
147
+ // -------------------------------------------------------------------------
148
+ // read_context
149
+ // -------------------------------------------------------------------------
150
+ server.addTool({
151
+ 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 深入。',
153
+ parameters: z.object({
154
+ mode: z
155
+ .enum(['focus', 'page'])
156
+ .optional()
157
+ .describe('视图模式:focus(聚焦)或 page(页面),默认 focus'),
158
+ ancestorLevels: z
159
+ .number()
160
+ .optional()
161
+ .describe('向上展示的祖先层级数'),
162
+ depth: z
163
+ .number()
164
+ .optional()
165
+ .describe('展开深度'),
166
+ maxNodes: z
167
+ .number()
168
+ .optional()
169
+ .describe('节点总数上限'),
170
+ maxSiblings: z
171
+ .number()
172
+ .optional()
173
+ .describe('同级节点显示上限'),
174
+ }),
175
+ execute: async (args) => {
176
+ const payload = {};
177
+ if (args.mode !== undefined)
178
+ payload.mode = args.mode;
179
+ if (args.ancestorLevels !== undefined)
180
+ payload.ancestorLevels = args.ancestorLevels;
181
+ if (args.depth !== undefined)
182
+ payload.depth = args.depth;
183
+ if (args.maxNodes !== undefined)
184
+ payload.maxNodes = args.maxNodes;
185
+ if (args.maxSiblings !== undefined)
186
+ payload.maxSiblings = args.maxSiblings;
187
+ const response = await callCli('read-context', payload);
188
+ const data = response.data;
189
+ if (data?.outline && typeof data.outline === 'string') {
190
+ return data.outline;
191
+ }
192
+ return JSON.stringify(response, null, 2);
193
+ },
194
+ });
195
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MCP Server 共享类型定义
3
+ */
4
+ /** remnote-bridge --json 模式的标准响应结构 */
5
+ export interface CliResponse {
6
+ ok: boolean;
7
+ command: string;
8
+ error?: string;
9
+ data?: unknown;
10
+ timestamp?: string;
11
+ [key: string]: unknown;
12
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * MCP Server 共享类型定义
3
+ */
4
+ export {};
@@ -0,0 +1,158 @@
1
+ # connect
2
+
3
+ > 启动守护进程,建立 CLI ↔ Plugin 通信通道。这是所有业务命令的前置步骤。
4
+
5
+ ---
6
+
7
+ ## 功能
8
+
9
+ `connect` 以 fork 子进程方式启动后台守护进程(daemon),daemon 内部启动三个服务:
10
+
11
+ | 服务 | 默认端口 | 用途 |
12
+ |------|----------|------|
13
+ | WS Server | 3002 | CLI 命令 ↔ daemon ↔ Plugin 的双向通信 |
14
+ | webpack-dev-server | 8080 | 将 remnote-plugin 热加载到 RemNote 浏览器 |
15
+ | ConfigServer | 3003 | HTTP 配置管理界面 |
16
+
17
+ daemon 启动后脱离父进程(detached),CLI 进程退出但 daemon 继续运行。
18
+
19
+ ---
20
+
21
+ ## 用法
22
+
23
+ ### 人类模式
24
+
25
+ ```bash
26
+ remnote-bridge connect
27
+ ```
28
+
29
+ 输出示例:
30
+
31
+ ```
32
+ 守护进程已启动(PID: 12345)
33
+ WS Server: ws://127.0.0.1:3002
34
+ webpack-dev-server: http://localhost:8080
35
+ 配置页面: http://127.0.0.1:3003
36
+ 超时: 30 分钟无 CLI 交互后自动关闭
37
+ ```
38
+
39
+ ### JSON 模式
40
+
41
+ ```bash
42
+ remnote-bridge --json connect
43
+ ```
44
+
45
+ ---
46
+
47
+ ## JSON 输出
48
+
49
+ ### 首次启动
50
+
51
+ ```json
52
+ {
53
+ "ok": true,
54
+ "command": "connect",
55
+ "alreadyRunning": false,
56
+ "pid": 12345,
57
+ "wsPort": 3002,
58
+ "devServerPort": 8080,
59
+ "configPort": 3003,
60
+ "timeoutMinutes": 30,
61
+ "timestamp": "2026-03-06T10:00:00.000Z"
62
+ }
63
+ ```
64
+
65
+ ### 已在运行
66
+
67
+ ```json
68
+ {
69
+ "ok": true,
70
+ "command": "connect",
71
+ "alreadyRunning": true,
72
+ "pid": 12345,
73
+ "wsPort": 3002,
74
+ "devServerPort": 8080,
75
+ "timestamp": "2026-03-06T10:00:00.000Z"
76
+ }
77
+ ```
78
+
79
+ ### 启动失败
80
+
81
+ ```json
82
+ {
83
+ "ok": false,
84
+ "command": "connect",
85
+ "error": "守护进程启动超时(10 秒)",
86
+ "timestamp": "2026-03-06T10:00:00.000Z"
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 启动流程
93
+
94
+ ```
95
+ 1. 检查 PID 文件 (.remnote-bridge.pid)
96
+ ├─ 已在运行 → 返回 ok + alreadyRunning: true
97
+ └─ 未运行 / stale PID → 继续
98
+
99
+ 2. fork 守护进程(detached, stdio 全部 ignore)
100
+
101
+ 3. daemon 内部按顺序启动:
102
+ ├─ WS Server(必须成功,否则 daemon 退出)
103
+ ├─ ConfigServer(非关键,失败不阻塞)
104
+ └─ webpack-dev-server(必须成功,否则 daemon 退出)
105
+
106
+ 4. daemon 写入 PID 文件
107
+
108
+ 5. daemon 通过 IPC 发送 ready 信号给父进程
109
+
110
+ 6. 父进程(CLI)收到 ready → 输出结果 → 退出
111
+ ├─ 10 秒内未收到 → 超时失败
112
+ └─ 收到 error → 启动失败
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 超时机制
118
+
119
+ daemon 启动后开始计时,默认 **30 分钟无 CLI 交互**自动关闭(执行优雅 shutdown)。每次收到 CLI 请求时重置计时器。
120
+
121
+ 超时时间可通过配置文件 `.remnote-bridge.json` 的 `daemonTimeoutMinutes` 字段调整。
122
+
123
+ ---
124
+
125
+ ## 幂等性
126
+
127
+ 重复调用 `connect` 是安全的。如果 daemon 已在运行,命令直接返回 `ok: true, alreadyRunning: true`,不会启动第二个实例。
128
+
129
+ ---
130
+
131
+ ## 退出码
132
+
133
+ | 退出码 | 含义 |
134
+ |--------|------|
135
+ | 0 | 启动成功,或已在运行 |
136
+ | 1 | 启动失败(超时、端口冲突、webpack-dev-server 异常等) |
137
+
138
+ ---
139
+
140
+ ## 配置依赖
141
+
142
+ | 配置项 | 默认值 | 说明 |
143
+ |--------|--------|------|
144
+ | wsPort | 3002 | WS Server 监听端口 |
145
+ | devServerPort | 8080 | webpack-dev-server 端口 |
146
+ | configPort | 3003 | ConfigServer 端口 |
147
+ | daemonTimeoutMinutes | 30 | 无活动自动关闭的分钟数 |
148
+
149
+ 三个端口不允许冲突(配置加载时校验)。
150
+
151
+ ---
152
+
153
+ ## 产生的文件
154
+
155
+ | 文件 | 位置 | 生命周期 |
156
+ |------|------|----------|
157
+ | `.remnote-bridge.pid` | 项目根目录 | daemon 运行期间存在,关闭时删除 |
158
+ | `.remnote-bridge.log` | 项目根目录 | 追加写入,跨会话保留 |
@@ -0,0 +1,146 @@
1
+ # disconnect
2
+
3
+ > 停止守护进程,释放端口、清空缓存、结束会话。
4
+
5
+ ---
6
+
7
+ ## 功能
8
+
9
+ `disconnect` 向守护进程发送 SIGTERM 信号,触发优雅关闭:
10
+
11
+ 1. 关闭 WS Server(断开所有连接)
12
+ 2. 关闭 ConfigServer
13
+ 3. 停止 webpack-dev-server 子进程
14
+ 4. 删除 PID 文件
15
+ 5. 内存缓存随进程退出自动消失
16
+
17
+ ---
18
+
19
+ ## 用法
20
+
21
+ ### 人类模式
22
+
23
+ ```bash
24
+ remnote-bridge disconnect
25
+ ```
26
+
27
+ 输出示例:
28
+
29
+ ```
30
+ 正在停止守护进程(PID: 12345)...
31
+ 守护进程已停止
32
+ ```
33
+
34
+ ### JSON 模式
35
+
36
+ ```bash
37
+ remnote-bridge --json disconnect
38
+ ```
39
+
40
+ ---
41
+
42
+ ## JSON 输出
43
+
44
+ ### 正常停止
45
+
46
+ ```json
47
+ {
48
+ "ok": true,
49
+ "command": "disconnect",
50
+ "wasRunning": true,
51
+ "pid": 12345,
52
+ "forced": false,
53
+ "timestamp": "2026-03-06T10:00:00.000Z"
54
+ }
55
+ ```
56
+
57
+ ### 强制终止(SIGTERM 超时后 SIGKILL)
58
+
59
+ ```json
60
+ {
61
+ "ok": true,
62
+ "command": "disconnect",
63
+ "wasRunning": true,
64
+ "pid": 12345,
65
+ "forced": true,
66
+ "timestamp": "2026-03-06T10:00:00.000Z"
67
+ }
68
+ ```
69
+
70
+ ### 未在运行
71
+
72
+ ```json
73
+ {
74
+ "ok": true,
75
+ "command": "disconnect",
76
+ "wasRunning": false,
77
+ "timestamp": "2026-03-06T10:00:00.000Z"
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 关闭流程
84
+
85
+ ```
86
+ 1. 检查 PID 文件
87
+ └─ 未运行 → 返回 ok + wasRunning: false
88
+
89
+ 2. 发送 SIGTERM
90
+ ├─ 进程已退出 → 清理 PID 文件 → 返回 ok
91
+ └─ 进程仍在 → 进入等待
92
+
93
+ 3. 轮询等待退出(每 200ms 检查一次)
94
+ ├─ 10 秒内退出 → 清理 PID 文件 → 返回 ok + forced: false
95
+ └─ 超时未退出 → 发送 SIGKILL → 清理 PID 文件 → 返回 ok + forced: true
96
+ ```
97
+
98
+ ### daemon 优雅关闭(SIGTERM 触发)
99
+
100
+ daemon 收到 SIGTERM 后依次执行:
101
+
102
+ 1. 停止超时计时器
103
+ 2. 关闭 WS Server(所有 WS 连接断开)
104
+ 3. 关闭 ConfigServer(HTTP 服务停止)
105
+ 4. 停止 webpack-dev-server(先 SIGTERM,5 秒后 SIGKILL)
106
+ 5. 删除 PID 文件
107
+ 6. 关闭日志流
108
+ 7. `process.exit(0)`
109
+
110
+ ---
111
+
112
+ ## 副作用
113
+
114
+ | 影响 | 说明 |
115
+ |------|------|
116
+ | **缓存清空** | 所有 `rem:*` 和 `tree:*` 缓存随 daemon 进程退出消失 |
117
+ | **Plugin 断开** | WS 连接关闭,Plugin 进入重连循环(指数退避) |
118
+ | **端口释放** | wsPort / devServerPort / configPort 全部释放 |
119
+ | **PID 文件删除** | `.remnote-bridge.pid` 被删除 |
120
+ | **日志保留** | `.remnote-bridge.log` 保留(追加写入,不删除) |
121
+
122
+ ---
123
+
124
+ ## 退出码
125
+
126
+ | 退出码 | 含义 |
127
+ |--------|------|
128
+ | 0 | 停止成功,或本来就未运行 |
129
+
130
+ `disconnect` 总是返回 `ok: true`,无论 daemon 是否在运行。
131
+
132
+ ---
133
+
134
+ ## 幂等性
135
+
136
+ 重复调用 `disconnect` 是安全的。如果 daemon 已经停止,命令直接返回 `ok: true, wasRunning: false`。
137
+
138
+ ---
139
+
140
+ ## 输出字段说明
141
+
142
+ | 字段 | 类型 | 说明 |
143
+ |------|------|------|
144
+ | `wasRunning` | boolean | 调用前 daemon 是否在运行 |
145
+ | `pid` | number | daemon 的进程 ID(仅 wasRunning=true 时) |
146
+ | `forced` | boolean | 是否使用 SIGKILL 强制终止(仅 wasRunning=true 时) |