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.
- package/dist/cli/addon/addon-manager.js +163 -0
- package/dist/cli/addon/registry.js +24 -0
- package/dist/cli/commands/addon.js +149 -0
- package/dist/cli/commands/clean.js +121 -52
- package/dist/cli/commands/connect.js +72 -33
- package/dist/cli/commands/disconnect.js +19 -19
- package/dist/cli/commands/edit-rem.js +8 -36
- package/dist/cli/commands/edit-tree.js +3 -20
- package/dist/cli/commands/health.js +19 -18
- package/dist/cli/commands/read-context.js +3 -20
- package/dist/cli/commands/read-globe.js +3 -20
- package/dist/cli/commands/read-rem.js +6 -32
- package/dist/cli/commands/read-tree.js +3 -20
- package/dist/cli/commands/search.js +97 -21
- package/dist/cli/config.js +148 -72
- package/dist/cli/daemon/daemon.js +104 -24
- package/dist/cli/daemon/dev-server.js +9 -1
- package/dist/cli/daemon/pid.js +36 -22
- package/dist/cli/daemon/registry.js +160 -0
- package/dist/cli/daemon/send-request.js +11 -11
- package/dist/cli/daemon/static-server.js +97 -34
- package/dist/cli/handlers/edit-handler.js +49 -140
- package/dist/cli/handlers/read-handler.js +9 -9
- package/dist/cli/handlers/rem-cache.js +10 -5
- package/dist/cli/handlers/tree-parser.js +16 -9
- package/dist/cli/main.js +67 -19
- package/dist/cli/protocol.js +18 -4
- package/dist/cli/server/config-server.js +280 -14
- package/dist/cli/server/ws-server.js +93 -44
- package/dist/cli/utils/output.js +29 -0
- package/dist/mcp/format.js +43 -0
- package/dist/mcp/index.js +0 -55
- package/dist/mcp/instructions.js +424 -216
- package/dist/mcp/resources/edit-rem-guide.js +37 -158
- package/dist/mcp/resources/edit-tree-guide.js +1 -1
- package/dist/mcp/resources/error-reference.js +9 -13
- package/dist/mcp/resources/rem-object-fields.js +6 -6
- package/dist/mcp/tools/edit-tools.js +69 -8
- package/dist/mcp/tools/infra-tools.js +44 -8
- package/dist/mcp/tools/read-tools.js +136 -20
- package/package.json +2 -2
- package/remnote-plugin/dist/bridge_widget-sandbox.js +17 -17
- package/remnote-plugin/dist/bridge_widget.js +17 -17
- package/remnote-plugin/dist/index-sandbox.js +31 -31
- package/remnote-plugin/dist/index.js +31 -31
- package/remnote-plugin/dist/manifest.json +1 -1
- package/remnote-plugin/package.json +1 -1
- package/remnote-plugin/public/manifest.json +1 -1
- package/remnote-plugin/src/bridge/multi-connection-manager.ts +151 -0
- package/remnote-plugin/src/bridge/websocket-client.ts +62 -16
- package/remnote-plugin/src/services/index.ts +0 -8
- package/remnote-plugin/src/services/read-rem.ts +1 -9
- package/remnote-plugin/src/services/search.ts +13 -10
- package/remnote-plugin/src/settings.ts +9 -7
- package/remnote-plugin/src/utils/index.ts +0 -5
- package/remnote-plugin/src/widgets/bridge_widget.tsx +105 -20
- package/remnote-plugin/src/widgets/index.tsx +41 -44
- package/remnote-plugin/webpack.config.js +35 -0
- package/skills/remnote-bridge/SKILL.md +45 -40
- package/skills/remnote-bridge/instructions/addon.md +134 -0
- package/skills/remnote-bridge/instructions/clean.md +110 -0
- package/skills/remnote-bridge/instructions/connect.md +80 -37
- package/skills/remnote-bridge/instructions/disconnect.md +22 -9
- package/skills/remnote-bridge/instructions/edit-rem.md +113 -327
- package/skills/remnote-bridge/instructions/health.md +23 -13
- package/skills/remnote-bridge/instructions/install-skill.md +58 -0
- package/skills/remnote-bridge/instructions/overall.md +99 -35
- package/skills/remnote-bridge/instructions/read-rem.md +15 -15
- package/skills/remnote-bridge/instructions/search.md +77 -18
- package/skills/remnote-bridge/instructions/setup.md +5 -6
|
@@ -1,55 +1,59 @@
|
|
|
1
1
|
import { declareIndexPlugin, type ReactRNPlugin, WidgetLocation } from '@remnote/plugin-sdk';
|
|
2
2
|
import '../style.css';
|
|
3
3
|
import '../index.css';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { DEFAULT_PLUGIN_VERSION } from '../settings';
|
|
5
|
+
import { MultiConnectionManager } from '../bridge/multi-connection-manager';
|
|
6
6
|
import { createMessageRouter } from '../bridge/message-router';
|
|
7
|
-
// test-scripts 已完成数据收集,不再打包进生产代码
|
|
8
|
-
// 如需重跑,取消注释对应 import 和 onActivate 调用
|
|
9
|
-
// import { runActionTests } from '../test-scripts/test-actions';
|
|
10
|
-
// import { runRichTextBuilderTest } from '../test-scripts/test-richtext-builder';
|
|
11
|
-
// import { runRichTextRemainingTest } from '../test-scripts/test-richtext-remaining';
|
|
12
|
-
// import { runRichTextMatrixTest } from '../test-scripts/test-richtext-matrix';
|
|
13
|
-
// import { runPowerupRenderingTest } from '../test-scripts/test-powerup-rendering';
|
|
14
7
|
|
|
15
|
-
let
|
|
8
|
+
let connectionManager: MultiConnectionManager | null = null;
|
|
16
9
|
// 本地日志缓冲区:避免 onLog 并发读写 plugin.storage 的竞态
|
|
17
10
|
const logBuffer: Array<{ time: number; message: string; level: string }> = [];
|
|
18
11
|
let logFlushPending = false;
|
|
19
12
|
|
|
20
13
|
async function onActivate(plugin: ReactRNPlugin) {
|
|
21
|
-
// 注册 WS Server URL 设置
|
|
22
|
-
await plugin.settings.registerStringSetting({
|
|
23
|
-
id: SETTING_WS_URL,
|
|
24
|
-
title: 'Bridge WS Server URL',
|
|
25
|
-
description: '守护进程 WebSocket Server 地址(默认 ws://127.0.0.1:3002)',
|
|
26
|
-
defaultValue: DEFAULT_WS_URL,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
14
|
// 注册 Bridge Widget(右侧边栏)
|
|
30
15
|
await plugin.app.registerWidget('bridge_widget', WidgetLocation.RightSidebar, {
|
|
31
16
|
dimensions: { height: 'auto', width: '100%' },
|
|
32
17
|
widgetTabIcon: 'https://cdn.jsdelivr.net/npm/lucide-static@0.460.0/icons/globe.svg',
|
|
33
18
|
});
|
|
34
19
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
// 自动发现孪生槽位:Plugin 由 daemon 的 static-server 提供服务,
|
|
21
|
+
// fetch('/api/discovery') 是同源请求,返回 daemon 的 slotIndex 等信息
|
|
22
|
+
let twinSlotIndex = 0;
|
|
23
|
+
let instanceName = '';
|
|
24
|
+
let configPort = 29102;
|
|
25
|
+
try {
|
|
26
|
+
const resp = await fetch('/api/discovery');
|
|
27
|
+
if (resp.ok) {
|
|
28
|
+
const data = await resp.json();
|
|
29
|
+
if (typeof data.slotIndex === 'number') {
|
|
30
|
+
twinSlotIndex = data.slotIndex;
|
|
31
|
+
}
|
|
32
|
+
if (data.instance) {
|
|
33
|
+
instanceName = data.instance;
|
|
34
|
+
}
|
|
35
|
+
if (data.configPort) {
|
|
36
|
+
configPort = data.configPort;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// discovery 失败(dev 模式 / 非 daemon 环境),使用默认值
|
|
41
|
+
}
|
|
42
|
+
// 存储实例名和配置端口,供 widget 显示
|
|
43
|
+
await plugin.storage.setSession('bridge-instance', instanceName);
|
|
44
|
+
await plugin.storage.setSession('bridge-config-port', configPort);
|
|
38
45
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
// 创建 MultiConnectionManager
|
|
47
|
+
connectionManager = new MultiConnectionManager({
|
|
48
|
+
twinSlotIndex,
|
|
42
49
|
pluginVersion: DEFAULT_PLUGIN_VERSION,
|
|
43
50
|
sdkReady: true,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
maxReconnectDelay: 30000,
|
|
47
|
-
onStatusChange: (status) => {
|
|
48
|
-
void plugin.storage.setSession('bridge-status', status);
|
|
51
|
+
onSlotsChange: (slots) => {
|
|
52
|
+
void plugin.storage.setSession('bridge-slots', slots);
|
|
49
53
|
},
|
|
50
|
-
onLog: (message, level) => {
|
|
51
|
-
logBuffer.push({ time: Date.now(), message
|
|
52
|
-
if (logBuffer.length >
|
|
54
|
+
onLog: (slotIndex, message, level) => {
|
|
55
|
+
logBuffer.push({ time: Date.now(), message: `[${slotIndex}] ${message}`, level });
|
|
56
|
+
if (logBuffer.length > 50) logBuffer.splice(0, logBuffer.length - 50);
|
|
53
57
|
// 合并写入:避免并发 read-modify-write 竞态
|
|
54
58
|
if (!logFlushPending) {
|
|
55
59
|
logFlushPending = true;
|
|
@@ -61,22 +65,15 @@ async function onActivate(plugin: ReactRNPlugin) {
|
|
|
61
65
|
},
|
|
62
66
|
});
|
|
63
67
|
|
|
64
|
-
// 路由守护进程转发的请求到 services
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
wsClient.connect();
|
|
68
|
+
// 路由守护进程转发的请求到 services 层(所有连接共享同一个 router)
|
|
69
|
+
connectionManager.setMessageHandler(createMessageRouter(plugin));
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
// runActionTests(plugin).catch((err) => console.error('[ACTION-TEST] 顶层错误:', err));
|
|
71
|
-
// runRichTextBuilderTest(plugin).catch((err) => console.error('[RICHTEXT-BUILDER-TEST] 顶层错误:', err));
|
|
72
|
-
// runRichTextRemainingTest(plugin).catch((err) => console.error('[RICHTEXT-REMAINING-TEST] 顶层错误:', err));
|
|
73
|
-
// runRichTextMatrixTest(plugin).catch((err) => console.error('[RICHTEXT-MATRIX-TEST] 顶层错误:', err));
|
|
74
|
-
// runPowerupRenderingTest(plugin).catch((err) => console.error('[POWERUP-RENDER] 顶层错误:', err));
|
|
71
|
+
connectionManager.start();
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
async function onDeactivate(_: ReactRNPlugin) {
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
connectionManager?.stop();
|
|
76
|
+
connectionManager = null;
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
declareIndexPlugin(onActivate, onDeactivate);
|
|
@@ -119,6 +119,41 @@ if (isProd) {
|
|
|
119
119
|
'Access-Control-Allow-Origin': '*',
|
|
120
120
|
'Access-Control-Allow-Headers': 'baggage, sentry-trace',
|
|
121
121
|
},
|
|
122
|
+
// 劫持 /api/discovery 和 /manifest.json,与 StaticServer 行为一致
|
|
123
|
+
setupMiddlewares: (middlewares, devServer) => {
|
|
124
|
+
const instance = process.env.DISCOVERY_INSTANCE || 'default';
|
|
125
|
+
|
|
126
|
+
devServer.app.get('/api/discovery', (req, res) => {
|
|
127
|
+
res.json({
|
|
128
|
+
wsPort: parseInt(process.env.DISCOVERY_WS_PORT || '29100', 10),
|
|
129
|
+
configPort: parseInt(process.env.DISCOVERY_CONFIG_PORT || '29102', 10),
|
|
130
|
+
instance,
|
|
131
|
+
slotIndex: parseInt(process.env.DISCOVERY_SLOT_INDEX || '0', 10),
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 多实例支持:非 default 实例动态修改 manifest 的 id 和 name,
|
|
136
|
+
// 使 RemNote 能同时加载多个同源 Plugin(RemNote 按 id 去重)
|
|
137
|
+
if (instance !== 'default') {
|
|
138
|
+
devServer.app.get('/manifest.json', (req, res) => {
|
|
139
|
+
const fs = require('fs');
|
|
140
|
+
const manifestPath = path.resolve(__dirname, 'public', 'manifest.json');
|
|
141
|
+
try {
|
|
142
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
143
|
+
manifest.id = `${manifest.id}_${instance}`;
|
|
144
|
+
manifest.name = `${manifest.name} (${instance})`;
|
|
145
|
+
// webpack-dev-server 的 headers 配置不覆盖自定义路由,需手动设置 CORS
|
|
146
|
+
res.set('Access-Control-Allow-Origin', '*');
|
|
147
|
+
res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
148
|
+
res.json(manifest);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
res.status(500).send('manifest parse error');
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return middlewares;
|
|
156
|
+
},
|
|
122
157
|
};
|
|
123
158
|
}
|
|
124
159
|
|
|
@@ -22,6 +22,9 @@ description: "RemNote 知识库操作指南。通过 remnote-bridge 命令行工
|
|
|
22
22
|
| read-globe | `instructions/read-globe.md` |
|
|
23
23
|
| read-context | `instructions/read-context.md` |
|
|
24
24
|
| search | `instructions/search.md` |
|
|
25
|
+
| addon | `instructions/addon.md` |
|
|
26
|
+
| clean | `instructions/clean.md` |
|
|
27
|
+
| install-skill | `instructions/install-skill.md` |
|
|
25
28
|
| 全局概览 | `instructions/overall.md` |
|
|
26
29
|
|
|
27
30
|
---
|
|
@@ -89,7 +92,7 @@ RemNote 推荐的知识结构化方法:
|
|
|
89
92
|
| **Tag** | `##` | Rem 的 tags 数组 | read-rem 的 `tags` 字段 |
|
|
90
93
|
| **Portal** | `((` | 嵌入实时视图(**编辑同步**) | read-tree 标记 `type:portal refs:id1,id2` |
|
|
91
94
|
|
|
92
|
-
Portal 的编辑同步意味着修改一处会影响另一处。Portal 引用的 Rem 不会被 read-tree 自动展开,需要对 refs 中的 ID 单独 read-tree。Portal 引用列表可通过 `edit-rem`
|
|
95
|
+
Portal 的编辑同步意味着修改一处会影响另一处。Portal 引用的 Rem 不会被 read-tree 自动展开,需要对 refs 中的 ID 单独 read-tree。Portal 引用列表可通过 `edit-rem` 修改(直接修改 changes 中的 `portalDirectlyIncludedRem` 数组)。
|
|
93
96
|
|
|
94
97
|
#### Portal 操作速查
|
|
95
98
|
|
|
@@ -97,9 +100,9 @@ Portal 的编辑同步意味着修改一处会影响另一处。Portal 引用的
|
|
|
97
100
|
|:-----|:-----|:-----|
|
|
98
101
|
| 创建 Portal | `edit-tree` | 新增行 `<!--portal refs:id1,id2-->` |
|
|
99
102
|
| 删除 Portal | `edit-tree` | 从大纲中移除 Portal 行(与删除普通行相同) |
|
|
100
|
-
| 修改引用列表(增删引用的 Rem) | `edit-rem` |
|
|
103
|
+
| 修改引用列表(增删引用的 Rem) | `edit-rem` | 直接修改 changes 中的 `portalDirectlyIncludedRem` 数组 |
|
|
101
104
|
| 移动 Portal(换父节点/位置) | `edit-tree` | 与移动普通行相同 |
|
|
102
|
-
| 读取 Portal | `read-rem` | 自动输出
|
|
105
|
+
| 读取 Portal | `read-rem` | 自动输出 8 字段简化 JSON |
|
|
103
106
|
|
|
104
107
|
### RichText 格式
|
|
105
108
|
|
|
@@ -181,7 +184,7 @@ Portal 的编辑同步意味着修改一处会影响另一处。Portal 引用的
|
|
|
181
184
|
{ "i": "a", "onlyAudio": true, "url": "https://example.com/audio.mp3" }
|
|
182
185
|
```
|
|
183
186
|
|
|
184
|
-
> **注意**:在 RemObject 的格式化 JSON 中,数组内的对象会展开为多行(每个 key 一行,缩进 4+2 空格)。以上为简写——构造 edit-rem 的
|
|
187
|
+
> **注意**:在 RemObject 的格式化 JSON 中,数组内的对象会展开为多行(每个 key 一行,缩进 4+2 空格)。以上为简写——构造 edit-rem 的 changes 中 RichText 值时,直接传入数组即可。
|
|
185
188
|
|
|
186
189
|
#### highlightColor(Rem 级别)vs h(RichText 行内)
|
|
187
190
|
|
|
@@ -192,7 +195,7 @@ Portal 的编辑同步意味着修改一处会影响另一处。Portal 引用的
|
|
|
192
195
|
|
|
193
196
|
#### 序列化确定性
|
|
194
197
|
|
|
195
|
-
RichText 对象内部按 **key 字母序排列**(`sortRichTextKeys()`),确保 JSON 始终一致。`_id` 中的 `_`(U+005F)排在所有小写字母(`a`=U+0061
|
|
198
|
+
RichText 对象内部按 **key 字母序排列**(`sortRichTextKeys()`),确保 JSON 始终一致。`_id` 中的 `_`(U+005F)排在所有小写字母(`a`=U+0061)之前。防线 2(乐观并发检测)依赖此确定性序列化来比较缓存与最新数据。
|
|
196
199
|
|
|
197
200
|
### Powerup 机制与噪音过滤
|
|
198
201
|
|
|
@@ -255,7 +258,7 @@ Rem 的属性(文本、类型、格式、标签) → edit-rem (前置
|
|
|
255
258
|
1. 打开 RemNote 桌面端或网页端
|
|
256
259
|
2. 点击左侧边栏底部的插件图标(拼图形状)
|
|
257
260
|
3. 点击「开发你的插件」(Develop Your Plugin)
|
|
258
|
-
4. 在输入框中填入
|
|
261
|
+
4. 在输入框中填入 connect 输出的 Plugin 服务地址(如 `http://localhost:29101`)
|
|
259
262
|
5. 等待插件加载完成
|
|
260
263
|
|
|
261
264
|
**非首次使用**(之前已加载过此插件):
|
|
@@ -271,20 +274,15 @@ Rem 的属性(文本、类型、格式、标签) → edit-rem (前置
|
|
|
271
274
|
|
|
272
275
|
#### 首次使用(setup)
|
|
273
276
|
|
|
274
|
-
setup 会弹出 Chrome
|
|
275
|
-
1. **登录 RemNote**
|
|
276
|
-
2. **配置 dev plugin**:插件图标 → 开发你的插件 → 填入 `http://localhost:8080`
|
|
277
|
+
setup 会弹出 Chrome 窗口,用户只需 **登录 RemNote**,然后**彻底退出 Chrome**(macOS 必须 Cmd+Q,仅关窗口不够)。
|
|
277
278
|
|
|
278
|
-
|
|
279
|
+
setup 只负责保存登录凭证——配置 dev plugin 是 connect 之后的事(见标准模式说明)。
|
|
279
280
|
|
|
280
281
|
**Agent 交互方式**:
|
|
281
282
|
```
|
|
282
283
|
1. 调用 setup
|
|
283
284
|
2. 立即告知用户:
|
|
284
|
-
"已打开 Chrome
|
|
285
|
-
1. 登录 RemNote
|
|
286
|
-
2. 在 RemNote 中配置开发插件:点击左下角插件图标 → 开发你的插件 → 输入 http://localhost:8080
|
|
287
|
-
3. 完成后彻底退出 Chrome(macOS 请按 Cmd+Q)"
|
|
285
|
+
"已打开 Chrome 浏览器。请登录 RemNote,完成后彻底退出 Chrome(macOS 请按 Cmd+Q)"
|
|
288
286
|
3. 等待 setup 返回(阻塞,最长 10 分钟)
|
|
289
287
|
4. 成功 → 进入下一步 connect --headless
|
|
290
288
|
```
|
|
@@ -334,14 +332,16 @@ Agent 应始终使用 JSON 模式调用命令。
|
|
|
334
332
|
```bash
|
|
335
333
|
# 正确:位置参数 = JSON 字符串
|
|
336
334
|
remnote-bridge read-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","fields":["text","type"]}'
|
|
337
|
-
remnote-bridge search --json '{"query":"机器学习","
|
|
335
|
+
remnote-bridge search --json '{"query":"机器学习","limit":10}'
|
|
336
|
+
remnote-bridge edit-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","changes":{"type":"concept"}}'
|
|
337
|
+
|
|
338
|
+
# 人类模式:
|
|
339
|
+
remnote-bridge edit-rem kLrIOHJLyMd8Y2lyA --changes '{"type":"concept"}'
|
|
338
340
|
|
|
339
341
|
# 错误:禁止混用裸 remId + --json
|
|
340
342
|
remnote-bridge read-rem kLrIOHJLyMd8Y2lyA --json # 会失败!
|
|
341
343
|
```
|
|
342
344
|
|
|
343
|
-
注意:search 的 JSON 参数名是 `numResults`(不是 `limit`)。
|
|
344
|
-
|
|
345
345
|
### 中文搜索限制(重要)
|
|
346
346
|
|
|
347
347
|
search 调用 RemNote SDK 官方搜索方法,其分词基于空格分割。**中文、日文、韩文等无空格分词的语言搜索效果差**——SDK 将多字词拆为单字符 token 匹配,导致返回 0 结果或不相关结果。RemNote 本地桌面版已优化此问题,Web 版未优化。
|
|
@@ -361,7 +361,7 @@ search 调用 RemNote SDK 官方搜索方法,其分词基于空格分割。**
|
|
|
361
361
|
|
|
362
362
|
---
|
|
363
363
|
|
|
364
|
-
## 5.
|
|
364
|
+
## 5. 安全机制
|
|
365
365
|
|
|
366
366
|
### 防线 1:缓存存在性
|
|
367
367
|
|
|
@@ -371,9 +371,11 @@ search 调用 RemNote SDK 官方搜索方法,其分词基于空格分割。**
|
|
|
371
371
|
|
|
372
372
|
edit 时从 SDK 重新读取最新数据,与缓存严格比较。被外部修改则拒绝编辑且**不更新缓存**——迫使 Agent 重新 read。
|
|
373
373
|
|
|
374
|
-
### 防线 3:str_replace
|
|
374
|
+
### 防线 3:str_replace 精确匹配(仅 edit-tree)
|
|
375
375
|
|
|
376
|
-
`oldStr`
|
|
376
|
+
`oldStr` 必须在大纲文本中恰好匹配 1 次。**仅适用于 edit-tree**。
|
|
377
|
+
|
|
378
|
+
edit-rem 使用字段直接修改(changes 对象),不经过 str_replace,改为**字段白名单校验**(非法字段产生警告,枚举值非法则拒绝)。
|
|
377
379
|
|
|
378
380
|
### 缓存更新规则
|
|
379
381
|
|
|
@@ -381,7 +383,8 @@ edit 时从 SDK 重新读取最新数据,与缓存严格比较。被外部修
|
|
|
381
383
|
|:-----|:---------|:-----------|
|
|
382
384
|
| 写入成功 | 从 SDK 重新读取 → 更新缓存 | 可继续编辑 |
|
|
383
385
|
| 防线 2 拒绝 / 部分写入失败 | **不更新缓存** | 必须重新 read |
|
|
384
|
-
|
|
|
386
|
+
| 枚举值非法(edit-rem) | 缓存保持不变 | 检查允许的值范围后重试 |
|
|
387
|
+
| 防线 3 拒绝 / JSON 语法错误(edit-tree) | 缓存保持不变 | 调整 oldStr/newStr 后**直接重试** |
|
|
385
388
|
| 操作执行中异常(edit-tree) | 已执行的操作保留(**无回滚**),不更新缓存 | 必须重新 read-tree |
|
|
386
389
|
|
|
387
390
|
---
|
|
@@ -539,20 +542,21 @@ SDK bug 自动修复:移动行进入多行闪卡父节点时自动设 `isCardI
|
|
|
539
542
|
|
|
540
543
|
---
|
|
541
544
|
|
|
542
|
-
## 8. edit-rem
|
|
545
|
+
## 8. edit-rem 字段修改要点
|
|
543
546
|
|
|
544
|
-
|
|
547
|
+
edit-rem 通过 `changes` 对象直接指定要修改的字段及其目标值,无需 str_replace。
|
|
545
548
|
|
|
546
|
-
###
|
|
549
|
+
### 使用方式
|
|
547
550
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
+
```bash
|
|
552
|
+
# JSON 模式
|
|
553
|
+
edit-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","changes":{"type":"concept","text":["新标题"]}}'
|
|
551
554
|
|
|
555
|
+
# 人类模式
|
|
556
|
+
edit-rem kLrIOHJLyMd8Y2lyA --changes '{"type":"concept","text":["新标题"]}'
|
|
552
557
|
```
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
```
|
|
558
|
+
|
|
559
|
+
`changes` 对象中只需包含要修改的字段,未提及的字段保持不变。
|
|
556
560
|
|
|
557
561
|
### 21 个可编辑字段
|
|
558
562
|
|
|
@@ -573,9 +577,9 @@ tags, sources, positionAmongstSiblings, portalDirectlyIncludedRem
|
|
|
573
577
|
| `fontSize` | `null` → 调用 `setFontSize(undefined)`(恢复普通大小) |
|
|
574
578
|
| `todoStatus` | 依赖 `isTodo=true` 才生效;`null` 被跳过(清除 todo 应设 `isTodo=false`) |
|
|
575
579
|
| `type` | 不可设为 `portal`(只能通过 SDK `createPortal()` 创建) |
|
|
576
|
-
| `parent` + `positionAmongstSiblings` | 共享同一 SDK 调用 `setParent(parentId, position)`,**应在同一次
|
|
580
|
+
| `parent` + `positionAmongstSiblings` | 共享同一 SDK 调用 `setParent(parentId, position)`,**应在同一次 changes 中同时修改** |
|
|
577
581
|
| `tags` / `sources` | **Diff 机制**:对比当前 vs 目标数组,逐项 add/remove。必须列出完整目标数组,缺少的会被删除 |
|
|
578
|
-
| `portalDirectlyIncludedRem` | Portal 专用可写。**Diff 机制**:对比当前 vs 目标数组,逐项 addToPortal/removeFromPortal。仅 type=portal
|
|
582
|
+
| `portalDirectlyIncludedRem` | Portal 专用可写。**Diff 机制**:对比当前 vs 目标数组,逐项 addToPortal/removeFromPortal。仅 type=portal 时可修改 |
|
|
579
583
|
|
|
580
584
|
### 常用只读字段(修改只产生警告,不生效)
|
|
581
585
|
|
|
@@ -588,8 +592,8 @@ aliases, descendants, siblingRem, isTable, portalType, propertyType
|
|
|
588
592
|
|
|
589
593
|
| 模式 | 字段数 | 用法 |
|
|
590
594
|
|:-----|:-------|:-----|
|
|
591
|
-
| 默认 |
|
|
592
|
-
| Portal 简化 |
|
|
595
|
+
| 默认 | 33(RW + R) | 常用场景 |
|
|
596
|
+
| Portal 简化 | 8(id, type, portalType, portalDirectlyIncludedRem, parent, positionAmongstSiblings, createdAt, updatedAt) | type=portal 时自动切换,`--full` 和 `--fields` 可覆盖 |
|
|
593
597
|
| `--full` | 51(含低频 R-F) | 需要 Powerup 标识、时间戳等 |
|
|
594
598
|
| `--fields` | 自选 + id | 精确获取特定字段 |
|
|
595
599
|
|
|
@@ -605,22 +609,23 @@ aliases, descendants, siblingRem, isTable, portalType, propertyType
|
|
|
605
609
|
| Plugin 未连接 | RemNote 未打开 | 打开 RemNote(health 三层:daemon→Plugin→SDK 链式依赖) |
|
|
606
610
|
| has not been read yet | 未先 read | 执行对应 read 命令(search 结果不算 read!) |
|
|
607
611
|
| has been modified since last read | 被外部修改 | 重新 read(必须,不可直接重试) |
|
|
608
|
-
|
|
|
609
|
-
| old_str
|
|
610
|
-
|
|
|
612
|
+
| Invalid value for 'field' | 枚举字段值不合法(edit-rem) | 检查该字段允许的值范围 |
|
|
613
|
+
| old_str not found | oldStr 不精确(edit-tree) | 检查引号、空格、换行 |
|
|
614
|
+
| old_str matches N locations | oldStr 不够具体(edit-tree) | 扩大 oldStr 范围,包含更多上下文 |
|
|
611
615
|
| content_modified | edit-tree 中改了行内容 | 用 edit-rem |
|
|
612
616
|
| orphan_detected | 删了父但留了子 | 同时删除所有子行 |
|
|
613
617
|
| folded_delete | 删了有隐藏子节点的行 | 用更大 depth 重新 read-tree |
|
|
614
618
|
| elided_modified | 删/改了省略占位符 | 用更大 depth/maxSiblings 重新 read-tree |
|
|
615
619
|
| indent_skip | 缩进跳级(如 0→4 空格) | 每级 2 空格,不可跳级 |
|
|
616
620
|
| children_captured | 新行劫持已有子节点 | 把新行插到兄弟末尾 |
|
|
617
|
-
| old_str not found in simplified Portal JSON | Portal 编辑时 oldStr 不匹配简化 JSON | 检查 Portal 简化 JSON 格式 |
|
|
618
621
|
| Rem not found | remId 无效或已删除 | 用 search 重新定位 |
|
|
619
622
|
|
|
620
623
|
---
|
|
621
624
|
|
|
622
625
|
## 10. 配置
|
|
623
626
|
|
|
624
|
-
|
|
627
|
+
配置文件:`~/.remnote-bridge/config.json`(全局目录,所有实例共享)
|
|
628
|
+
|
|
629
|
+
端口由槽位自动分配(槽位 0: 29100/29101/29102,槽位 1: 29110/29111/29112,以此类推),最多 4 个并发实例。
|
|
625
630
|
|
|
626
|
-
|
|
631
|
+
关键默认值:超时 30 分钟、maxNodes 200、maxSiblings 20、readTreeDepth 3、readGlobeDepth -1(无限)、缓存上限 200 条。
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# addon
|
|
2
|
+
|
|
3
|
+
> 管理增强项目(addon):查看状态、安装、卸载。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
`addon` 命令组管理 remnote-bridge 的增强项目(如 `remnote-rag` 语义搜索)。增强项目是独立安装的可选组件,扩展核心功能。
|
|
10
|
+
|
|
11
|
+
三个子命令:
|
|
12
|
+
|
|
13
|
+
| 子命令 | 功能 |
|
|
14
|
+
|:-------|:-----|
|
|
15
|
+
| `addon list` | 查看所有可用增强项目的状态 |
|
|
16
|
+
| `addon install <name>` | 安装并启用指定增强项目 |
|
|
17
|
+
| `addon uninstall <name>` | 卸载指定增强项目 |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 用法
|
|
22
|
+
|
|
23
|
+
### 人类模式
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 查看所有增强项目
|
|
27
|
+
remnote-bridge addon list
|
|
28
|
+
|
|
29
|
+
# 安装增强项目
|
|
30
|
+
remnote-bridge addon install remnote-rag
|
|
31
|
+
|
|
32
|
+
# 卸载增强项目
|
|
33
|
+
remnote-bridge addon uninstall remnote-rag
|
|
34
|
+
|
|
35
|
+
# 卸载并清理数据目录
|
|
36
|
+
remnote-bridge addon uninstall remnote-rag --purge
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### JSON 模式
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
remnote-bridge --json addon list
|
|
43
|
+
remnote-bridge --json addon install remnote-rag
|
|
44
|
+
remnote-bridge --json addon uninstall remnote-rag
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## JSON 输出
|
|
50
|
+
|
|
51
|
+
### addon list
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"ok": true,
|
|
56
|
+
"command": "addon-list",
|
|
57
|
+
"addons": [
|
|
58
|
+
{
|
|
59
|
+
"name": "remnote-rag",
|
|
60
|
+
"enabled": true,
|
|
61
|
+
"installed": true,
|
|
62
|
+
"settingsValid": true,
|
|
63
|
+
"missingSettings": [],
|
|
64
|
+
"description": "语义搜索增强"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### addon install(成功)
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"ok": true,
|
|
75
|
+
"command": "addon-install",
|
|
76
|
+
"name": "remnote-rag",
|
|
77
|
+
"action": "installed"
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### addon install(已安装)
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"ok": true,
|
|
86
|
+
"command": "addon-install",
|
|
87
|
+
"name": "remnote-rag",
|
|
88
|
+
"action": "already-installed"
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### addon uninstall
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"ok": true,
|
|
97
|
+
"command": "addon-uninstall",
|
|
98
|
+
"name": "remnote-rag",
|
|
99
|
+
"purged": false
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 失败
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"ok": false,
|
|
108
|
+
"command": "addon-install",
|
|
109
|
+
"name": "unknown-addon",
|
|
110
|
+
"error": "未知的增强项目: unknown-addon。可用项目: remnote-rag"
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 退出码
|
|
117
|
+
|
|
118
|
+
| 退出码 | 含义 |
|
|
119
|
+
|:-------|:-----|
|
|
120
|
+
| 0 | 操作成功 |
|
|
121
|
+
| 1 | 操作失败(未知名称、安装/卸载错误等) |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 幂等性
|
|
126
|
+
|
|
127
|
+
- `addon install`:已安装时返回 `action: "already-installed"`,自动标记为启用
|
|
128
|
+
- `addon uninstall`:已卸载时安全返回
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 配置依赖
|
|
133
|
+
|
|
134
|
+
增强项目的启用/禁用状态保存在全局配置 `~/.remnote-bridge/config.json` 中。每个 addon 的独立配置存储在 `~/.remnote-bridge/addons/<name>/config.json` 中。安装后需要在配置页面或配置文件中填写必需的配置项(如 API Key)。
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# clean
|
|
2
|
+
|
|
3
|
+
> 清理所有 daemon 进程、PID 文件、日志、注册表和 addon 数据。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
`clean` 执行全面清理,适用于系统状态异常或需要彻底重置的场景。
|
|
10
|
+
|
|
11
|
+
按顺序执行以下清理步骤:
|
|
12
|
+
|
|
13
|
+
1. **停止所有运行中的 daemon**(遍历注册表,SIGTERM → 5 秒后 SIGKILL)
|
|
14
|
+
2. **清理实例文件**(`~/.remnote-bridge/instances/` 下的 PID 和日志文件)
|
|
15
|
+
3. **清理注册表**(`~/.remnote-bridge/registry.json`)
|
|
16
|
+
4. **清理旧版项目根文件**(`.remnote-bridge.pid`、`.remnote-bridge.log`、`.remnote-bridge.json`,向后兼容)
|
|
17
|
+
5. **清理已安装的 Skill**(`~/.claude/skills/remnote-bridge/`)
|
|
18
|
+
6. **清理 addon 数据目录**(如 remnote-rag 的索引数据)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 用法
|
|
23
|
+
|
|
24
|
+
### 人类模式
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
remnote-bridge clean
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
输出示例:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
已删除: /Users/xxx/.remnote-bridge/instances/0.pid
|
|
34
|
+
已删除: /Users/xxx/.remnote-bridge/instances/0.log
|
|
35
|
+
已删除: /Users/xxx/.remnote-bridge/registry.json
|
|
36
|
+
|
|
37
|
+
清理完成,共删除 3 项
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### JSON 模式
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
remnote-bridge --json clean
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## JSON 输出
|
|
49
|
+
|
|
50
|
+
### 清理成功
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"ok": true,
|
|
55
|
+
"command": "clean",
|
|
56
|
+
"removed": [
|
|
57
|
+
"/Users/xxx/.remnote-bridge/instances/0.pid",
|
|
58
|
+
"/Users/xxx/.remnote-bridge/instances/0.log",
|
|
59
|
+
"/Users/xxx/.remnote-bridge/registry.json"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 部分失败
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"ok": false,
|
|
69
|
+
"command": "clean",
|
|
70
|
+
"removed": ["/Users/xxx/.remnote-bridge/instances/0.pid"],
|
|
71
|
+
"errors": ["删除失败: /Users/xxx/.remnote-bridge/instances/0.log — EPERM"]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 无需清理
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"ok": true,
|
|
80
|
+
"command": "clean",
|
|
81
|
+
"removed": []
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 退出码
|
|
88
|
+
|
|
89
|
+
| 退出码 | 含义 |
|
|
90
|
+
|:-------|:-----|
|
|
91
|
+
| 0 | 清理完成(包括无需清理的情况) |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 适用场景
|
|
96
|
+
|
|
97
|
+
| 场景 | 说明 |
|
|
98
|
+
|:-----|:-----|
|
|
99
|
+
| daemon 进程残留 | `disconnect` 无法正常停止时 |
|
|
100
|
+
| 端口被占用 | 多次启动失败后的端口残留 |
|
|
101
|
+
| 注册表损坏 | 注册表文件不一致时重置 |
|
|
102
|
+
| 完整重装 | 清除所有状态从零开始 |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## AI Agent 注意事项
|
|
107
|
+
|
|
108
|
+
- `clean` 是**破坏性操作**——会停止所有 daemon、清空所有缓存和注册表
|
|
109
|
+
- 执行 `clean` 后需要重新 `connect` 才能继续操作
|
|
110
|
+
- 通常只在排查问题或重置环境时使用,正常流程不需要
|