@comty.io/core 0.1.8 → 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/README.md +32 -3
- package/comty-sdk.js +3891 -1655
- package/package.json +4 -2
- package/src/anchors.js +168 -403
- package/src/browser.js +3327 -1212
- package/src/comments.js +6 -6
- package/src/dev.mjs +317 -112
- package/src/inspect-markup.js +1 -1
- package/src/measurements.js +5 -4
- package/src/resilient-anchor.js +333 -0
- package/src/resilient-anchor.ts +551 -0
- package/src/style.js +60 -33
- package/src/vite.mjs +123 -38
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 =
|
|
100
|
+
const existingPopover = root.querySelector(
|
|
101
101
|
".comty-status-popover",
|
|
102
102
|
);
|
|
103
|
-
|
|
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
|
-
!
|
|
162
|
-
closeEvent.
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
3
|
-
import { spawn } from
|
|
4
|
-
import { readFile } from
|
|
5
|
-
import { realpathSync } from
|
|
6
|
-
import { resolve, delimiter } from
|
|
7
|
-
import { parseArgs } from
|
|
8
|
-
import { pathToFileURL } from
|
|
9
|
-
import { createCompanion, isLoopback } from
|
|
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({
|
|
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 (
|
|
14
|
-
|
|
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)
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
const
|
|
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);
|
|
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 {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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);
|
|
81
|
+
response.writeHead(403);
|
|
82
|
+
response.end();
|
|
83
|
+
return;
|
|
40
84
|
}
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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);
|
|
131
|
+
void companion.handle(request, response);
|
|
132
|
+
return;
|
|
48
133
|
}
|
|
49
134
|
const destination = new URL(upstream);
|
|
50
|
-
destination.pathname = url.pathname;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
incoming
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
response.writeHead(
|
|
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
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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(
|
|
90
|
-
|
|
91
|
-
|
|
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 {
|
|
94
|
-
|
|
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;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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(
|
|
106
|
-
upstreamSocket.on(
|
|
107
|
-
socket.on(
|
|
108
|
-
upstreamSocket.on(
|
|
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(
|
|
111
|
-
|
|
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) => {
|
|
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) {
|
|
117
|
-
|
|
118
|
-
|
|
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 ===
|
|
279
|
+
if (process.platform === "win32") child.kill(signal);
|
|
126
280
|
else process.kill(-child.pid, signal);
|
|
127
|
-
} catch (error) {
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error.code !== "ESRCH") throw error;
|
|
283
|
+
}
|
|
128
284
|
};
|
|
129
|
-
kill(
|
|
130
|
-
const force = setTimeout(() => kill(
|
|
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([
|
|
134
|
-
|
|
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,
|
|
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(
|
|
144
|
-
|
|
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 =>
|
|
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(
|
|
156
|
-
console.log(
|
|
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() !==
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 (
|
|
171
|
-
|
|
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
|
}
|
package/src/inspect-markup.js
CHANGED
|
@@ -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 (
|
|
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) {
|
package/src/measurements.js
CHANGED
|
@@ -154,10 +154,11 @@ export function createMeasurements({ root, isComtyElement, isElementTreeHidden,
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
function isHostEvent(event) {
|
|
157
|
-
|
|
158
|
-
if (!
|
|
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
|
|
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.
|
|
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);
|