@dinoxx/dinox-cli 1.0.22 → 1.1.1

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 (147) hide show
  1. package/README.md +33 -10
  2. package/dist/auth/userInfo.d.ts +2 -0
  3. package/dist/auth/userInfo.js +55 -14
  4. package/dist/cli.js +36 -2
  5. package/dist/commands/auth/index.d.ts +35 -0
  6. package/dist/commands/auth/index.js +382 -83
  7. package/dist/commands/boxes/index.js +2 -6
  8. package/dist/commands/boxes/model.d.ts +3 -0
  9. package/dist/commands/boxes/model.js +16 -1
  10. package/dist/commands/boxes/operations.js +18 -8
  11. package/dist/commands/boxes/repo.js +13 -11
  12. package/dist/commands/boxes/store.js +1 -1
  13. package/dist/commands/config/index.js +7 -5
  14. package/dist/commands/descriptor/register.js +9 -0
  15. package/dist/commands/doctor.d.ts +123 -0
  16. package/dist/commands/doctor.js +539 -0
  17. package/dist/commands/graph/index.d.ts +1 -1
  18. package/dist/commands/graph/index.js +35 -21
  19. package/dist/commands/graph/repo.js +92 -17
  20. package/dist/commands/meta/index.js +4 -4
  21. package/dist/commands/meta/repo.js +4 -3
  22. package/dist/commands/notes/actions.d.ts +1 -1
  23. package/dist/commands/notes/actions.js +121 -78
  24. package/dist/commands/notes/bulk.d.ts +1 -0
  25. package/dist/commands/notes/bulk.js +23 -17
  26. package/dist/commands/notes/contentEngine.js +69 -11
  27. package/dist/commands/notes/contentPatch.d.ts +4 -1
  28. package/dist/commands/notes/contentPatch.js +105 -41
  29. package/dist/commands/notes/contentPatchCommand.js +28 -11
  30. package/dist/commands/notes/descriptors.js +50 -20
  31. package/dist/commands/notes/dryRun.d.ts +1 -0
  32. package/dist/commands/notes/dryRun.js +4 -7
  33. package/dist/commands/notes/export.d.ts +6 -0
  34. package/dist/commands/notes/export.js +26 -6
  35. package/dist/commands/notes/metadataMutation.d.ts +1 -0
  36. package/dist/commands/notes/metadataMutation.js +14 -3
  37. package/dist/commands/notes/repo.d.ts +46 -4
  38. package/dist/commands/notes/repo.js +113 -28
  39. package/dist/commands/notes/runtime.d.ts +2 -1
  40. package/dist/commands/notes/runtime.js +17 -1
  41. package/dist/commands/notes/searchSql.js +13 -6
  42. package/dist/commands/notes/writeReceipt.d.ts +20 -0
  43. package/dist/commands/notes/writeReceipt.js +82 -0
  44. package/dist/commands/prompt/index.js +3 -3
  45. package/dist/commands/prompt/repo.d.ts +4 -1
  46. package/dist/commands/prompt/repo.js +17 -7
  47. package/dist/commands/runtime.d.ts +14 -2
  48. package/dist/commands/runtime.js +89 -19
  49. package/dist/commands/schema/renderSkills.js +5 -2
  50. package/dist/commands/schema/spec.js +4 -0
  51. package/dist/commands/storage/index.js +8 -2
  52. package/dist/commands/storage/repo.d.ts +4 -0
  53. package/dist/commands/storage/repo.js +356 -92
  54. package/dist/commands/sync.d.ts +23 -1
  55. package/dist/commands/sync.js +105 -60
  56. package/dist/commands/tags/content.js +9 -0
  57. package/dist/commands/tags/index.js +2 -6
  58. package/dist/commands/tags/operations.js +15 -10
  59. package/dist/commands/tags/repo.d.ts +2 -0
  60. package/dist/commands/tags/repo.js +18 -5
  61. package/dist/commands/tags/store.js +1 -1
  62. package/dist/commands/todo/contentModel.d.ts +4 -3
  63. package/dist/commands/todo/contentModel.js +78 -28
  64. package/dist/commands/todo/dryRun.d.ts +1 -0
  65. package/dist/commands/todo/dryRun.js +17 -13
  66. package/dist/commands/todo/index.d.ts +6 -0
  67. package/dist/commands/todo/index.js +89 -123
  68. package/dist/commands/todo/repo.d.ts +36 -1
  69. package/dist/commands/todo/repo.js +146 -37
  70. package/dist/commands/todo/taskIdentity.d.ts +2 -0
  71. package/dist/commands/todo/taskIdentity.js +64 -0
  72. package/dist/commands/todo/taskModel.js +27 -17
  73. package/dist/commands/update.d.ts +13 -1
  74. package/dist/commands/update.js +135 -23
  75. package/dist/commands/writeDryRun.d.ts +3 -2
  76. package/dist/commands/writeDryRun.js +4 -3
  77. package/dist/commands/writeDurability.d.ts +5 -0
  78. package/dist/commands/writeDurability.js +22 -0
  79. package/dist/config/paths.js +6 -1
  80. package/dist/config/resolve.d.ts +2 -0
  81. package/dist/config/resolve.js +23 -9
  82. package/dist/config/store.d.ts +3 -0
  83. package/dist/config/store.js +61 -14
  84. package/dist/config/types.d.ts +4 -0
  85. package/dist/daemon/client.d.ts +26 -1
  86. package/dist/daemon/client.js +236 -12
  87. package/dist/daemon/ensure.js +74 -12
  88. package/dist/daemon/index.d.ts +42 -0
  89. package/dist/daemon/index.js +202 -52
  90. package/dist/daemon/lifecycle.d.ts +44 -0
  91. package/dist/daemon/lifecycle.js +118 -0
  92. package/dist/daemon/pid.d.ts +19 -6
  93. package/dist/daemon/pid.js +147 -13
  94. package/dist/daemon/route.d.ts +14 -0
  95. package/dist/daemon/route.js +40 -0
  96. package/dist/daemon/server.d.ts +11 -0
  97. package/dist/daemon/server.js +201 -50
  98. package/dist/daemon/types.d.ts +29 -1
  99. package/dist/daemon/types.js +1 -0
  100. package/dist/dinox.js +25 -1
  101. package/dist/powersync/connector.js +31 -16
  102. package/dist/powersync/exclusiveRuntime.d.ts +20 -0
  103. package/dist/powersync/exclusiveRuntime.js +48 -0
  104. package/dist/powersync/runtime.d.ts +25 -4
  105. package/dist/powersync/runtime.js +255 -35
  106. package/dist/powersync/syncGate.d.ts +4 -1
  107. package/dist/powersync/syncGate.js +11 -2
  108. package/dist/powersync/syncPass.d.ts +2 -0
  109. package/dist/powersync/syncPass.js +15 -4
  110. package/dist/powersync/tokenIndex.d.ts +13 -0
  111. package/dist/powersync/tokenIndex.js +216 -37
  112. package/dist/powersync/uploader.js +107 -52
  113. package/dist/utils/abort.js +0 -4
  114. package/dist/utils/asyncRelease.d.ts +2 -0
  115. package/dist/utils/asyncRelease.js +25 -0
  116. package/dist/utils/bidlinkCanonicalization.js +3 -0
  117. package/dist/utils/dryRun.d.ts +1 -1
  118. package/dist/utils/errors.d.ts +15 -1
  119. package/dist/utils/errors.js +223 -5
  120. package/dist/utils/output.d.ts +1 -0
  121. package/dist/utils/output.js +51 -15
  122. package/dist/utils/redact.js +1 -1
  123. package/dist/utils/sqliteLease.d.ts +8 -0
  124. package/dist/utils/sqliteLease.js +59 -0
  125. package/dist/utils/syncTimeout.d.ts +7 -1
  126. package/dist/utils/syncTimeout.js +8 -7
  127. package/dist/utils/terminalSanitization.js +6 -2
  128. package/dist/utils/tiptapMarkdown.js +49 -3
  129. package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
  130. package/dist/utils/tiptapMarkdownSyntax.js +249 -0
  131. package/dist/utils/tokenize.js +24 -2
  132. package/dist/utils/updateNotice.js +60 -12
  133. package/package.json +14 -10
  134. package/dist/commands/mcp/auth.d.ts +0 -4
  135. package/dist/commands/mcp/auth.js +0 -75
  136. package/dist/commands/mcp/index.d.ts +0 -2
  137. package/dist/commands/mcp/index.js +0 -86
  138. package/dist/commands/mcp/noteContentTools.d.ts +0 -222
  139. package/dist/commands/mcp/noteContentTools.js +0 -390
  140. package/dist/commands/mcp/server.d.ts +0 -34
  141. package/dist/commands/mcp/server.js +0 -424
  142. package/dist/commands/mcp/tools.d.ts +0 -2
  143. package/dist/commands/mcp/tools.js +0 -32
  144. package/dist/commands/mcp/types.d.ts +0 -39
  145. package/dist/commands/mcp/types.js +0 -1
  146. package/dist/utils/noteContentEngine.d.ts +0 -333
  147. package/dist/utils/noteContentEngine.js +0 -740
@@ -1,222 +0,0 @@
1
- import type { AbstractPowerSyncDatabase } from '@powersync/common';
2
- import { type NoteEditOperation, type TiptapJsonNode } from '../../utils/noteContentEngine.js';
3
- import type { MCPTool } from './types.js';
4
- export declare const NOTE_CONTENT_READ_TOOL_NAME = "noteContentRead";
5
- export declare const NOTE_CONTENT_EDIT_TOOL_NAME = "noteContentEdit";
6
- type ReadInput = {
7
- noteId: string;
8
- from?: number | null;
9
- cursor?: {
10
- from: number;
11
- contentHash?: string | null;
12
- } | null;
13
- nodeRange?: string | null;
14
- includeJson?: boolean | null;
15
- maxChars?: number | null;
16
- maxNodes?: number | null;
17
- };
18
- type EditInput = {
19
- readToken: string;
20
- operations: NoteEditOperation[];
21
- };
22
- type Runtime = {
23
- userId: string;
24
- };
25
- export declare const noteContentMcpTools: MCPTool[];
26
- export declare class NoteContentMcpService {
27
- #private;
28
- private readonly getDatabase;
29
- private readonly now;
30
- constructor(getDatabase: () => AbstractPowerSyncDatabase, now?: () => number);
31
- read(input: ReadInput, runtime: Runtime): Promise<{
32
- range?: {
33
- json?: TiptapJsonNode[] | undefined;
34
- nodeRange: string;
35
- nodeRangeTuple: readonly [number, number];
36
- format: "dinox-shorthand";
37
- from: number;
38
- text: string;
39
- textHash: string;
40
- nodeHash: string;
41
- preferredTarget: {
42
- nodeRange: string;
43
- anchorIds: string[];
44
- expectedTextHash: string;
45
- expectedNodeHash: string;
46
- };
47
- shorthand: string;
48
- markdown: string;
49
- textWindow: {
50
- offset: number;
51
- length: number;
52
- total: number;
53
- nextOffset: number | null;
54
- };
55
- shorthandWindow: {
56
- offset: number;
57
- length: number;
58
- total: number;
59
- nextOffset: number | null;
60
- };
61
- markdownWindow: {
62
- offset: number;
63
- length: number;
64
- total: number;
65
- nextOffset: number | null;
66
- };
67
- isTruncated: boolean;
68
- nextCursor: {
69
- from: number;
70
- contentHash: string;
71
- } | null;
72
- } | undefined;
73
- success: boolean;
74
- hasError: boolean;
75
- docChanged: boolean;
76
- document: null;
77
- noteId: string;
78
- title: string;
79
- readToken: string;
80
- readTokenExpiresAt: string;
81
- base: {
82
- contentHash: string;
83
- updatedAt: string | null;
84
- version: number | null;
85
- };
86
- totalNodes: number;
87
- totalCharCount: number;
88
- estimatedTokens: number;
89
- from: number;
90
- nodeRange: readonly [number, number];
91
- format: "dinox-shorthand";
92
- content: string;
93
- canReadMore: boolean;
94
- nextCursor: {
95
- from: number;
96
- contentHash: string;
97
- } | null;
98
- schema: {
99
- version: string;
100
- readFormat: string;
101
- editProtocol: string;
102
- operations: ("insertBlocksAfter" | "insertBlocksBefore" | "replaceBlocks" | "deleteBlocks" | "replaceText" | "updateBlockAttrs" | "replaceContainerContent")[];
103
- };
104
- readFormatInstructions: string;
105
- chunks: {
106
- index: number;
107
- nodeRange: string;
108
- anchorIds: string[];
109
- textPreview: string;
110
- charCount: number;
111
- estimatedTokens: number;
112
- }[];
113
- blocks: {
114
- preferredTarget: {
115
- anchorId: string;
116
- nodeRange: string;
117
- expectedTextHash: string;
118
- expectedNodeHash: string;
119
- };
120
- anchorId: string;
121
- hasStableAnchor: boolean;
122
- nodeIndex: number;
123
- nodeRange: string;
124
- type: string;
125
- editMode: string;
126
- level: number | null;
127
- textPreview: string;
128
- textHash: string;
129
- nodeHash: string;
130
- charCount: number;
131
- attrs: Record<string, unknown> | undefined;
132
- }[];
133
- page: {
134
- json?: TiptapJsonNode[] | undefined;
135
- nodeRange: string;
136
- nodeRangeTuple: readonly [number, number];
137
- format: "dinox-shorthand";
138
- from: number;
139
- text: string;
140
- textHash: string;
141
- nodeHash: string;
142
- preferredTarget: {
143
- nodeRange: string;
144
- anchorIds: string[];
145
- expectedTextHash: string;
146
- expectedNodeHash: string;
147
- };
148
- shorthand: string;
149
- markdown: string;
150
- textWindow: {
151
- offset: number;
152
- length: number;
153
- total: number;
154
- nextOffset: number | null;
155
- };
156
- shorthandWindow: {
157
- offset: number;
158
- length: number;
159
- total: number;
160
- nextOffset: number | null;
161
- };
162
- markdownWindow: {
163
- offset: number;
164
- length: number;
165
- total: number;
166
- nextOffset: number | null;
167
- };
168
- isTruncated: boolean;
169
- nextCursor: {
170
- from: number;
171
- contentHash: string;
172
- } | null;
173
- };
174
- }>;
175
- edit(input: EditInput, runtime: Runtime): Promise<{
176
- success: boolean;
177
- hasError: boolean;
178
- docChanged: boolean;
179
- document: null;
180
- saved: boolean;
181
- noteId: string;
182
- title: string;
183
- changed: boolean;
184
- appliedOperations: number;
185
- operationResults: {
186
- operationIndex: number;
187
- type: "insertBlocksAfter" | "insertBlocksBefore" | "replaceBlocks" | "deleteBlocks" | "replaceText" | "updateBlockAttrs" | "replaceContainerContent";
188
- success: boolean;
189
- target: string;
190
- nodeRange: string;
191
- error: null;
192
- }[];
193
- updatedAt: string | null;
194
- before: {
195
- contentHash: string;
196
- totalNodes: number;
197
- };
198
- after: {
199
- contentHash: string;
200
- totalNodes: number;
201
- version: number | null;
202
- };
203
- }>;
204
- }
205
- export declare function buildNoteContentToolError(error: unknown, operations?: NoteEditOperation[]): {
206
- success: boolean;
207
- hasError: boolean;
208
- docChanged: boolean;
209
- document: null;
210
- reason: string;
211
- error: string;
212
- shouldRetry: boolean;
213
- operationResults: {
214
- operationIndex: number;
215
- type: "insertBlocksAfter" | "insertBlocksBefore" | "replaceBlocks" | "deleteBlocks" | "replaceText" | "updateBlockAttrs" | "replaceContainerContent";
216
- success: boolean;
217
- target: string;
218
- nodeRange: null;
219
- error: string;
220
- }[];
221
- };
222
- export {};
@@ -1,390 +0,0 @@
1
- import { generateId } from '../../utils/id.js';
2
- import { nowIsoUtc } from '../../utils/time.js';
3
- import { NoteContentEditError, applyNoteEditPatch, blockNodeHash, blockTextHash, buildNoteEditContext, getNoteContentReadInstructions, getNoteContentSchemaSummary, parseNodeRangeString, serializeAnchoredMarkdown, serializeAnchoredShorthand, topNodes, } from '../../utils/noteContentEngine.js';
4
- export const NOTE_CONTENT_READ_TOOL_NAME = 'noteContentRead';
5
- export const NOTE_CONTENT_EDIT_TOOL_NAME = 'noteContentEdit';
6
- const DEFAULT_MAX_RANGE_CHARS = 20_000;
7
- const DEFAULT_READ_PAGE_MAX_NODES = 24;
8
- const READ_TOKEN_TTL_MS = 30 * 60 * 1000;
9
- const MAX_READ_TOKENS = 500;
10
- export const noteContentMcpTools = [
11
- {
12
- name: NOTE_CONTENT_READ_TOOL_NAME,
13
- description: 'Read canonical note content as paginated dinox-shorthand with block hashes, readToken, and preferred edit targets.',
14
- inputSchema: {
15
- type: 'object',
16
- properties: {
17
- noteId: { type: 'string', description: 'Target note ID.' },
18
- from: { type: 'number', description: 'Zero-based top-level node index to read from.' },
19
- cursor: {
20
- type: 'object',
21
- description: 'Continuation cursor returned by noteContentRead.',
22
- properties: {
23
- from: { type: 'number' },
24
- contentHash: { type: 'string' },
25
- },
26
- },
27
- nodeRange: { type: 'string', description: 'Top-level node range such as "0:5", "5:", or ":10".' },
28
- includeJson: { type: 'boolean', description: 'Return local Tiptap JSON for diagnostics. Do not write raw JSON back.' },
29
- maxChars: { type: 'number', description: 'Maximum characters for the local range text.' },
30
- maxNodes: { type: 'number', description: 'Maximum top-level nodes to include when nodeRange is not set.' },
31
- },
32
- required: ['noteId'],
33
- },
34
- },
35
- {
36
- name: NOTE_CONTENT_EDIT_TOOL_NAME,
37
- description: 'Persist structured edits to an existing note after noteContentRead returned a readToken. Use structured blocks, not Markdown strings or raw Tiptap JSON.',
38
- inputSchema: {
39
- type: 'object',
40
- properties: {
41
- readToken: { type: 'string', description: 'readToken returned by noteContentRead.' },
42
- operations: {
43
- type: 'array',
44
- description: 'Structured edit operations. Prefer preferredTarget and expectedNodeHash/expectedTextHash for destructive edits.',
45
- items: { type: 'object' },
46
- },
47
- },
48
- required: ['readToken', 'operations'],
49
- },
50
- },
51
- ];
52
- export class NoteContentMcpService {
53
- getDatabase;
54
- now;
55
- #readTokens = new Map();
56
- constructor(getDatabase, now = () => Date.now()) {
57
- this.getDatabase = getDatabase;
58
- this.now = now;
59
- }
60
- async read(input, runtime) {
61
- const row = await this.#readNote(this.getDatabase(), input.noteId);
62
- this.#assertUser(row, runtime.userId);
63
- const context = buildNoteEditContext({
64
- noteId: row.id,
65
- title: row.title,
66
- contentJson: row.content_json,
67
- updatedAt: row.updated_at,
68
- version: normalizeVersion(row.version),
69
- });
70
- const token = this.#rememberReadToken(context, runtime);
71
- const page = this.#readPage(context.document, context.blocks, context.contentHash, input);
72
- return {
73
- success: true,
74
- hasError: false,
75
- docChanged: false,
76
- document: null,
77
- noteId: context.noteId,
78
- title: context.title,
79
- readToken: token.token,
80
- readTokenExpiresAt: new Date(token.expiresAtMs).toISOString(),
81
- base: {
82
- contentHash: context.contentHash,
83
- updatedAt: context.updatedAt,
84
- version: context.version,
85
- },
86
- totalNodes: context.blocks.length,
87
- totalCharCount: context.totalCharCount,
88
- estimatedTokens: context.estimatedTokens,
89
- from: page.from,
90
- nodeRange: page.nodeRangeTuple,
91
- format: page.format,
92
- content: page.shorthand,
93
- canReadMore: Boolean(page.nextCursor),
94
- nextCursor: page.nextCursor,
95
- schema: getNoteContentSchemaSummary(),
96
- readFormatInstructions: getNoteContentReadInstructions(),
97
- chunks: context.chunks,
98
- blocks: context.blocks.map(({ text, ...block }) => ({
99
- ...block,
100
- preferredTarget: {
101
- anchorId: block.anchorId,
102
- nodeRange: block.nodeRange,
103
- expectedTextHash: block.textHash,
104
- expectedNodeHash: block.nodeHash,
105
- },
106
- })),
107
- page,
108
- ...(input.nodeRange ? { range: page } : {}),
109
- };
110
- }
111
- async edit(input, runtime) {
112
- const token = this.#requireReadToken(input.readToken, runtime);
113
- const db = this.getDatabase();
114
- try {
115
- const result = await runWrite(db, async (tx) => {
116
- const row = await this.#readNote(tx, token.noteId);
117
- this.#assertUser(row, runtime.userId);
118
- this.#assertTokenMatchesRow(token, row);
119
- const patch = {
120
- base: {
121
- contentHash: token.contentHash,
122
- updatedAt: token.updatedAt,
123
- version: token.version,
124
- },
125
- operations: input.operations,
126
- };
127
- const applied = applyNoteEditPatch({
128
- noteId: row.id,
129
- title: row.title,
130
- contentJson: row.content_json,
131
- updatedAt: row.updated_at,
132
- version: normalizeVersion(row.version),
133
- createAnchorId: () => generateId(),
134
- patch,
135
- });
136
- if (!applied.changed)
137
- return buildSuccess(applied, false);
138
- const savedAt = nowIsoUtc();
139
- await updateNoteProjection(tx, row.id, {
140
- content_json: applied.projection.content_json,
141
- content_text: applied.projection.content_text,
142
- content_md: applied.projection.content_md,
143
- images: applied.projection.images,
144
- bid_links: applied.projection.bid_links,
145
- updated_at: savedAt,
146
- });
147
- return buildSuccess(applied, true, savedAt, nextVersion(row.version));
148
- });
149
- this.#readTokens.delete(token.token);
150
- return result;
151
- }
152
- catch (error) {
153
- if (error instanceof NoteContentEditError && error.code === 'content_conflict') {
154
- this.#readTokens.delete(token.token);
155
- }
156
- throw error;
157
- }
158
- }
159
- #readPage(document, blocks, contentHash, input) {
160
- const nodes = topNodes(document);
161
- const parsed = resolveReadPageRange(input, nodes.length);
162
- if (input.cursor?.contentHash && input.cursor.contentHash !== contentHash) {
163
- throw new Error('noteContentRead cursor 已过期,请重新从 from: 0 读取笔记内容');
164
- }
165
- const selectedNodes = nodes.slice(parsed.start, parsed.end);
166
- const maxChars = clampInt(input.maxChars, 1, 100_000, DEFAULT_MAX_RANGE_CHARS);
167
- const text = truncateContent(selectedNodes.map((node) => blocks[nodes.indexOf(node)]?.text ?? '').join('\n\n').trim(), maxChars);
168
- const markdown = truncateContent(serializeAnchoredMarkdown(selectedNodes, blocks, parsed.start), maxChars);
169
- const shorthand = truncateContent(serializeAnchoredShorthand(selectedNodes, blocks, parsed.start), maxChars);
170
- const textHash = blockTextHash(blocks, parsed.start, parsed.end);
171
- const nodeHash = blockNodeHash(blocks, parsed.start, parsed.end);
172
- const nextCursor = parsed.end < nodes.length ? { from: parsed.end, contentHash } : null;
173
- return {
174
- nodeRange: `${parsed.start}:${parsed.end}`,
175
- nodeRangeTuple: [parsed.start, parsed.end],
176
- format: 'dinox-shorthand',
177
- from: parsed.start,
178
- text: text.value,
179
- textHash,
180
- nodeHash,
181
- preferredTarget: {
182
- nodeRange: `${parsed.start}:${parsed.end}`,
183
- anchorIds: blocks.slice(parsed.start, parsed.end).map((block) => block.anchorId),
184
- expectedTextHash: textHash,
185
- expectedNodeHash: nodeHash,
186
- },
187
- shorthand: shorthand.value,
188
- markdown: markdown.value,
189
- textWindow: text.window,
190
- shorthandWindow: shorthand.window,
191
- markdownWindow: markdown.window,
192
- isTruncated: text.isTruncated || shorthand.isTruncated || markdown.isTruncated,
193
- nextCursor,
194
- ...(input.includeJson ? { json: selectedNodes } : {}),
195
- };
196
- }
197
- async #readNote(db, noteId) {
198
- const id = normalizeText(noteId);
199
- if (!id)
200
- throw new Error('noteId 不能为空');
201
- const row = await db.getOptional('SELECT * FROM c_note WHERE id = ? LIMIT 1', [id]);
202
- if (!row || Number(row.is_del ?? 0) === 1)
203
- throw new Error('笔记不存在或已删除');
204
- return row;
205
- }
206
- #assertUser(row, userId) {
207
- const owner = normalizeText(row.user_no) || normalizeText(row.user_id);
208
- const actor = normalizeText(userId);
209
- if (!actor || !owner || owner !== actor)
210
- throw new Error('无权更新此笔记');
211
- }
212
- #rememberReadToken(context, runtime) {
213
- this.#pruneReadTokens();
214
- const token = {
215
- token: generateId(),
216
- noteId: context.noteId,
217
- userId: normalizeText(runtime.userId),
218
- contentHash: context.contentHash,
219
- updatedAt: context.updatedAt,
220
- version: context.version,
221
- expiresAtMs: this.now() + READ_TOKEN_TTL_MS,
222
- };
223
- this.#readTokens.set(token.token, token);
224
- this.#pruneReadTokens();
225
- return token;
226
- }
227
- #requireReadToken(tokenId, runtime) {
228
- const id = normalizeText(tokenId);
229
- if (!id)
230
- throw new Error(`缺少 readToken,请先调用 ${NOTE_CONTENT_READ_TOOL_NAME} 读取笔记内容`);
231
- this.#pruneReadTokens();
232
- const token = this.#readTokens.get(id);
233
- if (!token)
234
- throw new Error(`readToken 已失效,请重新调用 ${NOTE_CONTENT_READ_TOOL_NAME} 读取最新笔记内容`);
235
- if (token.userId !== normalizeText(runtime.userId))
236
- throw new Error('readToken 与当前用户不匹配,请重新读取笔记内容');
237
- return token;
238
- }
239
- #assertTokenMatchesRow(token, row) {
240
- if (token.noteId !== row.id)
241
- throw new NoteContentEditError('content_conflict', 'readToken 与目标笔记不匹配,请重新读取笔记内容');
242
- if (token.updatedAt !== (row.updated_at ?? null) || token.version !== normalizeVersion(row.version)) {
243
- throw new NoteContentEditError('content_conflict', '笔记内容已变化,请重新读取后再编辑');
244
- }
245
- }
246
- #pruneReadTokens() {
247
- const now = this.now();
248
- for (const [token, record] of this.#readTokens) {
249
- if (record.expiresAtMs <= now)
250
- this.#readTokens.delete(token);
251
- }
252
- while (this.#readTokens.size > MAX_READ_TOKENS) {
253
- const oldest = this.#readTokens.keys().next().value;
254
- if (!oldest)
255
- break;
256
- this.#readTokens.delete(oldest);
257
- }
258
- }
259
- }
260
- export function buildNoteContentToolError(error, operations = []) {
261
- const message = error instanceof Error ? error.message : String(error);
262
- const code = error instanceof NoteContentEditError ? error.code : 'unexpectedError';
263
- return {
264
- success: false,
265
- hasError: true,
266
- docChanged: false,
267
- document: null,
268
- reason: mapToolErrorReason(code),
269
- error: message,
270
- shouldRetry: isRetryable(code, message),
271
- operationResults: operations.map((operation, index) => ({
272
- operationIndex: index,
273
- type: operation.op,
274
- success: false,
275
- target: describeOperationTarget(operation.target),
276
- nodeRange: null,
277
- error: message,
278
- })),
279
- };
280
- }
281
- async function updateNoteProjection(db, noteId, values) {
282
- const columns = await db.getAll('PRAGMA table_info(c_note)', []);
283
- const columnNames = new Set(columns.map((row) => normalizeText(row.name)).filter(Boolean));
284
- for (const required of ['content_json', 'content_text', 'content_md']) {
285
- if (!columnNames.has(required))
286
- throw new Error(`c_note table is missing required column: ${required}`);
287
- }
288
- const updates = [];
289
- const params = [];
290
- for (const [column, value] of Object.entries(values)) {
291
- if (!columnNames.has(column))
292
- continue;
293
- updates.push(`${column} = ?`);
294
- params.push(value);
295
- }
296
- if (columnNames.has('version'))
297
- updates.push('version = COALESCE(version, 0) + 1');
298
- params.push(noteId);
299
- await db.execute(`
300
- UPDATE c_note
301
- SET ${updates.join(', ')}
302
- WHERE id = ?
303
- `, params);
304
- }
305
- async function runWrite(db, callback) {
306
- if (typeof db.writeTransaction === 'function')
307
- return db.writeTransaction(callback);
308
- if (typeof db.writeLock === 'function')
309
- return db.writeLock(callback);
310
- return callback(db);
311
- }
312
- function buildSuccess(result, saved, updatedAt, version) {
313
- return {
314
- success: true,
315
- hasError: false,
316
- docChanged: result.changed,
317
- document: null,
318
- saved,
319
- noteId: result.noteId,
320
- title: result.after.title,
321
- changed: result.changed,
322
- appliedOperations: result.appliedOperations,
323
- operationResults: result.operationResults,
324
- updatedAt: updatedAt ?? result.after.updatedAt,
325
- before: { contentHash: result.before.contentHash, totalNodes: result.before.blocks.length },
326
- after: { contentHash: result.after.contentHash, totalNodes: result.after.blocks.length, version: version ?? result.after.version },
327
- };
328
- }
329
- function resolveReadPageRange(input, total) {
330
- if (input.nodeRange)
331
- return parseNodeRangeString(input.nodeRange, total);
332
- const start = clampInt(input.cursor?.from ?? input.from, 0, total, 0);
333
- const maxNodes = clampInt(input.maxNodes, 1, 100, DEFAULT_READ_PAGE_MAX_NODES);
334
- return { start, end: Math.min(total, start + maxNodes) };
335
- }
336
- function truncateContent(value, maxChars) {
337
- const isTruncated = value.length > maxChars;
338
- const length = isTruncated ? maxChars : value.length;
339
- return {
340
- value: isTruncated ? value.slice(0, maxChars) : value,
341
- isTruncated,
342
- window: { offset: 0, length, total: value.length, nextOffset: isTruncated ? length : null },
343
- };
344
- }
345
- function clampInt(value, min, max, fallback) {
346
- const number = typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : fallback;
347
- return Math.max(min, Math.min(max, number));
348
- }
349
- function normalizeVersion(value) {
350
- const number = Number(value);
351
- return Number.isFinite(number) ? number : null;
352
- }
353
- function nextVersion(value) {
354
- const current = normalizeVersion(value);
355
- return current == null ? null : current + 1;
356
- }
357
- function normalizeText(value) {
358
- return typeof value === 'string' ? value.trim() : '';
359
- }
360
- function mapToolErrorReason(code) {
361
- if (code === 'invalid_plan' || code === 'invalid_content')
362
- return 'validationError';
363
- if (code === 'content_conflict')
364
- return 'contentConflict';
365
- if (code === 'target_not_found' || code === 'target_conflict')
366
- return 'targetError';
367
- return 'unexpectedError';
368
- }
369
- function isRetryable(code, message) {
370
- return code === 'content_conflict' || code === 'target_not_found' || code === 'target_conflict' || /readToken|cursor|过期|变化|重新读取/i.test(message);
371
- }
372
- function describeOperationTarget(target) {
373
- if (!target)
374
- return 'unknown';
375
- if (target.anchorId)
376
- return target.anchorId;
377
- if (target.anchorIds?.length)
378
- return target.anchorIds.join(',');
379
- if (target.nodeRange)
380
- return target.nodeRange;
381
- if (typeof target.nodeIndex === 'number')
382
- return String(target.nodeIndex);
383
- if (target.expectedNodeHash)
384
- return target.expectedNodeHash;
385
- if (target.expectedTextHash)
386
- return target.expectedTextHash;
387
- if (target.textMatch)
388
- return target.textMatch;
389
- return 'unknown';
390
- }
@@ -1,34 +0,0 @@
1
- import type { IncomingMessage, Server } from 'node:http';
2
- type JsonRpcId = string | number | null;
3
- type MCPRequest = {
4
- jsonrpc: '2.0';
5
- id?: JsonRpcId;
6
- method: string;
7
- params?: any;
8
- };
9
- type MCPResponse = {
10
- jsonrpc: '2.0';
11
- id: JsonRpcId;
12
- result?: any;
13
- error?: {
14
- code: number;
15
- message: string;
16
- };
17
- };
18
- export declare class MCPServer {
19
- private db;
20
- private readonly noteContent;
21
- initialize(): Promise<void>;
22
- handleRequest(req: MCPRequest): Promise<MCPResponse | null>;
23
- private handleInitialize;
24
- private handleToolsList;
25
- private handleToolsCall;
26
- private requireDb;
27
- private getRuntime;
28
- close(): Promise<void>;
29
- }
30
- export declare function startMCPServer(port?: number, host?: string, authToken?: string): Promise<{
31
- server: Server<typeof IncomingMessage, typeof import("http").ServerResponse>;
32
- mcpServer: MCPServer;
33
- }>;
34
- export {};