@comty.io/core 0.1.2 → 0.1.4

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/dev.mjs DELETED
@@ -1,169 +0,0 @@
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';
10
-
11
- export async function startDev({ cwd = process.cwd(), target = 'http://127.0.0.1:5173', port = 4387, provider = 'codex', model, projectKey, apiUrl, command = [] } = {}) {
12
- 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');
15
- }
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 comments = await readFile(new URL('./comments.js', import.meta.url));
22
- const measurements = await readFile(new URL('./measurements.js', import.meta.url));
23
- let companion, origin, child, closing, finished;
24
- const sockets = new Set();
25
- const proxies = new Set();
26
- const server = createServer((request, response) => {
27
- if (!origin || request.headers.host !== new URL(origin).host) {
28
- response.writeHead(403); response.end('Comty is available on its loopback URL only'); return;
29
- }
30
- let url;
31
- try { url = new URL(request.url, origin); }
32
- catch { response.writeHead(400); response.end('Invalid request target'); return; }
33
- if (url.origin !== origin) { response.writeHead(400); response.end('Invalid request target'); return; }
34
- if (url.pathname.startsWith('/__comty/')) {
35
- if (request.headers.origin && request.headers.origin !== origin) {
36
- response.writeHead(403); response.end(); return;
37
- }
38
- if (request.method === 'GET' && ['/__comty/browser.js', '/__comty/style.js', '/__comty/anchors.js', '/__comty/capture.js', '/__comty/comments.js', '/__comty/measurements.js', '/__comty/start.js'].includes(url.pathname)) {
39
- response.writeHead(200, { 'Content-Type': 'text/javascript', 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff' });
40
- 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('/comments.js') ? comments : url.pathname.endsWith('/measurements.js') ? measurements :
41
- `import { initComty } from '/__comty/browser.js';\ninitComty(${JSON.stringify({ projectKey, apiUrl, agent: { url: `${origin}/__comty`, token: companion.token } })}).catch(console.error);`);
42
- return;
43
- }
44
- void companion.handle(request, response); return;
45
- }
46
- const destination = new URL(upstream);
47
- destination.pathname = url.pathname; destination.search = url.search;
48
- const proxy = httpRequest(destination, {
49
- method: request.method,
50
- headers: { ...request.headers, host: upstream.host, 'accept-encoding': 'identity' },
51
- }, incoming => {
52
- const headers = { ...incoming.headers };
53
- if (headers.location?.startsWith(upstream.origin)) headers.location = headers.location.replace(upstream.origin, origin);
54
- if (!headers['content-type']?.includes('text/html') || request.method === 'HEAD') {
55
- response.writeHead(incoming.statusCode, headers); incoming.pipe(response); return;
56
- }
57
- // ponytail: buffer development HTML for injection; use the Vite integration for streamed HTML.
58
- const chunks = []; let length = 0;
59
- incoming.on('data', chunk => {
60
- length += chunk.length;
61
- if (length > 8 * 1024 * 1024) incoming.destroy(new Error('HTML exceeds the 8 MiB development limit'));
62
- else chunks.push(chunk);
63
- });
64
- incoming.on('error', error => { if (!response.headersSent) response.writeHead(502); response.end(error.message); });
65
- incoming.on('end', () => {
66
- if (incoming.headers['content-encoding'] && incoming.headers['content-encoding'] !== 'identity') {
67
- response.writeHead(502); response.end('Comty requires uncompressed development HTML'); return;
68
- }
69
- const html = Buffer.concat(chunks).toString('utf8');
70
- const script = '<script type="module" src="/__comty/start.js"></script>';
71
- const injected = /<head(?:\s[^>]*)?>/i.test(html) ? html.replace(/<head(?:\s[^>]*)?>/i, match => match + script) : script + html;
72
- delete headers['content-length']; delete headers.etag; delete headers['content-encoding'];
73
- headers['cache-control'] = 'no-store';
74
- response.writeHead(incoming.statusCode, headers); response.end(injected);
75
- });
76
- });
77
- proxies.add(proxy); proxy.on('close', () => proxies.delete(proxy));
78
- proxy.on('error', () => {
79
- if (!response.headersSent) response.writeHead(502, { 'Content-Type': 'text/plain' });
80
- response.end(`Development server unavailable at ${upstream.origin}. Check its terminal output and reload.`);
81
- });
82
- request.on('aborted', () => proxy.destroy());
83
- response.on('close', () => proxy.destroy());
84
- request.pipe(proxy);
85
- });
86
- server.on('connection', socket => { sockets.add(socket); socket.on('close', () => sockets.delete(socket)); });
87
- server.on('upgrade', (request, socket, head) => {
88
- if (request.headers.host !== new URL(origin).host || (request.headers.origin && request.headers.origin !== origin)) return socket.destroy();
89
- let url;
90
- try { url = new URL(request.url, origin); } catch { return socket.destroy(); }
91
- if (url.origin !== origin || url.pathname.startsWith('/__comty/')) return socket.destroy();
92
- const destination = new URL(upstream);
93
- destination.pathname = url.pathname; destination.search = url.search;
94
- const proxy = httpRequest(destination, { headers: { ...request.headers, host: upstream.host } });
95
- proxies.add(proxy); proxy.on('close', () => proxies.delete(proxy));
96
- proxy.on('upgrade', (response, upstreamSocket, upstreamHead) => {
97
- sockets.add(upstreamSocket); upstreamSocket.on('close', () => sockets.delete(upstreamSocket));
98
- 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`);
99
- if (upstreamHead.length) socket.write(upstreamHead);
100
- if (head.length) upstreamSocket.write(head);
101
- socket.pipe(upstreamSocket).pipe(socket);
102
- socket.on('error', () => upstreamSocket.destroy());
103
- upstreamSocket.on('error', () => socket.destroy());
104
- socket.on('close', () => upstreamSocket.destroy());
105
- upstreamSocket.on('close', () => socket.destroy());
106
- });
107
- proxy.on('response', response => { response.resume(); socket.destroy(); });
108
- proxy.on('error', () => socket.destroy());
109
- proxy.end();
110
- });
111
- await new Promise((resolve, reject) => { server.once('error', reject); server.listen(port, '127.0.0.1', resolve); });
112
- origin = `http://127.0.0.1:${server.address().port}`;
113
- if (new URL(origin).port === upstream.port) { server.close(); throw new Error('Comty and the application must use different ports'); }
114
- try { companion = createCompanion({ cwd, origin, provider, model }); }
115
- catch (error) { server.close(); throw error; }
116
- async function close() {
117
- if (closing) return closing;
118
- closing = (async () => {
119
- const kill = signal => {
120
- if (!child?.pid) return;
121
- try {
122
- if (process.platform === 'win32') child.kill(signal);
123
- else process.kill(-child.pid, signal);
124
- } catch (error) { if (error.code !== 'ESRCH') throw error; }
125
- };
126
- kill('SIGTERM');
127
- const force = setTimeout(() => kill('SIGKILL'), 2000);
128
- for (const proxy of proxies) proxy.destroy();
129
- for (const socket of sockets) socket.destroy();
130
- await Promise.all([companion.close(), new Promise(resolve => server.close(resolve)),
131
- child && child.exitCode === null && child.signalCode === null ? new Promise(resolve => child.once('close', resolve)) : Promise.resolve()]);
132
- clearTimeout(force);
133
- })();
134
- return closing;
135
- }
136
- if (command.length) {
137
- child = spawn(command[0], command.slice(1), {
138
- cwd, stdio: 'inherit', detached: process.platform !== 'win32', env: { ...process.env, PATH: `${resolve(cwd, 'node_modules/.bin')}${delimiter}${process.env.PATH || ''}` },
139
- });
140
- child.on('error', error => { console.error(`Comty could not start ${command[0]}: ${error.message}`); });
141
- finished = new Promise(resolve => child.once('close', code => resolve(Number.isInteger(code) && code >= 0 ? code : 1))).then(async code => {
142
- await close();
143
- return code;
144
- });
145
- finished.catch(error => console.error(`Comty shutdown failed: ${error.message}`));
146
- }
147
- return { origin, close, finished };
148
- }
149
-
150
- async function main() {
151
- const args = process.argv.slice(2);
152
- if (!args.length || args.includes('--help')) {
153
- console.log('Usage: comty dev [--agent codex|claude|cursor] [--model MODEL] [--target http://127.0.0.1:5173] [--port 4387] [--project-key KEY --api-url URL] -- vite --strictPort'); return;
154
- }
155
- if (args.shift() !== 'dev') throw new Error('Expected comty dev. Use --help for usage.');
156
- const split = args.indexOf('--');
157
- const { values } = parseArgs({ args: split < 0 ? args : args.slice(0, split), options: {
158
- target: { type: 'string' }, port: { type: 'string' }, agent: { type: 'string' }, model: { type: 'string' }, 'project-key': { type: 'string' }, 'api-url': { type: 'string' },
159
- } });
160
- const session = await startDev({ target: values.target, port: values.port === undefined ? 4387 : Number(values.port), provider: values.agent, model: values.model,
161
- projectKey: values['project-key'], apiUrl: values['api-url'], command: split < 0 ? [] : args.slice(split + 1) });
162
- console.log(`Comty: ${session.origin} — open this URL to annotate. Local tasks: .comty/tasks.json`);
163
- for (const signal of ['SIGINT', 'SIGTERM']) process.once(signal, () => { void session.close().catch(console.error); });
164
- if (session.finished) process.exitCode = await session.finished;
165
- }
166
-
167
- if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
168
- main().catch(error => { console.error(`Comty: ${error.message}`); process.exitCode = 1; });
169
- }
package/src/vite.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import type { Plugin } from 'vite';
2
- import type { ComtyOptions } from './browser.js';
3
-
4
- /** Development only: leaves production builds untouched. */
5
- export function comty(options?: Pick<ComtyOptions, 'projectKey' | 'apiUrl'> & { agent?: 'codex' | 'claude' | 'cursor'; model?: string }): Plugin;
package/src/vite.mjs DELETED
@@ -1,66 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { createCompanion, isLoopback } from './companion.mjs';
3
-
4
- /** Starts the local companion on Vite's own origin, without a second port. */
5
- export function comty(options = {}) {
6
- let companion, origin, start;
7
- return {
8
- name: 'comty', apply: 'serve',
9
- configureServer(server) {
10
- const host = server.config.server.host;
11
- if (host === true || (typeof host === 'string' && !isLoopback(host))) {
12
- throw new Error('Comty Agent requires Vite to bind localhost or 127.0.0.1');
13
- }
14
- if (!server.httpServer) throw new Error('Comty requires a Vite HTTP server (middleware mode is unsupported)');
15
- const runtime = new URL('./browser.js', import.meta.url);
16
- const style = new URL('./style.js', import.meta.url);
17
- const anchors = new URL('./anchors.js', import.meta.url);
18
- const capture = new URL('./capture.js', import.meta.url);
19
- const comments = new URL('./comments.js', import.meta.url);
20
- const measurements = new URL('./measurements.js', import.meta.url);
21
- start = new Promise((resolve, reject) => {
22
- const onError = error => {
23
- // Vite retries the next port after EADDRINUSE unless strictPort is set.
24
- if (error.code !== 'EADDRINUSE' || server.config.server.strictPort) reject(error);
25
- };
26
- server.httpServer.on('error', onError);
27
- server.httpServer.once('listening', () => {
28
- server.httpServer.off('error', onError);
29
- try {
30
- const address = server.httpServer.address();
31
- const hostname = typeof host === 'string' ? host : 'localhost';
32
- origin = `${server.config.server.https ? 'https' : 'http'}://${hostname}:${address.port}`;
33
- companion = createCompanion({ cwd: server.config.root, origin, provider: options.agent, model: options.model });
34
- server.httpServer.once('close', () => { void companion.close().catch(error => server.config.logger.error(`Comty: ${error.message}`)); });
35
- resolve();
36
- } catch (error) { reject(error); }
37
- });
38
- });
39
- start.catch(error => server.config.logger.error(`Comty: ${error.message}`));
40
- server.middlewares.use(async (request, response, next) => {
41
- if (!request.url?.startsWith('/__comty/')) return next();
42
- try {
43
- await start;
44
- if (request.headers.host !== new URL(origin).host || (request.headers.origin && request.headers.origin !== origin)) {
45
- response.writeHead(403); response.end(); return;
46
- }
47
- const path = new URL(request.url, origin).pathname;
48
- if (request.method === 'GET' && ['/__comty/browser.js', '/__comty/style.js', '/__comty/anchors.js', '/__comty/capture.js', '/__comty/comments.js', '/__comty/measurements.js', '/__comty/start.js'].includes(path)) {
49
- response.setHeader('Content-Type', 'text/javascript'); response.setHeader('Cache-Control', 'no-store');
50
- response.setHeader('X-Content-Type-Options', 'nosniff');
51
- response.end(path.endsWith('/browser.js') ? await readFile(runtime) : path.endsWith('/style.js') ? await readFile(style) : path.endsWith('/anchors.js') ? await readFile(anchors) : path.endsWith('/capture.js') ? await readFile(capture) : path.endsWith('/comments.js') ? await readFile(comments) : path.endsWith('/measurements.js') ? await readFile(measurements) :
52
- `import { initComty } from '/__comty/browser.js';\ninitComty(${JSON.stringify({ projectKey: options.projectKey, apiUrl: options.apiUrl, agent: { url: `${origin}/__comty`, token: companion.token } })}).catch(console.error);`);
53
- } else await companion.handle(request, response);
54
- } catch (error) { next(error); }
55
- });
56
- },
57
- transformIndexHtml: {
58
- order: 'pre',
59
- async handler() {
60
- await start;
61
- return [{ tag: 'script', attrs: { type: 'module', src: '/__comty/start.js', 'vite-ignore': '' }, injectTo: 'head-prepend' }];
62
- },
63
- },
64
- async closeBundle() { await companion?.close(); },
65
- };
66
- }