remnote-bridge 0.1.12 → 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.
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * edit-rem 命令
3
3
  *
4
- * 通过 str_replace 编辑 Rem 的 JSON 序列化。
5
- * 三道防线保证安全:缓存存在性、并发检测、精确匹配。
4
+ * 直接修改 Rem 的属性字段。
5
+ * 两道防线保证安全:缓存存在性、并发检测。
6
6
  * - 退出码:0 成功 / 1 业务错误 / 2 守护进程不可达
7
7
  */
8
8
  import { sendDaemonRequest } from '../daemon/send-request.js';
9
9
  import { jsonOutput, handleCommandError } from '../utils/output.js';
10
10
  export async function editRemCommand(remId, options) {
11
- const { json, oldStr, newStr } = options;
11
+ const { json, changes } = options;
12
12
  let result;
13
13
  try {
14
- result = await sendDaemonRequest('edit_rem', { remId, oldStr, newStr });
14
+ result = await sendDaemonRequest('edit_rem', { remId, changes });
15
15
  }
16
16
  catch (err) {
17
17
  handleCommandError(err, 'edit-rem', json);
@@ -32,7 +32,7 @@ export async function editRemCommand(remId, options) {
32
32
  else {
33
33
  if (editResult.ok) {
34
34
  if (editResult.changes.length === 0) {
35
- console.log('无变更(old_str 和 new_str 产生相同结果)');
35
+ console.log('无变更(未发现可写入的变更字段)');
36
36
  }
37
37
  else {
38
38
  console.log(`已更新字段: ${editResult.changes.join(', ')}`);
@@ -20,7 +20,9 @@ export async function readRemCommand(remId, options = {}) {
20
20
  payload.full = true;
21
21
  }
22
22
  else if (fields) {
23
- payload.fields = fields.split(',').map(f => f.trim()).filter(Boolean);
23
+ payload.fields = Array.isArray(fields)
24
+ ? fields
25
+ : fields.split(',').map(f => f.trim()).filter(Boolean);
24
26
  }
25
27
  let result;
26
28
  try {
@@ -1,17 +1,12 @@
1
1
  /**
2
2
  * EditHandler — edit-rem 命令的编排器
3
3
  *
4
- * 在 daemon 层实现三道防线 + str_replace + 后处理校验。
4
+ * 在 daemon 层实现两道防线 + 字段白名单校验 + 直接转发 changes。
5
5
  * Plugin 只负责原子写入(write_rem_fields)。
6
6
  *
7
7
  * 防线 1:缓存存在性检查(必须先 read 再 edit)
8
8
  * 防线 2:乐观并发检测(当前 JSON 与缓存 JSON 比较)
9
- * 防线 3:str_replace 精确匹配(old_str 必须唯一匹配)
10
- *
11
- * Portal 专用路径:type === 'portal' 时,在简化 JSON(9 字段)上执行 str_replace,
12
- * 推导变更后调用专用写入逻辑。
13
9
  */
14
- import { PORTAL_FIELDS } from './read-handler.js';
15
10
  /** 只读字段集合 — 变更这些字段只产生警告,不执行写入 */
16
11
  const READ_ONLY_FIELDS = new Set([
17
12
  'id',
@@ -30,10 +25,22 @@ const READ_ONLY_FIELDS = new Set([
30
25
  'isPowerup', 'isPowerupEnum', 'isPowerupProperty',
31
26
  'isPowerupPropertyListItem', 'isPowerupSlot',
32
27
  ]);
33
- /** Portal 简化 JSON 中的只读字段 */
34
- const PORTAL_READONLY_FIELDS = new Set([
35
- 'id', 'type', 'portalType', 'children', 'createdAt', 'updatedAt',
28
+ /** 可写字段白名单 21 个可写字段 */
29
+ const WRITABLE_FIELDS = new Set([
30
+ 'text', 'backText', 'type', 'isDocument', 'parent',
31
+ 'fontSize', 'highlightColor',
32
+ 'isTodo', 'todoStatus', 'isCode', 'isQuote', 'isListItem', 'isCardItem', 'isSlot', 'isProperty',
33
+ 'enablePractice', 'practiceDirection',
34
+ 'tags', 'sources', 'positionAmongstSiblings', 'portalDirectlyIncludedRem',
36
35
  ]);
36
+ /** 枚举字段的合法值 */
37
+ const ENUM_VALUES = {
38
+ type: new Set(['concept', 'descriptor', 'default']),
39
+ practiceDirection: new Set(['forward', 'backward', 'both', 'none']),
40
+ highlightColor: new Set(['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Purple', 'Gray', 'Brown', 'Pink', null]),
41
+ fontSize: new Set(['H1', 'H2', 'H3', null]),
42
+ todoStatus: new Set(['Finished', 'Unfinished', null]),
43
+ };
37
44
  export class EditHandler {
38
45
  cache;
39
46
  forwardToPlugin;
@@ -42,145 +49,61 @@ export class EditHandler {
42
49
  this.forwardToPlugin = forwardToPlugin;
43
50
  }
44
51
  async handleEditRem(payload) {
45
- const { remId, oldStr, newStr } = payload;
52
+ const { remId, changes } = payload;
53
+ if (!changes || typeof changes !== 'object' || Object.keys(changes).length === 0) {
54
+ return { ok: true, changes: [], warnings: [] };
55
+ }
46
56
  // ── 防线 1: 缓存存在性检查 ──
47
- const cachedJson = this.cache.get('rem:' + remId);
48
- if (!cachedJson) {
57
+ const cachedObj = this.cache.get('rem:' + remId);
58
+ if (!cachedObj) {
49
59
  throw new Error(`Rem ${remId} has not been read yet. Read it first before editing.`);
50
60
  }
51
61
  // ── 防线 2: 乐观并发检测 ──
52
62
  const currentRemObject = await this.forwardToPlugin('read_rem', { remId });
53
63
  const currentJson = JSON.stringify(currentRemObject, null, 2);
64
+ const cachedJson = JSON.stringify(cachedObj, null, 2);
54
65
  if (currentJson !== cachedJson) {
55
66
  // 不更新缓存 — 迫使 AI re-read
56
67
  throw new Error(`Rem ${remId} has been modified since last read. Please read it again before editing.`);
57
68
  }
58
- // ── 检测 Portal 类型,分流到专用路径 ──
59
- const cachedObj = JSON.parse(cachedJson);
60
- if (cachedObj.type === 'portal') {
61
- return this.handlePortalEdit(remId, cachedJson, cachedObj, oldStr, newStr);
62
- }
63
- // ── 普通 Rem 路径 ──
64
- return this.handleNormalEdit(remId, cachedJson, cachedObj, oldStr, newStr);
65
- }
66
- /** Portal 专用编辑路径:在简化 JSON 上执行 str_replace */
67
- async handlePortalEdit(remId, cachedJson, fullObj, oldStr, newStr) {
68
- // 从完整对象提取简化 JSON
69
- const simplified = {};
70
- for (const field of PORTAL_FIELDS) {
71
- if (field in fullObj) {
72
- simplified[field] = fullObj[field];
73
- }
74
- }
75
- const simplifiedJson = JSON.stringify(simplified, null, 2);
76
- // ── 防线 3: str_replace 在简化 JSON 上精确匹配 ──
77
- const matchCount = countOccurrences(simplifiedJson, oldStr);
78
- if (matchCount === 0) {
79
- throw new Error(`old_str not found in the simplified Portal JSON of rem ${remId}`);
80
- }
81
- if (matchCount > 1) {
82
- throw new Error(`old_str matches ${matchCount} locations in Portal rem ${remId}. ` +
83
- `Make old_str more specific to match exactly once.`);
84
- }
85
- const modifiedSimplifiedJson = simplifiedJson.replace(oldStr, newStr);
86
- // JSON 解析
87
- let modified;
88
- try {
89
- modified = JSON.parse(modifiedSimplifiedJson);
90
- }
91
- catch {
92
- throw new Error('The replacement produced invalid JSON. Check your new_str for syntax errors.');
93
- }
94
- // 推导变更字段
95
- const changes = {};
69
+ // ── 遍历 changes keys:分类过滤 ──
96
70
  const warnings = [];
97
- for (const key of Object.keys(modified)) {
98
- if (JSON.stringify(modified[key]) !== JSON.stringify(simplified[key])) {
99
- if (PORTAL_READONLY_FIELDS.has(key)) {
100
- warnings.push(`Field '${key}' is read-only and was ignored`);
101
- }
102
- else {
103
- changes[key] = modified[key];
104
- }
71
+ const writableChanges = {};
72
+ for (const key of Object.keys(changes)) {
73
+ if (READ_ONLY_FIELDS.has(key)) {
74
+ warnings.push(`Field '${key}' is read-only and was ignored`);
75
+ }
76
+ else if (!WRITABLE_FIELDS.has(key)) {
77
+ warnings.push(`Field '${key}' is unknown and was ignored`);
78
+ }
79
+ else {
80
+ writableChanges[key] = changes[key];
105
81
  }
106
82
  }
107
- // 空变更检查
108
- if (Object.keys(changes).length === 0) {
109
- return { ok: true, changes: [], warnings };
110
- }
111
- // ── 发送变更到 Plugin ──
112
- const writeResult = (await this.forwardToPlugin('write_rem_fields', {
113
- remId,
114
- changes,
115
- }));
116
- if (writeResult.failed) {
117
- return {
118
- ok: false,
119
- changes: [],
120
- warnings,
121
- error: `Failed to update field '${writeResult.failed.field}': ${writeResult.failed.error}`,
122
- appliedChanges: writeResult.applied,
123
- failedField: writeResult.failed.field,
124
- };
125
- }
126
- // ── 写入成功 → 从 Plugin 重新获取完整 Rem 并更新缓存(D5)──
127
- const freshRemObject = await this.forwardToPlugin('read_rem', { remId });
128
- const freshJson = JSON.stringify(freshRemObject, null, 2);
129
- this.cache.set('rem:' + remId, freshJson);
130
- return {
131
- ok: true,
132
- changes: Object.keys(changes),
133
- warnings,
134
- };
135
- }
136
- /** 普通 Rem 编辑路径:在完整 JSON 上执行 str_replace */
137
- async handleNormalEdit(remId, cachedJson, original, oldStr, newStr) {
138
- // ── 防线 3: str_replace 精确匹配 ──
139
- const matchCount = countOccurrences(cachedJson, oldStr);
140
- if (matchCount === 0) {
141
- throw new Error(`old_str not found in the serialized JSON of rem ${remId}`);
142
- }
143
- if (matchCount > 1) {
144
- throw new Error(`old_str matches ${matchCount} locations in rem ${remId}. ` +
145
- `Make old_str more specific to match exactly once.`);
146
- }
147
- const modifiedJson = cachedJson.replace(oldStr, newStr);
148
- // 1. JSON 解析
149
- let modified;
150
- try {
151
- modified = JSON.parse(modifiedJson);
152
- }
153
- catch {
154
- throw new Error('The replacement produced invalid JSON. Check your new_str for syntax errors.');
155
- }
156
- // 2. 推导变更字段
157
- const changes = {};
158
- const warnings = [];
159
- for (const key of Object.keys(modified)) {
160
- if (JSON.stringify(modified[key]) !== JSON.stringify(original[key])) {
161
- if (READ_ONLY_FIELDS.has(key)) {
162
- warnings.push(`Field '${key}' is read-only and was ignored`);
163
- }
164
- else {
165
- changes[key] = modified[key];
83
+ // ── 枚举值范围校验 ──
84
+ for (const [field, allowedValues] of Object.entries(ENUM_VALUES)) {
85
+ if (field in writableChanges) {
86
+ const value = writableChanges[field];
87
+ if (!allowedValues.has(value)) {
88
+ throw new Error(`Invalid value for '${field}': ${JSON.stringify(value)}. Allowed: ${[...allowedValues].map(v => JSON.stringify(v)).join(', ')}`);
166
89
  }
167
90
  }
168
91
  }
169
- // 3. 语义一致性校验
170
- if ('todoStatus' in changes && changes.todoStatus !== null) {
171
- const isTodo = modified.isTodo ?? original.isTodo;
92
+ // ── 语义校验:todoStatus 非 null 但 isTodo 未启用 ──
93
+ if ('todoStatus' in writableChanges && writableChanges.todoStatus !== null) {
94
+ const isTodo = writableChanges.isTodo ?? cachedObj.isTodo;
172
95
  if (!isTodo) {
173
96
  warnings.push("Setting 'todoStatus' without 'isTodo: true' may have no effect");
174
97
  }
175
98
  }
176
- // 4. 空变更检查
177
- if (Object.keys(changes).length === 0) {
99
+ // ── 空变更检查 ──
100
+ if (Object.keys(writableChanges).length === 0) {
178
101
  return { ok: true, changes: [], warnings };
179
102
  }
180
103
  // ── 发送变更到 Plugin ──
181
104
  const writeResult = (await this.forwardToPlugin('write_rem_fields', {
182
105
  remId,
183
- changes,
106
+ changes: writableChanges,
184
107
  }));
185
108
  if (writeResult.failed) {
186
109
  // 部分失败 — 不更新缓存(迫使 AI re-read)
@@ -193,27 +116,13 @@ export class EditHandler {
193
116
  failedField: writeResult.failed.field,
194
117
  };
195
118
  }
196
- // ── 写入成功 → 从 Plugin 重新获取完整 Rem 并更新缓存(D5)──
119
+ // ── 写入成功 → 从 Plugin 重新获取完整 Rem 并更新缓存 ──
197
120
  const freshRemObject = await this.forwardToPlugin('read_rem', { remId });
198
- const freshJson = JSON.stringify(freshRemObject, null, 2);
199
- this.cache.set('rem:' + remId, freshJson);
121
+ this.cache.set('rem:' + remId, freshRemObject);
200
122
  return {
201
123
  ok: true,
202
- changes: Object.keys(changes),
124
+ changes: Object.keys(writableChanges),
203
125
  warnings,
204
126
  };
205
127
  }
206
128
  }
207
- /** 统计 needle 在 haystack 中出现的次数 */
208
- function countOccurrences(haystack, needle) {
209
- let count = 0;
210
- let pos = 0;
211
- while (true) {
212
- pos = haystack.indexOf(needle, pos);
213
- if (pos === -1)
214
- break;
215
- count++;
216
- pos += needle.length; // 非重叠匹配,与 String.replace 行为一致
217
- }
218
- return count;
219
- }
@@ -44,17 +44,16 @@ export class ReadHandler {
44
44
  const includePowerup = payload.includePowerup ?? false;
45
45
  // 转发到 Plugin
46
46
  const remObject = await this.forwardToPlugin('read_rem', { remId, includePowerup });
47
- // 缓存完整 JSON
48
- const fullJson = JSON.stringify(remObject, null, 2);
49
- this.cache.set(cacheKey, fullJson);
50
- this.onLog?.(`缓存 Rem ${remId.slice(0, 8)}... (${fullJson.length} bytes)`, 'info');
47
+ // 缓存完整 RemObject 对象
48
+ this.cache.set(cacheKey, remObject);
49
+ this.onLog?.(`缓存 Rem ${remId.slice(0, 8)}...`, 'info');
51
50
  // 字段过滤
52
51
  const fields = payload.fields;
53
52
  const full = payload.full;
54
53
  let result;
55
54
  if (full) {
56
- // --full → 返回完整对象(含 R-F 字段)
57
- result = remObject;
55
+ // --full → 返回完整对象(含 R-F 字段)。浅拷贝避免污染缓存对象。
56
+ result = { ...remObject };
58
57
  }
59
58
  else if (fields) {
60
59
  // --fields 过滤:只返回指定字段 + id
@@ -1,8 +1,13 @@
1
1
  /**
2
- * RemCache — LRU 缓存,存储 Rem 的序列化 JSON
2
+ * RemCache — LRU 缓存,存储 Rem 数据
3
3
  *
4
4
  * 缓存存储在 daemon 内存中,生命周期与 daemon 一致。
5
5
  * disconnect 关闭 daemon → 缓存自然消失。
6
+ *
7
+ * 泛化值类型:不同 key 前缀存储不同类型的数据:
8
+ * - rem:{remId} → RemObject 对象
9
+ * - tree:{remId} → Markdown outline 字符串
10
+ * - tree-depth:{remId} 等 → 参数值字符串
6
11
  */
7
12
  export class RemCache {
8
13
  cache = new Map();
@@ -15,19 +20,19 @@ export class RemCache {
15
20
  if (!entry)
16
21
  return null;
17
22
  entry.lastAccess = Date.now();
18
- return entry.json;
23
+ return entry.data;
19
24
  }
20
25
  /** 获取缓存条目的创建时间(ISO 8601),不存在返回 null */
21
26
  getCreatedAt(remId) {
22
27
  const entry = this.cache.get(remId);
23
28
  return entry ? entry.createdAt : null;
24
29
  }
25
- set(remId, json) {
30
+ set(remId, data) {
26
31
  const now = Date.now();
27
32
  const createdAt = new Date(now).toISOString();
28
33
  if (this.cache.has(remId)) {
29
34
  const entry = this.cache.get(remId);
30
- entry.json = json;
35
+ entry.data = data;
31
36
  entry.lastAccess = now;
32
37
  entry.createdAt = createdAt;
33
38
  return;
@@ -46,7 +51,7 @@ export class RemCache {
46
51
  this.cache.delete(oldestId);
47
52
  }
48
53
  }
49
- this.cache.set(remId, { json, lastAccess: now, createdAt });
54
+ this.cache.set(remId, { data, lastAccess: now, createdAt });
50
55
  }
51
56
  has(remId) {
52
57
  return this.cache.has(remId);
package/dist/cli/main.js CHANGED
@@ -121,7 +121,7 @@ program
121
121
  const input = parseJsonInput('read-rem', remIdOrJson);
122
122
  if (!input)
123
123
  return;
124
- await readRemCommand(input.remId, { json, fields: input.fields?.join(','), full: input.full, includePowerup: input.includePowerup });
124
+ await readRemCommand(input.remId, { json, fields: input.fields, full: input.full, includePowerup: input.includePowerup });
125
125
  }
126
126
  else {
127
127
  if (!remIdOrJson) {
@@ -282,7 +282,7 @@ program
282
282
  process.exitCode = 1;
283
283
  return;
284
284
  }
285
- await searchCommand(input.query, { json, limit: input.numResults?.toString() });
285
+ await searchCommand(input.query, { json, limit: (input.limit ?? input.numResults)?.toString() });
286
286
  }
287
287
  else {
288
288
  if (!queryOrJson) {
@@ -295,16 +295,15 @@ program
295
295
  });
296
296
  program
297
297
  .command('edit-rem [remIdOrJson]')
298
- .description('通过 str_replace 编辑 Rem 的 JSON 字段')
299
- .option('--old-str <oldStr>', '要替换的原始文本片段')
300
- .option('--new-str <newStr>', '替换后的新文本片段')
298
+ .description('直接修改 Rem 的属性字段')
299
+ .option('--changes <changesJson>', '要修改的字段及新值(JSON 字符串)')
301
300
  .action(async (remIdOrJson, cmdOpts) => {
302
301
  const { json } = program.opts();
303
302
  if (json) {
304
- const input = parseJsonInput('edit-rem', remIdOrJson, ['oldStr', 'newStr']);
303
+ const input = parseJsonInput('edit-rem', remIdOrJson, ['changes']);
305
304
  if (!input)
306
305
  return;
307
- await editRemCommand(input.remId, { json, oldStr: input.oldStr, newStr: input.newStr });
306
+ await editRemCommand(input.remId, { json, changes: input.changes });
308
307
  }
309
308
  else {
310
309
  if (!remIdOrJson) {
@@ -312,12 +311,21 @@ program
312
311
  process.exitCode = 1;
313
312
  return;
314
313
  }
315
- if (!cmdOpts.oldStr || cmdOpts.newStr === undefined) {
316
- console.error('错误: --old-str 和 --new-str 是必需的');
314
+ if (!cmdOpts.changes) {
315
+ console.error('错误: --changes 是必需的');
316
+ process.exitCode = 1;
317
+ return;
318
+ }
319
+ let changes;
320
+ try {
321
+ changes = JSON.parse(cmdOpts.changes);
322
+ }
323
+ catch {
324
+ console.error('错误: --changes 不是合法的 JSON');
317
325
  process.exitCode = 1;
318
326
  return;
319
327
  }
320
- await editRemCommand(remIdOrJson, { json, oldStr: cmdOpts.oldStr, newStr: cmdOpts.newStr });
328
+ await editRemCommand(remIdOrJson, { json, changes });
321
329
  }
322
330
  });
323
331
  // mcp 子命令
@@ -0,0 +1,43 @@
1
+ /**
2
+ * MCP 返回值格式化工具
3
+ *
4
+ * 两种模式:
5
+ * - formatFrontmatter:YAML frontmatter + body(read 类工具)
6
+ * - formatDataJson:剥离 wrapper 的 JSON(action/infra 工具)
7
+ */
8
+ // ---------------------------------------------------------------------------
9
+ // 模式 A:Frontmatter + Body
10
+ // ---------------------------------------------------------------------------
11
+ /**
12
+ * 将元数据序列化为 YAML frontmatter,拼接 body 内容。
13
+ *
14
+ * - null/undefined 的值自动跳过
15
+ * - 数字/布尔:裸值(`nodeCount: 42`)
16
+ * - 字符串:JSON 引号(`mode: "focus"`)
17
+ * - 数组/对象:JSON 内联(`ancestors: [{"id":"x"}]`)
18
+ * - 无元数据时省略 `---` 分隔符,直接返回 body
19
+ */
20
+ export function formatFrontmatter(meta, body) {
21
+ const entries = Object.entries(meta).filter(([, v]) => v !== undefined && v !== null);
22
+ if (entries.length === 0)
23
+ return body;
24
+ const lines = entries.map(([k, v]) => {
25
+ if (typeof v === 'number' || typeof v === 'boolean')
26
+ return `${k}: ${v}`;
27
+ return `${k}: ${JSON.stringify(v)}`;
28
+ });
29
+ return `---\n${lines.join('\n')}\n---\n\n${body}`;
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // 模式 B:Data JSON(剥离 ok/command/timestamp wrapper)
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * 从 CLI 响应中剥离 ok/command/timestamp,返回剩余字段的 JSON。
36
+ *
37
+ * 用于 action/infra 工具——AI 不需要看到冗余的 wrapper 字段。
38
+ * callCli 已在 ok===false 时抛出 CliError,成功路径 ok 始终为 true。
39
+ */
40
+ export function formatDataJson(response) {
41
+ const { ok: _ok, command: _cmd, timestamp: _ts, ...rest } = response;
42
+ return JSON.stringify(rest, null, 2);
43
+ }
package/dist/mcp/index.js CHANGED
@@ -9,12 +9,6 @@ import { SERVER_INSTRUCTIONS } from './instructions.js';
9
9
  import { registerReadTools } from './tools/read-tools.js';
10
10
  import { registerEditTools } from './tools/edit-tools.js';
11
11
  import { registerInfraTools } from './tools/infra-tools.js';
12
- import { OUTLINE_FORMAT_CONTENT } from './resources/outline-format.js';
13
- import { REM_OBJECT_FIELDS_CONTENT } from './resources/rem-object-fields.js';
14
- import { EDIT_REM_GUIDE_CONTENT } from './resources/edit-rem-guide.js';
15
- import { EDIT_TREE_GUIDE_CONTENT } from './resources/edit-tree-guide.js';
16
- import { ERROR_REFERENCE_CONTENT } from './resources/error-reference.js';
17
- import { SEPARATOR_FLASHCARD_CONTENT } from './resources/separator-flashcard.js';
18
12
  export async function startMcpServer() {
19
13
  const server = new FastMCP({
20
14
  name: 'remnote-bridge',
@@ -24,54 +18,5 @@ export async function startMcpServer() {
24
18
  registerInfraTools(server);
25
19
  registerReadTools(server);
26
20
  registerEditTools(server);
27
- // Resources
28
- server.addResource({
29
- uri: 'remnote://outline-format',
30
- name: 'Markdown 大纲格式规范',
31
- mimeType: 'text/markdown',
32
- async load() {
33
- return { text: OUTLINE_FORMAT_CONTENT };
34
- },
35
- });
36
- server.addResource({
37
- uri: 'remnote://rem-object-fields',
38
- name: 'RemObject 字段完整参考',
39
- mimeType: 'text/markdown',
40
- async load() {
41
- return { text: REM_OBJECT_FIELDS_CONTENT };
42
- },
43
- });
44
- server.addResource({
45
- uri: 'remnote://edit-rem-guide',
46
- name: 'edit_rem 操作指南',
47
- mimeType: 'text/markdown',
48
- async load() {
49
- return { text: EDIT_REM_GUIDE_CONTENT };
50
- },
51
- });
52
- server.addResource({
53
- uri: 'remnote://edit-tree-guide',
54
- name: 'edit_tree 操作指南',
55
- mimeType: 'text/markdown',
56
- async load() {
57
- return { text: EDIT_TREE_GUIDE_CONTENT };
58
- },
59
- });
60
- server.addResource({
61
- uri: 'remnote://error-reference',
62
- name: '错误诊断与恢复参考',
63
- mimeType: 'text/markdown',
64
- async load() {
65
- return { text: ERROR_REFERENCE_CONTENT };
66
- },
67
- });
68
- server.addResource({
69
- uri: 'remnote://separator-flashcard',
70
- name: '分隔符与闪卡参考',
71
- mimeType: 'text/markdown',
72
- async load() {
73
- return { text: SEPARATOR_FLASHCARD_CONTENT };
74
- },
75
- });
76
21
  await server.start({ transportType: 'stdio' });
77
22
  }