@comty.io/core 0.1.9 → 0.1.10

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/src/comments.js CHANGED
@@ -97,10 +97,10 @@ export function createCommentThread(context) {
97
97
  statusMenuButton.disabled = comment.local_agent || !context.canManage;
98
98
  statusMenuButton.onclick = (event) => {
99
99
  event.stopPropagation();
100
- const existingPopover = document.querySelector(
100
+ const existingPopover = root.querySelector(
101
101
  ".comty-status-popover",
102
102
  );
103
- document
103
+ root
104
104
  .querySelectorAll(".comty-popover")
105
105
  .forEach((popover) => popover.remove());
106
106
  if (existingPopover) return;
@@ -158,8 +158,8 @@ export function createCommentThread(context) {
158
158
 
159
159
  const closeStatusPopover = (closeEvent) => {
160
160
  if (
161
- !popover.contains(closeEvent.target) &&
162
- closeEvent.target !== statusMenuButton
161
+ !closeEvent.composedPath().includes(popover) &&
162
+ !closeEvent.composedPath().includes(statusMenuButton)
163
163
  ) {
164
164
  popover.remove();
165
165
  document.removeEventListener("click", closeStatusPopover);
@@ -184,7 +184,7 @@ export function createCommentThread(context) {
184
184
  trigger,
185
185
  { onEdit, onDelete },
186
186
  ) => {
187
- document
187
+ root
188
188
  .querySelectorAll(".comty-entry-actions-popover")
189
189
  .forEach((popover) => popover.remove());
190
190
 
@@ -232,7 +232,7 @@ export function createCommentThread(context) {
232
232
  });
233
233
 
234
234
  const closeEntryActions = (event) => {
235
- if (!popover.contains(event.target) && event.target !== trigger) {
235
+ if (!event.composedPath().includes(popover) && !event.composedPath().includes(trigger)) {
236
236
  popover.remove();
237
237
  document.removeEventListener("click", closeEntryActions);
238
238
  }
package/src/dev.mjs CHANGED
@@ -1,172 +1,377 @@
1
1
  #!/usr/bin/env node
2
- import { createServer, request as httpRequest } from 'node:http';
3
- import { spawn } from 'node:child_process';
4
- import { readFile } from 'node:fs/promises';
5
- import { realpathSync } from 'node:fs';
6
- import { resolve, delimiter } from 'node:path';
7
- import { parseArgs } from 'node:util';
8
- import { pathToFileURL } from 'node:url';
9
- import { createCompanion, isLoopback } from './companion.mjs';
2
+ import { createServer, request as httpRequest } from "node:http";
3
+ import { spawn } from "node:child_process";
4
+ import { readFile } from "node:fs/promises";
5
+ import { realpathSync } from "node:fs";
6
+ import { resolve, delimiter } from "node:path";
7
+ import { parseArgs } from "node:util";
8
+ import { pathToFileURL } from "node:url";
9
+ import { createCompanion, isLoopback } from "./companion.mjs";
10
10
 
11
- export async function startDev({ cwd = process.cwd(), target = 'http://127.0.0.1:5173', port = 4387, provider = 'codex', model, projectKey, apiUrl, command = [] } = {}) {
11
+ export async function startDev({
12
+ cwd = process.cwd(),
13
+ target = "http://127.0.0.1:5173",
14
+ port = 4387,
15
+ provider = "codex",
16
+ model,
17
+ projectKey,
18
+ apiUrl,
19
+ command = [],
20
+ } = {}) {
12
21
  const upstream = new URL(target);
13
- if (upstream.protocol !== 'http:' || !isLoopback(upstream.hostname) || upstream.username || upstream.password || upstream.pathname !== '/' || upstream.search || upstream.hash) {
14
- throw new Error('--target must be a loopback HTTP origin, e.g. http://127.0.0.1:5173');
22
+ if (
23
+ upstream.protocol !== "http:" ||
24
+ !isLoopback(upstream.hostname) ||
25
+ upstream.username ||
26
+ upstream.password ||
27
+ upstream.pathname !== "/" ||
28
+ upstream.search ||
29
+ upstream.hash
30
+ ) {
31
+ throw new Error(
32
+ "--target must be a loopback HTTP origin, e.g. http://127.0.0.1:5173",
33
+ );
15
34
  }
16
- if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('Invalid --port');
17
- const runtime = await readFile(new URL('./browser.js', import.meta.url));
18
- const style = await readFile(new URL('./style.js', import.meta.url));
19
- const anchors = await readFile(new URL('./anchors.js', import.meta.url));
20
- const capture = await readFile(new URL('./capture.js', import.meta.url));
21
- const captureEngine = await readFile(new URL('./capture-engine.js', import.meta.url));
22
- const captureResources = await readFile(new URL('./capture-resources.js', import.meta.url));
23
- const comments = await readFile(new URL('./comments.js', import.meta.url));
24
- const measurements = await readFile(new URL('./measurements.js', import.meta.url));
25
- const markup = await readFile(new URL('./inspect-markup.js', import.meta.url));
35
+ if (!Number.isInteger(port) || port < 0 || port > 65535)
36
+ throw new Error("Invalid --port");
37
+ const runtime = await readFile(new URL("./browser.js", import.meta.url));
38
+ const style = await readFile(new URL("./style.js", import.meta.url));
39
+ const anchors = await readFile(new URL("./anchors.js", import.meta.url));
40
+ const resilientAnchors = await readFile(
41
+ new URL("./resilient-anchor.js", import.meta.url),
42
+ );
43
+ const capture = await readFile(new URL("./capture.js", import.meta.url));
44
+ const captureEngine = await readFile(
45
+ new URL("./capture-engine.js", import.meta.url),
46
+ );
47
+ const captureResources = await readFile(
48
+ new URL("./capture-resources.js", import.meta.url),
49
+ );
50
+ const comments = await readFile(new URL("./comments.js", import.meta.url));
51
+ const measurements = await readFile(
52
+ new URL("./measurements.js", import.meta.url),
53
+ );
54
+ const markup = await readFile(
55
+ new URL("./inspect-markup.js", import.meta.url),
56
+ );
26
57
  let companion, origin, child, closing, finished;
27
58
  const sockets = new Set();
28
59
  const proxies = new Set();
29
60
  const server = createServer((request, response) => {
30
61
  if (!origin || request.headers.host !== new URL(origin).host) {
31
- response.writeHead(403); response.end('Comty is available on its loopback URL only'); return;
62
+ response.writeHead(403);
63
+ response.end("Comty is available on its loopback URL only");
64
+ return;
32
65
  }
33
66
  let url;
34
- try { url = new URL(request.url, origin); }
35
- catch { response.writeHead(400); response.end('Invalid request target'); return; }
36
- if (url.origin !== origin) { response.writeHead(400); response.end('Invalid request target'); return; }
37
- if (url.pathname.startsWith('/__comty/')) {
67
+ try {
68
+ url = new URL(request.url, origin);
69
+ } catch {
70
+ response.writeHead(400);
71
+ response.end("Invalid request target");
72
+ return;
73
+ }
74
+ if (url.origin !== origin) {
75
+ response.writeHead(400);
76
+ response.end("Invalid request target");
77
+ return;
78
+ }
79
+ if (url.pathname.startsWith("/__comty/")) {
38
80
  if (request.headers.origin && request.headers.origin !== origin) {
39
- response.writeHead(403); response.end(); return;
81
+ response.writeHead(403);
82
+ response.end();
83
+ return;
40
84
  }
41
- if (request.method === 'GET' && ['/__comty/browser.js', '/__comty/style.js', '/__comty/anchors.js', '/__comty/capture.js', '/__comty/capture-engine.js', '/__comty/capture-resources.js', '/__comty/comments.js', '/__comty/measurements.js', '/__comty/inspect-markup.js', '/__comty/start.js'].includes(url.pathname)) {
42
- response.writeHead(200, { 'Content-Type': 'text/javascript', 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff' });
43
- response.end(url.pathname.endsWith('/browser.js') ? runtime : url.pathname.endsWith('/style.js') ? style : url.pathname.endsWith('/anchors.js') ? anchors : url.pathname.endsWith('/capture.js') ? capture : url.pathname.endsWith('/capture-engine.js') ? captureEngine : url.pathname.endsWith('/capture-resources.js') ? captureResources : url.pathname.endsWith('/comments.js') ? comments : url.pathname.endsWith('/measurements.js') ? measurements : url.pathname.endsWith('/inspect-markup.js') ? markup :
44
- `import { initComty } from '/__comty/browser.js';\ninitComty(${JSON.stringify({ projectKey, apiUrl, agent: { url: `${origin}/__comty`, token: companion.token } })}).catch(console.error);`);
85
+ if (
86
+ request.method === "GET" &&
87
+ [
88
+ "/__comty/browser.js",
89
+ "/__comty/style.js",
90
+ "/__comty/anchors.js",
91
+ "/__comty/resilient-anchor.js",
92
+ "/__comty/capture.js",
93
+ "/__comty/capture-engine.js",
94
+ "/__comty/capture-resources.js",
95
+ "/__comty/comments.js",
96
+ "/__comty/measurements.js",
97
+ "/__comty/inspect-markup.js",
98
+ "/__comty/start.js",
99
+ ].includes(url.pathname)
100
+ ) {
101
+ response.writeHead(200, {
102
+ "Content-Type": "text/javascript",
103
+ "Cache-Control": "no-store",
104
+ "X-Content-Type-Options": "nosniff",
105
+ });
106
+ response.end(
107
+ url.pathname.endsWith("/browser.js")
108
+ ? runtime
109
+ : url.pathname.endsWith("/style.js")
110
+ ? style
111
+ : url.pathname.endsWith("/anchors.js")
112
+ ? anchors
113
+ : url.pathname.endsWith("/resilient-anchor.js")
114
+ ? resilientAnchors
115
+ : url.pathname.endsWith("/capture.js")
116
+ ? capture
117
+ : url.pathname.endsWith("/capture-engine.js")
118
+ ? captureEngine
119
+ : url.pathname.endsWith("/capture-resources.js")
120
+ ? captureResources
121
+ : url.pathname.endsWith("/comments.js")
122
+ ? comments
123
+ : url.pathname.endsWith("/measurements.js")
124
+ ? measurements
125
+ : url.pathname.endsWith("/inspect-markup.js")
126
+ ? markup
127
+ : `import { initComty } from '/__comty/browser.js';\ninitComty(${JSON.stringify({ projectKey, apiUrl, agent: { url: `${origin}/__comty`, token: companion.token } })}).catch(console.error);`,
128
+ );
45
129
  return;
46
130
  }
47
- void companion.handle(request, response); return;
131
+ void companion.handle(request, response);
132
+ return;
48
133
  }
49
134
  const destination = new URL(upstream);
50
- destination.pathname = url.pathname; destination.search = url.search;
51
- const proxy = httpRequest(destination, {
52
- method: request.method,
53
- headers: { ...request.headers, host: upstream.host, 'accept-encoding': 'identity' },
54
- }, incoming => {
55
- const headers = { ...incoming.headers };
56
- if (headers.location?.startsWith(upstream.origin)) headers.location = headers.location.replace(upstream.origin, origin);
57
- if (!headers['content-type']?.includes('text/html') || request.method === 'HEAD') {
58
- response.writeHead(incoming.statusCode, headers); incoming.pipe(response); return;
59
- }
60
- // ponytail: buffer development HTML for injection; use the Vite integration for streamed HTML.
61
- const chunks = []; let length = 0;
62
- incoming.on('data', chunk => {
63
- length += chunk.length;
64
- if (length > 8 * 1024 * 1024) incoming.destroy(new Error('HTML exceeds the 8 MiB development limit'));
65
- else chunks.push(chunk);
66
- });
67
- incoming.on('error', error => { if (!response.headersSent) response.writeHead(502); response.end(error.message); });
68
- incoming.on('end', () => {
69
- if (incoming.headers['content-encoding'] && incoming.headers['content-encoding'] !== 'identity') {
70
- response.writeHead(502); response.end('Comty requires uncompressed development HTML'); return;
135
+ destination.pathname = url.pathname;
136
+ destination.search = url.search;
137
+ const proxy = httpRequest(
138
+ destination,
139
+ {
140
+ method: request.method,
141
+ headers: {
142
+ ...request.headers,
143
+ host: upstream.host,
144
+ "accept-encoding": "identity",
145
+ },
146
+ },
147
+ (incoming) => {
148
+ const headers = { ...incoming.headers };
149
+ if (headers.location?.startsWith(upstream.origin))
150
+ headers.location = headers.location.replace(upstream.origin, origin);
151
+ if (
152
+ !headers["content-type"]?.includes("text/html") ||
153
+ request.method === "HEAD"
154
+ ) {
155
+ response.writeHead(incoming.statusCode, headers);
156
+ incoming.pipe(response);
157
+ return;
71
158
  }
72
- const html = Buffer.concat(chunks).toString('utf8');
73
- const script = '<script type="module" src="/__comty/start.js"></script>';
74
- const injected = /<head(?:\s[^>]*)?>/i.test(html) ? html.replace(/<head(?:\s[^>]*)?>/i, match => match + script) : script + html;
75
- delete headers['content-length']; delete headers.etag; delete headers['content-encoding'];
76
- headers['cache-control'] = 'no-store';
77
- response.writeHead(incoming.statusCode, headers); response.end(injected);
78
- });
159
+ // ponytail: buffer development HTML for injection; use the Vite integration for streamed HTML.
160
+ const chunks = [];
161
+ let length = 0;
162
+ incoming.on("data", (chunk) => {
163
+ length += chunk.length;
164
+ if (length > 8 * 1024 * 1024)
165
+ incoming.destroy(
166
+ new Error("HTML exceeds the 8 MiB development limit"),
167
+ );
168
+ else chunks.push(chunk);
169
+ });
170
+ incoming.on("error", (error) => {
171
+ if (!response.headersSent) response.writeHead(502);
172
+ response.end(error.message);
173
+ });
174
+ incoming.on("end", () => {
175
+ if (
176
+ incoming.headers["content-encoding"] &&
177
+ incoming.headers["content-encoding"] !== "identity"
178
+ ) {
179
+ response.writeHead(502);
180
+ response.end("Comty requires uncompressed development HTML");
181
+ return;
182
+ }
183
+ const html = Buffer.concat(chunks).toString("utf8");
184
+ const script =
185
+ '<script type="module" src="/__comty/start.js"></script>';
186
+ const injected = /<head(?:\s[^>]*)?>/i.test(html)
187
+ ? html.replace(/<head(?:\s[^>]*)?>/i, (match) => match + script)
188
+ : script + html;
189
+ delete headers["content-length"];
190
+ delete headers.etag;
191
+ delete headers["content-encoding"];
192
+ headers["cache-control"] = "no-store";
193
+ response.writeHead(incoming.statusCode, headers);
194
+ response.end(injected);
195
+ });
196
+ },
197
+ );
198
+ proxies.add(proxy);
199
+ proxy.on("close", () => proxies.delete(proxy));
200
+ proxy.on("error", () => {
201
+ if (!response.headersSent)
202
+ response.writeHead(502, { "Content-Type": "text/plain" });
203
+ response.end(
204
+ `Development server unavailable at ${upstream.origin}. Check its terminal output and reload.`,
205
+ );
79
206
  });
80
- proxies.add(proxy); proxy.on('close', () => proxies.delete(proxy));
81
- proxy.on('error', () => {
82
- if (!response.headersSent) response.writeHead(502, { 'Content-Type': 'text/plain' });
83
- response.end(`Development server unavailable at ${upstream.origin}. Check its terminal output and reload.`);
84
- });
85
- request.on('aborted', () => proxy.destroy());
86
- response.on('close', () => proxy.destroy());
207
+ request.on("aborted", () => proxy.destroy());
208
+ response.on("close", () => proxy.destroy());
87
209
  request.pipe(proxy);
88
210
  });
89
- server.on('connection', socket => { sockets.add(socket); socket.on('close', () => sockets.delete(socket)); });
90
- server.on('upgrade', (request, socket, head) => {
91
- if (request.headers.host !== new URL(origin).host || (request.headers.origin && request.headers.origin !== origin)) return socket.destroy();
211
+ server.on("connection", (socket) => {
212
+ sockets.add(socket);
213
+ socket.on("close", () => sockets.delete(socket));
214
+ });
215
+ server.on("upgrade", (request, socket, head) => {
216
+ if (
217
+ request.headers.host !== new URL(origin).host ||
218
+ (request.headers.origin && request.headers.origin !== origin)
219
+ )
220
+ return socket.destroy();
92
221
  let url;
93
- try { url = new URL(request.url, origin); } catch { return socket.destroy(); }
94
- if (url.origin !== origin || url.pathname.startsWith('/__comty/')) return socket.destroy();
222
+ try {
223
+ url = new URL(request.url, origin);
224
+ } catch {
225
+ return socket.destroy();
226
+ }
227
+ if (url.origin !== origin || url.pathname.startsWith("/__comty/"))
228
+ return socket.destroy();
95
229
  const destination = new URL(upstream);
96
- destination.pathname = url.pathname; destination.search = url.search;
97
- const proxy = httpRequest(destination, { headers: { ...request.headers, host: upstream.host } });
98
- proxies.add(proxy); proxy.on('close', () => proxies.delete(proxy));
99
- proxy.on('upgrade', (response, upstreamSocket, upstreamHead) => {
100
- sockets.add(upstreamSocket); upstreamSocket.on('close', () => sockets.delete(upstreamSocket));
101
- socket.write(`HTTP/1.1 ${response.statusCode} ${response.statusMessage}\r\n${response.rawHeaders.reduce((text, value, index, all) => index % 2 ? text : text + value + ': ' + all[index + 1] + '\r\n', '')}\r\n`);
230
+ destination.pathname = url.pathname;
231
+ destination.search = url.search;
232
+ const proxy = httpRequest(destination, {
233
+ headers: { ...request.headers, host: upstream.host },
234
+ });
235
+ proxies.add(proxy);
236
+ proxy.on("close", () => proxies.delete(proxy));
237
+ proxy.on("upgrade", (response, upstreamSocket, upstreamHead) => {
238
+ sockets.add(upstreamSocket);
239
+ upstreamSocket.on("close", () => sockets.delete(upstreamSocket));
240
+ socket.write(
241
+ `HTTP/1.1 ${response.statusCode} ${response.statusMessage}\r\n${response.rawHeaders.reduce((text, value, index, all) => (index % 2 ? text : text + value + ": " + all[index + 1] + "\r\n"), "")}\r\n`,
242
+ );
102
243
  if (upstreamHead.length) socket.write(upstreamHead);
103
244
  if (head.length) upstreamSocket.write(head);
104
245
  socket.pipe(upstreamSocket).pipe(socket);
105
- socket.on('error', () => upstreamSocket.destroy());
106
- upstreamSocket.on('error', () => socket.destroy());
107
- socket.on('close', () => upstreamSocket.destroy());
108
- upstreamSocket.on('close', () => socket.destroy());
246
+ socket.on("error", () => upstreamSocket.destroy());
247
+ upstreamSocket.on("error", () => socket.destroy());
248
+ socket.on("close", () => upstreamSocket.destroy());
249
+ upstreamSocket.on("close", () => socket.destroy());
109
250
  });
110
- proxy.on('response', response => { response.resume(); socket.destroy(); });
111
- proxy.on('error', () => socket.destroy());
251
+ proxy.on("response", (response) => {
252
+ response.resume();
253
+ socket.destroy();
254
+ });
255
+ proxy.on("error", () => socket.destroy());
112
256
  proxy.end();
113
257
  });
114
- await new Promise((resolve, reject) => { server.once('error', reject); server.listen(port, '127.0.0.1', resolve); });
258
+ await new Promise((resolve, reject) => {
259
+ server.once("error", reject);
260
+ server.listen(port, "127.0.0.1", resolve);
261
+ });
115
262
  origin = `http://127.0.0.1:${server.address().port}`;
116
- if (new URL(origin).port === upstream.port) { server.close(); throw new Error('Comty and the application must use different ports'); }
117
- try { companion = createCompanion({ cwd, origin, provider, model }); }
118
- catch (error) { server.close(); throw error; }
263
+ if (new URL(origin).port === upstream.port) {
264
+ server.close();
265
+ throw new Error("Comty and the application must use different ports");
266
+ }
267
+ try {
268
+ companion = createCompanion({ cwd, origin, provider, model });
269
+ } catch (error) {
270
+ server.close();
271
+ throw error;
272
+ }
119
273
  async function close() {
120
274
  if (closing) return closing;
121
275
  closing = (async () => {
122
- const kill = signal => {
276
+ const kill = (signal) => {
123
277
  if (!child?.pid) return;
124
278
  try {
125
- if (process.platform === 'win32') child.kill(signal);
279
+ if (process.platform === "win32") child.kill(signal);
126
280
  else process.kill(-child.pid, signal);
127
- } catch (error) { if (error.code !== 'ESRCH') throw error; }
281
+ } catch (error) {
282
+ if (error.code !== "ESRCH") throw error;
283
+ }
128
284
  };
129
- kill('SIGTERM');
130
- const force = setTimeout(() => kill('SIGKILL'), 2000);
285
+ kill("SIGTERM");
286
+ const force = setTimeout(() => kill("SIGKILL"), 2000);
131
287
  for (const proxy of proxies) proxy.destroy();
132
288
  for (const socket of sockets) socket.destroy();
133
- await Promise.all([companion.close(), new Promise(resolve => server.close(resolve)),
134
- child && child.exitCode === null && child.signalCode === null ? new Promise(resolve => child.once('close', resolve)) : Promise.resolve()]);
289
+ await Promise.all([
290
+ companion.close(),
291
+ new Promise((resolve) => server.close(resolve)),
292
+ child && child.exitCode === null && child.signalCode === null
293
+ ? new Promise((resolve) => child.once("close", resolve))
294
+ : Promise.resolve(),
295
+ ]);
135
296
  clearTimeout(force);
136
297
  })();
137
298
  return closing;
138
299
  }
139
300
  if (command.length) {
140
301
  child = spawn(command[0], command.slice(1), {
141
- cwd, stdio: 'inherit', detached: process.platform !== 'win32', env: { ...process.env, PATH: `${resolve(cwd, 'node_modules/.bin')}${delimiter}${process.env.PATH || ''}` },
302
+ cwd,
303
+ stdio: "inherit",
304
+ detached: process.platform !== "win32",
305
+ env: {
306
+ ...process.env,
307
+ PATH: `${resolve(cwd, "node_modules/.bin")}${delimiter}${process.env.PATH || ""}`,
308
+ },
142
309
  });
143
- child.on('error', error => { console.error(`Comty could not start ${command[0]}: ${error.message}`); });
144
- finished = new Promise(resolve => child.once('close', code => resolve(Number.isInteger(code) && code >= 0 ? code : 1))).then(async code => {
310
+ child.on("error", (error) => {
311
+ console.error(`Comty could not start ${command[0]}: ${error.message}`);
312
+ });
313
+ finished = new Promise((resolve) =>
314
+ child.once("close", (code) =>
315
+ resolve(Number.isInteger(code) && code >= 0 ? code : 1),
316
+ ),
317
+ ).then(async (code) => {
145
318
  await close();
146
319
  return code;
147
320
  });
148
- finished.catch(error => console.error(`Comty shutdown failed: ${error.message}`));
321
+ finished.catch((error) =>
322
+ console.error(`Comty shutdown failed: ${error.message}`),
323
+ );
149
324
  }
150
325
  return { origin, close, finished };
151
326
  }
152
327
 
153
328
  async function main() {
154
329
  const args = process.argv.slice(2);
155
- if (!args.length || args.includes('--help')) {
156
- console.log('Usage: comty dev [--agent codex|claude|cursor|gemini|grok] [--model MODEL] [--target http://127.0.0.1:5173] [--port 4387] [--project-key KEY --api-url URL] -- vite --strictPort'); return;
330
+ if (!args.length || args.includes("--help")) {
331
+ console.log(
332
+ "Usage: comty dev [--agent codex|claude|cursor|gemini|grok] [--model MODEL] [--target http://127.0.0.1:5173] [--port 4387] [--project-key KEY --api-url URL] -- vite --strictPort",
333
+ );
334
+ return;
157
335
  }
158
- if (args.shift() !== 'dev') throw new Error('Expected comty dev. Use --help for usage.');
159
- const split = args.indexOf('--');
160
- const { values } = parseArgs({ args: split < 0 ? args : args.slice(0, split), options: {
161
- target: { type: 'string' }, port: { type: 'string' }, agent: { type: 'string' }, model: { type: 'string' }, 'project-key': { type: 'string' }, 'api-url': { type: 'string' },
162
- } });
163
- const session = await startDev({ target: values.target, port: values.port === undefined ? 4387 : Number(values.port), provider: values.agent, model: values.model,
164
- projectKey: values['project-key'], apiUrl: values['api-url'], command: split < 0 ? [] : args.slice(split + 1) });
165
- console.log(`Comty: ${session.origin} — open this URL to annotate with the local agent.`);
166
- for (const signal of ['SIGINT', 'SIGTERM']) process.once(signal, () => { void session.close().catch(console.error); });
336
+ if (args.shift() !== "dev")
337
+ throw new Error("Expected comty dev. Use --help for usage.");
338
+ const split = args.indexOf("--");
339
+ const { values } = parseArgs({
340
+ args: split < 0 ? args : args.slice(0, split),
341
+ options: {
342
+ target: { type: "string" },
343
+ port: { type: "string" },
344
+ agent: { type: "string" },
345
+ model: { type: "string" },
346
+ "project-key": { type: "string" },
347
+ "api-url": { type: "string" },
348
+ },
349
+ });
350
+ const session = await startDev({
351
+ target: values.target,
352
+ port: values.port === undefined ? 4387 : Number(values.port),
353
+ provider: values.agent,
354
+ model: values.model,
355
+ projectKey: values["project-key"],
356
+ apiUrl: values["api-url"],
357
+ command: split < 0 ? [] : args.slice(split + 1),
358
+ });
359
+ console.log(
360
+ `Comty: ${session.origin} — open this URL to annotate with the local agent.`,
361
+ );
362
+ for (const signal of ["SIGINT", "SIGTERM"])
363
+ process.once(signal, () => {
364
+ void session.close().catch(console.error);
365
+ });
167
366
  if (session.finished) process.exitCode = await session.finished;
168
367
  }
169
368
 
170
- if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
171
- main().catch(error => { console.error(`Comty: ${error.message}`); process.exitCode = 1; });
369
+ if (
370
+ process.argv[1] &&
371
+ import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href
372
+ ) {
373
+ main().catch((error) => {
374
+ console.error(`Comty: ${error.message}`);
375
+ process.exitCode = 1;
376
+ });
172
377
  }
@@ -135,7 +135,7 @@ export function createInspectMarkup({ root, icon, onChange, capture, onError })
135
135
  colorField.setAttribute('aria-description', `Saturation ${Math.round(saturation * 100)}%, brightness ${Math.round(brightness * 100)}%. Left and right arrows adjust saturation. Up and down arrows adjust brightness.`);
136
136
  hueInput.value = String(Math.round(hue));
137
137
  hueInput.setAttribute('aria-valuetext', `${Math.round(hue)} degrees`);
138
- if (document.activeElement !== hexInput) { hexInput.value = color.toUpperCase(); hexInput.removeAttribute('aria-invalid'); }
138
+ if (root.getRootNode().activeElement !== hexInput) { hexInput.value = color.toUpperCase(); hexInput.removeAttribute('aria-invalid'); }
139
139
  }
140
140
  picker.onclick = () => { pickerOpen = !pickerOpen; sync(); };
141
141
  function moveColor(event) {
@@ -154,10 +154,11 @@ export function createMeasurements({ root, isComtyElement, isElementTreeHidden,
154
154
  }
155
155
  }
156
156
  function isHostEvent(event) {
157
- if (!enabled || isInteractionBlocked() || !(event.target instanceof Element)) return false;
158
- if (!isComtyElement(event.target)) return true;
157
+ const target = event.composedPath()[0];
158
+ if (!enabled || isInteractionBlocked() || !(target instanceof Element)) return false;
159
+ if (!isComtyElement(target)) return true;
159
160
  // The outer ruler strip must still let us anchor to the page's top/left edge.
160
- return event.target.matches(".comty-ruler") && (event.clientX + scrollX <= 10 || event.clientY + scrollY <= 10);
161
+ return target.matches(".comty-ruler") && (event.clientX + scrollX <= 10 || event.clientY + scrollY <= 10);
161
162
  }
162
163
  document.addEventListener("pointermove", event => {
163
164
  if (!enabled) return;
@@ -192,7 +193,7 @@ export function createMeasurements({ root, isComtyElement, isElementTreeHidden,
192
193
  if (isHostEvent(event)) { event.preventDefault(); event.stopImmediatePropagation(); }
193
194
  }, true);
194
195
  document.addEventListener("keydown", event => {
195
- if (!enabled || isInteractionBlocked() || event.key !== "Escape" || event.target.closest?.(".comty-auth-dialog, input, textarea, [contenteditable=true]")) return;
196
+ if (!enabled || isInteractionBlocked() || event.key !== "Escape" || event.composedPath()[0].closest?.(".comty-auth-dialog, input, textarea, [contenteditable=true]")) return;
196
197
  event.preventDefault(); event.stopImmediatePropagation();
197
198
  if (drag) cancel(); else onExit();
198
199
  }, true);