affine-mcp-server 1.2.2 → 1.4.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.
- package/README.md +99 -23
- package/bin/affine-mcp +0 -0
- package/dist/index.js +1 -3
- package/dist/tools/accessTokens.js +3 -23
- package/dist/tools/auth.js +0 -8
- package/dist/tools/blobStorage.js +59 -35
- package/dist/tools/comments.js +4 -48
- package/dist/tools/docs.js +331 -197
- package/dist/tools/history.js +0 -36
- package/dist/tools/notifications.js +25 -54
- package/dist/tools/user.js +0 -5
- package/dist/tools/userCRUD.js +20 -152
- package/dist/tools/workspaces.js +35 -106
- package/dist/ws.js +25 -3
- package/package.json +15 -5
- package/dist/tools/updates.js +0 -32
package/dist/tools/docs.js
CHANGED
|
@@ -4,6 +4,29 @@ import { wsUrlFromGraphQLEndpoint, connectWorkspaceSocket, joinWorkspace, loadDo
|
|
|
4
4
|
import * as Y from "yjs";
|
|
5
5
|
const WorkspaceId = z.string().min(1, "workspaceId required");
|
|
6
6
|
const DocId = z.string().min(1, "docId required");
|
|
7
|
+
const APPEND_BLOCK_TYPE_VALUES = [
|
|
8
|
+
"paragraph",
|
|
9
|
+
"heading1",
|
|
10
|
+
"heading2",
|
|
11
|
+
"heading3",
|
|
12
|
+
"quote",
|
|
13
|
+
"bulleted_list",
|
|
14
|
+
"numbered_list",
|
|
15
|
+
"todo",
|
|
16
|
+
"code",
|
|
17
|
+
"divider",
|
|
18
|
+
];
|
|
19
|
+
const AppendBlockType = z.enum(APPEND_BLOCK_TYPE_VALUES);
|
|
20
|
+
function blockVersion(flavour) {
|
|
21
|
+
switch (flavour) {
|
|
22
|
+
case "affine:page":
|
|
23
|
+
return 2;
|
|
24
|
+
case "affine:surface":
|
|
25
|
+
return 5;
|
|
26
|
+
default:
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
7
30
|
export function registerDocTools(server, gql, defaults) {
|
|
8
31
|
// helpers
|
|
9
32
|
function generateId() {
|
|
@@ -19,6 +42,182 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
19
42
|
const cookie = gql.cookie || headers.Cookie || '';
|
|
20
43
|
return { endpoint, cookie };
|
|
21
44
|
}
|
|
45
|
+
function makeText(content) {
|
|
46
|
+
const yText = new Y.Text();
|
|
47
|
+
if (content.length > 0) {
|
|
48
|
+
yText.insert(0, content);
|
|
49
|
+
}
|
|
50
|
+
return yText;
|
|
51
|
+
}
|
|
52
|
+
function asText(value) {
|
|
53
|
+
if (value instanceof Y.Text)
|
|
54
|
+
return value.toString();
|
|
55
|
+
if (typeof value === "string")
|
|
56
|
+
return value;
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
function childIdsFrom(value) {
|
|
60
|
+
if (!(value instanceof Y.Array))
|
|
61
|
+
return [];
|
|
62
|
+
const childIds = [];
|
|
63
|
+
value.forEach((entry) => {
|
|
64
|
+
if (typeof entry === "string") {
|
|
65
|
+
childIds.push(entry);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(entry)) {
|
|
69
|
+
for (const child of entry) {
|
|
70
|
+
if (typeof child === "string") {
|
|
71
|
+
childIds.push(child);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return childIds;
|
|
77
|
+
}
|
|
78
|
+
function setSysFields(block, blockId, flavour) {
|
|
79
|
+
block.set("sys:id", blockId);
|
|
80
|
+
block.set("sys:flavour", flavour);
|
|
81
|
+
block.set("sys:version", blockVersion(flavour));
|
|
82
|
+
}
|
|
83
|
+
function findBlockIdByFlavour(blocks, flavour) {
|
|
84
|
+
for (const [, value] of blocks) {
|
|
85
|
+
const block = value;
|
|
86
|
+
if (block?.get && block.get("sys:flavour") === flavour) {
|
|
87
|
+
return String(block.get("sys:id"));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function ensureNoteBlock(blocks) {
|
|
93
|
+
const existingNoteId = findBlockIdByFlavour(blocks, "affine:note");
|
|
94
|
+
if (existingNoteId) {
|
|
95
|
+
return existingNoteId;
|
|
96
|
+
}
|
|
97
|
+
const pageId = findBlockIdByFlavour(blocks, "affine:page");
|
|
98
|
+
if (!pageId) {
|
|
99
|
+
throw new Error("Document has no page block; unable to insert content.");
|
|
100
|
+
}
|
|
101
|
+
const noteId = generateId();
|
|
102
|
+
const note = new Y.Map();
|
|
103
|
+
setSysFields(note, noteId, "affine:note");
|
|
104
|
+
note.set("sys:parent", pageId);
|
|
105
|
+
note.set("sys:children", new Y.Array());
|
|
106
|
+
note.set("prop:xywh", "[0,0,800,95]");
|
|
107
|
+
note.set("prop:index", "a0");
|
|
108
|
+
note.set("prop:hidden", false);
|
|
109
|
+
note.set("prop:displayMode", "both");
|
|
110
|
+
const background = new Y.Map();
|
|
111
|
+
background.set("light", "#ffffff");
|
|
112
|
+
background.set("dark", "#252525");
|
|
113
|
+
note.set("prop:background", background);
|
|
114
|
+
blocks.set(noteId, note);
|
|
115
|
+
const page = blocks.get(pageId);
|
|
116
|
+
let pageChildren = page.get("sys:children");
|
|
117
|
+
if (!(pageChildren instanceof Y.Array)) {
|
|
118
|
+
pageChildren = new Y.Array();
|
|
119
|
+
page.set("sys:children", pageChildren);
|
|
120
|
+
}
|
|
121
|
+
pageChildren.push([noteId]);
|
|
122
|
+
return noteId;
|
|
123
|
+
}
|
|
124
|
+
function createBlock(noteId, parsed) {
|
|
125
|
+
const blockId = generateId();
|
|
126
|
+
const block = new Y.Map();
|
|
127
|
+
const content = parsed.text ?? "";
|
|
128
|
+
switch (parsed.type) {
|
|
129
|
+
case "paragraph":
|
|
130
|
+
case "heading1":
|
|
131
|
+
case "heading2":
|
|
132
|
+
case "heading3":
|
|
133
|
+
case "quote": {
|
|
134
|
+
setSysFields(block, blockId, "affine:paragraph");
|
|
135
|
+
block.set("sys:parent", noteId);
|
|
136
|
+
block.set("sys:children", new Y.Array());
|
|
137
|
+
const blockType = parsed.type === "heading1"
|
|
138
|
+
? "h1"
|
|
139
|
+
: parsed.type === "heading2"
|
|
140
|
+
? "h2"
|
|
141
|
+
: parsed.type === "heading3"
|
|
142
|
+
? "h3"
|
|
143
|
+
: parsed.type === "quote"
|
|
144
|
+
? "quote"
|
|
145
|
+
: "text";
|
|
146
|
+
block.set("prop:type", blockType);
|
|
147
|
+
block.set("prop:text", makeText(content));
|
|
148
|
+
return { blockId, block, flavour: "affine:paragraph", blockType };
|
|
149
|
+
}
|
|
150
|
+
case "bulleted_list":
|
|
151
|
+
case "numbered_list":
|
|
152
|
+
case "todo": {
|
|
153
|
+
setSysFields(block, blockId, "affine:list");
|
|
154
|
+
block.set("sys:parent", noteId);
|
|
155
|
+
block.set("sys:children", new Y.Array());
|
|
156
|
+
const blockType = parsed.type === "bulleted_list"
|
|
157
|
+
? "bulleted"
|
|
158
|
+
: parsed.type === "numbered_list"
|
|
159
|
+
? "numbered"
|
|
160
|
+
: "todo";
|
|
161
|
+
block.set("prop:type", blockType);
|
|
162
|
+
if (blockType === "todo") {
|
|
163
|
+
block.set("prop:checked", Boolean(parsed.checked));
|
|
164
|
+
}
|
|
165
|
+
block.set("prop:text", makeText(content));
|
|
166
|
+
return { blockId, block, flavour: "affine:list", blockType };
|
|
167
|
+
}
|
|
168
|
+
case "code": {
|
|
169
|
+
setSysFields(block, blockId, "affine:code");
|
|
170
|
+
block.set("sys:parent", noteId);
|
|
171
|
+
block.set("sys:children", new Y.Array());
|
|
172
|
+
block.set("prop:language", (parsed.language || "txt").toLowerCase());
|
|
173
|
+
if (parsed.caption) {
|
|
174
|
+
block.set("prop:caption", parsed.caption);
|
|
175
|
+
}
|
|
176
|
+
block.set("prop:text", makeText(content));
|
|
177
|
+
return { blockId, block, flavour: "affine:code" };
|
|
178
|
+
}
|
|
179
|
+
case "divider": {
|
|
180
|
+
setSysFields(block, blockId, "affine:divider");
|
|
181
|
+
block.set("sys:parent", noteId);
|
|
182
|
+
block.set("sys:children", new Y.Array());
|
|
183
|
+
return { blockId, block, flavour: "affine:divider" };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function appendBlockInternal(parsed) {
|
|
188
|
+
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
189
|
+
if (!workspaceId)
|
|
190
|
+
throw new Error("workspaceId is required");
|
|
191
|
+
const { endpoint, cookie } = await getCookieAndEndpoint();
|
|
192
|
+
const wsUrl = wsUrlFromGraphQLEndpoint(endpoint);
|
|
193
|
+
const socket = await connectWorkspaceSocket(wsUrl, cookie);
|
|
194
|
+
try {
|
|
195
|
+
await joinWorkspace(socket, workspaceId);
|
|
196
|
+
const doc = new Y.Doc();
|
|
197
|
+
const snapshot = await loadDoc(socket, workspaceId, parsed.docId);
|
|
198
|
+
if (snapshot.missing) {
|
|
199
|
+
Y.applyUpdate(doc, Buffer.from(snapshot.missing, "base64"));
|
|
200
|
+
}
|
|
201
|
+
const prevSV = Y.encodeStateVector(doc);
|
|
202
|
+
const blocks = doc.getMap("blocks");
|
|
203
|
+
const noteId = ensureNoteBlock(blocks);
|
|
204
|
+
const { blockId, block, flavour, blockType } = createBlock(noteId, parsed);
|
|
205
|
+
blocks.set(blockId, block);
|
|
206
|
+
const note = blocks.get(noteId);
|
|
207
|
+
let noteChildren = note.get("sys:children");
|
|
208
|
+
if (!(noteChildren instanceof Y.Array)) {
|
|
209
|
+
noteChildren = new Y.Array();
|
|
210
|
+
note.set("sys:children", noteChildren);
|
|
211
|
+
}
|
|
212
|
+
noteChildren.push([blockId]);
|
|
213
|
+
const delta = Y.encodeStateAsUpdate(doc, prevSV);
|
|
214
|
+
await pushDocUpdate(socket, workspaceId, parsed.docId, Buffer.from(delta).toString("base64"));
|
|
215
|
+
return { appended: true, blockId, flavour, blockType };
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
socket.disconnect();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
22
221
|
const listDocsHandler = async (parsed) => {
|
|
23
222
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
24
223
|
if (!workspaceId) {
|
|
@@ -38,16 +237,6 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
38
237
|
after: z.string().optional()
|
|
39
238
|
}
|
|
40
239
|
}, listDocsHandler);
|
|
41
|
-
server.registerTool("affine_list_docs", {
|
|
42
|
-
title: "List Documents",
|
|
43
|
-
description: "List documents in a workspace (GraphQL).",
|
|
44
|
-
inputSchema: {
|
|
45
|
-
workspaceId: z.string().describe("Workspace ID (optional if default set).").optional(),
|
|
46
|
-
first: z.number().optional(),
|
|
47
|
-
offset: z.number().optional(),
|
|
48
|
-
after: z.string().optional()
|
|
49
|
-
}
|
|
50
|
-
}, listDocsHandler);
|
|
51
240
|
const getDocHandler = async (parsed) => {
|
|
52
241
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
53
242
|
if (!workspaceId) {
|
|
@@ -65,78 +254,103 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
65
254
|
docId: DocId
|
|
66
255
|
}
|
|
67
256
|
}, getDocHandler);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
workspaceId: z.string().optional(),
|
|
73
|
-
docId: DocId
|
|
257
|
+
const readDocHandler = async (parsed) => {
|
|
258
|
+
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
259
|
+
if (!workspaceId) {
|
|
260
|
+
throw new Error("workspaceId is required. Provide it as a parameter or set AFFINE_WORKSPACE_ID in environment.");
|
|
74
261
|
}
|
|
75
|
-
|
|
76
|
-
|
|
262
|
+
const { endpoint, cookie } = await getCookieAndEndpoint();
|
|
263
|
+
const wsUrl = wsUrlFromGraphQLEndpoint(endpoint);
|
|
264
|
+
const socket = await connectWorkspaceSocket(wsUrl, cookie);
|
|
77
265
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
266
|
+
await joinWorkspace(socket, workspaceId);
|
|
267
|
+
const snapshot = await loadDoc(socket, workspaceId, parsed.docId);
|
|
268
|
+
if (!snapshot.missing) {
|
|
269
|
+
return text({
|
|
270
|
+
docId: parsed.docId,
|
|
271
|
+
title: null,
|
|
272
|
+
exists: false,
|
|
273
|
+
blockCount: 0,
|
|
274
|
+
blocks: [],
|
|
275
|
+
plainText: "",
|
|
276
|
+
});
|
|
81
277
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
278
|
+
const doc = new Y.Doc();
|
|
279
|
+
Y.applyUpdate(doc, Buffer.from(snapshot.missing, "base64"));
|
|
280
|
+
const blocks = doc.getMap("blocks");
|
|
281
|
+
const pageId = findBlockIdByFlavour(blocks, "affine:page");
|
|
282
|
+
const noteId = findBlockIdByFlavour(blocks, "affine:note");
|
|
283
|
+
const visited = new Set();
|
|
284
|
+
const blockRows = [];
|
|
285
|
+
const plainTextLines = [];
|
|
286
|
+
let title = "";
|
|
287
|
+
const visit = (blockId) => {
|
|
288
|
+
if (visited.has(blockId))
|
|
289
|
+
return;
|
|
290
|
+
visited.add(blockId);
|
|
291
|
+
const raw = blocks.get(blockId);
|
|
292
|
+
if (!(raw instanceof Y.Map))
|
|
293
|
+
return;
|
|
294
|
+
const flavour = raw.get("sys:flavour");
|
|
295
|
+
const parentId = raw.get("sys:parent");
|
|
296
|
+
const type = raw.get("prop:type");
|
|
297
|
+
const textValue = asText(raw.get("prop:text"));
|
|
298
|
+
const language = raw.get("prop:language");
|
|
299
|
+
const checked = raw.get("prop:checked");
|
|
300
|
+
const childIds = childIdsFrom(raw.get("sys:children"));
|
|
301
|
+
if (flavour === "affine:page") {
|
|
302
|
+
title = asText(raw.get("prop:title")) || title;
|
|
303
|
+
}
|
|
304
|
+
if (textValue.length > 0) {
|
|
305
|
+
plainTextLines.push(textValue);
|
|
306
|
+
}
|
|
307
|
+
blockRows.push({
|
|
308
|
+
id: blockId,
|
|
309
|
+
parentId: typeof parentId === "string" ? parentId : null,
|
|
310
|
+
flavour: typeof flavour === "string" ? flavour : null,
|
|
311
|
+
type: typeof type === "string" ? type : null,
|
|
312
|
+
text: textValue.length > 0 ? textValue : null,
|
|
313
|
+
checked: typeof checked === "boolean" ? checked : null,
|
|
314
|
+
language: typeof language === "string" ? language : null,
|
|
315
|
+
childIds,
|
|
316
|
+
});
|
|
317
|
+
for (const childId of childIds) {
|
|
318
|
+
visit(childId);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
if (pageId) {
|
|
322
|
+
visit(pageId);
|
|
323
|
+
}
|
|
324
|
+
else if (noteId) {
|
|
325
|
+
visit(noteId);
|
|
326
|
+
}
|
|
327
|
+
for (const [id] of blocks) {
|
|
328
|
+
const blockId = String(id);
|
|
329
|
+
if (!visited.has(blockId)) {
|
|
330
|
+
visit(blockId);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return text({
|
|
334
|
+
docId: parsed.docId,
|
|
335
|
+
title: title || null,
|
|
336
|
+
exists: true,
|
|
337
|
+
blockCount: blockRows.length,
|
|
338
|
+
blocks: blockRows,
|
|
339
|
+
plainText: plainTextLines.join("\n"),
|
|
340
|
+
});
|
|
108
341
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
112
|
-
if (!workspaceId) {
|
|
113
|
-
throw new Error("workspaceId is required. Provide it as a parameter or set AFFINE_WORKSPACE_ID in environment.");
|
|
342
|
+
finally {
|
|
343
|
+
socket.disconnect();
|
|
114
344
|
}
|
|
115
|
-
// Note: AFFiNE doesn't have a separate 'recentlyUpdatedDocs' field, just use docs
|
|
116
|
-
const query = `query RecentDocs($workspaceId:String!, $first:Int, $offset:Int, $after:String){ workspace(id:$workspaceId){ docs(pagination:{first:$first, offset:$offset, after:$after}){ totalCount pageInfo{ hasNextPage endCursor } edges{ cursor node{ id workspaceId title summary public defaultRole createdAt updatedAt } } } } }`;
|
|
117
|
-
const data = await gql.request(query, { workspaceId, first: parsed.first, offset: parsed.offset, after: parsed.after });
|
|
118
|
-
return text(data.workspace.docs);
|
|
119
345
|
};
|
|
120
|
-
server.registerTool("
|
|
121
|
-
title: "
|
|
122
|
-
description: "
|
|
123
|
-
inputSchema: {
|
|
124
|
-
workspaceId: z.string().optional(),
|
|
125
|
-
first: z.number().optional(),
|
|
126
|
-
offset: z.number().optional(),
|
|
127
|
-
after: z.string().optional()
|
|
128
|
-
}
|
|
129
|
-
}, recentDocsHandler);
|
|
130
|
-
server.registerTool("affine_recent_docs", {
|
|
131
|
-
title: "Recent Documents",
|
|
132
|
-
description: "List recently updated docs in a workspace.",
|
|
346
|
+
server.registerTool("read_doc", {
|
|
347
|
+
title: "Read Document Content",
|
|
348
|
+
description: "Read document block content via WebSocket snapshot (blocks + plain text).",
|
|
133
349
|
inputSchema: {
|
|
134
|
-
workspaceId:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
}, recentDocsHandler);
|
|
350
|
+
workspaceId: WorkspaceId.optional(),
|
|
351
|
+
docId: DocId,
|
|
352
|
+
},
|
|
353
|
+
}, readDocHandler);
|
|
140
354
|
const publishDocHandler = async (parsed) => {
|
|
141
355
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
142
356
|
if (!workspaceId) {
|
|
@@ -155,15 +369,6 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
155
369
|
mode: z.enum(["Page", "Edgeless"]).optional()
|
|
156
370
|
}
|
|
157
371
|
}, publishDocHandler);
|
|
158
|
-
server.registerTool("affine_publish_doc", {
|
|
159
|
-
title: "Publish Document",
|
|
160
|
-
description: "Publish a doc (make public).",
|
|
161
|
-
inputSchema: {
|
|
162
|
-
workspaceId: z.string().optional(),
|
|
163
|
-
docId: z.string(),
|
|
164
|
-
mode: z.enum(["Page", "Edgeless"]).optional()
|
|
165
|
-
}
|
|
166
|
-
}, publishDocHandler);
|
|
167
372
|
const revokeDocHandler = async (parsed) => {
|
|
168
373
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
169
374
|
if (!workspaceId) {
|
|
@@ -181,14 +386,6 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
181
386
|
docId: z.string()
|
|
182
387
|
}
|
|
183
388
|
}, revokeDocHandler);
|
|
184
|
-
server.registerTool("affine_revoke_doc", {
|
|
185
|
-
title: "Revoke Document",
|
|
186
|
-
description: "Revoke a doc's public access.",
|
|
187
|
-
inputSchema: {
|
|
188
|
-
workspaceId: z.string().optional(),
|
|
189
|
-
docId: z.string()
|
|
190
|
-
}
|
|
191
|
-
}, revokeDocHandler);
|
|
192
389
|
// CREATE DOC (high-level)
|
|
193
390
|
const createDocHandler = async (parsed) => {
|
|
194
391
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
@@ -205,8 +402,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
205
402
|
const blocks = ydoc.getMap('blocks');
|
|
206
403
|
const pageId = generateId();
|
|
207
404
|
const page = new Y.Map();
|
|
208
|
-
page
|
|
209
|
-
page.set('sys:flavour', 'affine:page');
|
|
405
|
+
setSysFields(page, pageId, "affine:page");
|
|
210
406
|
const titleText = new Y.Text();
|
|
211
407
|
titleText.insert(0, parsed.title || 'Untitled');
|
|
212
408
|
page.set('prop:title', titleText);
|
|
@@ -215,21 +411,27 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
215
411
|
blocks.set(pageId, page);
|
|
216
412
|
const surfaceId = generateId();
|
|
217
413
|
const surface = new Y.Map();
|
|
218
|
-
surface
|
|
219
|
-
surface.set('sys:flavour', 'affine:surface');
|
|
414
|
+
setSysFields(surface, surfaceId, "affine:surface");
|
|
220
415
|
surface.set('sys:parent', pageId);
|
|
221
416
|
surface.set('sys:children', new Y.Array());
|
|
417
|
+
const elements = new Y.Map();
|
|
418
|
+
elements.set("type", "$blocksuite:internal:native$");
|
|
419
|
+
elements.set("value", new Y.Map());
|
|
420
|
+
surface.set("prop:elements", elements);
|
|
222
421
|
blocks.set(surfaceId, surface);
|
|
223
422
|
children.push([surfaceId]);
|
|
224
423
|
const noteId = generateId();
|
|
225
424
|
const note = new Y.Map();
|
|
226
|
-
note
|
|
227
|
-
note.set('sys:flavour', 'affine:note');
|
|
425
|
+
setSysFields(note, noteId, "affine:note");
|
|
228
426
|
note.set('sys:parent', pageId);
|
|
229
|
-
note.set('prop:displayMode', '
|
|
230
|
-
note.set('prop:xywh', '[0,0,800,
|
|
427
|
+
note.set('prop:displayMode', 'both');
|
|
428
|
+
note.set('prop:xywh', '[0,0,800,95]');
|
|
231
429
|
note.set('prop:index', 'a0');
|
|
232
|
-
note.set('prop:
|
|
430
|
+
note.set('prop:hidden', false);
|
|
431
|
+
const background = new Y.Map();
|
|
432
|
+
background.set("light", "#ffffff");
|
|
433
|
+
background.set("dark", "#252525");
|
|
434
|
+
note.set("prop:background", background);
|
|
233
435
|
const noteChildren = new Y.Array();
|
|
234
436
|
note.set('sys:children', noteChildren);
|
|
235
437
|
blocks.set(noteId, note);
|
|
@@ -237,8 +439,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
237
439
|
if (parsed.content) {
|
|
238
440
|
const paraId = generateId();
|
|
239
441
|
const para = new Y.Map();
|
|
240
|
-
para
|
|
241
|
-
para.set('sys:flavour', 'affine:paragraph');
|
|
442
|
+
setSysFields(para, paraId, "affine:paragraph");
|
|
242
443
|
para.set('sys:parent', noteId);
|
|
243
444
|
para.set('sys:children', new Y.Array());
|
|
244
445
|
para.set('prop:type', 'text');
|
|
@@ -293,90 +494,15 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
293
494
|
content: z.string().optional(),
|
|
294
495
|
},
|
|
295
496
|
}, createDocHandler);
|
|
296
|
-
server.registerTool('affine_create_doc', {
|
|
297
|
-
title: 'Create Document',
|
|
298
|
-
description: 'Create a new AFFiNE document with optional content',
|
|
299
|
-
inputSchema: {
|
|
300
|
-
workspaceId: z.string().optional(),
|
|
301
|
-
title: z.string().optional(),
|
|
302
|
-
content: z.string().optional(),
|
|
303
|
-
},
|
|
304
|
-
}, createDocHandler);
|
|
305
497
|
// APPEND PARAGRAPH
|
|
306
498
|
const appendParagraphHandler = async (parsed) => {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await joinWorkspace(socket, workspaceId);
|
|
315
|
-
const doc = new Y.Doc();
|
|
316
|
-
const snapshot = await loadDoc(socket, workspaceId, parsed.docId);
|
|
317
|
-
if (snapshot.missing) {
|
|
318
|
-
Y.applyUpdate(doc, Buffer.from(snapshot.missing, 'base64'));
|
|
319
|
-
}
|
|
320
|
-
const prevSV = Y.encodeStateVector(doc);
|
|
321
|
-
const blocks = doc.getMap('blocks');
|
|
322
|
-
// find a note block
|
|
323
|
-
let noteId = null;
|
|
324
|
-
for (const [key, val] of blocks) {
|
|
325
|
-
const m = val;
|
|
326
|
-
if (m?.get && m.get('sys:flavour') === 'affine:note') {
|
|
327
|
-
noteId = m.get('sys:id');
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (!noteId) {
|
|
332
|
-
// fallback: create a note under existing page
|
|
333
|
-
let pageId = null;
|
|
334
|
-
for (const [key, val] of blocks) {
|
|
335
|
-
const m = val;
|
|
336
|
-
if (m?.get && m.get('sys:flavour') === 'affine:page') {
|
|
337
|
-
pageId = m.get('sys:id');
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (!pageId)
|
|
342
|
-
throw new Error('Doc has no page block');
|
|
343
|
-
const note = new Y.Map();
|
|
344
|
-
noteId = generateId();
|
|
345
|
-
note.set('sys:id', noteId);
|
|
346
|
-
note.set('sys:flavour', 'affine:note');
|
|
347
|
-
note.set('sys:parent', pageId);
|
|
348
|
-
note.set('prop:displayMode', 'DocAndEdgeless');
|
|
349
|
-
note.set('prop:xywh', '[0,0,800,600]');
|
|
350
|
-
note.set('prop:index', 'a0');
|
|
351
|
-
note.set('prop:lockedBySelf', false);
|
|
352
|
-
note.set('sys:children', new Y.Array());
|
|
353
|
-
blocks.set(noteId, note);
|
|
354
|
-
const page = blocks.get(pageId);
|
|
355
|
-
const children = page.get('sys:children');
|
|
356
|
-
children.push([noteId]);
|
|
357
|
-
}
|
|
358
|
-
const paragraphId = generateId();
|
|
359
|
-
const para = new Y.Map();
|
|
360
|
-
para.set('sys:id', paragraphId);
|
|
361
|
-
para.set('sys:flavour', 'affine:paragraph');
|
|
362
|
-
para.set('sys:parent', noteId);
|
|
363
|
-
para.set('sys:children', new Y.Array());
|
|
364
|
-
para.set('prop:type', 'text');
|
|
365
|
-
const ptext = new Y.Text();
|
|
366
|
-
ptext.insert(0, parsed.text);
|
|
367
|
-
para.set('prop:text', ptext);
|
|
368
|
-
blocks.set(paragraphId, para);
|
|
369
|
-
const note = blocks.get(noteId);
|
|
370
|
-
const noteChildren = note.get('sys:children');
|
|
371
|
-
noteChildren.push([paragraphId]);
|
|
372
|
-
const delta = Y.encodeStateAsUpdate(doc, prevSV);
|
|
373
|
-
const deltaB64 = Buffer.from(delta).toString('base64');
|
|
374
|
-
await pushDocUpdate(socket, workspaceId, parsed.docId, deltaB64);
|
|
375
|
-
return text({ appended: true, paragraphId });
|
|
376
|
-
}
|
|
377
|
-
finally {
|
|
378
|
-
socket.disconnect();
|
|
379
|
-
}
|
|
499
|
+
const result = await appendBlockInternal({
|
|
500
|
+
workspaceId: parsed.workspaceId,
|
|
501
|
+
docId: parsed.docId,
|
|
502
|
+
type: "paragraph",
|
|
503
|
+
text: parsed.text,
|
|
504
|
+
});
|
|
505
|
+
return text({ appended: result.appended, paragraphId: result.blockId });
|
|
380
506
|
};
|
|
381
507
|
server.registerTool('append_paragraph', {
|
|
382
508
|
title: 'Append Paragraph',
|
|
@@ -387,15 +513,28 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
387
513
|
text: z.string(),
|
|
388
514
|
},
|
|
389
515
|
}, appendParagraphHandler);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
516
|
+
const appendBlockHandler = async (parsed) => {
|
|
517
|
+
const result = await appendBlockInternal(parsed);
|
|
518
|
+
return text({
|
|
519
|
+
appended: result.appended,
|
|
520
|
+
blockId: result.blockId,
|
|
521
|
+
flavour: result.flavour,
|
|
522
|
+
type: result.blockType || null,
|
|
523
|
+
});
|
|
524
|
+
};
|
|
525
|
+
server.registerTool("append_block", {
|
|
526
|
+
title: "Append Block",
|
|
527
|
+
description: "Append a slash-command style block (heading/list/todo/code/divider/quote) to a document.",
|
|
393
528
|
inputSchema: {
|
|
394
|
-
workspaceId:
|
|
395
|
-
docId:
|
|
396
|
-
|
|
529
|
+
workspaceId: WorkspaceId.optional(),
|
|
530
|
+
docId: DocId,
|
|
531
|
+
type: AppendBlockType.describe("Block type to append"),
|
|
532
|
+
text: z.string().optional().describe("Block content text"),
|
|
533
|
+
checked: z.boolean().optional().describe("Todo state when type is todo"),
|
|
534
|
+
language: z.string().optional().describe("Code language when type is code"),
|
|
535
|
+
caption: z.string().optional().describe("Code caption when type is code"),
|
|
397
536
|
},
|
|
398
|
-
},
|
|
537
|
+
}, appendBlockHandler);
|
|
399
538
|
// DELETE DOC
|
|
400
539
|
const deleteDocHandler = async (parsed) => {
|
|
401
540
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
@@ -441,9 +580,4 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
441
580
|
description: 'Delete a document and remove from workspace list',
|
|
442
581
|
inputSchema: { workspaceId: z.string().optional(), docId: z.string() },
|
|
443
582
|
}, deleteDocHandler);
|
|
444
|
-
server.registerTool('affine_delete_doc', {
|
|
445
|
-
title: 'Delete Document',
|
|
446
|
-
description: 'Delete a document and remove from workspace list',
|
|
447
|
-
inputSchema: { workspaceId: z.string().optional(), docId: z.string() },
|
|
448
|
-
}, deleteDocHandler);
|
|
449
583
|
}
|
package/dist/tools/history.js
CHANGED
|
@@ -9,16 +9,6 @@ export function registerHistoryTools(server, gql, defaults) {
|
|
|
9
9
|
const data = await gql.request(query, { workspaceId, guid: parsed.guid, take: parsed.take, before: parsed.before });
|
|
10
10
|
return text(data.workspace.histories);
|
|
11
11
|
};
|
|
12
|
-
server.registerTool("affine_list_histories", {
|
|
13
|
-
title: "List Histories",
|
|
14
|
-
description: "List doc histories (timestamps) for a doc.",
|
|
15
|
-
inputSchema: {
|
|
16
|
-
workspaceId: z.string().optional(),
|
|
17
|
-
guid: z.string(),
|
|
18
|
-
take: z.number().optional(),
|
|
19
|
-
before: z.string().optional()
|
|
20
|
-
}
|
|
21
|
-
}, listHistoriesHandler);
|
|
22
12
|
server.registerTool("list_histories", {
|
|
23
13
|
title: "List Histories",
|
|
24
14
|
description: "List doc histories (timestamps) for a doc.",
|
|
@@ -29,30 +19,4 @@ export function registerHistoryTools(server, gql, defaults) {
|
|
|
29
19
|
before: z.string().optional()
|
|
30
20
|
}
|
|
31
21
|
}, listHistoriesHandler);
|
|
32
|
-
const recoverDocHandler = async (parsed) => {
|
|
33
|
-
const workspaceId = parsed.workspaceId || defaults.workspaceId || parsed.workspaceId;
|
|
34
|
-
if (!workspaceId)
|
|
35
|
-
throw new Error("workspaceId required (or set AFFINE_WORKSPACE_ID)");
|
|
36
|
-
const mutation = `mutation Recover($workspaceId:String!,$guid:String!,$timestamp:DateTime!){ recoverDoc(workspaceId:$workspaceId, guid:$guid, timestamp:$timestamp) }`;
|
|
37
|
-
const data = await gql.request(mutation, { workspaceId, guid: parsed.guid, timestamp: parsed.timestamp });
|
|
38
|
-
return text({ recoveredAt: data.recoverDoc });
|
|
39
|
-
};
|
|
40
|
-
server.registerTool("affine_recover_doc", {
|
|
41
|
-
title: "Recover Document",
|
|
42
|
-
description: "Recover a doc to a previous timestamp.",
|
|
43
|
-
inputSchema: {
|
|
44
|
-
workspaceId: z.string().optional(),
|
|
45
|
-
guid: z.string(),
|
|
46
|
-
timestamp: z.string()
|
|
47
|
-
}
|
|
48
|
-
}, recoverDocHandler);
|
|
49
|
-
server.registerTool("recover_doc", {
|
|
50
|
-
title: "Recover Document",
|
|
51
|
-
description: "Recover a doc to a previous timestamp.",
|
|
52
|
-
inputSchema: {
|
|
53
|
-
workspaceId: z.string().optional(),
|
|
54
|
-
guid: z.string(),
|
|
55
|
-
timestamp: z.string()
|
|
56
|
-
}
|
|
57
|
-
}, recoverDocHandler);
|
|
58
22
|
}
|