@filipc77/cowrite 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.
@@ -0,0 +1,581 @@
1
+ #!/usr/bin/env node
2
+
3
+ // bin/cowrite.ts
4
+ import { parseArgs } from "util";
5
+ import { resolve as resolve5 } from "path";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+
8
+ // src/comment-store.ts
9
+ import { EventEmitter } from "events";
10
+ import { readFile, writeFile } from "fs/promises";
11
+ import { join, resolve } from "path";
12
+ import { randomUUID } from "crypto";
13
+ import { watch as chokidarWatch } from "chokidar";
14
+ var PERSIST_FILE = ".cowrite-comments.json";
15
+ var CommentStore = class extends EventEmitter {
16
+ comments = /* @__PURE__ */ new Map();
17
+ persistPath;
18
+ lastWriteTime = 0;
19
+ watcher = null;
20
+ constructor(projectDir) {
21
+ super();
22
+ this.persistPath = join(resolve(projectDir), PERSIST_FILE);
23
+ }
24
+ async load() {
25
+ try {
26
+ const data = await readFile(this.persistPath, "utf-8");
27
+ const arr = JSON.parse(data);
28
+ for (const c of arr) {
29
+ this.comments.set(c.id, c);
30
+ }
31
+ } catch {
32
+ }
33
+ }
34
+ async persist() {
35
+ this.lastWriteTime = Date.now();
36
+ const arr = Array.from(this.comments.values());
37
+ await writeFile(this.persistPath, JSON.stringify(arr, null, 2), "utf-8");
38
+ }
39
+ async reload() {
40
+ try {
41
+ const data = await readFile(this.persistPath, "utf-8");
42
+ const arr = JSON.parse(data);
43
+ this.comments.clear();
44
+ for (const c of arr) {
45
+ this.comments.set(c.id, c);
46
+ }
47
+ this.emit("change", null);
48
+ } catch {
49
+ this.comments.clear();
50
+ this.emit("change", null);
51
+ }
52
+ }
53
+ async startWatching() {
54
+ if (this.watcher) return;
55
+ this.watcher = chokidarWatch(this.persistPath, {
56
+ ignoreInitial: true,
57
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
58
+ });
59
+ this.watcher.on("change", async () => {
60
+ if (Date.now() - this.lastWriteTime < 200) return;
61
+ await this.reload();
62
+ });
63
+ this.watcher.on("add", async () => {
64
+ if (Date.now() - this.lastWriteTime < 200) return;
65
+ await this.reload();
66
+ });
67
+ }
68
+ async stopWatching() {
69
+ if (this.watcher) {
70
+ await this.watcher.close();
71
+ this.watcher = null;
72
+ }
73
+ }
74
+ add(params) {
75
+ const comment = {
76
+ id: randomUUID(),
77
+ file: params.file,
78
+ offset: params.offset,
79
+ length: params.length,
80
+ selectedText: params.selectedText,
81
+ comment: params.comment,
82
+ status: "pending",
83
+ replies: [],
84
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
85
+ resolvedAt: null
86
+ };
87
+ this.comments.set(comment.id, comment);
88
+ this.emit("change", comment);
89
+ this.persist().catch((err) => process.stderr.write(`Persist error: ${err}
90
+ `));
91
+ return comment;
92
+ }
93
+ resolve(commentId) {
94
+ const comment = this.comments.get(commentId);
95
+ if (!comment) return null;
96
+ comment.status = "resolved";
97
+ comment.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
98
+ this.emit("change", comment);
99
+ this.persist().catch((err) => process.stderr.write(`Persist error: ${err}
100
+ `));
101
+ return comment;
102
+ }
103
+ addReply(commentId, from, text) {
104
+ const comment = this.comments.get(commentId);
105
+ if (!comment) return null;
106
+ const reply = {
107
+ id: randomUUID(),
108
+ from,
109
+ text,
110
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
111
+ };
112
+ comment.replies.push(reply);
113
+ this.emit("change", comment);
114
+ this.persist().catch((err) => process.stderr.write(`Persist error: ${err}
115
+ `));
116
+ return reply;
117
+ }
118
+ get(commentId) {
119
+ return this.comments.get(commentId) ?? null;
120
+ }
121
+ getAll(filter) {
122
+ let results = Array.from(this.comments.values());
123
+ if (filter?.file) {
124
+ results = results.filter((c) => c.file === filter.file);
125
+ }
126
+ if (filter?.status && filter.status !== "all") {
127
+ results = results.filter((c) => c.status === filter.status);
128
+ }
129
+ return results.sort((a, b) => a.offset - b.offset);
130
+ }
131
+ getForFile(file) {
132
+ return this.getAll({ file });
133
+ }
134
+ /** Adjust comment offsets when file content changes */
135
+ adjustOffsets(file, oldContent, newContent) {
136
+ const fileComments = this.getForFile(file);
137
+ if (fileComments.length === 0) return;
138
+ for (const comment of fileComments) {
139
+ const searchStart = Math.max(0, comment.offset - 200);
140
+ const searchEnd = Math.min(newContent.length, comment.offset + comment.length + 200);
141
+ const searchRegion = newContent.slice(searchStart, searchEnd);
142
+ const idx = searchRegion.indexOf(comment.selectedText);
143
+ if (idx !== -1) {
144
+ comment.offset = searchStart + idx;
145
+ }
146
+ }
147
+ this.emit("change", null);
148
+ this.persist().catch((err) => process.stderr.write(`Persist error: ${err}
149
+ `));
150
+ }
151
+ clear() {
152
+ this.comments.clear();
153
+ this.emit("change", null);
154
+ this.persist().catch((err) => process.stderr.write(`Persist error: ${err}
155
+ `));
156
+ }
157
+ };
158
+
159
+ // src/mcp-server.ts
160
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
161
+ import { z } from "zod";
162
+
163
+ // src/utils.ts
164
+ import { marked } from "marked";
165
+ function renderToHtml(content, filePath) {
166
+ const isMarkdown = /\.(md|markdown|mdx)$/i.test(filePath);
167
+ if (isMarkdown) {
168
+ return renderMarkdownWithOffsets(content);
169
+ }
170
+ return renderPlainTextWithOffsets(content);
171
+ }
172
+ function renderMarkdownWithOffsets(content) {
173
+ const html = marked.parse(content, { async: false });
174
+ return `<div class="markdown-body" data-source-length="${content.length}">${html}</div>`;
175
+ }
176
+ function renderPlainTextWithOffsets(content) {
177
+ const lines = content.split("\n");
178
+ let offset = 0;
179
+ const htmlLines = [];
180
+ for (const line of lines) {
181
+ const escaped = escapeHtml(line);
182
+ htmlLines.push(`<span class="line" data-offset="${offset}" data-length="${line.length}">${escaped}</span>`);
183
+ offset += line.length + 1;
184
+ }
185
+ return `<pre class="plain-text" data-source-length="${content.length}">${htmlLines.join("\n")}</pre>`;
186
+ }
187
+ function escapeHtml(text) {
188
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
189
+ }
190
+ function annotateFileWithComments(content, comments) {
191
+ const sorted = [...comments].sort((a, b) => b.offset - a.offset);
192
+ let result = content;
193
+ for (const c of sorted) {
194
+ const marker = `[COMMENT #${c.id.slice(0, 8)}: "${c.comment}"]`;
195
+ const end = c.offset + c.length;
196
+ result = result.slice(0, end) + " " + marker + result.slice(end);
197
+ }
198
+ return result;
199
+ }
200
+
201
+ // src/mcp-server.ts
202
+ import { readFile as readFile2 } from "fs/promises";
203
+ import { resolve as resolve2 } from "path";
204
+ function createMcpServer(store, projectDir) {
205
+ const server = new McpServer({
206
+ name: "cowrite",
207
+ version: "0.1.0"
208
+ });
209
+ server.tool(
210
+ "get_pending_comments",
211
+ "Get comments from the live preview. Returns unresolved comments by default.",
212
+ {
213
+ file: z.string().optional().describe("Filter by file path"),
214
+ status: z.enum(["pending", "resolved", "all"]).optional().describe("Filter by status (default: pending)")
215
+ },
216
+ async ({ file, status }) => {
217
+ const filter = {};
218
+ if (file) filter.file = resolve2(projectDir, file);
219
+ filter.status = status ?? "pending";
220
+ const comments = store.getAll(filter);
221
+ return {
222
+ content: [
223
+ {
224
+ type: "text",
225
+ text: comments.length === 0 ? "No comments found." : JSON.stringify(comments, null, 2)
226
+ }
227
+ ]
228
+ };
229
+ }
230
+ );
231
+ server.tool(
232
+ "resolve_comment",
233
+ "Mark a comment as resolved/addressed.",
234
+ {
235
+ commentId: z.string().describe("The comment ID to resolve")
236
+ },
237
+ async ({ commentId }) => {
238
+ const comment = store.resolve(commentId);
239
+ if (!comment) {
240
+ return {
241
+ content: [{ type: "text", text: `Comment ${commentId} not found.` }],
242
+ isError: true
243
+ };
244
+ }
245
+ return {
246
+ content: [{ type: "text", text: `Comment ${commentId} resolved.` }]
247
+ };
248
+ }
249
+ );
250
+ server.tool(
251
+ "reply_to_comment",
252
+ "Reply to a comment from the agent.",
253
+ {
254
+ commentId: z.string().describe("The comment ID to reply to"),
255
+ reply: z.string().describe("The reply text")
256
+ },
257
+ async ({ commentId, reply }) => {
258
+ const replyObj = store.addReply(commentId, "agent", reply);
259
+ if (!replyObj) {
260
+ return {
261
+ content: [{ type: "text", text: `Comment ${commentId} not found.` }],
262
+ isError: true
263
+ };
264
+ }
265
+ return {
266
+ content: [{ type: "text", text: `Reply added to comment ${commentId}.` }]
267
+ };
268
+ }
269
+ );
270
+ server.tool(
271
+ "get_file_with_annotations",
272
+ "Get file content with inline comment markers showing where comments are anchored.",
273
+ {
274
+ file: z.string().describe("File path to annotate")
275
+ },
276
+ async ({ file }) => {
277
+ const filePath = resolve2(projectDir, file);
278
+ try {
279
+ const content = await readFile2(filePath, "utf-8");
280
+ const comments = store.getForFile(filePath);
281
+ const annotated = annotateFileWithComments(content, comments);
282
+ return {
283
+ content: [{ type: "text", text: annotated }]
284
+ };
285
+ } catch (err) {
286
+ return {
287
+ content: [{ type: "text", text: `Error reading file: ${err}` }],
288
+ isError: true
289
+ };
290
+ }
291
+ }
292
+ );
293
+ server.resource(
294
+ "all-comments",
295
+ "cowrite://comments",
296
+ { description: "Live list of all comments", mimeType: "application/json" },
297
+ async () => {
298
+ const comments = store.getAll();
299
+ return {
300
+ contents: [
301
+ {
302
+ uri: "cowrite://comments",
303
+ mimeType: "application/json",
304
+ text: JSON.stringify(comments, null, 2)
305
+ }
306
+ ]
307
+ };
308
+ }
309
+ );
310
+ store.on("change", () => {
311
+ server.server.notification({
312
+ method: "notifications/resources/updated",
313
+ params: { uri: "cowrite://comments" }
314
+ }).catch(() => {
315
+ });
316
+ });
317
+ return server;
318
+ }
319
+
320
+ // src/preview-server.ts
321
+ import { createServer } from "http";
322
+ import { readFile as readFile3 } from "fs/promises";
323
+ import { join as join2 } from "path";
324
+ import { WebSocketServer } from "ws";
325
+ var UI_DIR = join2(import.meta.dirname ?? new URL(".", import.meta.url).pathname, "..", "ui");
326
+ var MIME_TYPES = {
327
+ ".html": "text/html",
328
+ ".css": "text/css",
329
+ ".js": "application/javascript"
330
+ };
331
+ function createPreviewServer(store, watcher, port) {
332
+ const clients = /* @__PURE__ */ new Set();
333
+ const httpServer = createServer(async (req, res) => {
334
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
335
+ const pathname = url.pathname === "/" ? "/index.html" : url.pathname;
336
+ const ext = pathname.slice(pathname.lastIndexOf("."));
337
+ const mimeType = MIME_TYPES[ext];
338
+ if (mimeType) {
339
+ try {
340
+ let uiDir;
341
+ if (import.meta.dirname) {
342
+ uiDir = join2(import.meta.dirname, "..", "ui");
343
+ } else {
344
+ uiDir = join2(new URL(".", import.meta.url).pathname, "..", "ui");
345
+ }
346
+ const filePath = join2(uiDir, pathname);
347
+ const content = await readFile3(filePath, "utf-8");
348
+ res.writeHead(200, { "Content-Type": mimeType });
349
+ res.end(content);
350
+ return;
351
+ } catch {
352
+ }
353
+ }
354
+ if (pathname === "/api/state") {
355
+ const fileContent = watcher.getContent();
356
+ const html = renderToHtml(fileContent, watcher.getFilePath());
357
+ const comments = store.getForFile(watcher.getFilePath());
358
+ res.writeHead(200, { "Content-Type": "application/json" });
359
+ res.end(JSON.stringify({
360
+ file: watcher.getFilePath(),
361
+ content: fileContent,
362
+ html,
363
+ comments
364
+ }));
365
+ return;
366
+ }
367
+ res.writeHead(404, { "Content-Type": "text/plain" });
368
+ res.end("Not found");
369
+ });
370
+ const wss = new WebSocketServer({ server: httpServer });
371
+ wss.on("connection", (ws) => {
372
+ clients.add(ws);
373
+ const fileContent = watcher.getContent();
374
+ const html = renderToHtml(fileContent, watcher.getFilePath());
375
+ const comments = store.getForFile(watcher.getFilePath());
376
+ send(ws, { type: "file_update", file: watcher.getFilePath(), content: fileContent, html });
377
+ send(ws, { type: "comments_update", comments });
378
+ ws.on("message", (data) => {
379
+ try {
380
+ const msg = JSON.parse(data.toString());
381
+ handleClientMessage(msg);
382
+ } catch (err) {
383
+ send(ws, { type: "error", message: `Invalid message: ${err}` });
384
+ }
385
+ });
386
+ ws.on("close", () => {
387
+ clients.delete(ws);
388
+ });
389
+ });
390
+ function handleClientMessage(msg) {
391
+ switch (msg.type) {
392
+ case "comment_add":
393
+ store.add({
394
+ file: watcher.getFilePath(),
395
+ offset: msg.offset,
396
+ length: msg.length,
397
+ selectedText: msg.selectedText,
398
+ comment: msg.comment
399
+ });
400
+ break;
401
+ case "comment_reply":
402
+ store.addReply(msg.commentId, "user", msg.text);
403
+ break;
404
+ case "comment_resolve":
405
+ store.resolve(msg.commentId);
406
+ break;
407
+ }
408
+ }
409
+ store.on("change", () => {
410
+ const comments = store.getForFile(watcher.getFilePath());
411
+ broadcast({ type: "comments_update", comments });
412
+ });
413
+ watcher.on("change", (event) => {
414
+ store.adjustOffsets(event.file, event.oldContent, event.content);
415
+ const html = renderToHtml(event.content, event.file);
416
+ broadcast({ type: "file_update", file: event.file, content: event.content, html });
417
+ });
418
+ function send(ws, msg) {
419
+ if (ws.readyState === ws.OPEN) {
420
+ ws.send(JSON.stringify(msg));
421
+ }
422
+ }
423
+ function broadcast(msg) {
424
+ for (const client of clients) {
425
+ send(client, msg);
426
+ }
427
+ }
428
+ return {
429
+ start: () => new Promise((resolve6) => {
430
+ httpServer.listen(port, () => {
431
+ process.stderr.write(`Cowrite preview server running at http://localhost:${port}
432
+ `);
433
+ resolve6();
434
+ });
435
+ }),
436
+ stop: () => new Promise((resolvePromise, reject) => {
437
+ for (const client of clients) {
438
+ client.close();
439
+ }
440
+ httpServer.close((err) => {
441
+ if (err) reject(err);
442
+ else resolvePromise();
443
+ });
444
+ })
445
+ };
446
+ }
447
+
448
+ // src/file-watcher.ts
449
+ import { watch } from "chokidar";
450
+ import { readFile as readFile4 } from "fs/promises";
451
+ import { resolve as resolve4 } from "path";
452
+ import { EventEmitter as EventEmitter2 } from "events";
453
+ var FileWatcher = class extends EventEmitter2 {
454
+ watcher = null;
455
+ filePath;
456
+ lastContent = "";
457
+ constructor(filePath) {
458
+ super();
459
+ this.filePath = resolve4(filePath);
460
+ }
461
+ async start() {
462
+ this.lastContent = await readFile4(this.filePath, "utf-8");
463
+ this.watcher = watch(this.filePath, {
464
+ persistent: true,
465
+ ignoreInitial: true,
466
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
467
+ });
468
+ this.watcher.on("change", async () => {
469
+ try {
470
+ const newContent = await readFile4(this.filePath, "utf-8");
471
+ if (newContent !== this.lastContent) {
472
+ const oldContent = this.lastContent;
473
+ this.lastContent = newContent;
474
+ this.emit("change", {
475
+ file: this.filePath,
476
+ content: newContent,
477
+ oldContent
478
+ });
479
+ }
480
+ } catch (err) {
481
+ process.stderr.write(`File watch read error: ${err}
482
+ `);
483
+ }
484
+ });
485
+ return this.lastContent;
486
+ }
487
+ getContent() {
488
+ return this.lastContent;
489
+ }
490
+ getFilePath() {
491
+ return this.filePath;
492
+ }
493
+ async stop() {
494
+ if (this.watcher) {
495
+ await this.watcher.close();
496
+ this.watcher = null;
497
+ }
498
+ }
499
+ };
500
+
501
+ // bin/cowrite.ts
502
+ var USAGE = `
503
+ cowrite \u2014 Live commenting plugin for coding agent sessions
504
+
505
+ Usage:
506
+ cowrite preview <file> [--port N] Open browser preview + start MCP server
507
+ cowrite serve MCP-only mode (stdio, no preview)
508
+
509
+ Options:
510
+ --port, -p Port for preview server (default: 3377)
511
+ --help, -h Show this help
512
+ `;
513
+ async function main() {
514
+ const { values, positionals } = parseArgs({
515
+ args: process.argv.slice(2),
516
+ options: {
517
+ port: { type: "string", short: "p", default: "3377" },
518
+ help: { type: "boolean", short: "h", default: false }
519
+ },
520
+ allowPositionals: true,
521
+ strict: false
522
+ });
523
+ if (values.help || positionals.length === 0) {
524
+ process.stderr.write(USAGE);
525
+ process.exit(positionals.length === 0 && !values.help ? 1 : 0);
526
+ }
527
+ const command = positionals[0];
528
+ const projectDir = process.cwd();
529
+ if (command === "serve") {
530
+ const store = new CommentStore(projectDir);
531
+ await store.load();
532
+ await store.startWatching();
533
+ const mcpServer = createMcpServer(store, projectDir);
534
+ const transport = new StdioServerTransport();
535
+ await mcpServer.connect(transport);
536
+ process.stderr.write("Cowrite MCP server running on stdio\n");
537
+ } else if (command === "preview") {
538
+ const filePath = positionals[1];
539
+ if (!filePath) {
540
+ process.stderr.write("Error: preview command requires a file path\n");
541
+ process.stderr.write(USAGE);
542
+ process.exit(1);
543
+ }
544
+ const resolvedFile = resolve5(projectDir, filePath);
545
+ const port = parseInt(values.port, 10);
546
+ const store = new CommentStore(projectDir);
547
+ await store.load();
548
+ await store.startWatching();
549
+ const watcher = new FileWatcher(resolvedFile);
550
+ await watcher.start();
551
+ const preview = createPreviewServer(store, watcher, port);
552
+ await preview.start();
553
+ const mcpServer = createMcpServer(store, projectDir);
554
+ const transport = new StdioServerTransport();
555
+ await mcpServer.connect(transport);
556
+ process.stderr.write(`Cowrite MCP server running on stdio
557
+ `);
558
+ process.stderr.write(`Preview: http://localhost:${port}
559
+ `);
560
+ const shutdown = async () => {
561
+ process.stderr.write("Shutting down...\n");
562
+ await store.stopWatching();
563
+ await watcher.stop();
564
+ await preview.stop();
565
+ process.exit(0);
566
+ };
567
+ process.on("SIGINT", shutdown);
568
+ process.on("SIGTERM", shutdown);
569
+ } else {
570
+ process.stderr.write(`Unknown command: ${command}
571
+ `);
572
+ process.stderr.write(USAGE);
573
+ process.exit(1);
574
+ }
575
+ }
576
+ main().catch((err) => {
577
+ process.stderr.write(`Fatal error: ${err}
578
+ `);
579
+ process.exit(1);
580
+ });
581
+ //# sourceMappingURL=cowrite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../bin/cowrite.ts","../../src/comment-store.ts","../../src/mcp-server.ts","../../src/utils.ts","../../src/preview-server.ts","../../src/file-watcher.ts"],"sourcesContent":["import { parseArgs } from \"node:util\";\nimport { resolve } from \"node:path\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CommentStore } from \"../src/comment-store.js\";\nimport { createMcpServer } from \"../src/mcp-server.js\";\nimport { createPreviewServer } from \"../src/preview-server.js\";\nimport { FileWatcher } from \"../src/file-watcher.js\";\n\nconst USAGE = `\ncowrite — Live commenting plugin for coding agent sessions\n\nUsage:\n cowrite preview <file> [--port N] Open browser preview + start MCP server\n cowrite serve MCP-only mode (stdio, no preview)\n\nOptions:\n --port, -p Port for preview server (default: 3377)\n --help, -h Show this help\n`;\n\nasync function main() {\n const { values, positionals } = parseArgs({\n args: process.argv.slice(2),\n options: {\n port: { type: \"string\", short: \"p\", default: \"3377\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n allowPositionals: true,\n strict: false,\n });\n\n if (values.help || positionals.length === 0) {\n process.stderr.write(USAGE);\n process.exit(positionals.length === 0 && !values.help ? 1 : 0);\n }\n\n const command = positionals[0];\n const projectDir = process.cwd();\n\n if (command === \"serve\") {\n // MCP-only mode\n const store = new CommentStore(projectDir);\n await store.load();\n await store.startWatching();\n const mcpServer = createMcpServer(store, projectDir);\n const transport = new StdioServerTransport();\n await mcpServer.connect(transport);\n process.stderr.write(\"Cowrite MCP server running on stdio\\n\");\n } else if (command === \"preview\") {\n const filePath = positionals[1];\n if (!filePath) {\n process.stderr.write(\"Error: preview command requires a file path\\n\");\n process.stderr.write(USAGE);\n process.exit(1);\n }\n\n const resolvedFile = resolve(projectDir, filePath);\n const port = parseInt(values.port as string, 10);\n\n const store = new CommentStore(projectDir);\n await store.load();\n await store.startWatching();\n\n // Start file watcher\n const watcher = new FileWatcher(resolvedFile);\n await watcher.start();\n\n // Start preview server\n const preview = createPreviewServer(store, watcher, port);\n await preview.start();\n\n // Start MCP server on stdio\n const mcpServer = createMcpServer(store, projectDir);\n const transport = new StdioServerTransport();\n await mcpServer.connect(transport);\n\n process.stderr.write(`Cowrite MCP server running on stdio\\n`);\n process.stderr.write(`Preview: http://localhost:${port}\\n`);\n\n // Graceful shutdown\n const shutdown = async () => {\n process.stderr.write(\"Shutting down...\\n\");\n await store.stopWatching();\n await watcher.stop();\n await preview.stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n } else {\n process.stderr.write(`Unknown command: ${command}\\n`);\n process.stderr.write(USAGE);\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`Fatal error: ${err}\\n`);\n process.exit(1);\n});\n","import { EventEmitter } from \"node:events\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { watch as chokidarWatch, type FSWatcher } from \"chokidar\";\nimport type { Comment, Reply } from \"./types.js\";\n\nconst PERSIST_FILE = \".cowrite-comments.json\";\n\nexport class CommentStore extends EventEmitter {\n private comments: Map<string, Comment> = new Map();\n private persistPath: string;\n private lastWriteTime = 0;\n private watcher: FSWatcher | null = null;\n\n constructor(projectDir: string) {\n super();\n this.persistPath = join(resolve(projectDir), PERSIST_FILE);\n }\n\n async load(): Promise<void> {\n try {\n const data = await readFile(this.persistPath, \"utf-8\");\n const arr: Comment[] = JSON.parse(data);\n for (const c of arr) {\n this.comments.set(c.id, c);\n }\n } catch {\n // No existing file, start fresh\n }\n }\n\n private async persist(): Promise<void> {\n this.lastWriteTime = Date.now();\n const arr = Array.from(this.comments.values());\n await writeFile(this.persistPath, JSON.stringify(arr, null, 2), \"utf-8\");\n }\n\n async reload(): Promise<void> {\n try {\n const data = await readFile(this.persistPath, \"utf-8\");\n const arr: Comment[] = JSON.parse(data);\n this.comments.clear();\n for (const c of arr) {\n this.comments.set(c.id, c);\n }\n this.emit(\"change\", null);\n } catch {\n // File missing or invalid, clear state\n this.comments.clear();\n this.emit(\"change\", null);\n }\n }\n\n async startWatching(): Promise<void> {\n if (this.watcher) return;\n this.watcher = chokidarWatch(this.persistPath, {\n ignoreInitial: true,\n awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },\n });\n this.watcher.on(\"change\", async () => {\n if (Date.now() - this.lastWriteTime < 200) return;\n await this.reload();\n });\n this.watcher.on(\"add\", async () => {\n if (Date.now() - this.lastWriteTime < 200) return;\n await this.reload();\n });\n }\n\n async stopWatching(): Promise<void> {\n if (this.watcher) {\n await this.watcher.close();\n this.watcher = null;\n }\n }\n\n add(params: {\n file: string;\n offset: number;\n length: number;\n selectedText: string;\n comment: string;\n }): Comment {\n const comment: Comment = {\n id: randomUUID(),\n file: params.file,\n offset: params.offset,\n length: params.length,\n selectedText: params.selectedText,\n comment: params.comment,\n status: \"pending\",\n replies: [],\n createdAt: new Date().toISOString(),\n resolvedAt: null,\n };\n this.comments.set(comment.id, comment);\n this.emit(\"change\", comment);\n this.persist().catch((err) => process.stderr.write(`Persist error: ${err}\\n`));\n return comment;\n }\n\n resolve(commentId: string): Comment | null {\n const comment = this.comments.get(commentId);\n if (!comment) return null;\n comment.status = \"resolved\";\n comment.resolvedAt = new Date().toISOString();\n this.emit(\"change\", comment);\n this.persist().catch((err) => process.stderr.write(`Persist error: ${err}\\n`));\n return comment;\n }\n\n addReply(commentId: string, from: \"user\" | \"agent\", text: string): Reply | null {\n const comment = this.comments.get(commentId);\n if (!comment) return null;\n const reply: Reply = {\n id: randomUUID(),\n from,\n text,\n createdAt: new Date().toISOString(),\n };\n comment.replies.push(reply);\n this.emit(\"change\", comment);\n this.persist().catch((err) => process.stderr.write(`Persist error: ${err}\\n`));\n return reply;\n }\n\n get(commentId: string): Comment | null {\n return this.comments.get(commentId) ?? null;\n }\n\n getAll(filter?: { file?: string; status?: \"pending\" | \"resolved\" | \"all\" }): Comment[] {\n let results = Array.from(this.comments.values());\n if (filter?.file) {\n results = results.filter((c) => c.file === filter.file);\n }\n if (filter?.status && filter.status !== \"all\") {\n results = results.filter((c) => c.status === filter.status);\n }\n return results.sort((a, b) => a.offset - b.offset);\n }\n\n getForFile(file: string): Comment[] {\n return this.getAll({ file });\n }\n\n /** Adjust comment offsets when file content changes */\n adjustOffsets(file: string, oldContent: string, newContent: string): void {\n const fileComments = this.getForFile(file);\n if (fileComments.length === 0) return;\n\n for (const comment of fileComments) {\n // Try to find the selected text in the new content near original offset\n const searchStart = Math.max(0, comment.offset - 200);\n const searchEnd = Math.min(newContent.length, comment.offset + comment.length + 200);\n const searchRegion = newContent.slice(searchStart, searchEnd);\n const idx = searchRegion.indexOf(comment.selectedText);\n if (idx !== -1) {\n comment.offset = searchStart + idx;\n }\n // If not found, leave offset as-is (orphaned comment)\n }\n\n this.emit(\"change\", null);\n this.persist().catch((err) => process.stderr.write(`Persist error: ${err}\\n`));\n }\n\n clear(): void {\n this.comments.clear();\n this.emit(\"change\", null);\n this.persist().catch((err) => process.stderr.write(`Persist error: ${err}\\n`));\n }\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { CommentStore } from \"./comment-store.js\";\nimport { annotateFileWithComments } from \"./utils.js\";\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\nexport function createMcpServer(store: CommentStore, projectDir: string): McpServer {\n const server = new McpServer({\n name: \"cowrite\",\n version: \"0.1.0\",\n });\n\n // Tool: get_pending_comments\n server.tool(\n \"get_pending_comments\",\n \"Get comments from the live preview. Returns unresolved comments by default.\",\n {\n file: z.string().optional().describe(\"Filter by file path\"),\n status: z.enum([\"pending\", \"resolved\", \"all\"]).optional().describe(\"Filter by status (default: pending)\"),\n },\n async ({ file, status }) => {\n const filter: { file?: string; status?: \"pending\" | \"resolved\" | \"all\" } = {};\n if (file) filter.file = resolve(projectDir, file);\n filter.status = status ?? \"pending\";\n const comments = store.getAll(filter);\n return {\n content: [\n {\n type: \"text\" as const,\n text: comments.length === 0\n ? \"No comments found.\"\n : JSON.stringify(comments, null, 2),\n },\n ],\n };\n }\n );\n\n // Tool: resolve_comment\n server.tool(\n \"resolve_comment\",\n \"Mark a comment as resolved/addressed.\",\n {\n commentId: z.string().describe(\"The comment ID to resolve\"),\n },\n async ({ commentId }) => {\n const comment = store.resolve(commentId);\n if (!comment) {\n return {\n content: [{ type: \"text\" as const, text: `Comment ${commentId} not found.` }],\n isError: true,\n };\n }\n return {\n content: [{ type: \"text\" as const, text: `Comment ${commentId} resolved.` }],\n };\n }\n );\n\n // Tool: reply_to_comment\n server.tool(\n \"reply_to_comment\",\n \"Reply to a comment from the agent.\",\n {\n commentId: z.string().describe(\"The comment ID to reply to\"),\n reply: z.string().describe(\"The reply text\"),\n },\n async ({ commentId, reply }) => {\n const replyObj = store.addReply(commentId, \"agent\", reply);\n if (!replyObj) {\n return {\n content: [{ type: \"text\" as const, text: `Comment ${commentId} not found.` }],\n isError: true,\n };\n }\n return {\n content: [{ type: \"text\" as const, text: `Reply added to comment ${commentId}.` }],\n };\n }\n );\n\n // Tool: get_file_with_annotations\n server.tool(\n \"get_file_with_annotations\",\n \"Get file content with inline comment markers showing where comments are anchored.\",\n {\n file: z.string().describe(\"File path to annotate\"),\n },\n async ({ file }) => {\n const filePath = resolve(projectDir, file);\n try {\n const content = await readFile(filePath, \"utf-8\");\n const comments = store.getForFile(filePath);\n const annotated = annotateFileWithComments(content, comments);\n return {\n content: [{ type: \"text\" as const, text: annotated }],\n };\n } catch (err) {\n return {\n content: [{ type: \"text\" as const, text: `Error reading file: ${err}` }],\n isError: true,\n };\n }\n }\n );\n\n // Resource: cowrite://comments\n server.resource(\n \"all-comments\",\n \"cowrite://comments\",\n { description: \"Live list of all comments\", mimeType: \"application/json\" },\n async () => {\n const comments = store.getAll();\n return {\n contents: [\n {\n uri: \"cowrite://comments\",\n mimeType: \"application/json\",\n text: JSON.stringify(comments, null, 2),\n },\n ],\n };\n }\n );\n\n // Wire store changes to MCP resource notifications\n store.on(\"change\", () => {\n server.server.notification({\n method: \"notifications/resources/updated\",\n params: { uri: \"cowrite://comments\" },\n }).catch(() => {\n // Notification may fail if client doesn't support it, that's OK\n });\n });\n\n return server;\n}\n","import { marked } from \"marked\";\nimport type { Comment } from \"./types.js\";\n\n/**\n * Render file content as HTML. Markdown files get full rendering;\n * other text files are wrapped in <pre> with offset-tagged spans.\n */\nexport function renderToHtml(content: string, filePath: string): string {\n const isMarkdown = /\\.(md|markdown|mdx)$/i.test(filePath);\n if (isMarkdown) {\n return renderMarkdownWithOffsets(content);\n }\n return renderPlainTextWithOffsets(content);\n}\n\nfunction renderMarkdownWithOffsets(content: string): string {\n const html = marked.parse(content, { async: false }) as string;\n // Wrap the rendered HTML in a container with data attributes for offset mapping\n // The client-side JS will handle offset computation from the rendered text nodes\n return `<div class=\"markdown-body\" data-source-length=\"${content.length}\">${html}</div>`;\n}\n\nfunction renderPlainTextWithOffsets(content: string): string {\n const lines = content.split(\"\\n\");\n let offset = 0;\n const htmlLines: string[] = [];\n\n for (const line of lines) {\n const escaped = escapeHtml(line);\n htmlLines.push(`<span class=\"line\" data-offset=\"${offset}\" data-length=\"${line.length}\">${escaped}</span>`);\n offset += line.length + 1; // +1 for the newline\n }\n\n return `<pre class=\"plain-text\" data-source-length=\"${content.length}\">${htmlLines.join(\"\\n\")}</pre>`;\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Annotate file content with inline comment markers for the agent.\n * Inserts `[COMMENT #id: \"text\"]` at the comment offsets.\n */\nexport function annotateFileWithComments(content: string, comments: Comment[]): string {\n // Sort by offset descending so insertions don't shift earlier offsets\n const sorted = [...comments].sort((a, b) => b.offset - a.offset);\n let result = content;\n\n for (const c of sorted) {\n const marker = `[COMMENT #${c.id.slice(0, 8)}: \"${c.comment}\"]`;\n const end = c.offset + c.length;\n // Insert marker after the selected text\n result = result.slice(0, end) + \" \" + marker + result.slice(end);\n }\n\n return result;\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { WebSocketServer, type WebSocket } from \"ws\";\nimport type { CommentStore } from \"./comment-store.js\";\nimport type { FileWatcher } from \"./file-watcher.js\";\nimport { renderToHtml } from \"./utils.js\";\nimport type { WSClientMessage, WSServerMessage } from \"./types.js\";\n\nconst UI_DIR = join(import.meta.dirname ?? new URL(\".\", import.meta.url).pathname, \"..\", \"ui\");\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n};\n\nexport function createPreviewServer(\n store: CommentStore,\n watcher: FileWatcher,\n port: number\n): { start: () => Promise<void>; stop: () => Promise<void> } {\n const clients = new Set<WebSocket>();\n\n const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", `http://localhost:${port}`);\n const pathname = url.pathname === \"/\" ? \"/index.html\" : url.pathname;\n\n // Serve static UI files\n const ext = pathname.slice(pathname.lastIndexOf(\".\"));\n const mimeType = MIME_TYPES[ext];\n if (mimeType) {\n try {\n // Resolve UI_DIR properly for both dev (tsx) and built (dist) modes\n let uiDir: string;\n if (import.meta.dirname) {\n uiDir = join(import.meta.dirname, \"..\", \"ui\");\n } else {\n uiDir = join(new URL(\".\", import.meta.url).pathname, \"..\", \"ui\");\n }\n const filePath = join(uiDir, pathname);\n const content = await readFile(filePath, \"utf-8\");\n res.writeHead(200, { \"Content-Type\": mimeType });\n res.end(content);\n return;\n } catch {\n // Fall through to 404\n }\n }\n\n // API: GET /api/state — initial state for the client\n if (pathname === \"/api/state\") {\n const fileContent = watcher.getContent();\n const html = renderToHtml(fileContent, watcher.getFilePath());\n const comments = store.getForFile(watcher.getFilePath());\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({\n file: watcher.getFilePath(),\n content: fileContent,\n html,\n comments,\n }));\n return;\n }\n\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\"Not found\");\n });\n\n const wss = new WebSocketServer({ server: httpServer });\n\n wss.on(\"connection\", (ws: WebSocket) => {\n clients.add(ws);\n\n // Send initial state\n const fileContent = watcher.getContent();\n const html = renderToHtml(fileContent, watcher.getFilePath());\n const comments = store.getForFile(watcher.getFilePath());\n\n send(ws, { type: \"file_update\", file: watcher.getFilePath(), content: fileContent, html });\n send(ws, { type: \"comments_update\", comments });\n\n ws.on(\"message\", (data) => {\n try {\n const msg: WSClientMessage = JSON.parse(data.toString());\n handleClientMessage(msg);\n } catch (err) {\n send(ws, { type: \"error\", message: `Invalid message: ${err}` });\n }\n });\n\n ws.on(\"close\", () => {\n clients.delete(ws);\n });\n });\n\n function handleClientMessage(msg: WSClientMessage): void {\n switch (msg.type) {\n case \"comment_add\":\n store.add({\n file: watcher.getFilePath(),\n offset: msg.offset,\n length: msg.length,\n selectedText: msg.selectedText,\n comment: msg.comment,\n });\n break;\n case \"comment_reply\":\n store.addReply(msg.commentId, \"user\", msg.text);\n break;\n case \"comment_resolve\":\n store.resolve(msg.commentId);\n break;\n }\n }\n\n // Broadcast updates when comments change\n store.on(\"change\", () => {\n const comments = store.getForFile(watcher.getFilePath());\n broadcast({ type: \"comments_update\", comments });\n });\n\n // Broadcast file changes\n watcher.on(\"change\", (event: { file: string; content: string; oldContent: string }) => {\n store.adjustOffsets(event.file, event.oldContent, event.content);\n const html = renderToHtml(event.content, event.file);\n broadcast({ type: \"file_update\", file: event.file, content: event.content, html });\n });\n\n function send(ws: WebSocket, msg: WSServerMessage): void {\n if (ws.readyState === ws.OPEN) {\n ws.send(JSON.stringify(msg));\n }\n }\n\n function broadcast(msg: WSServerMessage): void {\n for (const client of clients) {\n send(client, msg);\n }\n }\n\n return {\n start: () =>\n new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n process.stderr.write(`Cowrite preview server running at http://localhost:${port}\\n`);\n resolve();\n });\n }),\n stop: () =>\n new Promise<void>((resolvePromise, reject) => {\n for (const client of clients) {\n client.close();\n }\n httpServer.close((err) => {\n if (err) reject(err);\n else resolvePromise();\n });\n }),\n };\n}\n","import { watch, type FSWatcher } from \"chokidar\";\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { EventEmitter } from \"node:events\";\n\nexport interface FileChangeEvent {\n file: string;\n content: string;\n}\n\nexport class FileWatcher extends EventEmitter {\n private watcher: FSWatcher | null = null;\n private filePath: string;\n private lastContent: string = \"\";\n\n constructor(filePath: string) {\n super();\n this.filePath = resolve(filePath);\n }\n\n async start(): Promise<string> {\n this.lastContent = await readFile(this.filePath, \"utf-8\");\n\n this.watcher = watch(this.filePath, {\n persistent: true,\n ignoreInitial: true,\n awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },\n });\n\n this.watcher.on(\"change\", async () => {\n try {\n const newContent = await readFile(this.filePath, \"utf-8\");\n if (newContent !== this.lastContent) {\n const oldContent = this.lastContent;\n this.lastContent = newContent;\n this.emit(\"change\", {\n file: this.filePath,\n content: newContent,\n oldContent,\n } as FileChangeEvent & { oldContent: string });\n }\n } catch (err) {\n process.stderr.write(`File watch read error: ${err}\\n`);\n }\n });\n\n return this.lastContent;\n }\n\n getContent(): string {\n return this.lastContent;\n }\n\n getFilePath(): string {\n return this.filePath;\n }\n\n async stop(): Promise<void> {\n if (this.watcher) {\n await this.watcher.close();\n this.watcher = null;\n }\n }\n}\n"],"mappings":"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","names":["resolve","readFile","resolve","readFile","join","join","readFile","resolve","readFile","resolve","EventEmitter","resolve"]}