@shadow-garden/bapbong-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/host.cjs +548 -0
- package/dist/host.d.ts +9 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +514 -0
- package/dist/index.cjs +616 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +581 -0
- package/dist/lib/contract.d.ts +144 -0
- package/dist/lib/contract.d.ts.map +1 -0
- package/dist/lib/headless-session.d.ts +27 -0
- package/dist/lib/headless-session.d.ts.map +1 -0
- package/dist/lib/pm-session.d.ts +57 -0
- package/dist/lib/pm-session.d.ts.map +1 -0
- package/dist/lib/server.d.ts +17 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/wire.d.ts +62 -0
- package/dist/lib/wire.d.ts.map +1 -0
- package/dist/session.cjs +372 -0
- package/dist/session.d.ts +10 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +339 -0
- package/package.json +98 -0
package/dist/session.cjs
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// packages/mcp/src/session.ts
|
|
21
|
+
var session_exports = {};
|
|
22
|
+
__export(session_exports, {
|
|
23
|
+
AnchorError: () => AnchorError,
|
|
24
|
+
NoDocumentError: () => NoDocumentError,
|
|
25
|
+
PmDocSession: () => PmDocSession,
|
|
26
|
+
RemoteSession: () => RemoteSession,
|
|
27
|
+
VersionConflictError: () => VersionConflictError,
|
|
28
|
+
executeOp: () => executeOp,
|
|
29
|
+
reviveError: () => reviveError
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(session_exports);
|
|
32
|
+
|
|
33
|
+
// packages/mcp/src/lib/contract.ts
|
|
34
|
+
var VersionConflictError = class extends Error {
|
|
35
|
+
constructor(current, expected) {
|
|
36
|
+
super(
|
|
37
|
+
`Document changed (version is now ${current}, you expected ${expected}). Call get_document again, re-locate your anchor, then retry.`
|
|
38
|
+
);
|
|
39
|
+
this.name = "VersionConflictError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var AnchorError = class extends Error {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "AnchorError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var NoDocumentError = class extends Error {
|
|
49
|
+
constructor(message = "No document is open. Ask the user to open a document first.") {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "NoDocumentError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// packages/mcp/src/lib/pm-session.ts
|
|
56
|
+
var MARK_BY_FLAG = { bold: "strong", italic: "em", underline: "underline", strike: "strike" };
|
|
57
|
+
var PmDocSession = class {
|
|
58
|
+
constructor(host) {
|
|
59
|
+
this.host = host;
|
|
60
|
+
this.capabilities = { selection: typeof host.selection === "function" };
|
|
61
|
+
}
|
|
62
|
+
host;
|
|
63
|
+
capabilities;
|
|
64
|
+
// ── reads ────────────────────────────────────────────────────────────
|
|
65
|
+
async snapshot() {
|
|
66
|
+
const blocks = this.textblocks().map(({ node }, index) => {
|
|
67
|
+
const block = { index, type: blockType(node), text: node.textContent };
|
|
68
|
+
const images = blockImages(node).map(({ node: img }, i) => ({
|
|
69
|
+
index: i,
|
|
70
|
+
alt: String(img.attrs["alt"] ?? ""),
|
|
71
|
+
width: Number(img.attrs["width"]) || 0,
|
|
72
|
+
height: Number(img.attrs["height"]) || 0,
|
|
73
|
+
rotation: Number(img.attrs["rotation"]) || 0,
|
|
74
|
+
kind: img.attrs["shape"] ? "shape" : "bitmap"
|
|
75
|
+
}));
|
|
76
|
+
if (images.length > 0) block.images = images;
|
|
77
|
+
return block;
|
|
78
|
+
});
|
|
79
|
+
return { docVersion: this.host.getVersion(), blocks, meta: this.host.meta() };
|
|
80
|
+
}
|
|
81
|
+
async find(query) {
|
|
82
|
+
return this.hits(query).map((h, i) => ({ blockIndex: h.blockIndex, occurrence: i + 1, context: h.context }));
|
|
83
|
+
}
|
|
84
|
+
async getSelection() {
|
|
85
|
+
const sel = this.host.selection?.();
|
|
86
|
+
if (!sel || sel.from === sel.to) return null;
|
|
87
|
+
const state = this.host.getState();
|
|
88
|
+
const text = state.doc.textBetween(sel.from, sel.to, "\n");
|
|
89
|
+
const blockIndex = this.textblocks().findIndex(
|
|
90
|
+
({ node, pos }) => sel.from >= pos && sel.from <= pos + node.nodeSize
|
|
91
|
+
);
|
|
92
|
+
return { text, blockIndex };
|
|
93
|
+
}
|
|
94
|
+
// ── mutations ────────────────────────────────────────────────────────
|
|
95
|
+
async replaceText(oldText, newText, opts = {}) {
|
|
96
|
+
this.checkVersion(opts.expectedVersion);
|
|
97
|
+
const hit = this.uniqueHit(oldText, opts.occurrence);
|
|
98
|
+
this.host.apply(this.host.getState().tr.insertText(newText, hit.from, hit.to));
|
|
99
|
+
return { docVersion: this.host.getVersion(), range: { from: hit.from, to: hit.from + newText.length } };
|
|
100
|
+
}
|
|
101
|
+
async insertContent(content, anchor, opts = {}) {
|
|
102
|
+
this.checkVersion(opts.expectedVersion);
|
|
103
|
+
const state = this.host.getState();
|
|
104
|
+
const { schema } = state;
|
|
105
|
+
const paragraphs = content.split("\n").map((line) => schema.node("paragraph", null, line.length > 0 ? [schema.text(line)] : []));
|
|
106
|
+
let insertAt;
|
|
107
|
+
if (anchor.position === "document_end") {
|
|
108
|
+
insertAt = state.doc.content.size;
|
|
109
|
+
} else {
|
|
110
|
+
const hit = this.uniqueHit(anchor.text, anchor.occurrence);
|
|
111
|
+
const block = this.textblocks()[hit.blockIndex];
|
|
112
|
+
insertAt = anchor.position === "before" ? block.pos : block.pos + block.node.nodeSize;
|
|
113
|
+
}
|
|
114
|
+
const inserted = paragraphs.reduce((size, node) => size + node.nodeSize, 0);
|
|
115
|
+
this.host.apply(state.tr.insert(insertAt, paragraphs));
|
|
116
|
+
return { docVersion: this.host.getVersion(), range: { from: insertAt, to: insertAt + inserted } };
|
|
117
|
+
}
|
|
118
|
+
async applyFormatting(target, format, opts = {}) {
|
|
119
|
+
this.checkVersion(opts.expectedVersion);
|
|
120
|
+
const hit = this.uniqueHit(target, opts.occurrence);
|
|
121
|
+
const state = this.host.getState();
|
|
122
|
+
const { schema } = state;
|
|
123
|
+
let tr = state.tr;
|
|
124
|
+
for (const [flag, markName] of Object.entries(MARK_BY_FLAG)) {
|
|
125
|
+
const want = format[flag];
|
|
126
|
+
if (want === void 0) continue;
|
|
127
|
+
const mark = schema.marks[markName];
|
|
128
|
+
if (!mark) continue;
|
|
129
|
+
tr = want ? tr.addMark(hit.from, hit.to, mark.create()) : tr.removeMark(hit.from, hit.to, mark);
|
|
130
|
+
}
|
|
131
|
+
if (format.align) {
|
|
132
|
+
const block = this.textblocks()[hit.blockIndex];
|
|
133
|
+
tr = tr.setNodeMarkup(block.pos, void 0, { ...block.node.attrs, align: format.align });
|
|
134
|
+
}
|
|
135
|
+
if (tr.steps.length === 0) {
|
|
136
|
+
return { docVersion: this.host.getVersion() };
|
|
137
|
+
}
|
|
138
|
+
this.host.apply(tr);
|
|
139
|
+
return { docVersion: this.host.getVersion(), range: { from: hit.from, to: hit.to } };
|
|
140
|
+
}
|
|
141
|
+
async updateImage(blockIndex, imageIndex, changes, opts = {}) {
|
|
142
|
+
this.checkVersion(opts.expectedVersion);
|
|
143
|
+
const blocks = this.textblocks();
|
|
144
|
+
const block = blocks[blockIndex];
|
|
145
|
+
if (!block) {
|
|
146
|
+
throw new AnchorError(
|
|
147
|
+
`blockIndex ${blockIndex} is out of range \u2014 the document has ${blocks.length} block(s). Block indexes change with every edit; call get_document again.`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const images = blockImages(block.node).map((img2) => ({ ...img2, pos: block.pos + 1 + img2.offset }));
|
|
151
|
+
if (images.length === 0) {
|
|
152
|
+
throw new AnchorError(`Block ${blockIndex} has no images. get_document lists each block's images.`);
|
|
153
|
+
}
|
|
154
|
+
const img = images[imageIndex];
|
|
155
|
+
if (!img) {
|
|
156
|
+
throw new AnchorError(
|
|
157
|
+
`imageIndex ${imageIndex} is out of range \u2014 block ${blockIndex} has ${images.length} image(s) (0-${images.length - 1}).`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
let tr = this.host.getState().tr;
|
|
161
|
+
if (changes.width !== void 0) tr = tr.setNodeAttribute(img.pos, "width", Math.max(1, Math.round(changes.width)));
|
|
162
|
+
if (changes.height !== void 0) tr = tr.setNodeAttribute(img.pos, "height", Math.max(1, Math.round(changes.height)));
|
|
163
|
+
if (changes.rotation !== void 0) {
|
|
164
|
+
tr = tr.setNodeAttribute(img.pos, "rotation", (changes.rotation % 360 + 360) % 360);
|
|
165
|
+
}
|
|
166
|
+
if (tr.steps.length === 0) return { docVersion: this.host.getVersion() };
|
|
167
|
+
this.host.apply(tr);
|
|
168
|
+
return { docVersion: this.host.getVersion(), range: { from: img.pos, to: img.pos + 1 } };
|
|
169
|
+
}
|
|
170
|
+
async save() {
|
|
171
|
+
await this.host.save();
|
|
172
|
+
}
|
|
173
|
+
async close() {
|
|
174
|
+
}
|
|
175
|
+
// ── internals ────────────────────────────────────────────────────────
|
|
176
|
+
checkVersion(expected) {
|
|
177
|
+
const current = this.host.getVersion();
|
|
178
|
+
if (expected !== void 0 && expected !== current) {
|
|
179
|
+
throw new VersionConflictError(current, expected);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** All textblocks (paragraphs, incl. inside table cells) in reading order. */
|
|
183
|
+
textblocks() {
|
|
184
|
+
const out = [];
|
|
185
|
+
this.host.getState().doc.descendants((node, pos) => {
|
|
186
|
+
if (node.isTextblock) {
|
|
187
|
+
out.push({ node, pos });
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
});
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
/** Every occurrence of `query`, atom-safe (matches never span images/fields
|
|
195
|
+
* or block boundaries), in document order with absolute PM positions. */
|
|
196
|
+
hits(query) {
|
|
197
|
+
if (query.length === 0) return [];
|
|
198
|
+
const out = [];
|
|
199
|
+
this.textblocks().forEach(({ node, pos }, blockIndex) => {
|
|
200
|
+
const segments = [];
|
|
201
|
+
let joined = "";
|
|
202
|
+
node.forEach((child, offset) => {
|
|
203
|
+
if (child.isText && child.text) {
|
|
204
|
+
segments.push({ joinedStart: joined.length, length: child.text.length, startPos: pos + 1 + offset });
|
|
205
|
+
joined += child.text;
|
|
206
|
+
} else {
|
|
207
|
+
joined += "\uFFFF";
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
let at = joined.indexOf(query);
|
|
211
|
+
while (at !== -1) {
|
|
212
|
+
const seg = segments.find((s) => at >= s.joinedStart && at < s.joinedStart + s.length);
|
|
213
|
+
if (seg) {
|
|
214
|
+
const from = seg.startPos + (at - seg.joinedStart);
|
|
215
|
+
out.push({
|
|
216
|
+
from,
|
|
217
|
+
to: from + query.length,
|
|
218
|
+
blockIndex,
|
|
219
|
+
context: contextAround(joined.replace(//g, " "), at, query.length)
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
at = joined.indexOf(query, at + 1);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
uniqueHit(text, occurrence) {
|
|
228
|
+
const all = this.hits(text);
|
|
229
|
+
if (all.length === 0) {
|
|
230
|
+
throw new AnchorError(
|
|
231
|
+
`Text not found in the document: ${JSON.stringify(clip(text))}. Anchors match within one paragraph \u2014 check get_document for the exact text.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (occurrence !== void 0) {
|
|
235
|
+
const hit = all[occurrence - 1];
|
|
236
|
+
if (!hit) {
|
|
237
|
+
throw new AnchorError(`occurrence ${occurrence} is out of range \u2014 the text matches ${all.length} time(s).`);
|
|
238
|
+
}
|
|
239
|
+
return hit;
|
|
240
|
+
}
|
|
241
|
+
if (all.length > 1) {
|
|
242
|
+
throw new AnchorError(
|
|
243
|
+
`The text matches ${all.length} times \u2014 pass occurrence (1-${all.length}) to pick one, or use a longer, unique anchor.`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return all[0];
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
function blockImages(block) {
|
|
250
|
+
const out = [];
|
|
251
|
+
block.forEach((child, offset) => {
|
|
252
|
+
if (child.type.name === "image") out.push({ node: child, offset });
|
|
253
|
+
});
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
function blockType(node) {
|
|
257
|
+
const heading = node.attrs["heading"];
|
|
258
|
+
if (typeof heading === "number" && heading >= 1) return `heading${heading}`;
|
|
259
|
+
return node.type.name;
|
|
260
|
+
}
|
|
261
|
+
function contextAround(text, at, len) {
|
|
262
|
+
const before = text.slice(Math.max(0, at - 30), at);
|
|
263
|
+
const after = text.slice(at + len, at + len + 30);
|
|
264
|
+
return `${before}\xAB${text.slice(at, at + len)}\xBB${after}`;
|
|
265
|
+
}
|
|
266
|
+
function clip(s) {
|
|
267
|
+
return s.length > 60 ? `${s.slice(0, 57)}\u2026` : s;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// packages/mcp/src/lib/wire.ts
|
|
271
|
+
async function executeOp(session, request) {
|
|
272
|
+
try {
|
|
273
|
+
if (!session) throw new NoDocumentError();
|
|
274
|
+
const value = await run(session, request);
|
|
275
|
+
return { id: request.id, ok: true, value };
|
|
276
|
+
} catch (err) {
|
|
277
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
278
|
+
return { id: request.id, ok: false, error: { name: e.name, message: e.message } };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function run(session, { op, args }) {
|
|
282
|
+
switch (op) {
|
|
283
|
+
case "snapshot":
|
|
284
|
+
return session.snapshot();
|
|
285
|
+
case "find":
|
|
286
|
+
return session.find(args[0]);
|
|
287
|
+
case "replaceText":
|
|
288
|
+
return session.replaceText(args[0], args[1], args[2]);
|
|
289
|
+
case "insertContent":
|
|
290
|
+
return session.insertContent(args[0], args[1], args[2]);
|
|
291
|
+
case "applyFormatting":
|
|
292
|
+
return session.applyFormatting(args[0], args[1], args[2]);
|
|
293
|
+
case "updateImage":
|
|
294
|
+
return session.updateImage(
|
|
295
|
+
args[0],
|
|
296
|
+
args[1],
|
|
297
|
+
args[2],
|
|
298
|
+
args[3]
|
|
299
|
+
);
|
|
300
|
+
case "getSelection":
|
|
301
|
+
return session.getSelection?.() ?? Promise.resolve(null);
|
|
302
|
+
case "save":
|
|
303
|
+
return session.save();
|
|
304
|
+
default:
|
|
305
|
+
return Promise.reject(new Error(`unknown session op: ${op}`));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function reviveError(error) {
|
|
309
|
+
switch (error.name) {
|
|
310
|
+
case "AnchorError":
|
|
311
|
+
return new AnchorError(error.message);
|
|
312
|
+
case "NoDocumentError":
|
|
313
|
+
return new NoDocumentError(error.message);
|
|
314
|
+
case "VersionConflictError": {
|
|
315
|
+
const revived = new VersionConflictError("?", "?");
|
|
316
|
+
revived.message = error.message;
|
|
317
|
+
return revived;
|
|
318
|
+
}
|
|
319
|
+
default: {
|
|
320
|
+
const generic = new Error(error.message);
|
|
321
|
+
generic.name = error.name;
|
|
322
|
+
return generic;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
var RemoteSession = class {
|
|
327
|
+
constructor(send, capabilities) {
|
|
328
|
+
this.send = send;
|
|
329
|
+
this.capabilities = capabilities;
|
|
330
|
+
}
|
|
331
|
+
send;
|
|
332
|
+
capabilities;
|
|
333
|
+
async call(op, args = []) {
|
|
334
|
+
return await this.send(op, args);
|
|
335
|
+
}
|
|
336
|
+
snapshot() {
|
|
337
|
+
return this.call("snapshot");
|
|
338
|
+
}
|
|
339
|
+
find(query) {
|
|
340
|
+
return this.call("find", [query]);
|
|
341
|
+
}
|
|
342
|
+
replaceText(oldText, newText, opts) {
|
|
343
|
+
return this.call("replaceText", [oldText, newText, opts]);
|
|
344
|
+
}
|
|
345
|
+
insertContent(content, anchor, opts) {
|
|
346
|
+
return this.call("insertContent", [content, anchor, opts]);
|
|
347
|
+
}
|
|
348
|
+
applyFormatting(target, format, opts) {
|
|
349
|
+
return this.call("applyFormatting", [target, format, opts]);
|
|
350
|
+
}
|
|
351
|
+
updateImage(blockIndex, imageIndex, changes, opts) {
|
|
352
|
+
return this.call("updateImage", [blockIndex, imageIndex, changes, opts]);
|
|
353
|
+
}
|
|
354
|
+
async getSelection() {
|
|
355
|
+
return this.call("getSelection");
|
|
356
|
+
}
|
|
357
|
+
async save() {
|
|
358
|
+
await this.call("save");
|
|
359
|
+
}
|
|
360
|
+
async close() {
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
364
|
+
0 && (module.exports = {
|
|
365
|
+
AnchorError,
|
|
366
|
+
NoDocumentError,
|
|
367
|
+
PmDocSession,
|
|
368
|
+
RemoteSession,
|
|
369
|
+
VersionConflictError,
|
|
370
|
+
executeOp,
|
|
371
|
+
reviveError
|
|
372
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe surface: the contract, the shared PM session semantics, and
|
|
3
|
+
* the wire protocol — zero runtime dependencies (prosemirror imports are
|
|
4
|
+
* type-only). The desktop WebView imports THIS to execute session ops against
|
|
5
|
+
* its live editor; it must never pull the MCP SDK into the web bundle.
|
|
6
|
+
*/
|
|
7
|
+
export * from './lib/contract.js';
|
|
8
|
+
export { PmDocSession, type PmSessionHost } from './lib/pm-session.js';
|
|
9
|
+
export { executeOp, reviveError, RemoteSession, type SessionOpName, type SessionOpRequest, type SessionOpResponse, } from './lib/wire.js';
|
|
10
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EACL,SAAS,EACT,WAAW,EACX,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC"}
|