agentic-documents 0.4.0 → 0.6.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +161 -11
  3. package/dist/{chunk-TFCRPEEM.js → chunk-34C47PDC.js} +1 -1
  4. package/dist/chunk-5IQX2W52.js +195 -0
  5. package/dist/chunk-6UYJV2EJ.js +163 -0
  6. package/dist/{chunk-R5XUTBC3.js → chunk-CN6UAW7B.js} +32 -9
  7. package/dist/chunk-DWVJIKVT.js +8 -0
  8. package/dist/{chunk-YBBQNCMC.js → chunk-GQ5E6T7Z.js} +18 -191
  9. package/dist/{chunk-FZA5LQV3.js → chunk-JBAQEIOH.js} +89 -12
  10. package/dist/chunk-KC5KFB5R.js +354 -0
  11. package/dist/{chunk-IU3YIQ4Y.js → chunk-V6TBTOHW.js} +38 -13
  12. package/dist/cli.js +16 -12
  13. package/dist/client.d.ts +23 -6
  14. package/dist/client.js +9 -2
  15. package/dist/{doc-C_8kCsl7.d.ts → doc-BXG9qG5X.d.ts} +8 -2
  16. package/dist/editor.css +37 -0
  17. package/dist/embed.d.ts +86 -0
  18. package/dist/embed.js +431 -0
  19. package/dist/formats.d.ts +52 -0
  20. package/dist/formats.js +24 -0
  21. package/dist/index.d.ts +12 -10
  22. package/dist/index.js +15 -9
  23. package/dist/live-worker.d.ts +2 -2
  24. package/dist/live-worker.js +7 -4
  25. package/dist/live.d.ts +6 -2
  26. package/dist/live.js +5 -3
  27. package/dist/{mcp-P2C75IIG.js → mcp-ZTUTC4CS.js} +5 -2
  28. package/dist/render-463xHb-x.d.ts +16 -0
  29. package/dist/{review-BQW2Ci2h.d.ts → review-Ds-i5Vnc.d.ts} +2 -16
  30. package/dist/{service-Do2Jwb5U.d.ts → service-Ck6oX-Kq.d.ts} +38 -5
  31. package/dist/ui/_headers +4 -0
  32. package/dist/ui/app.css +1 -0
  33. package/dist/ui/app.js +270 -0
  34. package/dist/ui/assets/inter-latin-ext-wght-normal-HA22NDSG.woff2 +0 -0
  35. package/dist/ui/assets/inter-latin-wght-normal-NRMW37G5.woff2 +0 -0
  36. package/dist/ui/assets/jetbrains-mono-latin-ext-wght-normal-TSLVDSQR.woff2 +0 -0
  37. package/dist/ui/assets/jetbrains-mono-latin-wght-normal-VBS632QH.woff2 +0 -0
  38. package/dist/ui/index.html +13 -0
  39. package/dist/worker.d.ts +2 -2
  40. package/dist/worker.js +7 -4
  41. package/llms.txt +1 -0
  42. package/package.json +43 -10
  43. package/dist/chunk-GYQFTF2E.js +0 -389
@@ -0,0 +1,354 @@
1
+ import {
2
+ AgenticDoc,
3
+ DocError,
4
+ checkAuthor
5
+ } from "./chunk-GQ5E6T7Z.js";
6
+
7
+ // src/formats.ts
8
+ import { DEFAULT_LIMITS, FormatError, readDocx, readOdt, writeDocx, writeOdt } from "agentic-formats";
9
+
10
+ // src/render.ts
11
+ var EXPORT_FORMATS = ["md", "html", "odt"];
12
+ var esc = (s) => String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
13
+ var url = (value) => /^(https?:|mailto:|[^:]*$)/i.test(String(value ?? "").trim()) ? esc(String(value).trim()) : "#";
14
+ var integer = (value, fallback) => Number.isFinite(Number(value)) ? Math.trunc(Number(value)) : fallback;
15
+ var INLINE_IMAGE = /^data:image\/(png|jpeg|gif|webp);base64,[a-z0-9+/=]+$/i;
16
+ var textOf = (node) => node.text ?? (node.content ?? []).map(textOf).join("");
17
+ var MARK_TAGS = { bold: "strong", italic: "em", strike: "s", code: "code" };
18
+ function image(node, remote) {
19
+ const src = String(node.attrs?.src ?? "").trim();
20
+ const alt = esc(node.attrs?.alt);
21
+ if (INLINE_IMAGE.test(src)) return `<img src="${src}" alt="${alt}">`;
22
+ if (remote && url(src) !== "#") return `<img src="${url(src)}" alt="${alt}">`;
23
+ return `<span class="image">[image${alt ? `: ${alt}` : ""}]</span>`;
24
+ }
25
+ function inline(node, remote) {
26
+ if (node.type === "hardBreak") return "<br>";
27
+ if (node.type === "image") return image(node, remote);
28
+ return (node.marks ?? []).reduce((html, mark) => {
29
+ if (mark.type === "link") return `<a href="${url(mark.attrs?.href)}">${html}</a>`;
30
+ const tag = MARK_TAGS[mark.type];
31
+ return tag ? `<${tag}>${html}</${tag}>` : html;
32
+ }, esc(node.text));
33
+ }
34
+ function block(node, remote) {
35
+ const kids = (node.content ?? []).map((n) => block(n, remote)).join("");
36
+ const text = (node.content ?? []).map((n) => inline(n, remote)).join("");
37
+ switch (node.type) {
38
+ case "paragraph":
39
+ return `<p>${text}</p>
40
+ `;
41
+ case "heading": {
42
+ const level = Math.min(6, Math.max(1, integer(node.attrs?.level, 1)));
43
+ return `<h${level}>${text}</h${level}>
44
+ `;
45
+ }
46
+ case "blockquote":
47
+ return `<blockquote>
48
+ ${kids}</blockquote>
49
+ `;
50
+ case "codeBlock": {
51
+ const lang = node.attrs?.language ? ` class="language-${esc(node.attrs.language)}"` : "";
52
+ return `<pre><code${lang}>${esc(textOf(node))}</code></pre>
53
+ `;
54
+ }
55
+ case "horizontalRule":
56
+ return "<hr>\n";
57
+ case "bulletList":
58
+ case "taskList":
59
+ return `<ul>
60
+ ${kids}</ul>
61
+ `;
62
+ case "orderedList":
63
+ return `<ol start="${integer(node.attrs?.start, 1)}">
64
+ ${kids}</ol>
65
+ `;
66
+ case "listItem":
67
+ return `<li>${kids}</li>
68
+ `;
69
+ case "taskItem":
70
+ return `<li><input type="checkbox" disabled${node.attrs?.checked ? " checked" : ""}> ${kids}</li>
71
+ `;
72
+ case "table":
73
+ return `<table>
74
+ ${kids}</table>
75
+ `;
76
+ case "tableRow":
77
+ return `<tr>${kids}</tr>
78
+ `;
79
+ case "tableHeader":
80
+ return `<th>${kids}</th>`;
81
+ case "tableCell":
82
+ return `<td>${kids}</td>`;
83
+ default:
84
+ return "";
85
+ }
86
+ }
87
+ function toHtml(blocks, remote) {
88
+ const title = textOf(blocks.find((b) => b.type === "heading") ?? { type: "doc" }) || "Document";
89
+ const css = "body{font-family:system-ui,sans-serif;max-width:46rem;margin:2rem auto;padding:0 1rem;line-height:1.5}table{border-collapse:collapse}td,th{border:1px solid #888;padding:.25rem .5rem}pre{overflow:auto}";
90
+ return `<!doctype html>
91
+ <html lang="en">
92
+ <head>
93
+ <meta charset="utf-8">
94
+ <title>${esc(title)}</title>
95
+ <style>${css}</style>
96
+ </head>
97
+ <body>
98
+ ${blocks.map((b) => block(b, remote)).join("")}</body>
99
+ </html>
100
+ `;
101
+ }
102
+ function exportText(doc, format, options = {}) {
103
+ return format === "html" ? toHtml(doc.toJSON().content ?? [], options.remoteImages === true) : doc.toMarkdown();
104
+ }
105
+
106
+ // src/package.ts
107
+ var DATA_IMAGE = /^data:(image\/(?:png|jpeg|gif|webp));base64,([a-z0-9+/=]+)$/i;
108
+ function toBase64(bytes) {
109
+ let text = "";
110
+ for (let i = 0; i < bytes.length; i += 32768) text += String.fromCharCode(...bytes.subarray(i, i + 32768));
111
+ return btoa(text);
112
+ }
113
+ var fromBase64 = (value) => Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
114
+
115
+ // src/xml.ts
116
+ var safeHref = (value) => {
117
+ const href = String(value ?? "").trim();
118
+ return /^(https?:|mailto:|#|[^:]*$)/i.test(href) ? href : void 0;
119
+ };
120
+
121
+ // src/formats.ts
122
+ var IMPORT_FORMATS = ["md", "odt", "docx"];
123
+ var FILE_FORMATS = ["md", "html", "odt", "docx"];
124
+ var DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
125
+ var CONTENT_TYPES = {
126
+ md: "text/markdown; charset=utf-8",
127
+ html: "text/html; charset=utf-8",
128
+ odt: "application/vnd.oasis.opendocument.text",
129
+ docx: DOCX_MIME
130
+ };
131
+ var PM_MARKS = { bold: "bold", italic: "italic", strike: "strike", code: "code" };
132
+ var MODEL_IMAGE = /^image\/(png|jpeg|gif)$/;
133
+ var TASK = ["\u2610 ", "\u2611 "];
134
+ var warner = () => {
135
+ const seen = /* @__PURE__ */ new Set();
136
+ return { warn: (line) => void seen.add(line), list: () => [...seen] };
137
+ };
138
+ function toTextDoc(blocks, title) {
139
+ const { warn, list } = warner();
140
+ const inlines = (nodes = [], extra = []) => nodes.flatMap((node) => {
141
+ if (node.type === "hardBreak") return [{ type: "break" }];
142
+ if (node.type === "image") {
143
+ const alt = typeof node.attrs?.alt === "string" ? node.attrs.alt : "";
144
+ const found = DATA_IMAGE.exec(String(node.attrs?.src ?? "").trim());
145
+ if (found && MODEL_IMAGE.test(found[1].toLowerCase())) {
146
+ try {
147
+ return [{ type: "image", data: fromBase64(found[2]), mime: found[1].toLowerCase(), ...alt ? { alt } : {} }];
148
+ } catch {
149
+ }
150
+ }
151
+ warn("an image that is not an embedded PNG, JPEG, or GIF is kept as its alt text");
152
+ return [{ type: "text", text: `[image${alt ? `: ${alt}` : ""}]` }];
153
+ }
154
+ if (!node.text) return [];
155
+ const marks = [...extra, ...(node.marks ?? []).flatMap((m) => PM_MARKS[m.type] ? [PM_MARKS[m.type]] : [])];
156
+ const href = safeHref((node.marks ?? []).find((m) => m.type === "link")?.attrs?.href);
157
+ return [{ type: "text", text: node.text, ...marks.length ? { marks } : {}, ...href ? { href } : {} }];
158
+ });
159
+ const listOf = (node) => {
160
+ const items = (node.content ?? []).map((item) => {
161
+ const [first, ...rest] = item.content ?? [];
162
+ const content = inlines(first?.content);
163
+ if (item.type === "taskItem") content.unshift({ type: "text", text: TASK[item.attrs?.checked ? 1 : 0] });
164
+ let sublist;
165
+ for (const child of rest) {
166
+ if (!sublist && /^(bulletList|orderedList|taskList)$/.test(child.type)) sublist = listOf(child);
167
+ else {
168
+ warn("a list item with more than one paragraph is kept as one paragraph with line breaks");
169
+ for (const b of toBlocks(child)) content.push({ type: "break" }, ...textOfBlock(b));
170
+ }
171
+ }
172
+ return { content, ...sublist ? { sublist } : {} };
173
+ });
174
+ if (node.type === "orderedList" && Number(node.attrs?.start ?? 1) !== 1) warn("a numbered list that does not start at 1 starts at 1");
175
+ return { type: "list", ordered: node.type === "orderedList", items };
176
+ };
177
+ const textOfBlock = (b) => b.type === "list" ? b.items.flatMap((i, n) => [...n ? [{ type: "break" }] : [], ...i.content]) : b.type === "table" ? b.rows.flat(2).flatMap(textOfBlock) : b.content;
178
+ const toBlocks = (node) => {
179
+ switch (node.type) {
180
+ case "paragraph":
181
+ return [{ type: "paragraph", content: inlines(node.content) }];
182
+ case "heading": {
183
+ const level = Math.min(6, Math.max(1, Math.trunc(Number(node.attrs?.level)) || 1));
184
+ return [{ type: "heading", level, content: inlines(node.content) }];
185
+ }
186
+ case "blockquote":
187
+ warn("a quote is kept as plain paragraphs");
188
+ return (node.content ?? []).flatMap(toBlocks);
189
+ case "codeBlock": {
190
+ warn("a code block is kept as a paragraph of code text");
191
+ const lines = (node.content ?? []).map((t) => t.text ?? "").join("").split("\n");
192
+ return [{ type: "paragraph", content: lines.flatMap((line, i) => [...i ? [{ type: "break" }] : [], ...line ? [{ type: "text", text: line, marks: ["code"] }] : []]) }];
193
+ }
194
+ case "horizontalRule":
195
+ warn("a horizontal rule is left out");
196
+ return [];
197
+ case "bulletList":
198
+ case "orderedList":
199
+ case "taskList":
200
+ return [listOf(node)];
201
+ case "table": {
202
+ const rows = node.content ?? [];
203
+ const header = rows.length > 0 && (rows[0].content ?? []).length > 0 && (rows[0].content ?? []).every((c) => c.type === "tableHeader");
204
+ return [{ type: "table", ...header ? { header: true } : {}, rows: rows.map((r) => (r.content ?? []).map((c) => (c.content ?? []).flatMap(toBlocks))) }];
205
+ }
206
+ default:
207
+ warn(`a ${node.type} block is left out`);
208
+ return [];
209
+ }
210
+ };
211
+ const model = { ...title ? { title } : {}, blocks: blocks.flatMap(toBlocks) };
212
+ return { model, warnings: list() };
213
+ }
214
+ function fromTextDoc(model) {
215
+ const { warn, list } = warner();
216
+ const text = (value, marks = []) => value ? [{ type: "text", text: value, ...marks.length ? { marks } : {} }] : [];
217
+ const inlines = (nodes) => nodes.flatMap((node) => {
218
+ switch (node.type) {
219
+ case "break":
220
+ return [{ type: "hardBreak" }];
221
+ case "image":
222
+ if (!MODEL_IMAGE.test(node.mime)) {
223
+ warn("an image that is not a PNG, JPEG, or GIF is kept as its alt text");
224
+ return text(`[image${node.alt ? `: ${node.alt}` : ""}]`);
225
+ }
226
+ return [{ type: "image", attrs: { src: `data:${node.mime};base64,${toBase64(node.data)}`, alt: node.alt ?? null, title: null } }];
227
+ case "footnote":
228
+ warn("a footnote is kept as text in brackets");
229
+ return [...text(" ["), ...inlines(node.content), ...text("]")];
230
+ case "text": {
231
+ const marks = [];
232
+ for (const mark of node.marks ?? []) {
233
+ if (mark === "bold" || mark === "italic" || mark === "strike" || mark === "code") marks.push({ type: mark });
234
+ else warn(`the ${mark} mark is left out; the text stays`);
235
+ }
236
+ const href = node.href === void 0 ? void 0 : safeHref(node.href);
237
+ if (node.href !== void 0 && !href) warn("a link that is not a web, mail, or relative address is left out; the text stays");
238
+ if (href) marks.push({ type: "link", attrs: { href } });
239
+ if (marks.length > 1 && marks.some((m) => m.type === "code")) {
240
+ warn("a mark or a link on code text is left out; the text stays as code");
241
+ return text(node.text, [{ type: "code" }]);
242
+ }
243
+ return text(node.text, marks);
244
+ }
245
+ default:
246
+ return [];
247
+ }
248
+ });
249
+ const paragraph = (content) => ({ type: "paragraph", ...content.length ? { content: inlines(content) } : {} });
250
+ const toNodes = (block2) => {
251
+ switch (block2.type) {
252
+ case "heading":
253
+ return [{ type: "heading", attrs: { level: block2.level }, content: inlines(block2.content) }];
254
+ case "paragraph":
255
+ return [paragraph(block2.content)];
256
+ case "list":
257
+ return [
258
+ {
259
+ type: block2.ordered ? "orderedList" : "bulletList",
260
+ ...block2.ordered ? { attrs: { start: 1, type: null } } : {},
261
+ content: block2.items.map((item) => ({ type: "listItem", content: [paragraph(item.content), ...item.sublist ? toNodes(item.sublist) : []] }))
262
+ }
263
+ ];
264
+ case "table": {
265
+ const rows = block2.rows.filter((r) => r.length > 0);
266
+ if (rows.length === 0) return [];
267
+ return [
268
+ {
269
+ type: "table",
270
+ content: rows.map((row, i) => ({
271
+ type: "tableRow",
272
+ content: row.map((cell) => {
273
+ const content = cell.flatMap(toNodes);
274
+ return { type: block2.header && i === 0 ? "tableHeader" : "tableCell", attrs: { colspan: 1, rowspan: 1, colwidth: null }, content: content.length ? content : [{ type: "paragraph" }] };
275
+ })
276
+ }))
277
+ }
278
+ ];
279
+ }
280
+ default:
281
+ return [];
282
+ }
283
+ };
284
+ const prune = (node) => node.content ? { ...node, content: node.content.filter((n) => n.type !== "text" || n.text).map(prune) } : node;
285
+ const blocks = model.blocks.flatMap(toNodes).map(prune);
286
+ return { blocks, ...model.title ? { title: model.title } : {}, warnings: list() };
287
+ }
288
+ var formatFailure = (error) => {
289
+ if (error instanceof FormatError) throw new DocError("INVALID_INPUT", `the file was refused (${error.code}): ${error.message}`);
290
+ throw error;
291
+ };
292
+ function importFile(input, format, author, options = {}) {
293
+ checkAuthor(author);
294
+ if (!IMPORT_FORMATS.includes(format)) throw new DocError("INVALID_INPUT", `import format must be one of ${IMPORT_FORMATS.join(", ")}`);
295
+ const maxBytes = options.limits?.maxInputBytes ?? DEFAULT_LIMITS.maxInputBytes;
296
+ if (format === "md") {
297
+ let markdown;
298
+ try {
299
+ markdown = typeof input === "string" ? input : new TextDecoder("utf-8", { fatal: true }).decode(input);
300
+ } catch {
301
+ throw new DocError("INVALID_INPUT", "a md file must be UTF-8 text");
302
+ }
303
+ if (new TextEncoder().encode(markdown).length > maxBytes) throw new DocError("INVALID_INPUT", `the file is larger than ${maxBytes} bytes`);
304
+ return { doc: AgenticDoc.fromMarkdown(markdown, author, options.doc), warnings: [] };
305
+ }
306
+ if (!(input instanceof Uint8Array)) throw new DocError("INVALID_INPUT", `a ${format} file must be bytes, not text`);
307
+ let read;
308
+ try {
309
+ read = format === "odt" ? readOdt(input, options.limits) : readDocx(input, options.limits);
310
+ } catch (error) {
311
+ return formatFailure(error);
312
+ }
313
+ const mapped = fromTextDoc(read.model);
314
+ return {
315
+ doc: AgenticDoc.fromBlocks(mapped.blocks, author, options.doc),
316
+ ...mapped.title ? { title: mapped.title } : {},
317
+ warnings: [...read.warnings, ...mapped.warnings]
318
+ };
319
+ }
320
+ var ODT_MIME = CONTENT_TYPES.odt;
321
+ function exportOdt(blocks) {
322
+ try {
323
+ return writeOdt(toTextDoc(blocks).model).bytes;
324
+ } catch (error) {
325
+ return formatFailure(error);
326
+ }
327
+ }
328
+ function exportFile(doc, format, options = {}) {
329
+ if (!FILE_FORMATS.includes(format)) throw new DocError("INVALID_INPUT", `export format must be one of ${FILE_FORMATS.join(", ")}`);
330
+ if (format === "md" || format === "html") return { format, contentType: CONTENT_TYPES[format], data: exportText(doc, format, options), warnings: [] };
331
+ const { model, warnings } = toTextDoc(doc.toJSON().content ?? [], options.title);
332
+ let written;
333
+ try {
334
+ written = format === "odt" ? writeOdt(model) : writeDocx(model);
335
+ } catch (error) {
336
+ return formatFailure(error);
337
+ }
338
+ return { format, contentType: CONTENT_TYPES[format], data: written.bytes, warnings: [...warnings, ...written.warnings] };
339
+ }
340
+
341
+ export {
342
+ EXPORT_FORMATS,
343
+ exportText,
344
+ toBase64,
345
+ IMPORT_FORMATS,
346
+ FILE_FORMATS,
347
+ DOCX_MIME,
348
+ toTextDoc,
349
+ fromTextDoc,
350
+ importFile,
351
+ ODT_MIME,
352
+ exportOdt,
353
+ exportFile
354
+ };
@@ -1,15 +1,17 @@
1
+ import {
2
+ commands
3
+ } from "./chunk-6UYJV2EJ.js";
1
4
  import {
2
5
  DocsService,
3
6
  MAX_LIVE_BYTES
4
- } from "./chunk-FZA5LQV3.js";
5
- import {
6
- commands
7
- } from "./chunk-GYQFTF2E.js";
7
+ } from "./chunk-JBAQEIOH.js";
8
8
  import {
9
9
  DocError,
10
- PRINCIPAL_KINDS,
11
10
  parseAuthor
12
- } from "./chunk-YBBQNCMC.js";
11
+ } from "./chunk-GQ5E6T7Z.js";
12
+ import {
13
+ PRINCIPAL_KINDS
14
+ } from "./chunk-DWVJIKVT.js";
13
15
 
14
16
  // src/worker.ts
15
17
  import { Hono } from "hono";
@@ -104,6 +106,22 @@ async function readCapped(request, cap) {
104
106
  }
105
107
  return whole;
106
108
  }
109
+ var WS_PROTOCOL = "agentic-docs";
110
+ var WS_BEARER = `${WS_PROTOCOL}.bearer.`;
111
+ function socketBearer(c) {
112
+ if (c.req.header("upgrade") !== "websocket" || !c.req.path.endsWith("/ws")) return void 0;
113
+ for (const offered of (c.req.header("sec-websocket-protocol") ?? "").split(",")) {
114
+ const part = offered.trim();
115
+ if (!part.startsWith(WS_BEARER)) continue;
116
+ try {
117
+ const base64 = part.slice(WS_BEARER.length).replace(/-/g, "+").replace(/_/g, "/");
118
+ return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(atob(base64), (ch) => ch.charCodeAt(0)));
119
+ } catch {
120
+ return void 0;
121
+ }
122
+ }
123
+ return void 0;
124
+ }
107
125
  var DOC_ID = /^[a-z0-9][a-z0-9_-]{0,63}$/;
108
126
  function createApp(source, options = {}) {
109
127
  const app2 = new Hono();
@@ -113,7 +131,7 @@ function createApp(source, options = {}) {
113
131
  });
114
132
  app2.get("/health", (c) => c.json({ ok: true, pack: "agentic-documents" }));
115
133
  app2.use("/api/v1/*", async (c, next) => {
116
- const bearer = /^Bearer (.+)$/.exec(c.req.header("authorization") ?? "")?.[1];
134
+ const bearer = /^Bearer (.+)$/.exec(c.req.header("authorization") ?? "")?.[1] ?? socketBearer(c);
117
135
  const identity = bearer === void 0 ? void 0 : identityFor(c.env?.AGENTIC_DOCS_TOKENS, bearer);
118
136
  if (!identity) return fail("UNAUTHENTICATED", "send a valid bearer token", 401);
119
137
  c.set("author", identity.principal);
@@ -130,23 +148,28 @@ function createApp(source, options = {}) {
130
148
  const { contributors } = await options.live(c.env ?? {}).meta(c.req.param("id"));
131
149
  return c.json({ contributors: contributors.map(({ id, kind, machine }) => ({ name: id, kind, machine })) });
132
150
  });
133
- app2.get("/api/v1/docs/:id/ws", (c) => {
151
+ app2.get("/api/v1/docs/:id/ws", async (c) => {
134
152
  const id = c.req.param("id");
135
153
  if (!options.live) return fail("NOT_FOUND", "this server hosts no live rooms", 404);
136
154
  if (c.req.header("upgrade") !== "websocket") return fail("UPGRADE_REQUIRED", "open this route as a WebSocket", 426);
137
155
  if (!DOC_ID.test(id)) return fail("INVALID_INPUT", `invalid document id "${id}"`, 400);
138
156
  const { kind, name } = c.get("author");
139
- return options.live(c.env ?? {}).connect(id, c.req.raw, { principal: { id: name, kind }, role: c.get("role"), name });
157
+ const opened = options.live(c.env ?? {}).connect(id, c.req.raw, { principal: { id: name, kind }, role: c.get("role"), name });
158
+ if (!(c.req.header("sec-websocket-protocol") ?? "").split(",").some((p) => p.trim() === WS_PROTOCOL)) return opened;
159
+ const room = await opened;
160
+ const socket = room.webSocket;
161
+ return socket ? new Response(null, { status: 101, webSocket: socket, headers: new Headers([...room.headers, ["sec-websocket-protocol", WS_PROTOCOL]]) }) : room;
140
162
  });
141
163
  async function run(c, command, input) {
142
164
  try {
143
165
  if (!command.readOnly && c.get("role") === "viewer") throw new DocError("FORBIDDEN", "this token is a viewer; it cannot change a document");
144
166
  const result = await command.run(await (typeof source === "function" ? source(c.env ?? {}) : source), input, c.get("author"));
145
167
  if (command.name === "export") {
146
- const { data, contentType, encoding, format } = result;
168
+ const { data, contentType, encoding, format, warnings } = result;
147
169
  const images = input.remoteImages === true || input.remoteImages === "true" ? "data: https:" : "data:";
148
170
  const disposition = format === "odt" ? { "content-disposition": `attachment; filename="${input.id}.odt"` } : {};
149
- const headers = { ...disposition, "content-type": contentType, "content-security-policy": `default-src 'none'; style-src 'unsafe-inline'; img-src ${images}; sandbox` };
171
+ const lost = warnings.length ? { "x-export-warnings": JSON.stringify(warnings).replace(/[^\x20-\x7e]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`) } : {};
172
+ const headers = { ...disposition, ...lost, "content-type": contentType, "content-security-policy": `default-src 'none'; style-src 'unsafe-inline'; img-src ${images}; sandbox` };
150
173
  return encoding === "base64" ? c.body(Uint8Array.from(atob(data), (ch) => ch.charCodeAt(0)), 200, headers) : c.body(data, 200, headers);
151
174
  }
152
175
  if (typeof result === "string") return c.body(result, 200, { "content-type": "text/markdown; charset=utf-8" });
@@ -171,12 +194,14 @@ function createApp(source, options = {}) {
171
194
  app2.post("/api/v1/docs/:id/checkpoint", async (c) => {
172
195
  if (c.get("role") === "viewer") throw new DocError("FORBIDDEN", "this token is a viewer; it cannot change a document");
173
196
  const update = await readCapped(c.req.raw, MAX_LIVE_BYTES);
174
- await (await serviceOf(c)).checkpoint(c.req.param("id"), update, c.get("author"));
197
+ await (await serviceOf(c)).checkpoint(c.req.param("id"), update, c.get("author"), { create: c.req.query("create") !== "false" });
175
198
  return c.json({ ok: true });
176
199
  });
177
200
  const create = commands.find((c) => c.name === "new");
201
+ const listing = commands.find((c) => c.name === "list");
178
202
  app2.post("/api/v1/docs", async (c) => run(c, create, await body(c)));
179
- for (const command of commands.filter((c) => c !== create)) {
203
+ app2.get("/api/v1/docs", (c) => run(c, listing, {}));
204
+ for (const command of commands.filter((c) => c !== create && c !== listing)) {
180
205
  const path = `/api/v1/docs/:id/${command.name}`;
181
206
  if (command.readOnly) app2.get(path, (c) => run(c, command, { ...c.req.query(), id: c.req.param("id") }));
182
207
  else app2.post(path, async (c) => run(c, command, { ...await body(c), id: c.req.param("id") }));
package/dist/cli.js CHANGED
@@ -1,25 +1,28 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ VERSION
4
+ } from "./chunk-5Y66UCTE.js";
2
5
  import {
3
6
  createFileBlobs,
4
7
  createFileStore
5
- } from "./chunk-TFCRPEEM.js";
8
+ } from "./chunk-34C47PDC.js";
6
9
  import {
7
10
  LiveService,
8
11
  whoAmI
9
- } from "./chunk-R5XUTBC3.js";
10
- import {
11
- DocsService
12
- } from "./chunk-FZA5LQV3.js";
13
- import {
14
- VERSION
15
- } from "./chunk-5Y66UCTE.js";
12
+ } from "./chunk-CN6UAW7B.js";
16
13
  import {
17
14
  commands
18
- } from "./chunk-GYQFTF2E.js";
15
+ } from "./chunk-6UYJV2EJ.js";
16
+ import {
17
+ DocsService
18
+ } from "./chunk-JBAQEIOH.js";
19
+ import "./chunk-5IQX2W52.js";
20
+ import "./chunk-KC5KFB5R.js";
19
21
  import {
20
22
  DocError,
21
23
  parseAuthor
22
- } from "./chunk-YBBQNCMC.js";
24
+ } from "./chunk-GQ5E6T7Z.js";
25
+ import "./chunk-DWVJIKVT.js";
23
26
 
24
27
  // src/cli.ts
25
28
  import { readFileSync, writeFileSync } from "fs";
@@ -118,7 +121,7 @@ async function main(argv, env = process.env) {
118
121
  author = named && scopes?.length ? { ...named, scopes } : named;
119
122
  }
120
123
  if (name === "mcp") {
121
- const { startMcp } = await import("./mcp-P2C75IIG.js");
124
+ const { startMcp } = await import("./mcp-ZTUTC4CS.js");
122
125
  await startMcp(service, author);
123
126
  return 0;
124
127
  }
@@ -138,7 +141,8 @@ async function main(argv, env = process.env) {
138
141
  }
139
142
  const result = await command.run(service, input, author);
140
143
  if (name === "export") {
141
- const { data, encoding } = result;
144
+ const { data, encoding, warnings } = result;
145
+ for (const line of warnings) console.error(`warning: ${line}`);
142
146
  const bytes = encoding === "base64" ? Buffer.from(data, "base64") : data;
143
147
  if (out === void 0) process.stdout.write(bytes);
144
148
  else writeFileSync(out, bytes);
package/dist/client.d.ts CHANGED
@@ -1,7 +1,10 @@
1
- import { P as PrincipalKind, S as Section, E as EditEntry } from './doc-C_8kCsl7.js';
2
- export { A as Anchor } from './doc-C_8kCsl7.js';
3
- import { D as DocErrorCode, S as Suggestion, T as Thread, E as ExportFormat } from './review-BQW2Ci2h.js';
1
+ import { e as PrincipalKind, S as Section, E as EditEntry } from './doc-BXG9qG5X.js';
2
+ export { a as Anchor } from './doc-BXG9qG5X.js';
3
+ import { E as ExportFormat } from './render-463xHb-x.js';
4
+ import { a as DocErrorCode, S as Suggestion, T as Thread } from './review-Ds-i5Vnc.js';
5
+ import { d as DocSummary } from './service-Ck6oX-Kq.js';
4
6
  import 'yjs';
7
+ import 'agentic-identity';
5
8
 
6
9
  /** The place an edit goes: a section id, or exact text. */
7
10
  type Where = {
@@ -69,6 +72,18 @@ declare function createClient(options: ClientOptions): {
69
72
  }, call?: CallOptions) => Promise<{
70
73
  id: string;
71
74
  }>;
75
+ /** `GET /api/v1/docs`: documents with titles, creators, and last edits. */
76
+ list: (call?: CallOptions) => Promise<DocSummary[]>;
77
+ /** `POST /rename`: give a document a title. */
78
+ rename: (id: string, input: {
79
+ title: string;
80
+ }, call?: CallOptions) => Promise<{
81
+ ok: true;
82
+ }>;
83
+ /** `POST /remove`: delete a document. An agent needs the `docs:remove` scope. */
84
+ remove: (id: string, call?: CallOptions) => Promise<{
85
+ ok: true;
86
+ }>;
72
87
  /** `GET /read`: the whole document, or one section, as Markdown. */
73
88
  read(id: string, input?: {
74
89
  section?: string;
@@ -161,11 +176,13 @@ declare function createClient(options: ClientOptions): {
161
176
  }>;
162
177
  /** `GET /state`: the stored document as a Yjs update. */
163
178
  state(id: string, call?: CallOptions): Promise<Uint8Array>;
164
- /** `POST /checkpoint`: merge a Yjs update into the stored document. */
165
- checkpoint(id: string, update: Uint8Array, call?: CallOptions): Promise<{
179
+ /** `POST /checkpoint`: merge a Yjs update into the stored document. It makes the document when there is none, unless `create` is false: then it fails with `NO_DOCUMENT`. */
180
+ checkpoint(id: string, update: Uint8Array, call?: CallOptions & {
181
+ create?: boolean;
182
+ }): Promise<{
166
183
  ok: true;
167
184
  }>;
168
185
  };
169
186
  type DocsClient = ReturnType<typeof createClient>;
170
187
 
171
- export { type CallOptions, type ClientOptions, type Contributor, DocsApiError, type DocsApiErrorCode, type DocsClient, EditEntry, ExportFormat, type Me, Section, Suggestion, Thread, type Where, createClient };
188
+ export { type CallOptions, type ClientOptions, type Contributor, DocSummary, DocsApiError, type DocsApiErrorCode, type DocsClient, EditEntry, ExportFormat, type Me, Section, Suggestion, Thread, type Where, createClient };
package/dist/client.js CHANGED
@@ -72,6 +72,12 @@ function createClient(options) {
72
72
  me: (call) => get("/api/v1/me", {}, (v) => isObject(v) && typeof v.kind === "string" && typeof v.name === "string" && Array.isArray(v.scopes) && (v.role === "editor" || v.role === "viewer"), "the token identity", call),
73
73
  /** `POST /api/v1/docs`. */
74
74
  create: (input, call) => withKey("/api/v1/docs", input, "id", call),
75
+ /** `GET /api/v1/docs`: documents with titles, creators, and last edits. */
76
+ list: (call) => list("/api/v1/docs", {}, call),
77
+ /** `POST /rename`: give a document a title. */
78
+ rename: (id, input, call) => ok(`${doc(id)}/rename`, input, call),
79
+ /** `POST /remove`: delete a document. An agent needs the `docs:remove` scope. */
80
+ remove: (id, call) => ok(`${doc(id)}/remove`, {}, call),
75
81
  /** `GET /read`: the whole document, or one section, as Markdown. */
76
82
  async read(id, input = {}, call) {
77
83
  return (await send(`${doc(id)}/read`, { method: "GET", query: input }, call, "text/markdown")).text();
@@ -102,9 +108,10 @@ function createClient(options) {
102
108
  async state(id, call) {
103
109
  return new Uint8Array(await (await send(`${doc(id)}/state`, { method: "GET" }, call, "application/octet-stream")).arrayBuffer());
104
110
  },
105
- /** `POST /checkpoint`: merge a Yjs update into the stored document. */
111
+ /** `POST /checkpoint`: merge a Yjs update into the stored document. It makes the document when there is none, unless `create` is false: then it fails with `NO_DOCUMENT`. */
106
112
  async checkpoint(id, update, call) {
107
- return await json(await send(`${doc(id)}/checkpoint`, { method: "POST", body: update }, call), isOk, "{ ok: true }");
113
+ const query = call?.create === false ? { create: false } : void 0;
114
+ return await json(await send(`${doc(id)}/checkpoint`, { method: "POST", body: update, query }, call), isOk, "{ ok: true }");
108
115
  }
109
116
  };
110
117
  }
@@ -18,6 +18,7 @@ declare function blocksToMarkdown(nodes: PmNode[]): string;
18
18
 
19
19
  declare const PRINCIPAL_KINDS: readonly ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
20
20
  type PrincipalKind = (typeof PRINCIPAL_KINDS)[number];
21
+
21
22
  /** Who made a change. Every mutation needs one, so machine-made work stays labeled. */
22
23
  interface Principal {
23
24
  kind: PrincipalKind;
@@ -51,13 +52,16 @@ interface Section {
51
52
  title: string;
52
53
  }
53
54
  interface EditEntry {
54
- op: "create" | "append" | "insert" | "replace" | "delete";
55
+ /** `live` ties the editor client of an agent to that agent, so the text the client types is marked as its work. Its counts stay 0. */
56
+ op: "create" | "append" | "insert" | "replace" | "delete" | "live";
55
57
  at: string;
56
58
  author: Principal;
57
59
  /** Counts of characters (text targets) or blocks (section targets). Never content. */
58
60
  inserted: number;
59
61
  deleted: number;
60
62
  section?: string;
63
+ /** The Yjs client id of the replica that made the edit. Text it wrote carries this id, so a UI can mark text by author kind. Written by the client, like the rest of the log. */
64
+ client?: number;
61
65
  /** Set when an accepted suggestion caused the edit. `author` is who suggested it. */
62
66
  suggestion?: {
63
67
  id: string;
@@ -87,6 +91,8 @@ declare class AgenticDoc {
87
91
  readonly now: () => Date;
88
92
  constructor(ydoc?: Y.Doc, options?: DocOptions);
89
93
  static fromMarkdown(markdown: string, author: Principal, options?: DocOptions): AgenticDoc;
94
+ /** Make a document from blocks in ProseMirror JSON form. Blocks that the schema does not allow fail with INVALID_INPUT. */
95
+ static fromBlocks(content: PmNode[], author: Principal, options?: DocOptions): AgenticDoc;
90
96
  static fromUpdate(update: Uint8Array, options?: DocOptions): AgenticDoc;
91
97
  encode(): Uint8Array;
92
98
  applyUpdate(update: Uint8Array): void;
@@ -123,4 +129,4 @@ declare class AgenticDoc {
123
129
  append(markdown: string, author: Principal): void;
124
130
  }
125
131
 
126
- export { type Anchor as A, type DocOptions as D, type EditEntry as E, type PrincipalKind as P, type Section as S, type Target as T, type Principal as a, type PmNode as b, AgenticDoc as c, type AnchorTarget as d, PRINCIPAL_KINDS as e, type SectionTarget as f, type TextTarget as g, blocksToMarkdown as h, markdownToBlocks as m, parseAuthor as p };
132
+ export { AgenticDoc as A, type DocOptions as D, type EditEntry as E, type Principal as P, type Section as S, type Target as T, type Anchor as a, type AnchorTarget as b, PRINCIPAL_KINDS as c, type PmNode as d, type PrincipalKind as e, type SectionTarget as f, type TextTarget as g, blocksToMarkdown as h, markdownToBlocks as m, parseAuthor as p };