@kamilio/wire 0.1.2 → 0.1.4

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 (110) hide show
  1. package/README.md +1 -1
  2. package/bin/wire-mcp.mjs +1 -1
  3. package/dist/adapters/root.d.ts +5 -2
  4. package/dist/adapters/root.d.ts.map +1 -1
  5. package/dist/adapters/root.js +58 -30
  6. package/dist/adapters/root.js.map +1 -1
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/auth.js +9 -36
  9. package/dist/auth.js.map +1 -1
  10. package/dist/cli.js +2 -2
  11. package/dist/cli.js.map +1 -1
  12. package/dist/executable.d.ts +3 -1
  13. package/dist/executable.d.ts.map +1 -1
  14. package/dist/executable.js +38 -12
  15. package/dist/executable.js.map +1 -1
  16. package/dist/hooks.d.ts.map +1 -1
  17. package/dist/hooks.js +22 -7
  18. package/dist/hooks.js.map +1 -1
  19. package/node_modules/provider-asana/dist/asana-project.d.ts.map +1 -1
  20. package/node_modules/provider-asana/dist/asana-project.js +3 -0
  21. package/node_modules/provider-asana/dist/asana-project.js.map +1 -1
  22. package/node_modules/provider-asana/src/asana-project.ts +2 -0
  23. package/node_modules/provider-chatgpt/dist/chatgpt.d.ts.map +1 -1
  24. package/node_modules/provider-chatgpt/dist/chatgpt.js +24 -3
  25. package/node_modules/provider-chatgpt/dist/chatgpt.js.map +1 -1
  26. package/node_modules/provider-chatgpt/src/chatgpt.ts +21 -3
  27. package/node_modules/provider-gmail/dist/gmail.d.ts.map +1 -1
  28. package/node_modules/provider-gmail/dist/gmail.js +8 -2
  29. package/node_modules/provider-gmail/dist/gmail.js.map +1 -1
  30. package/node_modules/provider-gmail/src/gmail.ts +8 -2
  31. package/node_modules/provider-google-docs/dist/google-docs.d.ts.map +1 -1
  32. package/node_modules/provider-google-docs/dist/google-docs.js +373 -9
  33. package/node_modules/provider-google-docs/dist/google-docs.js.map +1 -1
  34. package/node_modules/provider-google-docs/dist/google-forms.d.ts +28 -0
  35. package/node_modules/provider-google-docs/dist/google-forms.d.ts.map +1 -0
  36. package/node_modules/provider-google-docs/dist/google-forms.js +191 -0
  37. package/node_modules/provider-google-docs/dist/google-forms.js.map +1 -0
  38. package/node_modules/provider-google-docs/dist/index.d.ts +26 -0
  39. package/node_modules/provider-google-docs/dist/index.d.ts.map +1 -1
  40. package/node_modules/provider-google-docs/dist/index.js +3 -1
  41. package/node_modules/provider-google-docs/dist/index.js.map +1 -1
  42. package/node_modules/provider-google-docs/src/google-docs.ts +342 -10
  43. package/node_modules/provider-google-docs/src/google-forms.ts +179 -0
  44. package/node_modules/provider-google-docs/src/index.ts +3 -1
  45. package/node_modules/provider-notion/dist/notion-sync.d.ts +2 -2
  46. package/node_modules/provider-notion/dist/notion-sync.d.ts.map +1 -1
  47. package/node_modules/provider-notion/dist/notion-sync.js +89 -38
  48. package/node_modules/provider-notion/dist/notion-sync.js.map +1 -1
  49. package/node_modules/provider-notion/dist/notion.js +1 -1
  50. package/node_modules/provider-notion/dist/notion.js.map +1 -1
  51. package/node_modules/provider-notion/src/notion-sync.ts +90 -35
  52. package/node_modules/provider-notion/src/notion.ts +1 -1
  53. package/node_modules/provider-slack/dist/slack.d.ts.map +1 -1
  54. package/node_modules/provider-slack/dist/slack.js +14 -7
  55. package/node_modules/provider-slack/dist/slack.js.map +1 -1
  56. package/node_modules/provider-slack/src/slack.ts +14 -5
  57. package/node_modules/provider-zoom/dist/zoom-hub.d.ts.map +1 -1
  58. package/node_modules/provider-zoom/dist/zoom-hub.js +42 -35
  59. package/node_modules/provider-zoom/dist/zoom-hub.js.map +1 -1
  60. package/node_modules/provider-zoom/src/zoom-hub.ts +40 -35
  61. package/node_modules/wire-core/dist/core/model.d.ts +1 -1
  62. package/node_modules/wire-core/dist/core/model.d.ts.map +1 -1
  63. package/node_modules/wire-core/dist/core/transform.d.ts +1 -0
  64. package/node_modules/wire-core/dist/core/transform.d.ts.map +1 -1
  65. package/node_modules/wire-core/dist/core/transform.js +4 -0
  66. package/node_modules/wire-core/dist/core/transform.js.map +1 -1
  67. package/node_modules/wire-core/dist/index.d.ts +3 -2
  68. package/node_modules/wire-core/dist/index.d.ts.map +1 -1
  69. package/node_modules/wire-core/dist/index.js +2 -2
  70. package/node_modules/wire-core/dist/index.js.map +1 -1
  71. package/node_modules/wire-core/dist/operations.d.ts +3 -0
  72. package/node_modules/wire-core/dist/operations.d.ts.map +1 -1
  73. package/node_modules/wire-core/dist/operations.js +50 -9
  74. package/node_modules/wire-core/dist/operations.js.map +1 -1
  75. package/node_modules/wire-core/dist/ports.d.ts +1 -0
  76. package/node_modules/wire-core/dist/ports.d.ts.map +1 -1
  77. package/node_modules/wire-core/dist/runtime/chrome.d.ts +1 -0
  78. package/node_modules/wire-core/dist/runtime/chrome.d.ts.map +1 -1
  79. package/node_modules/wire-core/dist/runtime/chrome.js +91 -83
  80. package/node_modules/wire-core/dist/runtime/chrome.js.map +1 -1
  81. package/node_modules/wire-core/dist/runtime/cookies.d.ts.map +1 -1
  82. package/node_modules/wire-core/dist/runtime/cookies.js +7 -2
  83. package/node_modules/wire-core/dist/runtime/cookies.js.map +1 -1
  84. package/node_modules/wire-core/dist/runtime/node.d.ts.map +1 -1
  85. package/node_modules/wire-core/dist/runtime/node.js +4 -0
  86. package/node_modules/wire-core/dist/runtime/node.js.map +1 -1
  87. package/node_modules/wire-core/dist/storage/registry.d.ts.map +1 -1
  88. package/node_modules/wire-core/dist/storage/registry.js +95 -208
  89. package/node_modules/wire-core/dist/storage/registry.js.map +1 -1
  90. package/node_modules/wire-core/dist/storage/workspace.d.ts +3 -0
  91. package/node_modules/wire-core/dist/storage/workspace.d.ts.map +1 -1
  92. package/node_modules/wire-core/dist/storage/workspace.js +10 -1
  93. package/node_modules/wire-core/dist/storage/workspace.js.map +1 -1
  94. package/node_modules/wire-core/package.json +4 -0
  95. package/node_modules/wire-core/src/core/model.ts +1 -1
  96. package/node_modules/wire-core/src/core/transform.ts +5 -1
  97. package/node_modules/wire-core/src/index.ts +3 -2
  98. package/node_modules/wire-core/src/operations.ts +56 -10
  99. package/node_modules/wire-core/src/ports.ts +1 -0
  100. package/node_modules/wire-core/src/runtime/chrome.ts +99 -80
  101. package/node_modules/wire-core/src/runtime/cookies.ts +4 -2
  102. package/node_modules/wire-core/src/runtime/node.ts +4 -0
  103. package/node_modules/wire-core/src/storage/registry.ts +48 -15
  104. package/node_modules/wire-core/src/storage/workspace.ts +10 -1
  105. package/package.json +1 -1
  106. package/src/adapters/root.ts +51 -31
  107. package/src/auth.ts +10 -29
  108. package/src/cli.ts +2 -2
  109. package/src/executable.ts +38 -12
  110. package/src/hooks.ts +21 -6
@@ -1,4 +1,5 @@
1
1
  import { defineService } from "wire-core";
2
+ import { inflateRawSync } from "node:zlib";
2
3
  import type { FetchedDocument, JsonObject, JsonValue, RuntimeCapabilities, Source } from "wire-core";
3
4
 
4
5
  async function cookieHeader(runtime: RuntimeCapabilities): Promise<string> {
@@ -17,7 +18,7 @@ function filenameTitle(value: string, label: string): string {
17
18
  const bare = /filename=([^;]+)/i.exec(value);
18
19
  if (encoded === null && quoted === null && bare === null) throw new Error(`Google ${label} did not include a filename`);
19
20
  const filename = encoded !== null ? decodeURIComponent(encoded[1]!) : quoted !== null ? quoted[1]! : bare![1]!.trim();
20
- return filename.replace(/\.(csv|html|md|txt|xlsx)$/i, "");
21
+ return filename.replace(/\.(csv|html|md|pptx|txt|xlsx)$/i, "");
21
22
  }
22
23
 
23
24
  async function googleExport(runtime: RuntimeCapabilities, url: string, label: string): Promise<Readonly<{ title: string; text: string }>> {
@@ -29,6 +30,15 @@ async function googleExport(runtime: RuntimeCapabilities, url: string, label: st
29
30
  return Object.freeze({ title: filenameTitle(disposition, label), text: await response.text() });
30
31
  }
31
32
 
33
+ async function googleExportBytes(runtime: RuntimeCapabilities, url: string, label: string): Promise<Readonly<{ title: string; bytes: Uint8Array }>> {
34
+ const response = await runtime.http.request(url, { headers: { Cookie: await cookieHeader(runtime) } });
35
+ if (response.status === 401 || response.status === 403) throw cookieAuthenticationError();
36
+ if (!response.ok) throw new Error(`Google ${label} failed: HTTP ${response.status}`);
37
+ const disposition = response.headers.get("content-disposition");
38
+ if (disposition === null) throw cookieAuthenticationError();
39
+ return Object.freeze({ title: filenameTitle(disposition, label), bytes: new Uint8Array(await response.arrayBuffer()) });
40
+ }
41
+
32
42
  async function googleText(runtime: RuntimeCapabilities, url: string, label: string): Promise<string> {
33
43
  const response = await runtime.http.request(url, { headers: { Cookie: await cookieHeader(runtime) } });
34
44
  if (response.status === 401 || response.status === 403) throw cookieAuthenticationError();
@@ -174,6 +184,268 @@ function documentTab(source: Source): string | null {
174
184
  return typeof tab === "string" && tab !== "" ? tab : null;
175
185
  }
176
186
 
187
+ type XmlChild = XmlNode | string;
188
+ type XmlNode = Readonly<{ name: string; attributes: ReadonlyMap<string, string>; children: readonly XmlChild[] }>;
189
+
190
+ function decodeXml(value: string): string {
191
+ return value.replace(/&(#x[0-9a-fA-F]+|#\d+|amp|lt|gt|quot|apos);/g, (_match, entity: string) => {
192
+ if (entity.startsWith("#x")) return String.fromCodePoint(Number.parseInt(entity.slice(2), 16));
193
+ if (entity.startsWith("#")) return String.fromCodePoint(Number.parseInt(entity.slice(1), 10));
194
+ if (entity === "amp") return "&";
195
+ if (entity === "lt") return "<";
196
+ if (entity === "gt") return ">";
197
+ if (entity === "quot") return "\"";
198
+ if (entity === "apos") return "'";
199
+ throw new Error(`PPTX XML unknown entity: ${entity}`);
200
+ });
201
+ }
202
+
203
+ function xmlTagEnd(xml: string, start: number): number {
204
+ let quote: string | null = null;
205
+ for (let index = start + 1; index < xml.length; index += 1) {
206
+ const char = xml[index]!;
207
+ if (quote === null && char === ">") return index;
208
+ if (quote === null && (char === "\"" || char === "'")) quote = char;
209
+ else if (quote === char) quote = null;
210
+ }
211
+ throw new Error("PPTX XML tag is unterminated");
212
+ }
213
+
214
+ function parseAttributes(value: string): ReadonlyMap<string, string> {
215
+ const attributes = new Map<string, string>();
216
+ let index = 0;
217
+ while (index < value.length) {
218
+ while (index < value.length && /\s/.test(value[index]!)) index += 1;
219
+ if (index >= value.length) break;
220
+ const nameStart = index;
221
+ while (index < value.length && !/[\s=]/.test(value[index]!)) index += 1;
222
+ const name = value.slice(nameStart, index);
223
+ while (index < value.length && /\s/.test(value[index]!)) index += 1;
224
+ if (value[index] !== "=") throw new Error(`PPTX XML attribute ${name} is invalid`);
225
+ index += 1;
226
+ while (index < value.length && /\s/.test(value[index]!)) index += 1;
227
+ const quote = value[index]!;
228
+ if (quote !== "\"" && quote !== "'") throw new Error(`PPTX XML attribute ${name} is unquoted`);
229
+ index += 1;
230
+ const textStart = index;
231
+ while (index < value.length && value[index] !== quote) index += 1;
232
+ attributes.set(name, decodeXml(value.slice(textStart, index)));
233
+ index += 1;
234
+ }
235
+ return attributes;
236
+ }
237
+
238
+ function parseXml(xml: string): XmlNode {
239
+ const root: { name: string; attributes: ReadonlyMap<string, string>; children: XmlChild[] } = { name: "#document", attributes: new Map(), children: [] };
240
+ const stack: { name: string; attributes: ReadonlyMap<string, string>; children: XmlChild[] }[] = [root];
241
+ let index = 0;
242
+ while (index < xml.length) {
243
+ if (xml.startsWith("<?", index)) {
244
+ index = xml.indexOf("?>", index) + 2;
245
+ } else if (xml.startsWith("<!--", index)) {
246
+ index = xml.indexOf("-->", index) + 3;
247
+ } else if (xml.startsWith("<![CDATA[", index)) {
248
+ const end = xml.indexOf("]]>", index);
249
+ stack[stack.length - 1]!.children.push(xml.slice(index + 9, end));
250
+ index = end + 3;
251
+ } else if (xml.startsWith("</", index)) {
252
+ const end = xmlTagEnd(xml, index);
253
+ const name = xml.slice(index + 2, end).trim();
254
+ const node = stack.pop()!;
255
+ if (node.name !== name) throw new Error(`PPTX XML closing tag mismatch: ${name}`);
256
+ stack[stack.length - 1]!.children.push(Object.freeze({ name: node.name, attributes: node.attributes, children: Object.freeze(node.children) }));
257
+ index = end + 1;
258
+ } else if (xml[index] === "<") {
259
+ const end = xmlTagEnd(xml, index);
260
+ const raw = xml.slice(index + 1, end).trim();
261
+ const selfClosing = raw.endsWith("/");
262
+ const tag = selfClosing ? raw.slice(0, -1).trimEnd() : raw;
263
+ const nameEnd = tag.search(/\s/);
264
+ const name = nameEnd === -1 ? tag : tag.slice(0, nameEnd);
265
+ const attributes = parseAttributes(nameEnd === -1 ? "" : tag.slice(nameEnd + 1));
266
+ if (selfClosing) stack[stack.length - 1]!.children.push(Object.freeze({ name, attributes, children: Object.freeze([]) }));
267
+ else stack.push({ name, attributes, children: [] });
268
+ index = end + 1;
269
+ } else {
270
+ const end = xml.indexOf("<", index);
271
+ const text = decodeXml(xml.slice(index, end === -1 ? xml.length : end));
272
+ if (text !== "") stack[stack.length - 1]!.children.push(text);
273
+ index = end === -1 ? xml.length : end;
274
+ }
275
+ }
276
+ if (stack.length !== 1) throw new Error("PPTX XML document is unclosed");
277
+ return Object.freeze({ name: root.name, attributes: root.attributes, children: Object.freeze(root.children) });
278
+ }
279
+
280
+ function isXmlNode(value: XmlChild): value is XmlNode {
281
+ return typeof value !== "string";
282
+ }
283
+
284
+ function xmlChildren(node: XmlNode, name: string): readonly XmlNode[] {
285
+ return node.children.filter(isXmlNode).filter((child) => child.name === name);
286
+ }
287
+
288
+ function xmlDescendants(node: XmlNode, name: string): readonly XmlNode[] {
289
+ return node.children.filter(isXmlNode).flatMap((child) => child.name === name ? [child, ...xmlDescendants(child, name)] : xmlDescendants(child, name));
290
+ }
291
+
292
+ function xmlText(node: XmlNode): string {
293
+ return node.children.map((child) => typeof child === "string" ? child : xmlText(child)).join("");
294
+ }
295
+
296
+ function xmlAttribute(node: XmlNode, name: string): string {
297
+ const value = node.attributes.get(name);
298
+ if (value === undefined) throw new Error(`PPTX XML missing ${name}`);
299
+ return value;
300
+ }
301
+
302
+ function zipEndOfCentralDirectory(bytes: Uint8Array): number {
303
+ for (let index = bytes.length - 22; index >= Math.max(0, bytes.length - 65557); index -= 1) {
304
+ if (bytes[index] === 0x50 && bytes[index + 1] === 0x4b && bytes[index + 2] === 0x05 && bytes[index + 3] === 0x06) return index;
305
+ }
306
+ throw new Error("PPTX ZIP missing central directory");
307
+ }
308
+
309
+ function unzipFiles(bytes: Uint8Array): ReadonlyMap<string, Uint8Array> {
310
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
311
+ const end = zipEndOfCentralDirectory(bytes);
312
+ const totalEntries = view.getUint16(end + 10, true);
313
+ let centralOffset = view.getUint32(end + 16, true);
314
+ const files = new Map<string, Uint8Array>();
315
+ const decoder = new TextDecoder();
316
+ for (let entryIndex = 0; entryIndex < totalEntries; entryIndex += 1) {
317
+ if (view.getUint32(centralOffset, true) !== 0x02014b50) throw new Error("PPTX ZIP central directory entry is invalid");
318
+ const method = view.getUint16(centralOffset + 10, true);
319
+ const compressedSize = view.getUint32(centralOffset + 20, true);
320
+ const nameLength = view.getUint16(centralOffset + 28, true);
321
+ const extraLength = view.getUint16(centralOffset + 30, true);
322
+ const commentLength = view.getUint16(centralOffset + 32, true);
323
+ const localOffset = view.getUint32(centralOffset + 42, true);
324
+ const name = decoder.decode(bytes.subarray(centralOffset + 46, centralOffset + 46 + nameLength));
325
+ if (view.getUint32(localOffset, true) !== 0x04034b50) throw new Error("PPTX ZIP local file entry is invalid");
326
+ const localNameLength = view.getUint16(localOffset + 26, true);
327
+ const localExtraLength = view.getUint16(localOffset + 28, true);
328
+ const dataStart = localOffset + 30 + localNameLength + localExtraLength;
329
+ const compressed = bytes.subarray(dataStart, dataStart + compressedSize);
330
+ if (method === 0) files.set(name, compressed);
331
+ else if (method === 8) files.set(name, inflateRawSync(compressed));
332
+ else throw new Error(`PPTX ZIP compression method is unsupported: ${method}`);
333
+ centralOffset += 46 + nameLength + extraLength + commentLength;
334
+ }
335
+ return files;
336
+ }
337
+
338
+ function zipText(files: ReadonlyMap<string, Uint8Array>, name: string): string {
339
+ const bytes = files.get(name);
340
+ if (bytes === undefined) throw new Error(`PPTX missing ${name}`);
341
+ return new TextDecoder().decode(bytes);
342
+ }
343
+
344
+ function relationshipTargets(xml: string): ReadonlyMap<string, string> {
345
+ const relationships = new Map<string, string>();
346
+ for (const relationship of xmlDescendants(parseXml(xml), "Relationship")) relationships.set(xmlAttribute(relationship, "Id"), xmlAttribute(relationship, "Target"));
347
+ return relationships;
348
+ }
349
+
350
+ function resolvePartPath(basePath: string, target: string): string {
351
+ const parts = basePath.split("/");
352
+ parts.pop();
353
+ for (const segment of target.split("/")) {
354
+ if (segment === "..") parts.pop();
355
+ else if (segment !== ".") parts.push(segment);
356
+ }
357
+ return parts.join("/");
358
+ }
359
+
360
+ function slidePartPaths(files: ReadonlyMap<string, Uint8Array>): readonly string[] {
361
+ const presentation = parseXml(zipText(files, "ppt/presentation.xml"));
362
+ const relationships = relationshipTargets(zipText(files, "ppt/_rels/presentation.xml.rels"));
363
+ const slides = xmlDescendants(presentation, "p:sldId");
364
+ if (slides.length === 0) throw new Error("PPTX presentation has no slides");
365
+ return slides.map((slide) => {
366
+ const id = xmlAttribute(slide, "r:id");
367
+ const target = relationships.get(id);
368
+ if (target === undefined) throw new Error(`PPTX presentation missing slide relationship ${id}`);
369
+ return resolvePartPath("ppt/presentation.xml", target);
370
+ });
371
+ }
372
+
373
+ function slideRelationshipsPath(slidePath: string): string {
374
+ const parts = slidePath.split("/");
375
+ const filename = parts.pop();
376
+ if (filename === undefined) throw new Error("PPTX slide path is invalid");
377
+ parts.push("_rels", `${filename}.rels`);
378
+ return parts.join("/");
379
+ }
380
+
381
+ function markdownSpan(value: string, left: string, right: string): string {
382
+ const leading = /^[ \t]*/.exec(value)![0];
383
+ const trailing = /[ \t]*$/.exec(value)![0];
384
+ const core = value.slice(leading.length, value.length - trailing.length);
385
+ return core === "" ? value : `${leading}${left}${core}${right}${trailing}`;
386
+ }
387
+
388
+ function linkMarkdown(value: string, target: string): string {
389
+ const leading = /^[ \t]*/.exec(value)![0];
390
+ const trailing = /[ \t]*$/.exec(value)![0];
391
+ const core = value.slice(leading.length, value.length - trailing.length);
392
+ return core === "" ? value : `${leading}[${core.replace(/[[\]\\]/g, "\\$&")}](${target.replace(/\)/g, "%29")})${trailing}`;
393
+ }
394
+
395
+ function formattedRunMarkdown(run: XmlNode, relationships: ReadonlyMap<string, string>): string {
396
+ const text = xmlDescendants(run, "a:t").map(xmlText).join("");
397
+ const runProperties = xmlChildren(run, "a:rPr")[0];
398
+ if (runProperties === undefined) return text;
399
+ const hyperlink = xmlDescendants(runProperties, "a:hlinkClick")[0];
400
+ let markdown = text;
401
+ if (runProperties.attributes.get("u") !== undefined && runProperties.attributes.get("u") !== "none" && hyperlink === undefined) markdown = markdownSpan(markdown, "<u>", "</u>");
402
+ if (runProperties.attributes.get("strike") !== undefined && runProperties.attributes.get("strike") !== "noStrike") markdown = markdownSpan(markdown, "~~", "~~");
403
+ if (runProperties.attributes.get("b") === "1" && runProperties.attributes.get("i") === "1") markdown = markdownSpan(markdown, "***", "***");
404
+ else if (runProperties.attributes.get("b") === "1") markdown = markdownSpan(markdown, "**", "**");
405
+ else if (runProperties.attributes.get("i") === "1") markdown = markdownSpan(markdown, "_", "_");
406
+ if (hyperlink !== undefined) {
407
+ const id = xmlAttribute(hyperlink, "r:id");
408
+ const target = relationships.get(id);
409
+ if (target === undefined) throw new Error(`PPTX slide missing hyperlink relationship ${id}`);
410
+ markdown = linkMarkdown(markdown, target);
411
+ }
412
+ return markdown;
413
+ }
414
+
415
+ function paragraphMarkdown(paragraph: XmlNode, relationships: ReadonlyMap<string, string>): Readonly<{ text: string; list: "bullet" | "number" | null; level: number }> {
416
+ const pPr = xmlChildren(paragraph, "a:pPr")[0];
417
+ const list = pPr === undefined || xmlChildren(pPr, "a:buNone").length !== 0 ? null : xmlChildren(pPr, "a:buAutoNum").length !== 0 ? "number" : xmlChildren(pPr, "a:buChar").length !== 0 ? "bullet" : null;
418
+ const levelValue = pPr === undefined ? undefined : pPr.attributes.get("lvl");
419
+ const level = levelValue === undefined ? 0 : Number(levelValue);
420
+ const pieces: string[] = [];
421
+ for (const child of paragraph.children.filter(isXmlNode)) {
422
+ if (child.name === "a:br") pieces.push("\n");
423
+ if (child.name === "a:r" || child.name === "a:fld") pieces.push(formattedRunMarkdown(child, relationships));
424
+ }
425
+ return Object.freeze({ text: pieces.join("").replace(/[ \t]+$/gm, ""), list, level });
426
+ }
427
+
428
+ function slideMarkdown(slide: XmlNode, relationships: ReadonlyMap<string, string>): string {
429
+ const paragraphs = xmlDescendants(slide, "a:p").map((paragraph) => paragraphMarkdown(paragraph, relationships)).filter((paragraph) => paragraph.text.trim() !== "");
430
+ const lines = paragraphs.map((paragraph, index) => {
431
+ if (index === 0 && paragraph.list === null) return `## ${paragraph.text.trim()}`;
432
+ if (paragraph.list === "bullet") return `${" ".repeat(paragraph.level)}- ${paragraph.text.trim()}`;
433
+ if (paragraph.list === "number") return `${" ".repeat(paragraph.level)}1. ${paragraph.text.trim()}`;
434
+ return paragraph.text.trim();
435
+ });
436
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n");
437
+ }
438
+
439
+ function slidesMarkdown(bytes: Uint8Array): string {
440
+ const files = unzipFiles(bytes);
441
+ const slides = slidePartPaths(files).map((slidePath) => {
442
+ const slide = parseXml(zipText(files, slidePath));
443
+ const relationships = relationshipTargets(zipText(files, slideRelationshipsPath(slidePath)));
444
+ return slideMarkdown(slide, relationships);
445
+ });
446
+ return `${slides.join("\n\n---\n\n")}\n`;
447
+ }
448
+
177
449
  function urlParam(url: URL, name: string): string | null {
178
450
  const query = url.searchParams.get(name);
179
451
  const hash = new URLSearchParams(url.hash.slice(1)).get(name);
@@ -284,20 +556,60 @@ function docMarkdownEqual(left: string, right: string): boolean {
284
556
  return comparableDocMarkdown(left) === comparableDocMarkdown(right);
285
557
  }
286
558
 
287
- function textEdit(base: string, local: string): Readonly<{ base: string; local: string; before: string; after: string }> {
559
+ type TextEdit = Readonly<{ base: string; local: string; before: string; localBefore: string; after: string; localAfter: string }>;
560
+ type MarkdownSpan = Readonly<{ start: number; end: number }>;
561
+
562
+ function textEdit(base: string, local: string): TextEdit {
288
563
  let start = 0;
289
564
  while (start < base.length && start < local.length && base[start] === local[start]) start += 1;
290
565
  for (let window = 80; window >= 10; window -= 10) {
291
566
  for (let baseEnd = start; baseEnd <= Math.min(base.length - window, start + 2000); baseEnd += 1) {
292
567
  const anchor = base.slice(baseEnd, baseEnd + window);
293
568
  const localEnd = local.indexOf(anchor, start);
294
- if (localEnd !== -1 && localEnd <= start + 2000) return Object.freeze({ base: base.slice(start, baseEnd), local: local.slice(start, localEnd), before: base.slice(0, start), after: base.slice(baseEnd) });
569
+ if (localEnd !== -1 && localEnd <= start + 2000) return Object.freeze({ base: base.slice(start, baseEnd), local: local.slice(start, localEnd), before: base.slice(0, start), localBefore: "", after: base.slice(baseEnd), localAfter: local.slice(localEnd) });
295
570
  }
296
571
  }
297
572
  let baseEnd = base.length;
298
573
  let localEnd = local.length;
299
574
  while (baseEnd > start && localEnd > start && base[baseEnd - 1] === local[localEnd - 1]) { baseEnd -= 1; localEnd -= 1; }
300
- return Object.freeze({ base: base.slice(start, baseEnd), local: local.slice(start, localEnd), before: base.slice(0, start), after: base.slice(baseEnd) });
575
+ return Object.freeze({ base: base.slice(start, baseEnd), local: local.slice(start, localEnd), before: base.slice(0, start), localBefore: "", after: base.slice(baseEnd), localAfter: local.slice(localEnd) });
576
+ }
577
+
578
+ function textEdits(base: string, local: string): readonly TextEdit[] {
579
+ const edits: TextEdit[] = [];
580
+ let basePrefix = "";
581
+ let localPrefix = "";
582
+ let baseRest = base;
583
+ let localRest = local;
584
+ while (baseRest !== localRest) {
585
+ const edit = textEdit(baseRest, localRest);
586
+ const before = `${basePrefix}${edit.before}`;
587
+ const localBefore = `${localPrefix}${edit.before}`;
588
+ edits.push(Object.freeze({ ...edit, before, localBefore }));
589
+ basePrefix = base.slice(0, base.length - edit.after.length);
590
+ localPrefix = local.slice(0, local.length - edit.localAfter.length);
591
+ baseRest = edit.after;
592
+ localRest = edit.localAfter;
593
+ }
594
+ return edits;
595
+ }
596
+
597
+ function inlineMarkdownSpans(value: string): readonly MarkdownSpan[] {
598
+ const spans: MarkdownSpan[] = [];
599
+ for (const pattern of [/\[[^\]\n]+\]\([^)]+\)/g, /!\[[^\]\n]*\]\([^)]+\)/g, /`[^`\n]+`/g, /~~[^~\n]+~~/g, /\*\*[^*\n]+\*\*/g, /\*[^*\n]+\*/g, /(^|[^\w])__[^_\n]+__(?=[^\w]|$)/g, /(^|[^\w])_[^_\n]+_(?=[^\w]|$)/g, /<((?:https?|mailto):[^>\s]+)>/g, /<\/?(?:u|sup|sub|mark|span)(?:\s+[^>]*)?>/gi]) {
600
+ let match = pattern.exec(value);
601
+ while (match !== null) {
602
+ spans.push(Object.freeze({ start: match.index, end: match.index + match[0]!.length }));
603
+ match = pattern.exec(value);
604
+ }
605
+ }
606
+ return spans;
607
+ }
608
+
609
+ function editTouchesMarkdownSpan(before: string, value: string, edited: string): boolean {
610
+ const start = before.length;
611
+ const end = start + edited.length;
612
+ return inlineMarkdownSpans(value).some((span) => start === end ? start > span.start && start < span.end : start < span.end && end > span.start);
301
613
  }
302
614
 
303
615
  function context(value: string, side: "before" | "after", length: number): string {
@@ -368,15 +680,16 @@ function docTextRange(text: string, edit: Readonly<{ base: string; before: strin
368
680
  }
369
681
 
370
682
  async function uploadDocText(runtime: RuntimeCapabilities, documentId: string, key: string | null, tab: string, baseMarkdown: string, localMarkdown: string): Promise<void> {
371
- const edit = textEdit(baseMarkdown, localMarkdown);
683
+ const edits = textEdits(baseMarkdown, localMarkdown);
684
+ if (edits.some((edit) => editTouchesMarkdownSpan(edit.before, baseMarkdown, edit.base) || editTouchesMarkdownSpan(edit.localBefore, localMarkdown, edit.local))) throw new Error("Google Docs sync cannot preserve formatting in edited text");
372
685
  const editUrl = docEditUrl(documentId, key, tab);
373
686
  const session = docSession(await googleText(runtime, editUrl, "Docs editor"));
374
- const range = docTextRange(session.text, edit);
687
+ const ranges = edits.map((edit) => ({ edit, range: docTextRange(session.text, edit) })).sort((left, right) => right.range.start - left.range.start);
375
688
  const sid = runtime.clock.now().getTime().toString(16).padStart(16, "0").slice(-16);
376
- const commands = [
689
+ const commands = ranges.flatMap(({ edit, range }) => [
377
690
  ...(range.start === range.end ? [] : [{ ty: "ds", si: range.start + 1, ei: range.end }]),
378
691
  ...(edit.local === "" ? [] : [{ ty: "is", ibi: range.start + 1, s: markdownText(edit.local) }]),
379
- ];
692
+ ]);
380
693
  const params = new URLSearchParams({ id: documentId, sid, vc: "1", c: "1", w: "1", flr: "0", smv: "2147483647", smb: "[2147483647, AAE=]", token: session.token, ouid: session.ouid, includes_info_params: "true", cros_files: "false", nded: "false", tab });
381
694
  const body = new URLSearchParams({ rev: String(session.revision), bundles: JSON.stringify([{ commands, sid, reqId: 0 }]) });
382
695
  const response = await runtime.http.request(`https://docs.google.com/document/d/${encodeURIComponent(documentId)}/save?${params}`, { method: "POST", headers: { Cookie: await cookieHeader(runtime), "content-type": "application/x-www-form-urlencoded;charset=UTF-8", origin: "https://docs.google.com", referer: editUrl }, body: body.toString() });
@@ -401,6 +714,12 @@ async function fetchGoogleDocument(runtime: RuntimeCapabilities, source: Source)
401
714
  exportUrl = `https://docs.google.com/spreadsheets/d/${encodeURIComponent(documentId)}/export?${query}`;
402
715
  label = "Sheets CSV export";
403
716
  sheetGid = gid;
717
+ } else if (source.type === "presentation") {
718
+ const query = new URLSearchParams({ format: "pptx" });
719
+ const key = resourceKey(source);
720
+ if (key !== null) query.set("resourcekey", key);
721
+ exportUrl = `https://docs.google.com/presentation/d/${encodeURIComponent(documentId)}/export?${query}`;
722
+ label = "Slides PPTX export";
404
723
  } else {
405
724
  const query = new URLSearchParams({ format: "md" });
406
725
  const key = resourceKey(source);
@@ -410,6 +729,11 @@ async function fetchGoogleDocument(runtime: RuntimeCapabilities, source: Source)
410
729
  exportUrl = `https://docs.google.com/document/d/${encodeURIComponent(documentId)}/export?${query}`;
411
730
  label = "Docs Markdown export";
412
731
  }
732
+ if (source.type === "presentation") {
733
+ const exported = await googleExportBytes(runtime, exportUrl, label);
734
+ const markdown = slidesMarkdown(exported.bytes);
735
+ return Object.freeze({ title: exported.title, markdown, data: { document_id: documentId, title: exported.title, output_path: null, sheet_gid: sheetGid, markdown, rows: null, presentation: true } });
736
+ }
413
737
  const exported = await googleExport(runtime, exportUrl, label);
414
738
  const rows = source["sheet_gid"] === undefined ? null : parseCsv(exported.text);
415
739
  const markdown = rows === null ? exported.text : rowsMarkdown(rows);
@@ -419,6 +743,13 @@ async function fetchGoogleDocument(runtime: RuntimeCapabilities, source: Source)
419
743
 
420
744
  async function synchronizeGoogleDocument(runtime: RuntimeCapabilities, _url: string, source: Source, base: JsonValue, markdown: string): Promise<FetchedDocument> {
421
745
  const remote = await fetchGoogleDocument(runtime, source);
746
+ if (source.type === "presentation") {
747
+ const baseMarkdown = objectValue(base)["markdown"];
748
+ if (typeof baseMarkdown !== "string") throw new Error("Google sync base must include markdown");
749
+ if (markdown === baseMarkdown || markdown === remote.markdown) return remote;
750
+ if (remote.markdown === baseMarkdown) throw new Error("Google Slides sync is download-only. Revert local edits or use `wire download <url>` for a fresh copy.");
751
+ throw new Error("Google Slides changed remotely and locally. Resolve the conflict in Google Slides or the local Markdown file before syncing again.");
752
+ }
422
753
  const baseMarkdown = objectValue(base)["markdown"];
423
754
  const label = source["sheet_gid"] === undefined ? "Google Docs" : "Google Sheets";
424
755
  if (typeof baseMarkdown !== "string") throw new Error("Google sync base must include markdown");
@@ -448,12 +779,13 @@ async function synchronizeGoogleDocument(runtime: RuntimeCapabilities, _url: str
448
779
 
449
780
  export const googleDocsService = defineService<RuntimeCapabilities>({
450
781
  name: "google-docs",
451
- matches: (url) => url.hostname === "docs.google.com" && /^\/(document|spreadsheets)(?:\/u\/\d+)?\/d\/[^/]+(?:\/.*)?$/.test(url.pathname),
782
+ matches: (url) => url.hostname === "docs.google.com" && /^\/(document|presentation|spreadsheets)(?:\/u\/\d+)?\/d\/[^/]+(?:\/.*)?$/.test(url.pathname),
452
783
  parse: (url) => {
453
- const match = /^\/(document|spreadsheets)(?:\/u\/\d+)?\/d\/([^/]+)(?:\/.*)?$/.exec(url.pathname)!;
784
+ const match = /^\/(document|presentation|spreadsheets)(?:\/u\/\d+)?\/d\/([^/]+)(?:\/.*)?$/.exec(url.pathname)!;
454
785
  const documentId = match[2]!;
455
786
  const key = urlParam(url, "resourcekey");
456
787
  const resource_key = key === null || key === "" ? {} : { resource_key: key };
788
+ if (match[1] === "presentation") return Object.freeze({ service: "google-docs", identifier: documentId, type: "presentation", ...resource_key });
457
789
  if (match[1] === "spreadsheets") {
458
790
  const queryGid = url.searchParams.get("gid");
459
791
  const hashGid = new URLSearchParams(url.hash.slice(1)).get("gid");
@@ -0,0 +1,179 @@
1
+ import { defineService } from "wire-core";
2
+ import type { FetchedDocument, JsonObject, JsonValue, RuntimeCapabilities, Source } from "wire-core";
3
+
4
+ type FormAnswer = Readonly<{ questionId: string; text: string }>;
5
+
6
+ function formId(url: URL): string {
7
+ return /^\/forms(?:\/u\/\d+)?\/d\/([^/]+)(?:\/.*)?$/.exec(url.pathname)![1]!;
8
+ }
9
+
10
+ function formUrl(id: string): string {
11
+ return `https://docs.google.com/forms/d/${encodeURIComponent(id)}/edit`;
12
+ }
13
+
14
+ function formsAuthError(): Error {
15
+ return new Error("Google Forms API authentication is missing or expired. Set GOOGLE_FORMS_TOKEN_FILE to an OAuth token with Forms scopes, then retry.");
16
+ }
17
+
18
+ function apiDisabledError(body: JsonObject): Error | null {
19
+ const error = body["error"] as JsonObject | undefined;
20
+ if (error === undefined) return null;
21
+ const details = Array.isArray(error["details"]) ? error["details"] as readonly JsonObject[] : [];
22
+ const serviceDisabled = details.find((detail) => {
23
+ const metadata = detail["metadata"] as JsonObject | undefined;
24
+ return metadata !== undefined && metadata["service"] === "forms.googleapis.com";
25
+ });
26
+ if (serviceDisabled === undefined) return null;
27
+ const metadata = serviceDisabled["metadata"] as JsonObject;
28
+ const activationUrl = typeof metadata["activationUrl"] === "string" ? metadata["activationUrl"] : typeof metadata["containerInfo"] === "string" ? `https://console.developers.google.com/apis/api/forms.googleapis.com/overview?project=${metadata["containerInfo"]}` : "https://console.developers.google.com/apis/api/forms.googleapis.com/overview";
29
+ return new Error(`Google Forms API is disabled. Enable it at ${activationUrl} then retry.`);
30
+ }
31
+
32
+ async function formsJson(runtime: RuntimeCapabilities, url: string, label: string): Promise<JsonObject> {
33
+ const token = await runtime.googleFormsTokens.load();
34
+ const response = await runtime.http.request(url, { headers: { authorization: `Bearer ${token.token}` } });
35
+ const text = await response.text();
36
+ const body = text === "" ? {} : JSON.parse(text) as JsonObject;
37
+ if (response.status === 401) throw formsAuthError();
38
+ if (!response.ok) {
39
+ const disabled = apiDisabledError(body);
40
+ if (disabled !== null) throw disabled;
41
+ const error = body["error"] as JsonObject | undefined;
42
+ const message = typeof error?.["message"] === "string" ? error["message"] : text;
43
+ if (response.status === 403 && /insufficient authentication scopes/i.test(message)) throw new Error("Google Forms API token is missing required scopes. Regenerate GOOGLE_FORMS_TOKEN_FILE with forms.body and forms.responses.readonly scopes.");
44
+ throw new Error(`Google Forms API ${label} failed: HTTP ${response.status}${message === "" ? "" : ` ${message}`}`);
45
+ }
46
+ return body;
47
+ }
48
+
49
+ function optionalString(value: JsonValue | undefined): string | null {
50
+ return typeof value === "string" && value !== "" ? value : null;
51
+ }
52
+
53
+ function jsonObject(value: JsonValue): value is JsonObject {
54
+ return typeof value === "object" && value !== null && !Array.isArray(value);
55
+ }
56
+
57
+ function optionsMarkdown(options: JsonValue | undefined): string | null {
58
+ if (!Array.isArray(options)) return null;
59
+ return options.map((option) => {
60
+ const object = option as JsonObject;
61
+ return object["value"] as string;
62
+ }).join(", ");
63
+ }
64
+
65
+ function questionMarkdown(item: JsonObject): readonly string[] {
66
+ const lines = [`- ${item["title"] as string}`];
67
+ const questionItem = item["questionItem"] as JsonObject | undefined;
68
+ if (questionItem === undefined) return lines;
69
+ const question = questionItem["question"] as JsonObject;
70
+ lines.push(` - itemId: ${item["itemId"] as string}`);
71
+ lines.push(` - questionId: ${question["questionId"] as string}`);
72
+ if (question["required"] !== undefined) lines.push(` - required: ${String(question["required"])}`);
73
+ const textQuestion = question["textQuestion"] as JsonObject | undefined;
74
+ if (textQuestion !== undefined) lines.push(` - type: ${textQuestion["paragraph"] === true ? "paragraph" : "short_text"}`);
75
+ const choiceQuestion = question["choiceQuestion"] as JsonObject | undefined;
76
+ if (choiceQuestion !== undefined) {
77
+ lines.push(` - type: ${choiceQuestion["type"] as string}`);
78
+ const options = optionsMarkdown(choiceQuestion["options"]);
79
+ if (options !== null) lines.push(` - options: ${options}`);
80
+ }
81
+ const scaleQuestion = question["scaleQuestion"] as JsonObject | undefined;
82
+ if (scaleQuestion !== undefined) {
83
+ lines.push(" - type: scale");
84
+ lines.push(` - range: ${String(scaleQuestion["low"])} to ${String(scaleQuestion["high"])}`);
85
+ const lowLabel = optionalString(scaleQuestion["lowLabel"]);
86
+ const highLabel = optionalString(scaleQuestion["highLabel"]);
87
+ if (lowLabel !== null) lines.push(` - lowLabel: ${lowLabel}`);
88
+ if (highLabel !== null) lines.push(` - highLabel: ${highLabel}`);
89
+ }
90
+ if (question["dateQuestion"] !== undefined) lines.push(" - type: date");
91
+ if (question["timeQuestion"] !== undefined) lines.push(" - type: time");
92
+ const ratingQuestion = question["ratingQuestion"] as JsonObject | undefined;
93
+ if (ratingQuestion !== undefined) {
94
+ lines.push(" - type: rating");
95
+ lines.push(` - level: ${String(ratingQuestion["ratingScaleLevel"])}`);
96
+ lines.push(` - icon: ${String(ratingQuestion["iconType"])}`);
97
+ }
98
+ if (question["fileUploadQuestion"] !== undefined) lines.push(" - type: file_upload");
99
+ if (question["rowQuestion"] !== undefined) lines.push(" - type: row");
100
+ return lines;
101
+ }
102
+
103
+ function answerTexts(answer: JsonObject): readonly string[] {
104
+ const textAnswers = answer["textAnswers"] as JsonObject | undefined;
105
+ if (textAnswers !== undefined && Array.isArray(textAnswers["answers"])) return (textAnswers["answers"] as readonly JsonObject[]).map((value) => value["value"] as string);
106
+ return [JSON.stringify(answer)];
107
+ }
108
+
109
+ function formAnswers(response: JsonObject): readonly FormAnswer[] {
110
+ const answers = response["answers"] as JsonObject | undefined;
111
+ if (answers === undefined) return [];
112
+ return Object.entries(answers).flatMap(([questionId, answer]) => answerTexts(answer as JsonObject).map((text) => ({ questionId, text })));
113
+ }
114
+
115
+ function formMarkdown(form: JsonObject, responses: readonly JsonObject[]): string {
116
+ const info = form["info"] as JsonObject;
117
+ const title = info["title"] as string;
118
+ const id = form["formId"] as string;
119
+ const lines = [`# ${title}`, "", `- Form ID: ${id}`, `- Edit: ${formUrl(id)}`];
120
+ const responder = optionalString(form["responderUri"]);
121
+ if (responder !== null) lines.push(`- Responder: ${responder}`);
122
+ const publish = (form["publishSettings"] as JsonObject | undefined)?.["publishState"] as JsonObject | undefined;
123
+ if (publish !== undefined) {
124
+ lines.push(`- Published: ${String(publish["isPublished"])}`);
125
+ lines.push(`- Accepting responses: ${String(publish["isAcceptingResponses"])}`);
126
+ }
127
+ lines.push("", "## Items");
128
+ const items = form["items"] as readonly JsonObject[] | undefined;
129
+ if (items === undefined || items.length === 0) lines.push("No items.");
130
+ else for (const item of items) lines.push(...questionMarkdown(item));
131
+ lines.push("", "## Responses", "", `Response count: ${responses.length}`);
132
+ for (const response of responses) {
133
+ lines.push("", `### ${response["responseId"] as string}`);
134
+ const createTime = optionalString(response["createTime"]);
135
+ const submitted = optionalString(response["lastSubmittedTime"]);
136
+ if (createTime !== null) lines.push(`- Created: ${createTime}`);
137
+ if (submitted !== null) lines.push(`- Submitted: ${submitted}`);
138
+ for (const answer of formAnswers(response)) lines.push(`- ${answer.questionId}: ${answer.text}`);
139
+ }
140
+ return `${lines.join("\n")}\n`;
141
+ }
142
+
143
+ async function allResponses(runtime: RuntimeCapabilities, id: string): Promise<readonly JsonObject[]> {
144
+ const responses: JsonObject[] = [];
145
+ let pageToken: string | undefined;
146
+ for (;;) {
147
+ const url = new URL(`https://forms.googleapis.com/v1/forms/${encodeURIComponent(id)}/responses`);
148
+ if (pageToken !== undefined) url.searchParams.set("pageToken", pageToken);
149
+ const body = await formsJson(runtime, url.toString(), "responses.list");
150
+ if (Array.isArray(body["responses"])) responses.push(...body["responses"] as readonly JsonObject[]);
151
+ if (typeof body["nextPageToken"] !== "string") break;
152
+ pageToken = body["nextPageToken"];
153
+ }
154
+ return responses;
155
+ }
156
+
157
+ async function fetchGoogleForm(runtime: RuntimeCapabilities, source: Source): Promise<FetchedDocument> {
158
+ const form = await formsJson(runtime, `https://forms.googleapis.com/v1/forms/${encodeURIComponent(source.identifier)}`, "forms.get");
159
+ const responses = await allResponses(runtime, source.identifier);
160
+ const info = form["info"] as JsonObject;
161
+ const markdown = formMarkdown(form, responses);
162
+ return Object.freeze({
163
+ title: info["title"] as string,
164
+ markdown,
165
+ data: { form, responses, markdown },
166
+ });
167
+ }
168
+
169
+ export const googleFormsService = defineService<RuntimeCapabilities>({
170
+ name: "google-forms",
171
+ matches: (url) => url.hostname === "docs.google.com" && /^\/forms(?:\/u\/\d+)?\/d\/[^/]+(?:\/.*)?$/.test(url.pathname),
172
+ parse: (url) => Object.freeze({ service: "google-forms", identifier: formId(url), type: "form" }),
173
+ fetch: (runtime, _url, source) => fetchGoogleForm(runtime, source),
174
+ synchronize: async (runtime, _url, source, base, markdown) => {
175
+ const baseMarkdown = jsonObject(base) && typeof base["markdown"] === "string" ? base["markdown"] : null;
176
+ if (baseMarkdown !== null && markdown !== baseMarkdown) throw new Error("Google Forms sync is download-only. Revert local edits or use `wire download <url>` for a fresh copy.");
177
+ return fetchGoogleForm(runtime, source);
178
+ },
179
+ });
@@ -1,6 +1,8 @@
1
1
  import { googleDocsService } from "./google-docs.js";
2
+ import { googleFormsService } from "./google-forms.js";
2
3
 
3
4
  export { googleDocsService } from "./google-docs.js";
5
+ export { googleFormsService } from "./google-forms.js";
4
6
  export const googleDocsProvider = Object.freeze({
5
- services: [googleDocsService] as const,
7
+ services: [googleFormsService, googleDocsService] as const,
6
8
  });
@@ -34,7 +34,7 @@ export type NotionDiffSummary = Readonly<{
34
34
  }>;
35
35
  type UserMentions = Readonly<Record<string, string>>;
36
36
  export declare function parseNotionMarkdown(markdown: string): readonly NotionBlock[];
37
- export declare function buildNotionCreateOperations(blocks: readonly NotionBlock[], pageId: string, spaceId: string, userId: string, currentTime: number, createId?: () => string): Readonly<{
37
+ export declare function buildNotionCreateOperations(blocks: readonly NotionBlock[], pageId: string, spaceId: string, userId: string, currentTime: number, createId?: () => string, initialTableColumnOrder?: readonly string[]): Readonly<{
38
38
  operations: readonly NotionOperation[];
39
39
  topLevelIds: readonly string[];
40
40
  }>;
@@ -51,6 +51,6 @@ export declare function diffNotionBlockTrees(remoteTree: NotionTree, localBlocks
51
51
  export declare function renderNotionTreeToMarkdown(tree: NotionTree, mentions?: UserMentions): string;
52
52
  export declare function fetchNotionDocument(runtime: RuntimeCapabilities, url: string, source: Source): Promise<FetchedDocument>;
53
53
  export declare function uploadNotionDocument(runtime: RuntimeCapabilities, markdown: string, _markdownPath: string): Promise<UploadedDocument>;
54
- export declare function synchronizeNotionDocument(runtime: RuntimeCapabilities, url: string, base: JsonValue, markdown: string, _markdownPath: string): Promise<FetchedDocument>;
54
+ export declare function synchronizeNotionDocument(runtime: RuntimeCapabilities, url: string, source: Source, base: JsonValue, markdown: string, _markdownPath: string): Promise<FetchedDocument>;
55
55
  export {};
56
56
  //# sourceMappingURL=notion-sync.d.ts.map