@filipc77/cowrite 0.4.19 → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -18
- package/dist/bin/cowrite.js +46 -5
- package/dist/bin/cowrite.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,36 +42,57 @@ claude mcp add -s user cowrite -- npx -y @filipc77/cowrite serve
|
|
|
42
42
|
|
|
43
43
|
### 2. Open the preview
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
The browser opens automatically when the MCP server starts. The default port is `3377`, but if it's in use (e.g. running cowrite in multiple repos), it automatically picks the next available port. Use `--no-open` to disable auto-opening.
|
|
46
|
+
|
|
47
|
+
Pick a file and you'll see a live preview.
|
|
46
48
|
|
|
47
49
|
### 3. Select text and comment
|
|
48
50
|
|
|
49
|
-
Select any text in the preview
|
|
51
|
+
Select any text in the preview. A **Comment** button appears — click it to open the comment form. Your text selection stays intact, so you can still copy-paste normally.
|
|
52
|
+
|
|
53
|
+
### 4. Start the background watcher (recommended)
|
|
54
|
+
|
|
55
|
+
In your Claude Code session, type:
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
```
|
|
58
|
+
/watch
|
|
59
|
+
```
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
This starts a background watcher that handles comments as they arrive — without blocking your main conversation. You only need to do this once per session.
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
- **`wait_for_comment` tool** — For real-time, hands-free operation, the agent can call `wait_for_comment` which blocks until a comment arrives. Use the `/watch` skill to enter this mode.
|
|
63
|
+
### 5. The agent handles your comments
|
|
57
64
|
|
|
58
|
-
|
|
65
|
+
Even without `/watch`, comments reach the agent through auto-installed hooks:
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
- **`UserPromptSubmit` hook** — Whenever you send any message, pending comments are injected into the agent's context. The agent makes the change, replies in the browser preview, and resolves the comment.
|
|
68
|
+
- **`Stop` hook** — When the agent finishes any task, it checks for pending comments before going idle. Catches comments that arrive while the agent is busy.
|
|
69
|
+
|
|
70
|
+
### 6. Auto-installed integration
|
|
71
|
+
|
|
72
|
+
On first run, `cowrite serve` installs into your project's `.claude/` directory:
|
|
73
|
+
- **Hooks** (`UserPromptSubmit` + `Stop`) — surface pending comments to the agent automatically
|
|
74
|
+
- **Skills** (`/review` + `/watch`) — manual and background comment handling
|
|
75
|
+
- Hooks are merged into existing `settings.json` — your other settings are preserved
|
|
61
76
|
|
|
62
77
|
## CLI Reference
|
|
63
78
|
|
|
64
79
|
```
|
|
65
|
-
cowrite preview <file> [--port N] Open browser preview + start MCP server
|
|
66
|
-
cowrite serve
|
|
80
|
+
cowrite preview <file> [--port N] Open browser preview for a specific file + start MCP server
|
|
81
|
+
cowrite serve [--port N] Start MCP server + preview server (browse any file)
|
|
82
|
+
cowrite open [--port N] Open the browser to the preview URL
|
|
67
83
|
|
|
68
84
|
Options:
|
|
69
85
|
--port, -p Port for preview server (default: 3377)
|
|
86
|
+
--no-open Don't auto-open the browser
|
|
70
87
|
--help, -h Show help
|
|
71
88
|
```
|
|
72
89
|
|
|
73
90
|
## MCP Tools
|
|
74
91
|
|
|
92
|
+
### `get_preview_url`
|
|
93
|
+
|
|
94
|
+
Returns the URL of the live preview server. Ask the agent for this to know which port to open.
|
|
95
|
+
|
|
75
96
|
### `get_pending_comments`
|
|
76
97
|
|
|
77
98
|
Returns unresolved comments from the live preview.
|
|
@@ -131,15 +152,15 @@ Blocks until a new comment is posted in the live preview, then returns it. If pe
|
|
|
131
152
|
- **Persistent comments** — Comments are saved to `.cowrite-comments.json` in the project directory and survive restarts.
|
|
132
153
|
- **Offset-based anchoring** — Comments are anchored by character offset and selected text, not line numbers. When the file changes, Cowrite searches for the selected text near the original offset to re-anchor.
|
|
133
154
|
- **Agent replies** — The agent can reply to comments, and replies appear in the browser sidebar in real time.
|
|
134
|
-
- **
|
|
135
|
-
- **Auto-installed hooks** — `cowrite serve` automatically installs
|
|
155
|
+
- **Auto-port selection** — If port 3377 is in use (e.g. running cowrite in multiple repos), it automatically tries the next port (3378, 3379, etc.).
|
|
156
|
+
- **Auto-installed hooks** — `cowrite serve` automatically installs Claude Code `UserPromptSubmit` and `Stop` hooks that surface pending comments to the agent. Merges with existing settings.
|
|
136
157
|
|
|
137
158
|
## Skills
|
|
138
159
|
|
|
139
|
-
Cowrite ships with two built-in Claude Code skills:
|
|
160
|
+
Cowrite ships with two built-in Claude Code skills (auto-installed to `.claude/skills/`):
|
|
140
161
|
|
|
141
|
-
- **`/review`** — Check all pending comments,
|
|
142
|
-
- **`/watch`** —
|
|
162
|
+
- **`/review`** — Check all pending comments, make changes, reply in the preview, and resolve each one.
|
|
163
|
+
- **`/watch`** — Start a background watcher that handles comments as they arrive. Does **not** block the main conversation — you can keep working normally while comments are handled in the background.
|
|
143
164
|
|
|
144
165
|
## How Comments Flow
|
|
145
166
|
|
|
@@ -148,11 +169,12 @@ Cowrite ships with two built-in Claude Code skills:
|
|
|
148
169
|
3. The server adds the comment to the shared `CommentStore`
|
|
149
170
|
4. The store persists to `.cowrite-comments.json` and emits events
|
|
150
171
|
5. The agent receives the comment via one of:
|
|
151
|
-
- `
|
|
172
|
+
- The `Stop` hook catching it when the agent finishes its current task
|
|
152
173
|
- The `UserPromptSubmit` hook injecting it on the next user message
|
|
174
|
+
- `wait_for_comment` returning immediately (if `/watch` is active)
|
|
153
175
|
- The agent calling `get_pending_comments` directly
|
|
154
|
-
6. The agent makes
|
|
155
|
-
7. The browser receives the update via WebSocket and shows the resolved state
|
|
176
|
+
6. The agent makes the change, calls `reply_to_comment` (visible in the browser), then `resolve_comment`
|
|
177
|
+
7. The browser receives the update via WebSocket and shows the reply and resolved state
|
|
156
178
|
|
|
157
179
|
## Development
|
|
158
180
|
|
package/dist/bin/cowrite.js
CHANGED
|
@@ -169,7 +169,15 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
169
169
|
import { z } from "zod";
|
|
170
170
|
|
|
171
171
|
// src/utils.ts
|
|
172
|
+
import { exec } from "child_process";
|
|
172
173
|
import { marked } from "marked";
|
|
174
|
+
function openBrowser(url) {
|
|
175
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `cmd /c start "" "${url}"` : `xdg-open "${url}"`;
|
|
176
|
+
exec(cmd, (err) => {
|
|
177
|
+
if (err) process.stderr.write(`Could not open browser: ${err.message}
|
|
178
|
+
`);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
173
181
|
function renderToHtml(content, filePath) {
|
|
174
182
|
const isMarkdown = /\.(md|markdown|mdx)$/i.test(filePath);
|
|
175
183
|
if (isMarkdown) {
|
|
@@ -209,7 +217,7 @@ function annotateFileWithComments(content, comments) {
|
|
|
209
217
|
// src/mcp-server.ts
|
|
210
218
|
import { readFile as readFile2 } from "fs/promises";
|
|
211
219
|
import { relative, resolve as resolve2 } from "path";
|
|
212
|
-
function createMcpServer(store, projectDir) {
|
|
220
|
+
function createMcpServer(store, projectDir, getPreviewPort) {
|
|
213
221
|
const server = new McpServer(
|
|
214
222
|
{ name: "cowrite", version: "0.1.0" },
|
|
215
223
|
{ capabilities: { logging: {} } }
|
|
@@ -354,6 +362,23 @@ function createMcpServer(store, projectDir) {
|
|
|
354
362
|
});
|
|
355
363
|
}
|
|
356
364
|
);
|
|
365
|
+
server.tool(
|
|
366
|
+
"get_preview_url",
|
|
367
|
+
"Get the URL of the Cowrite live preview. Share this with the user so they can open it in their browser.",
|
|
368
|
+
{},
|
|
369
|
+
async () => {
|
|
370
|
+
const port = getPreviewPort?.();
|
|
371
|
+
if (!port) {
|
|
372
|
+
return {
|
|
373
|
+
content: [{ type: "text", text: "Preview server is not running." }],
|
|
374
|
+
isError: true
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
content: [{ type: "text", text: `http://localhost:${port}` }]
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
);
|
|
357
382
|
server.resource(
|
|
358
383
|
"all-comments",
|
|
359
384
|
"cowrite://comments",
|
|
@@ -757,9 +782,11 @@ cowrite \u2014 Live commenting plugin for coding agent sessions
|
|
|
757
782
|
Usage:
|
|
758
783
|
cowrite preview <file> [--port N] Open browser preview for a specific file + start MCP server
|
|
759
784
|
cowrite serve [--port N] Start MCP server + preview server (browse any file)
|
|
785
|
+
cowrite open [--port N] Open the browser to the preview URL
|
|
760
786
|
|
|
761
787
|
Options:
|
|
762
788
|
--port, -p Port for preview server (default: 3377)
|
|
789
|
+
--no-open Don't auto-open the browser
|
|
763
790
|
--help, -h Show this help
|
|
764
791
|
`;
|
|
765
792
|
function setupShutdown(store, preview) {
|
|
@@ -882,6 +909,7 @@ async function main() {
|
|
|
882
909
|
args: process.argv.slice(2),
|
|
883
910
|
options: {
|
|
884
911
|
port: { type: "string", short: "p", default: "3377" },
|
|
912
|
+
"no-open": { type: "boolean", default: false },
|
|
885
913
|
help: { type: "boolean", short: "h", default: false }
|
|
886
914
|
},
|
|
887
915
|
allowPositionals: true,
|
|
@@ -900,17 +928,21 @@ async function main() {
|
|
|
900
928
|
await store.load();
|
|
901
929
|
await store.startWatching();
|
|
902
930
|
const preview = createPreviewServer(store, projectDir, port);
|
|
931
|
+
let previewRunning = false;
|
|
903
932
|
try {
|
|
904
933
|
await preview.start();
|
|
905
|
-
|
|
934
|
+
previewRunning = true;
|
|
935
|
+
const previewUrl = `http://localhost:${preview.port}`;
|
|
936
|
+
process.stderr.write(`Preview: ${previewUrl}
|
|
906
937
|
`);
|
|
938
|
+
if (!values["no-open"]) openBrowser(previewUrl);
|
|
907
939
|
} catch (err) {
|
|
908
940
|
process.stderr.write(`Preview server failed: ${err}
|
|
909
941
|
`);
|
|
910
942
|
process.stderr.write(`MCP server will still run \u2014 comments sync via .cowrite-comments.json
|
|
911
943
|
`);
|
|
912
944
|
}
|
|
913
|
-
const mcpServer = createMcpServer(store, projectDir);
|
|
945
|
+
const mcpServer = createMcpServer(store, projectDir, () => previewRunning ? preview.port : null);
|
|
914
946
|
const transport = new StdioServerTransport();
|
|
915
947
|
await mcpServer.connect(transport);
|
|
916
948
|
process.stderr.write(`Cowrite MCP server running on stdio
|
|
@@ -928,22 +960,31 @@ async function main() {
|
|
|
928
960
|
await store.load();
|
|
929
961
|
await store.startWatching();
|
|
930
962
|
const preview = createPreviewServer(store, projectDir, port, resolvedFile);
|
|
963
|
+
let previewRunning2 = false;
|
|
931
964
|
try {
|
|
932
965
|
await preview.start();
|
|
933
|
-
|
|
966
|
+
previewRunning2 = true;
|
|
967
|
+
const previewUrl = `http://localhost:${preview.port}`;
|
|
968
|
+
process.stderr.write(`Preview: ${previewUrl}
|
|
934
969
|
`);
|
|
970
|
+
if (!values["no-open"]) openBrowser(previewUrl);
|
|
935
971
|
} catch (err) {
|
|
936
972
|
process.stderr.write(`Preview server failed: ${err}
|
|
937
973
|
`);
|
|
938
974
|
process.stderr.write(`MCP server will still run \u2014 comments sync via .cowrite-comments.json
|
|
939
975
|
`);
|
|
940
976
|
}
|
|
941
|
-
const mcpServer = createMcpServer(store, projectDir);
|
|
977
|
+
const mcpServer = createMcpServer(store, projectDir, () => previewRunning2 ? preview.port : null);
|
|
942
978
|
const transport = new StdioServerTransport();
|
|
943
979
|
await mcpServer.connect(transport);
|
|
944
980
|
process.stderr.write(`Cowrite MCP server running on stdio
|
|
945
981
|
`);
|
|
946
982
|
setupShutdown(store, preview);
|
|
983
|
+
} else if (command === "open") {
|
|
984
|
+
const url = `http://localhost:${port}`;
|
|
985
|
+
process.stderr.write(`Opening ${url}
|
|
986
|
+
`);
|
|
987
|
+
openBrowser(url);
|
|
947
988
|
} else {
|
|
948
989
|
process.stderr.write(`Unknown command: ${command}
|
|
949
990
|
`);
|
package/dist/bin/cowrite.js.map
CHANGED
|
@@ -1 +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, join } from \"node:path\";\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from \"node:fs\";\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\";\n\nconst USAGE = `\ncowrite — Live commenting plugin for coding agent sessions\n\nUsage:\n cowrite preview <file> [--port N] Open browser preview for a specific file + start MCP server\n cowrite serve [--port N] Start MCP server + preview server (browse any file)\n\nOptions:\n --port, -p Port for preview server (default: 3377)\n --help, -h Show this help\n`;\n\nfunction setupShutdown(store: CommentStore, preview: { stop: () => Promise<void>; [k: string]: any }) {\n let shuttingDown = false;\n const shutdown = () => {\n if (shuttingDown) {\n // Second signal — force exit immediately\n process.exit(1);\n }\n shuttingDown = true;\n process.stderr.write(\"Shutting down...\\n\");\n // Best-effort cleanup with a hard timeout\n Promise.allSettled([store.stopWatching(), preview.stop()])\n .finally(() => process.exit(0));\n setTimeout(() => process.exit(0), 2000);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nconst HOOK_SCRIPT = `#!/bin/bash\n# Auto-installed by cowrite — injects pending comments into Claude Code context.\n# Only outputs when there are pending comments. Silent otherwise.\nCOMMENTS_FILE=\"\\${CLAUDE_PROJECT_DIR:-.}/.cowrite-comments.json\"\nif [ ! -f \"$COMMENTS_FILE\" ] || [ ! -s \"$COMMENTS_FILE\" ]; then exit 0; fi\nPENDING=$(jq '[.[] | select(.status == \"pending\")] | length' \"$COMMENTS_FILE\" 2>/dev/null || echo 0)\nif [ \"$PENDING\" -eq 0 ]; then exit 0; fi\njq -r '[.[] | select(.status == \"pending\")] | \"COWRITE: \\\\(length) pending comment(s) from the live preview. For EACH comment: (1) make the requested change, (2) call reply_to_comment to explain what you did — the user reads replies in the browser, and (3) call resolve_comment.\\\\n\" + ([.[] | \"- [\\\\(.id)] File: \\\\(.file | split(\"/\") | last) | Text: \\\\\"\\\\(.selectedText)\\\\\" | Comment: \\\\(.comment)\"] | join(\"\\\\n\"))' \"$COMMENTS_FILE\" 2>/dev/null\n`;\n\nconst HOOK_ENTRY = {\n matcher: \"\",\n hooks: [{\n type: \"command\",\n command: `bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/inject-comments.sh\"`,\n }],\n};\n\nconst HOOK_SETTINGS = {\n hooks: {\n UserPromptSubmit: [HOOK_ENTRY],\n Stop: [HOOK_ENTRY],\n },\n};\n\nconst SKILL_REVIEW = `---\nname: review\ndescription: Check and address cowrite comments left by the user in the live preview\nuser_invocable: true\n---\n\n# Review Cowrite Comments\n\nCheck for any pending comments left in the Cowrite live preview and address them.\n\n## Steps\n\n1. Call the \\`get_pending_comments\\` tool to retrieve all unresolved comments.\n2. For each pending comment:\n a. Read the comment text and the selected text it refers to.\n b. Use \\`get_file_with_annotations\\` to see the comment in context.\n c. Make the requested change or reply explaining why you can't.\n d. Call \\`reply_to_comment\\` to acknowledge the feedback.\n e. Call \\`resolve_comment\\` to mark it as addressed.\n3. Summarize what was done.\n`;\n\nconst SKILL_WATCH = `---\nname: watch\ndescription: Start a background watcher for cowrite comments — does not block the main conversation\nuser_invocable: true\n---\n\n# Watch for Live Comments (Background)\n\nStart a background agent that watches for cowrite comments and handles them as they arrive. The main conversation stays free for other work.\n\n## Steps\n\n1. First, handle any existing pending comments:\n a. Call \\`get_pending_comments\\` to check for unresolved comments.\n b. For each pending comment, use \\`get_file_with_annotations\\` to see context, make the change, call \\`reply_to_comment\\`, and call \\`resolve_comment\\`.\n\n2. Then, launch a **background** watcher using the Task tool:\n - Use \\`subagent_type: \"general-purpose\"\\` and \\`run_in_background: true\\`\n - The background agent should call \\`wait_for_comment\\` in a loop\n - When a comment arrives, it handles it (read file, make change, reply, resolve)\n - On timeout, it re-calls \\`wait_for_comment\\` immediately\n - The loop continues until the user says stop\n\n3. Tell the user the background watcher is running and they can continue working normally. Comments will be handled automatically.\n`;\n\nfunction installClaudeIntegration(projectDir: string): void {\n const claudeDir = join(projectDir, \".claude\");\n const hooksDir = join(claudeDir, \"hooks\");\n const hookPath = join(hooksDir, \"inject-comments.sh\");\n const settingsPath = join(claudeDir, \"settings.json\");\n const reviewDir = join(claudeDir, \"skills\", \"review\");\n const watchDir = join(claudeDir, \"skills\", \"watch\");\n\n // Create directories\n for (const dir of [hooksDir, reviewDir, watchDir]) {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n }\n\n // Write hook script (always overwrite to keep in sync with cowrite version)\n writeFileSync(hookPath, HOOK_SCRIPT, \"utf-8\");\n chmodSync(hookPath, 0o755);\n\n // Write skills (always overwrite to keep in sync)\n writeFileSync(join(reviewDir, \"SKILL.md\"), SKILL_REVIEW, \"utf-8\");\n writeFileSync(join(watchDir, \"SKILL.md\"), SKILL_WATCH, \"utf-8\");\n\n // Merge cowrite hooks into settings.json (preserve existing settings)\n let settings: any = {};\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, \"utf-8\"));\n } catch {\n // Corrupt JSON — overwrite\n }\n }\n if (!settings.hooks) settings.hooks = {};\n let changed = false;\n for (const eventType of [\"UserPromptSubmit\", \"Stop\"] as const) {\n if (!Array.isArray(settings.hooks[eventType])) settings.hooks[eventType] = [];\n const hasCowriteHook = settings.hooks[eventType].some((entry: any) =>\n entry.hooks?.some((h: any) => h.command?.includes(\"inject-comments.sh\"))\n );\n if (!hasCowriteHook) {\n settings.hooks[eventType].push(HOOK_ENTRY);\n changed = true;\n }\n }\n if (changed) {\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\", \"utf-8\");\n }\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 const port = parseInt(values.port as string, 10);\n\n // Auto-install Claude Code hooks for comment propagation\n installClaudeIntegration(projectDir);\n\n if (command === \"serve\") {\n const store = new CommentStore(projectDir);\n await store.load();\n await store.startWatching();\n\n // Start preview server (non-fatal — MCP works even if port is taken)\n const preview = createPreviewServer(store, projectDir, port);\n try {\n await preview.start();\n process.stderr.write(`Preview: http://localhost:${preview.port}\\n`);\n } catch (err) {\n process.stderr.write(`Preview server failed: ${err}\\n`);\n process.stderr.write(`MCP server will still run — comments sync via .cowrite-comments.json\\n`);\n }\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\n setupShutdown(store, preview);\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\n const store = new CommentStore(projectDir);\n await store.load();\n await store.startWatching();\n\n // Start preview server (non-fatal — MCP works even if port is taken)\n const preview = createPreviewServer(store, projectDir, port, resolvedFile);\n try {\n await preview.start();\n process.stderr.write(`Preview: http://localhost:${preview.port}\\n`);\n } catch (err) {\n process.stderr.write(`Preview server failed: ${err}\\n`);\n process.stderr.write(`MCP server will still run — comments sync via .cowrite-comments.json\\n`);\n }\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\n setupShutdown(store, preview);\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 const oldIds = new Set(this.comments.keys());\n this.comments.clear();\n for (const c of arr) {\n this.comments.set(c.id, c);\n }\n // Emit \"new_comment\" for comments that didn't exist before\n for (const c of arr) {\n if (!oldIds.has(c.id)) {\n this.emit(\"new_comment\", c);\n }\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.emit(\"new_comment\", 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 { relative, resolve } from \"node:path\";\n\nexport function createMcpServer(store: CommentStore, projectDir: string): McpServer {\n const server = new McpServer(\n { name: \"cowrite\", version: \"0.1.0\" },\n { capabilities: { logging: {} } },\n );\n\n // Tool: get_pending_comments\n const getPendingTool = server.tool(\n \"get_pending_comments\",\n \"Get comments from the live preview (0 pending). Call this first to catch comments posted before you started listening.\",\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 // Tool: wait_for_comment\n server.tool(\n \"wait_for_comment\",\n \"Block until a new comment is posted in the live preview, then return it. This is the primary way to receive real-time comments — call it again immediately after handling each comment to keep listening. If it times out, call it again.\",\n {\n timeout: z.number().optional().describe(\"Max seconds to wait (default: 30)\"),\n },\n ({ timeout }, { signal }: { signal?: AbortSignal }) => {\n const maxWait = (timeout ?? 30) * 1000;\n\n // Check for comments that arrived while no one was listening\n const pending = store.getAll({ status: \"pending\" });\n if (pending.length > 0) {\n const latest = pending[pending.length - 1];\n const file = relative(projectDir, latest.file);\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({ ...latest, file }, null, 2),\n }],\n };\n }\n\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n cleanup();\n const count = store.getAll({ status: \"pending\" }).length;\n resolve({\n content: [{ type: \"text\" as const, text: count > 0\n ? `Timeout, but ${count} pending comment(s) exist. Call get_pending_comments now.`\n : \"No new comments yet. Call wait_for_comment again to keep listening.\" }],\n });\n }, maxWait);\n\n const onComment = (comment: { id: string; file: string; selectedText: string; comment: string }) => {\n cleanup();\n const file = relative(projectDir, comment.file);\n resolve({\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({ ...comment, file }, null, 2),\n }],\n });\n };\n\n const onAbort = () => {\n cleanup();\n resolve({\n content: [{ type: \"text\" as const, text: \"Cancelled. Call wait_for_comment again to resume listening.\" }],\n });\n };\n\n const cleanup = () => {\n clearTimeout(timer);\n store.off(\"new_comment\", onComment);\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n store.on(\"new_comment\", onComment);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n // If already aborted before we set up\n if (signal?.aborted) {\n onAbort();\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 if (!server.isConnected()) return;\n server.server.notification({\n method: \"notifications/resources/updated\",\n params: { uri: \"cowrite://comments\" },\n }).catch(() => {});\n });\n\n // --- Comment propagation signals ---\n // Only send signals when an MCP client is actually connected.\n // 1. Update tool description with pending count + sendToolListChanged\n // 2. sendLoggingMessage as additional context\n // Primary real-time mechanism is wait_for_comment via the /watch skill.\n store.on(\"new_comment\", (comment: { file: string; selectedText: string; comment: string }) => {\n if (!server.isConnected()) return;\n\n const count = store.getAll({ status: \"pending\" }).length;\n const file = relative(projectDir, comment.file);\n const selectedPreview = comment.selectedText.length > 80\n ? comment.selectedText.slice(0, 80) + \"...\"\n : comment.selectedText;\n\n // Signal 1: Update tool description + notify tool list changed\n try {\n getPendingTool.update({\n description: `Get comments from the live preview (${count} pending). Call this first to catch comments posted before you started listening.`,\n });\n server.sendToolListChanged();\n } catch {\n // Not connected or transport issue — skip\n }\n\n // Signal 2: Logging message\n server.sendLoggingMessage({\n level: \"warning\",\n data: `NEW COMMENT on ${file}: \"${comment.comment}\" (selected: \"${selectedPreview}\"). Call get_pending_comments to see it.`,\n }).catch(() => {});\n\n // Signal 3: Resource list changed\n server.server.notification({\n method: \"notifications/resources/list_changed\",\n }).catch(() => {});\n });\n\n // Update description count when comments are resolved\n store.on(\"change\", () => {\n const count = store.getAll({ status: \"pending\" }).length;\n try {\n getPendingTool.update({\n description: `Get comments from the live preview (${count} pending). Call this first to catch comments posted before you started listening.`,\n });\n } catch {\n // Ignore — may not be connected yet\n }\n });\n\n // Prompt: cowrite-workflow\n server.prompt(\n \"cowrite-workflow\",\n \"How to process live preview comments in a wait-handle-resolve loop\",\n () => ({\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: [\n \"You are monitoring a live code preview where users leave comments on selected text.\",\n \"\",\n \"Follow this loop:\",\n \"1. Call `get_pending_comments` to check for any comments already posted.\",\n \"2. Process each pending comment: read the file, make the requested change or reply, then call `resolve_comment`.\",\n \"3. Call `wait_for_comment` to block until the next comment arrives.\",\n \"4. When a comment arrives, process it the same way (step 2).\",\n \"5. Go back to step 3 and keep listening.\",\n \"\",\n \"Tips:\",\n \"- Use `get_file_with_annotations` to see comments in context within the file.\",\n \"- Use `reply_to_comment` to acknowledge or ask clarifying questions.\",\n \"- Always `resolve_comment` after addressing feedback.\",\n ].join(\"\\n\"),\n },\n },\n ],\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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\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, readdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve, relative } from \"node:path\";\nimport { WebSocketServer, type WebSocket } from \"ws\";\nimport type { CommentStore } from \"./comment-store.js\";\nimport { FileWatcher } from \"./file-watcher.js\";\nimport { renderToHtml } from \"./utils.js\";\nimport type { WSClientMessage, WSServerMessage } from \"./types.js\";\n\n// In dev (tsx): import.meta.dirname is src/, so ../ui works.\n// In built (dist/bin/cowrite.js): import.meta.dirname is dist/bin/, so ../../ui.\n// We find ui/ by checking which path actually contains index.html.\nfunction findUiDir(): string {\n const dir = import.meta.dirname ?? new URL(\".\", import.meta.url).pathname;\n // Try common locations relative to this file\n const candidates = [\n join(dir, \"..\", \"ui\"), // dev: src/../ui\n join(dir, \"..\", \"..\", \"ui\"), // built: dist/bin/../../ui\n ];\n return candidates.find((d) => {\n try { return existsSync(join(d, \"index.html\")); } catch { return false; }\n }) ?? candidates[0];\n}\nconst UI_DIR = findUiDir();\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n};\n\nconst IGNORED_DIRS = new Set([\"node_modules\", \".git\", \"dist\", \".next\", \".cache\", \"coverage\", \"__pycache__\"]);\n\nexport function createPreviewServer(\n store: CommentStore,\n projectDir: string,\n port: number,\n initialFile?: string\n): { port: number; start: () => Promise<void>; stop: () => Promise<void> } {\n const clients = new Set<WebSocket>();\n const clientFiles = new Map<WebSocket, string>(); // ws -> absolute file path\n const watchers = new Map<string, FileWatcher>(); // absolute path -> watcher\n const watcherListeners = new Map<string, (...args: any[]) => void>(); // path -> change listener\n\n const resolvedProjectDir = resolve(projectDir);\n\n function isInsideProject(filePath: string): boolean {\n const resolved = resolve(resolvedProjectDir, filePath);\n return resolved.startsWith(resolvedProjectDir);\n }\n\n async function getOrCreateWatcher(absPath: string): Promise<FileWatcher> {\n let watcher = watchers.get(absPath);\n if (!watcher) {\n watcher = new FileWatcher(absPath);\n await watcher.start();\n watchers.set(absPath, watcher);\n\n // Subscribe to file changes and broadcast to relevant clients\n const listener = (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 for (const [ws, file] of clientFiles) {\n if (file === absPath) {\n send(ws, { type: \"file_update\", file: event.file, content: event.content, html });\n }\n }\n };\n watcher.on(\"change\", listener);\n watcherListeners.set(absPath, listener);\n }\n return watcher;\n }\n\n async function listFiles(dir: string, prefix = \"\"): Promise<string[]> {\n const files: string[] = [];\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || IGNORED_DIRS.has(entry.name)) continue;\n const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n const sub = await listFiles(join(dir, entry.name), relPath);\n files.push(...sub);\n } else {\n files.push(relPath);\n }\n }\n } catch {\n // Permission denied or gone — skip\n }\n return files;\n }\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 const filePath = join(UI_DIR, 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/files — list project files for the file picker\n if (pathname === \"/api/files\") {\n const files = await listFiles(resolvedProjectDir);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ files }));\n return;\n }\n\n // API: GET /api/state?file=... — state for a specific file\n if (pathname === \"/api/state\") {\n const fileParam = url.searchParams.get(\"file\");\n if (!fileParam) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing file parameter\" }));\n return;\n }\n const absPath = resolve(resolvedProjectDir, fileParam);\n if (!isInsideProject(absPath)) {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Path outside project\" }));\n return;\n }\n try {\n const content = await readFile(absPath, \"utf-8\");\n const html = renderToHtml(content, absPath);\n const comments = store.getForFile(absPath);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ file: absPath, content, html, comments }));\n } catch {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"File not found\" }));\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 // Prevent unhandled WSS errors from crashing the process (e.g. EADDRINUSE)\n wss.on(\"error\", () => {});\n\n wss.on(\"connection\", async (ws: WebSocket) => {\n clients.add(ws);\n\n // If there's an initial file (preview mode), auto-assign it\n if (initialFile) {\n const absPath = resolve(resolvedProjectDir, initialFile);\n await switchClientFile(ws, absPath);\n }\n\n ws.on(\"message\", (data) => {\n try {\n const msg: WSClientMessage = JSON.parse(data.toString());\n handleClientMessage(ws, 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 clientFiles.delete(ws);\n });\n });\n\n async function switchClientFile(ws: WebSocket, absPath: string): Promise<void> {\n if (!isInsideProject(absPath)) {\n send(ws, { type: \"error\", message: \"Path outside project\" });\n return;\n }\n try {\n const watcher = await getOrCreateWatcher(absPath);\n clientFiles.set(ws, absPath);\n const content = watcher.getContent();\n const html = renderToHtml(content, absPath);\n const comments = store.getForFile(absPath);\n send(ws, { type: \"file_update\", file: absPath, content, html });\n send(ws, { type: \"comments_update\", comments });\n } catch (err) {\n send(ws, { type: \"error\", message: `Cannot open file: ${err}` });\n }\n }\n\n function handleClientMessage(ws: WebSocket, msg: WSClientMessage): void {\n switch (msg.type) {\n case \"switch_file\": {\n const absPath = resolve(resolvedProjectDir, msg.file);\n switchClientFile(ws, absPath);\n break;\n }\n case \"comment_add\": {\n const file = clientFiles.get(ws);\n if (!file) break;\n store.add({\n file,\n offset: msg.offset,\n length: msg.length,\n selectedText: msg.selectedText,\n comment: msg.comment,\n });\n break;\n }\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 comment updates to clients viewing the affected file\n store.on(\"change\", (comment: any) => {\n for (const [ws, file] of clientFiles) {\n // If we know which file changed, only notify relevant clients\n // If comment is null (e.g. adjustOffsets, reload), notify all\n if (!comment || comment.file === file) {\n const comments = store.getForFile(file);\n send(ws, { type: \"comments_update\", comments });\n }\n }\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 let actualPort = port;\n\n return {\n get port() { return actualPort; },\n start: () => {\n const maxRetries = 10;\n const tryListen = (p: number, attempt: number): Promise<void> =>\n new Promise<void>((res, rej) => {\n const onError = (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" && attempt < maxRetries) {\n httpServer.removeListener(\"error\", onError);\n res(tryListen(p + 1, attempt + 1));\n } else {\n rej(err);\n }\n };\n httpServer.on(\"error\", onError);\n httpServer.listen(p, () => {\n httpServer.removeListener(\"error\", onError);\n actualPort = p;\n process.stderr.write(`Cowrite preview server running at http://localhost:${p}\\n`);\n res();\n });\n });\n return tryListen(port, 0);\n },\n stop: async () => {\n for (const client of clients) {\n client.close();\n }\n for (const [path, watcher] of watchers) {\n const listener = watcherListeners.get(path);\n if (listener) watcher.off(\"change\", listener);\n await watcher.stop();\n }\n watchers.clear();\n watcherListeners.clear();\n await new Promise<void>((resolvePromise, reject) => {\n httpServer.close((err) => {\n if (err) reject(err);\n else resolvePromise();\n });\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","join","existsSync","readFile","resolve","readFile","join","resolve","readFile","resolve","EventEmitter","join","resolve","readFile","join","existsSync","resolve"]}
|
|
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, join } from \"node:path\";\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from \"node:fs\";\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 { openBrowser } from \"../src/utils.js\";\n\nconst USAGE = `\ncowrite — Live commenting plugin for coding agent sessions\n\nUsage:\n cowrite preview <file> [--port N] Open browser preview for a specific file + start MCP server\n cowrite serve [--port N] Start MCP server + preview server (browse any file)\n cowrite open [--port N] Open the browser to the preview URL\n\nOptions:\n --port, -p Port for preview server (default: 3377)\n --no-open Don't auto-open the browser\n --help, -h Show this help\n`;\n\nfunction setupShutdown(store: CommentStore, preview: { stop: () => Promise<void>; [k: string]: any }) {\n let shuttingDown = false;\n const shutdown = () => {\n if (shuttingDown) {\n // Second signal — force exit immediately\n process.exit(1);\n }\n shuttingDown = true;\n process.stderr.write(\"Shutting down...\\n\");\n // Best-effort cleanup with a hard timeout\n Promise.allSettled([store.stopWatching(), preview.stop()])\n .finally(() => process.exit(0));\n setTimeout(() => process.exit(0), 2000);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nconst HOOK_SCRIPT = `#!/bin/bash\n# Auto-installed by cowrite — injects pending comments into Claude Code context.\n# Only outputs when there are pending comments. Silent otherwise.\nCOMMENTS_FILE=\"\\${CLAUDE_PROJECT_DIR:-.}/.cowrite-comments.json\"\nif [ ! -f \"$COMMENTS_FILE\" ] || [ ! -s \"$COMMENTS_FILE\" ]; then exit 0; fi\nPENDING=$(jq '[.[] | select(.status == \"pending\")] | length' \"$COMMENTS_FILE\" 2>/dev/null || echo 0)\nif [ \"$PENDING\" -eq 0 ]; then exit 0; fi\njq -r '[.[] | select(.status == \"pending\")] | \"COWRITE: \\\\(length) pending comment(s) from the live preview. For EACH comment: (1) make the requested change, (2) call reply_to_comment to explain what you did — the user reads replies in the browser, and (3) call resolve_comment.\\\\n\" + ([.[] | \"- [\\\\(.id)] File: \\\\(.file | split(\"/\") | last) | Text: \\\\\"\\\\(.selectedText)\\\\\" | Comment: \\\\(.comment)\"] | join(\"\\\\n\"))' \"$COMMENTS_FILE\" 2>/dev/null\n`;\n\nconst HOOK_ENTRY = {\n matcher: \"\",\n hooks: [{\n type: \"command\",\n command: `bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/inject-comments.sh\"`,\n }],\n};\n\nconst HOOK_SETTINGS = {\n hooks: {\n UserPromptSubmit: [HOOK_ENTRY],\n Stop: [HOOK_ENTRY],\n },\n};\n\nconst SKILL_REVIEW = `---\nname: review\ndescription: Check and address cowrite comments left by the user in the live preview\nuser_invocable: true\n---\n\n# Review Cowrite Comments\n\nCheck for any pending comments left in the Cowrite live preview and address them.\n\n## Steps\n\n1. Call the \\`get_pending_comments\\` tool to retrieve all unresolved comments.\n2. For each pending comment:\n a. Read the comment text and the selected text it refers to.\n b. Use \\`get_file_with_annotations\\` to see the comment in context.\n c. Make the requested change or reply explaining why you can't.\n d. Call \\`reply_to_comment\\` to acknowledge the feedback.\n e. Call \\`resolve_comment\\` to mark it as addressed.\n3. Summarize what was done.\n`;\n\nconst SKILL_WATCH = `---\nname: watch\ndescription: Start a background watcher for cowrite comments — does not block the main conversation\nuser_invocable: true\n---\n\n# Watch for Live Comments (Background)\n\nStart a background agent that watches for cowrite comments and handles them as they arrive. The main conversation stays free for other work.\n\n## Steps\n\n1. First, handle any existing pending comments:\n a. Call \\`get_pending_comments\\` to check for unresolved comments.\n b. For each pending comment, use \\`get_file_with_annotations\\` to see context, make the change, call \\`reply_to_comment\\`, and call \\`resolve_comment\\`.\n\n2. Then, launch a **background** watcher using the Task tool:\n - Use \\`subagent_type: \"general-purpose\"\\` and \\`run_in_background: true\\`\n - The background agent should call \\`wait_for_comment\\` in a loop\n - When a comment arrives, it handles it (read file, make change, reply, resolve)\n - On timeout, it re-calls \\`wait_for_comment\\` immediately\n - The loop continues until the user says stop\n\n3. Tell the user the background watcher is running and they can continue working normally. Comments will be handled automatically.\n`;\n\nfunction installClaudeIntegration(projectDir: string): void {\n const claudeDir = join(projectDir, \".claude\");\n const hooksDir = join(claudeDir, \"hooks\");\n const hookPath = join(hooksDir, \"inject-comments.sh\");\n const settingsPath = join(claudeDir, \"settings.json\");\n const reviewDir = join(claudeDir, \"skills\", \"review\");\n const watchDir = join(claudeDir, \"skills\", \"watch\");\n\n // Create directories\n for (const dir of [hooksDir, reviewDir, watchDir]) {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n }\n\n // Write hook script (always overwrite to keep in sync with cowrite version)\n writeFileSync(hookPath, HOOK_SCRIPT, \"utf-8\");\n chmodSync(hookPath, 0o755);\n\n // Write skills (always overwrite to keep in sync)\n writeFileSync(join(reviewDir, \"SKILL.md\"), SKILL_REVIEW, \"utf-8\");\n writeFileSync(join(watchDir, \"SKILL.md\"), SKILL_WATCH, \"utf-8\");\n\n // Merge cowrite hooks into settings.json (preserve existing settings)\n let settings: any = {};\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, \"utf-8\"));\n } catch {\n // Corrupt JSON — overwrite\n }\n }\n if (!settings.hooks) settings.hooks = {};\n let changed = false;\n for (const eventType of [\"UserPromptSubmit\", \"Stop\"] as const) {\n if (!Array.isArray(settings.hooks[eventType])) settings.hooks[eventType] = [];\n const hasCowriteHook = settings.hooks[eventType].some((entry: any) =>\n entry.hooks?.some((h: any) => h.command?.includes(\"inject-comments.sh\"))\n );\n if (!hasCowriteHook) {\n settings.hooks[eventType].push(HOOK_ENTRY);\n changed = true;\n }\n }\n if (changed) {\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\", \"utf-8\");\n }\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 \"no-open\": { type: \"boolean\", default: false },\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 const port = parseInt(values.port as string, 10);\n\n // Auto-install Claude Code hooks for comment propagation\n installClaudeIntegration(projectDir);\n\n if (command === \"serve\") {\n const store = new CommentStore(projectDir);\n await store.load();\n await store.startWatching();\n\n // Start preview server (non-fatal — MCP works even if port is taken)\n const preview = createPreviewServer(store, projectDir, port);\n let previewRunning = false;\n try {\n await preview.start();\n previewRunning = true;\n const previewUrl = `http://localhost:${preview.port}`;\n process.stderr.write(`Preview: ${previewUrl}\\n`);\n if (!values[\"no-open\"]) openBrowser(previewUrl);\n } catch (err) {\n process.stderr.write(`Preview server failed: ${err}\\n`);\n process.stderr.write(`MCP server will still run — comments sync via .cowrite-comments.json\\n`);\n }\n\n // Start MCP server on stdio\n const mcpServer = createMcpServer(store, projectDir, () => previewRunning ? preview.port : null);\n const transport = new StdioServerTransport();\n await mcpServer.connect(transport);\n\n process.stderr.write(`Cowrite MCP server running on stdio\\n`);\n\n setupShutdown(store, preview);\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\n const store = new CommentStore(projectDir);\n await store.load();\n await store.startWatching();\n\n // Start preview server (non-fatal — MCP works even if port is taken)\n const preview = createPreviewServer(store, projectDir, port, resolvedFile);\n let previewRunning2 = false;\n try {\n await preview.start();\n previewRunning2 = true;\n const previewUrl = `http://localhost:${preview.port}`;\n process.stderr.write(`Preview: ${previewUrl}\\n`);\n if (!values[\"no-open\"]) openBrowser(previewUrl);\n } catch (err) {\n process.stderr.write(`Preview server failed: ${err}\\n`);\n process.stderr.write(`MCP server will still run — comments sync via .cowrite-comments.json\\n`);\n }\n\n // Start MCP server on stdio\n const mcpServer = createMcpServer(store, projectDir, () => previewRunning2 ? preview.port : null);\n const transport = new StdioServerTransport();\n await mcpServer.connect(transport);\n\n process.stderr.write(`Cowrite MCP server running on stdio\\n`);\n\n setupShutdown(store, preview);\n } else if (command === \"open\") {\n const url = `http://localhost:${port}`;\n process.stderr.write(`Opening ${url}\\n`);\n openBrowser(url);\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 const oldIds = new Set(this.comments.keys());\n this.comments.clear();\n for (const c of arr) {\n this.comments.set(c.id, c);\n }\n // Emit \"new_comment\" for comments that didn't exist before\n for (const c of arr) {\n if (!oldIds.has(c.id)) {\n this.emit(\"new_comment\", c);\n }\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.emit(\"new_comment\", 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 { relative, resolve } from \"node:path\";\n\nexport function createMcpServer(store: CommentStore, projectDir: string, getPreviewPort?: () => number | null): McpServer {\n const server = new McpServer(\n { name: \"cowrite\", version: \"0.1.0\" },\n { capabilities: { logging: {} } },\n );\n\n // Tool: get_pending_comments\n const getPendingTool = server.tool(\n \"get_pending_comments\",\n \"Get comments from the live preview (0 pending). Call this first to catch comments posted before you started listening.\",\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 // Tool: wait_for_comment\n server.tool(\n \"wait_for_comment\",\n \"Block until a new comment is posted in the live preview, then return it. This is the primary way to receive real-time comments — call it again immediately after handling each comment to keep listening. If it times out, call it again.\",\n {\n timeout: z.number().optional().describe(\"Max seconds to wait (default: 30)\"),\n },\n ({ timeout }, { signal }: { signal?: AbortSignal }) => {\n const maxWait = (timeout ?? 30) * 1000;\n\n // Check for comments that arrived while no one was listening\n const pending = store.getAll({ status: \"pending\" });\n if (pending.length > 0) {\n const latest = pending[pending.length - 1];\n const file = relative(projectDir, latest.file);\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({ ...latest, file }, null, 2),\n }],\n };\n }\n\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n cleanup();\n const count = store.getAll({ status: \"pending\" }).length;\n resolve({\n content: [{ type: \"text\" as const, text: count > 0\n ? `Timeout, but ${count} pending comment(s) exist. Call get_pending_comments now.`\n : \"No new comments yet. Call wait_for_comment again to keep listening.\" }],\n });\n }, maxWait);\n\n const onComment = (comment: { id: string; file: string; selectedText: string; comment: string }) => {\n cleanup();\n const file = relative(projectDir, comment.file);\n resolve({\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({ ...comment, file }, null, 2),\n }],\n });\n };\n\n const onAbort = () => {\n cleanup();\n resolve({\n content: [{ type: \"text\" as const, text: \"Cancelled. Call wait_for_comment again to resume listening.\" }],\n });\n };\n\n const cleanup = () => {\n clearTimeout(timer);\n store.off(\"new_comment\", onComment);\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n store.on(\"new_comment\", onComment);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n // If already aborted before we set up\n if (signal?.aborted) {\n onAbort();\n }\n });\n }\n );\n\n // Tool: get_preview_url\n server.tool(\n \"get_preview_url\",\n \"Get the URL of the Cowrite live preview. Share this with the user so they can open it in their browser.\",\n {},\n async () => {\n const port = getPreviewPort?.();\n if (!port) {\n return {\n content: [{ type: \"text\" as const, text: \"Preview server is not running.\" }],\n isError: true,\n };\n }\n return {\n content: [{ type: \"text\" as const, text: `http://localhost:${port}` }],\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 if (!server.isConnected()) return;\n server.server.notification({\n method: \"notifications/resources/updated\",\n params: { uri: \"cowrite://comments\" },\n }).catch(() => {});\n });\n\n // --- Comment propagation signals ---\n // Only send signals when an MCP client is actually connected.\n // 1. Update tool description with pending count + sendToolListChanged\n // 2. sendLoggingMessage as additional context\n // Primary real-time mechanism is wait_for_comment via the /watch skill.\n store.on(\"new_comment\", (comment: { file: string; selectedText: string; comment: string }) => {\n if (!server.isConnected()) return;\n\n const count = store.getAll({ status: \"pending\" }).length;\n const file = relative(projectDir, comment.file);\n const selectedPreview = comment.selectedText.length > 80\n ? comment.selectedText.slice(0, 80) + \"...\"\n : comment.selectedText;\n\n // Signal 1: Update tool description + notify tool list changed\n try {\n getPendingTool.update({\n description: `Get comments from the live preview (${count} pending). Call this first to catch comments posted before you started listening.`,\n });\n server.sendToolListChanged();\n } catch {\n // Not connected or transport issue — skip\n }\n\n // Signal 2: Logging message\n server.sendLoggingMessage({\n level: \"warning\",\n data: `NEW COMMENT on ${file}: \"${comment.comment}\" (selected: \"${selectedPreview}\"). Call get_pending_comments to see it.`,\n }).catch(() => {});\n\n // Signal 3: Resource list changed\n server.server.notification({\n method: \"notifications/resources/list_changed\",\n }).catch(() => {});\n });\n\n // Update description count when comments are resolved\n store.on(\"change\", () => {\n const count = store.getAll({ status: \"pending\" }).length;\n try {\n getPendingTool.update({\n description: `Get comments from the live preview (${count} pending). Call this first to catch comments posted before you started listening.`,\n });\n } catch {\n // Ignore — may not be connected yet\n }\n });\n\n // Prompt: cowrite-workflow\n server.prompt(\n \"cowrite-workflow\",\n \"How to process live preview comments in a wait-handle-resolve loop\",\n () => ({\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: [\n \"You are monitoring a live code preview where users leave comments on selected text.\",\n \"\",\n \"Follow this loop:\",\n \"1. Call `get_pending_comments` to check for any comments already posted.\",\n \"2. Process each pending comment: read the file, make the requested change or reply, then call `resolve_comment`.\",\n \"3. Call `wait_for_comment` to block until the next comment arrives.\",\n \"4. When a comment arrives, process it the same way (step 2).\",\n \"5. Go back to step 3 and keep listening.\",\n \"\",\n \"Tips:\",\n \"- Use `get_file_with_annotations` to see comments in context within the file.\",\n \"- Use `reply_to_comment` to acknowledge or ask clarifying questions.\",\n \"- Always `resolve_comment` after addressing feedback.\",\n ].join(\"\\n\"),\n },\n },\n ],\n })\n );\n\n return server;\n}\n","import { exec } from \"node:child_process\";\nimport { marked } from \"marked\";\nimport type { Comment } from \"./types.js\";\n\n/**\n * Open a URL in the user's default browser (fire-and-forget).\n */\nexport function openBrowser(url: string): void {\n const cmd = process.platform === \"darwin\" ? `open \"${url}\"`\n : process.platform === \"win32\" ? `cmd /c start \"\" \"${url}\"`\n : `xdg-open \"${url}\"`;\n exec(cmd, (err) => {\n if (err) process.stderr.write(`Could not open browser: ${err.message}\\n`);\n });\n}\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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\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, readdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve, relative } from \"node:path\";\nimport { WebSocketServer, type WebSocket } from \"ws\";\nimport type { CommentStore } from \"./comment-store.js\";\nimport { FileWatcher } from \"./file-watcher.js\";\nimport { renderToHtml } from \"./utils.js\";\nimport type { WSClientMessage, WSServerMessage } from \"./types.js\";\n\n// In dev (tsx): import.meta.dirname is src/, so ../ui works.\n// In built (dist/bin/cowrite.js): import.meta.dirname is dist/bin/, so ../../ui.\n// We find ui/ by checking which path actually contains index.html.\nfunction findUiDir(): string {\n const dir = import.meta.dirname ?? new URL(\".\", import.meta.url).pathname;\n // Try common locations relative to this file\n const candidates = [\n join(dir, \"..\", \"ui\"), // dev: src/../ui\n join(dir, \"..\", \"..\", \"ui\"), // built: dist/bin/../../ui\n ];\n return candidates.find((d) => {\n try { return existsSync(join(d, \"index.html\")); } catch { return false; }\n }) ?? candidates[0];\n}\nconst UI_DIR = findUiDir();\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n};\n\nconst IGNORED_DIRS = new Set([\"node_modules\", \".git\", \"dist\", \".next\", \".cache\", \"coverage\", \"__pycache__\"]);\n\nexport function createPreviewServer(\n store: CommentStore,\n projectDir: string,\n port: number,\n initialFile?: string\n): { port: number; start: () => Promise<void>; stop: () => Promise<void> } {\n const clients = new Set<WebSocket>();\n const clientFiles = new Map<WebSocket, string>(); // ws -> absolute file path\n const watchers = new Map<string, FileWatcher>(); // absolute path -> watcher\n const watcherListeners = new Map<string, (...args: any[]) => void>(); // path -> change listener\n\n const resolvedProjectDir = resolve(projectDir);\n\n function isInsideProject(filePath: string): boolean {\n const resolved = resolve(resolvedProjectDir, filePath);\n return resolved.startsWith(resolvedProjectDir);\n }\n\n async function getOrCreateWatcher(absPath: string): Promise<FileWatcher> {\n let watcher = watchers.get(absPath);\n if (!watcher) {\n watcher = new FileWatcher(absPath);\n await watcher.start();\n watchers.set(absPath, watcher);\n\n // Subscribe to file changes and broadcast to relevant clients\n const listener = (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 for (const [ws, file] of clientFiles) {\n if (file === absPath) {\n send(ws, { type: \"file_update\", file: event.file, content: event.content, html });\n }\n }\n };\n watcher.on(\"change\", listener);\n watcherListeners.set(absPath, listener);\n }\n return watcher;\n }\n\n async function listFiles(dir: string, prefix = \"\"): Promise<string[]> {\n const files: string[] = [];\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || IGNORED_DIRS.has(entry.name)) continue;\n const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n const sub = await listFiles(join(dir, entry.name), relPath);\n files.push(...sub);\n } else {\n files.push(relPath);\n }\n }\n } catch {\n // Permission denied or gone — skip\n }\n return files;\n }\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 const filePath = join(UI_DIR, 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/files — list project files for the file picker\n if (pathname === \"/api/files\") {\n const files = await listFiles(resolvedProjectDir);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ files }));\n return;\n }\n\n // API: GET /api/state?file=... — state for a specific file\n if (pathname === \"/api/state\") {\n const fileParam = url.searchParams.get(\"file\");\n if (!fileParam) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing file parameter\" }));\n return;\n }\n const absPath = resolve(resolvedProjectDir, fileParam);\n if (!isInsideProject(absPath)) {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Path outside project\" }));\n return;\n }\n try {\n const content = await readFile(absPath, \"utf-8\");\n const html = renderToHtml(content, absPath);\n const comments = store.getForFile(absPath);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ file: absPath, content, html, comments }));\n } catch {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"File not found\" }));\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 // Prevent unhandled WSS errors from crashing the process (e.g. EADDRINUSE)\n wss.on(\"error\", () => {});\n\n wss.on(\"connection\", async (ws: WebSocket) => {\n clients.add(ws);\n\n // If there's an initial file (preview mode), auto-assign it\n if (initialFile) {\n const absPath = resolve(resolvedProjectDir, initialFile);\n await switchClientFile(ws, absPath);\n }\n\n ws.on(\"message\", (data) => {\n try {\n const msg: WSClientMessage = JSON.parse(data.toString());\n handleClientMessage(ws, 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 clientFiles.delete(ws);\n });\n });\n\n async function switchClientFile(ws: WebSocket, absPath: string): Promise<void> {\n if (!isInsideProject(absPath)) {\n send(ws, { type: \"error\", message: \"Path outside project\" });\n return;\n }\n try {\n const watcher = await getOrCreateWatcher(absPath);\n clientFiles.set(ws, absPath);\n const content = watcher.getContent();\n const html = renderToHtml(content, absPath);\n const comments = store.getForFile(absPath);\n send(ws, { type: \"file_update\", file: absPath, content, html });\n send(ws, { type: \"comments_update\", comments });\n } catch (err) {\n send(ws, { type: \"error\", message: `Cannot open file: ${err}` });\n }\n }\n\n function handleClientMessage(ws: WebSocket, msg: WSClientMessage): void {\n switch (msg.type) {\n case \"switch_file\": {\n const absPath = resolve(resolvedProjectDir, msg.file);\n switchClientFile(ws, absPath);\n break;\n }\n case \"comment_add\": {\n const file = clientFiles.get(ws);\n if (!file) break;\n store.add({\n file,\n offset: msg.offset,\n length: msg.length,\n selectedText: msg.selectedText,\n comment: msg.comment,\n });\n break;\n }\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 comment updates to clients viewing the affected file\n store.on(\"change\", (comment: any) => {\n for (const [ws, file] of clientFiles) {\n // If we know which file changed, only notify relevant clients\n // If comment is null (e.g. adjustOffsets, reload), notify all\n if (!comment || comment.file === file) {\n const comments = store.getForFile(file);\n send(ws, { type: \"comments_update\", comments });\n }\n }\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 let actualPort = port;\n\n return {\n get port() { return actualPort; },\n start: () => {\n const maxRetries = 10;\n const tryListen = (p: number, attempt: number): Promise<void> =>\n new Promise<void>((res, rej) => {\n const onError = (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" && attempt < maxRetries) {\n httpServer.removeListener(\"error\", onError);\n res(tryListen(p + 1, attempt + 1));\n } else {\n rej(err);\n }\n };\n httpServer.on(\"error\", onError);\n httpServer.listen(p, () => {\n httpServer.removeListener(\"error\", onError);\n actualPort = p;\n process.stderr.write(`Cowrite preview server running at http://localhost:${p}\\n`);\n res();\n });\n });\n return tryListen(port, 0);\n },\n stop: async () => {\n for (const client of clients) {\n client.close();\n }\n for (const [path, watcher] of watchers) {\n const listener = watcherListeners.get(path);\n if (listener) watcher.off(\"change\", listener);\n await watcher.stop();\n }\n watchers.clear();\n watcherListeners.clear();\n await new Promise<void>((resolvePromise, reject) => {\n httpServer.close((err) => {\n if (err) reject(err);\n else resolvePromise();\n });\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","join","existsSync","readFile","resolve","readFile","join","resolve","readFile","resolve","EventEmitter","join","resolve","readFile","join","existsSync","resolve"]}
|