@comty.io/core 0.1.5 → 0.1.7
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 +82 -2
- package/comty-sdk.js +1350 -140
- package/package.json +17 -6
- package/src/acp.mjs +68 -0
- package/src/agent-process.mjs +89 -0
- package/src/anchors.js +58 -17
- package/src/browser.d.ts +3 -1
- package/src/browser.js +820 -88
- package/src/capture.js +10 -8
- package/src/claude-permission.mjs +37 -0
- package/src/claude.mjs +119 -0
- package/src/codex.mjs +64 -0
- package/src/comments.js +31 -11
- package/src/companion.mjs +201 -0
- package/src/cursor.mjs +49 -0
- package/src/dev.mjs +172 -0
- package/src/gemini.mjs +3 -0
- package/src/grok.mjs +3 -0
- package/src/inspect-markup.js +283 -0
- package/src/measurements.js +3 -3
- package/src/style.js +145 -13
- package/src/vite.d.ts +5 -0
- package/src/vite.mjs +69 -0
package/package.json
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comty.io/core",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Comty visual comments and
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Comty visual comments, inspector and local agent tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=22.12"
|
|
9
9
|
},
|
|
10
10
|
"sideEffects": false,
|
|
11
|
+
"bin": {
|
|
12
|
+
"comty": "src/dev.mjs"
|
|
13
|
+
},
|
|
11
14
|
"exports": {
|
|
12
15
|
".": {
|
|
13
16
|
"types": "./src/browser.d.ts",
|
|
14
17
|
"import": "./src/browser.js"
|
|
18
|
+
},
|
|
19
|
+
"./vite": {
|
|
20
|
+
"types": "./src/vite.d.ts",
|
|
21
|
+
"import": "./src/vite.mjs"
|
|
15
22
|
}
|
|
16
23
|
},
|
|
17
24
|
"files": [
|
|
@@ -25,14 +32,18 @@
|
|
|
25
32
|
"scripts": {
|
|
26
33
|
"build": "node scripts/build.mjs",
|
|
27
34
|
"check:build": "node scripts/build.mjs --check",
|
|
28
|
-
"test": "node --test tests/measurements.test.mjs tests/anchors.test.mjs tests/package.test.mjs tests/packed.test.mjs",
|
|
35
|
+
"test": "node --experimental-websocket --test tests/measurements.test.mjs tests/anchors.test.mjs tests/package.test.mjs tests/packed.test.mjs tests/companion.test.mjs tests/codex.test.mjs tests/claude.test.mjs tests/cursor.test.mjs tests/acp.test.mjs",
|
|
29
36
|
"test:capture": "node --test tests/capture.browser.test.mjs",
|
|
30
|
-
"test:inspector": "node --test tests/inspector.browser.test.mjs",
|
|
37
|
+
"test:inspector": "node --test tests/inspector.browser.test.mjs tests/inspect-markup.browser.test.mjs tests/idle-network.browser.test.mjs",
|
|
38
|
+
"test:placement": "node --test tests/placement.browser.test.mjs",
|
|
39
|
+
"test:anchors": "node --test tests/anchors.test.mjs tests/anchor-reload.browser.test.mjs",
|
|
40
|
+
"test:vite": "node --test tests/vite.test.mjs",
|
|
31
41
|
"prepack": "npm run build"
|
|
32
42
|
},
|
|
33
43
|
"devDependencies": {
|
|
34
|
-
"esbuild": "0.
|
|
44
|
+
"esbuild": "0.28.2",
|
|
35
45
|
"html-to-image": "1.11.13",
|
|
36
|
-
"playwright": "1.55.1"
|
|
46
|
+
"playwright": "1.55.1",
|
|
47
|
+
"vite": "8.1.5"
|
|
37
48
|
}
|
|
38
49
|
}
|
package/src/acp.mjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { openAgentProcess } from './agent-process.mjs';
|
|
3
|
+
import { parseReceipt, receiptSchema, taskPrompt } from './codex.mjs';
|
|
4
|
+
|
|
5
|
+
const agents = {
|
|
6
|
+
gemini: { executable: 'gemini', command: ['--acp'], label: 'Gemini' },
|
|
7
|
+
grok: { executable: 'grok', command: ['--no-auto-update', 'agent', 'stdio'], label: 'Grok' },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function runAcp({ provider, cwd, task, signal, approve, progress, model, executable, args = [] }) {
|
|
11
|
+
const agent = agents[provider];
|
|
12
|
+
if (!agent) throw new Error(`Unsupported ACP agent: ${provider}`);
|
|
13
|
+
cwd = realpathSync(cwd);
|
|
14
|
+
let output = '';
|
|
15
|
+
let resolveReceipt;
|
|
16
|
+
const receiptReady = new Promise(resolve => { resolveReceipt = resolve; });
|
|
17
|
+
const rpc = openAgentProcess({
|
|
18
|
+
executable: executable || agent.executable, args: [...args, ...(model ? ['--model', model] : []), ...agent.command], cwd, signal, label: agent.label,
|
|
19
|
+
async onRequest(method, params) {
|
|
20
|
+
if (method === 'session/request_permission') {
|
|
21
|
+
const allow = await approve({ type: method, details: params.toolCall });
|
|
22
|
+
const option = params.options?.find(item => item.kind === (allow ? 'allow_once' : 'reject_once'));
|
|
23
|
+
return { outcome: option ? { outcome: 'selected', optionId: option.optionId } : { outcome: 'cancelled' } };
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`${agent.label} needs an unsupported interaction: ${method}`);
|
|
26
|
+
},
|
|
27
|
+
onNotification(method, params) {
|
|
28
|
+
if (method !== 'session/update' || params?.update?.sessionUpdate !== 'agent_message_chunk') return;
|
|
29
|
+
if (params.update.content?.type === 'text') {
|
|
30
|
+
output += params.update.content.text;
|
|
31
|
+
if (output.length > 1024 * 1024) throw new Error(`${agent.label} response exceeded 1 MiB`);
|
|
32
|
+
progress(output.slice(-4000));
|
|
33
|
+
if (output.trimEnd().endsWith('</comty_result>')) resolveReceipt();
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
const init = await rpc.request('initialize', {
|
|
39
|
+
protocolVersion: 1, clientInfo: { name: 'comty', version: '0.1.0' },
|
|
40
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
|
|
41
|
+
});
|
|
42
|
+
const authMethods = new Set(init?.authMethods?.map(method => method.id) || []);
|
|
43
|
+
const methodId = provider === 'gemini' ? (process.env.GEMINI_API_KEY && authMethods.has('gemini-api-key') ? 'gemini-api-key' : 'oauth-personal')
|
|
44
|
+
: (process.env.XAI_API_KEY && authMethods.has('xai.api_key') ? 'xai.api_key' : 'cached_token');
|
|
45
|
+
if (provider === 'grok' && !authMethods.has(methodId)) throw new Error('Run grok login first, or set XAI_API_KEY');
|
|
46
|
+
if (provider === 'gemini' && !authMethods.has(methodId)) throw new Error('Sign in to Gemini CLI first, or set GEMINI_API_KEY');
|
|
47
|
+
await rpc.request('authenticate', { methodId, ...(provider === 'grok' ? { _meta: { headless: true } } : {}) });
|
|
48
|
+
const session = await rpc.request('session/new', { cwd, mcpServers: [] });
|
|
49
|
+
if (!session?.sessionId) throw new Error(`${agent.label} did not create a session`);
|
|
50
|
+
const result = await rpc.request('session/prompt', {
|
|
51
|
+
sessionId: session.sessionId,
|
|
52
|
+
prompt: [{ type: 'text', text: `${taskPrompt(task)}\n\nEnd your final response with <comty_result>JSON</comty_result>. The JSON must match this schema: ${JSON.stringify(receiptSchema)}. Write nothing after the closing tag.` }],
|
|
53
|
+
}, 0);
|
|
54
|
+
if (result?.stopReason !== 'end_turn') throw new Error(`${agent.label} stopped: ${result?.stopReason || 'unknown reason'}`);
|
|
55
|
+
if (!output.trimEnd().endsWith('</comty_result>')) {
|
|
56
|
+
let timeout;
|
|
57
|
+
try {
|
|
58
|
+
await Promise.race([receiptReady, new Promise((_, reject) => {
|
|
59
|
+
timeout = setTimeout(() => reject(new Error(`${agent.label} did not return a completion receipt`)), 2000);
|
|
60
|
+
})]);
|
|
61
|
+
} finally { clearTimeout(timeout); }
|
|
62
|
+
}
|
|
63
|
+
const text = output.trim();
|
|
64
|
+
const start = text.lastIndexOf('<comty_result>');
|
|
65
|
+
if (start < 0 || !text.endsWith('</comty_result>')) throw new Error(`${agent.label} did not return a completion receipt`);
|
|
66
|
+
return parseReceipt(text.slice(start + '<comty_result>'.length, -'</comty_result>'.length));
|
|
67
|
+
} finally { await rpc.close(); }
|
|
68
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
// Codex app-server and ACP agents use newline-delimited JSON-RPC on stdio.
|
|
4
|
+
export function openAgentProcess({ executable, args, cwd, signal, label, onRequest, onNotification }) {
|
|
5
|
+
signal.throwIfAborted();
|
|
6
|
+
const child = spawn(executable, args, { cwd, stdio: ['pipe', 'pipe', 'pipe'], detached: process.platform !== 'win32' });
|
|
7
|
+
const pending = new Map();
|
|
8
|
+
let nextId = 0, buffer = '', stderr = '', closed = false, failureError, closing, rejectFailure;
|
|
9
|
+
const failure = new Promise((_, reject) => { rejectFailure = reject; });
|
|
10
|
+
failure.catch(() => {});
|
|
11
|
+
const exited = new Promise(resolve => child.once('close', () => { closed = true; resolve(); }));
|
|
12
|
+
function fail(error) {
|
|
13
|
+
failureError ||= error;
|
|
14
|
+
rejectFailure(failureError);
|
|
15
|
+
for (const waiter of pending.values()) { clearTimeout(waiter.timeout); waiter.reject(failureError); }
|
|
16
|
+
pending.clear();
|
|
17
|
+
}
|
|
18
|
+
function send(value) {
|
|
19
|
+
if (failureError) throw failureError;
|
|
20
|
+
if (closed || child.stdin.destroyed) throw new Error(`${label} connection closed`);
|
|
21
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', ...value })}\n`);
|
|
22
|
+
}
|
|
23
|
+
async function receive(message) {
|
|
24
|
+
if (message.method && message.id !== undefined) {
|
|
25
|
+
try { send({ id: message.id, result: await onRequest(message.method, message.params) }); }
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (!failureError) send({ id: message.id, error: { code: -32603, message: error.message } });
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
} else if (message.id !== undefined) {
|
|
31
|
+
const waiter = pending.get(message.id);
|
|
32
|
+
if (!waiter) return;
|
|
33
|
+
clearTimeout(waiter.timeout); pending.delete(message.id);
|
|
34
|
+
if (message.error) waiter.reject(new Error(message.error.message || `${label} request failed`));
|
|
35
|
+
else waiter.resolve(message.result);
|
|
36
|
+
} else if (message.method) await onNotification(message.method, message.params);
|
|
37
|
+
}
|
|
38
|
+
child.stdout.setEncoding('utf8');
|
|
39
|
+
child.stdout.on('data', chunk => {
|
|
40
|
+
buffer += chunk;
|
|
41
|
+
if (buffer.length > 8 * 1024 * 1024) return fail(new Error(`${label} output exceeded the message limit`));
|
|
42
|
+
let index;
|
|
43
|
+
while ((index = buffer.indexOf('\n')) !== -1) {
|
|
44
|
+
const line = buffer.slice(0, index); buffer = buffer.slice(index + 1);
|
|
45
|
+
if (!line.trim()) continue;
|
|
46
|
+
try { void receive(JSON.parse(line)).catch(fail); } catch (error) { fail(error); }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
child.stderr.on('data', chunk => { stderr = (stderr + chunk).slice(-4000); });
|
|
50
|
+
child.stdin.on('error', fail);
|
|
51
|
+
child.on('error', error => fail(new Error(`Cannot start ${label}: ${error.message}. Install and sign in to its CLI.`)));
|
|
52
|
+
child.on('close', code => fail(new Error(`${label} exited (${code}): ${stderr || 'connection closed'}`)));
|
|
53
|
+
const abort = () => fail(signal.reason || new Error('Task cancelled'));
|
|
54
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
55
|
+
return {
|
|
56
|
+
failure,
|
|
57
|
+
notify: (method, params) => send({ method, params }),
|
|
58
|
+
request(method, params, timeoutMs = 30_000) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
signal.throwIfAborted();
|
|
61
|
+
if (failureError) throw failureError;
|
|
62
|
+
const id = ++nextId;
|
|
63
|
+
const timeout = timeoutMs ? setTimeout(() => {
|
|
64
|
+
pending.delete(id); reject(new Error(`${label} timed out: ${method}`));
|
|
65
|
+
}, timeoutMs) : undefined;
|
|
66
|
+
pending.set(id, { resolve, reject, timeout });
|
|
67
|
+
try { send({ id, method, params }); } catch (error) {
|
|
68
|
+
clearTimeout(timeout); pending.delete(id); reject(error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
close() {
|
|
73
|
+
if (closing) return closing;
|
|
74
|
+
signal.removeEventListener('abort', abort);
|
|
75
|
+
fail(new Error(`${label} session closed`));
|
|
76
|
+
const kill = signalName => {
|
|
77
|
+
if (closed) return;
|
|
78
|
+
try {
|
|
79
|
+
if (process.platform === 'win32') child.kill(signalName);
|
|
80
|
+
else if (child.pid) process.kill(-child.pid, signalName);
|
|
81
|
+
} catch (error) { if (error.code !== 'ESRCH') throw error; }
|
|
82
|
+
};
|
|
83
|
+
kill('SIGTERM');
|
|
84
|
+
const timer = setTimeout(() => kill('SIGKILL'), 2000);
|
|
85
|
+
closing = exited.finally(() => clearTimeout(timer));
|
|
86
|
+
return closing;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
package/src/anchors.js
CHANGED
|
@@ -11,6 +11,14 @@ export function createAnchorMatcher({
|
|
|
11
11
|
return haystack.toLowerCase().includes(needle.toLowerCase());
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function similarText(a, b) {
|
|
15
|
+
if (!a || !b) return false;
|
|
16
|
+
const words = value => new Set(value.toLowerCase().split(/\s+/).filter(Boolean));
|
|
17
|
+
const left = words(a), right = words(b);
|
|
18
|
+
const common = [...left].filter(word => right.has(word)).length;
|
|
19
|
+
return common >= 2 && common / Math.max(left.size, right.size) >= 0.7;
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
function scoreFingerprint(el, fingerprint) {
|
|
15
23
|
if (!el || !fingerprint) return 0;
|
|
16
24
|
if (
|
|
@@ -80,6 +88,8 @@ export function createAnchorMatcher({
|
|
|
80
88
|
score += 15;
|
|
81
89
|
}
|
|
82
90
|
|
|
91
|
+
if (text !== fingerprint.text && similarText(text, fingerprint.text)) score += 24;
|
|
92
|
+
|
|
83
93
|
if (typeof fingerprint.siblingIndex === "number" && el.parentElement) {
|
|
84
94
|
const siblings = [...el.parentElement.children].filter(
|
|
85
95
|
(child) => child.tagName === el.tagName,
|
|
@@ -157,6 +167,8 @@ export function createAnchorMatcher({
|
|
|
157
167
|
if (ancestorScore > 0) {
|
|
158
168
|
score += Math.min(ancestorScore, 40) * (index === 0 ? 0.5 : 0.2);
|
|
159
169
|
}
|
|
170
|
+
// Distinguish repeated cards by their section, even when dimensions match.
|
|
171
|
+
if (index < 3 && ancestors[index].text && getElementTextSignature(parent) === ancestors[index].text) score += 18;
|
|
160
172
|
parent = parent.parentElement;
|
|
161
173
|
}
|
|
162
174
|
|
|
@@ -171,46 +183,75 @@ export function createAnchorMatcher({
|
|
|
171
183
|
return score;
|
|
172
184
|
}
|
|
173
185
|
|
|
174
|
-
function matchesAnchorIdentity(el, fingerprint) {
|
|
186
|
+
function matchesAnchorIdentity(el, fingerprint, ignoreDescendantText = false, allowTextChanges = true) {
|
|
175
187
|
if (!el || !fingerprint || el.tagName.toLowerCase() !== fingerprint.tag) return false;
|
|
176
188
|
const attributes = { ariaLabel: "aria-label", name: "name", href: "href", src: "src", alt: "alt", title: "title", role: "role" };
|
|
177
189
|
for (const [key, attribute] of Object.entries(attributes)) {
|
|
178
190
|
if (fingerprint[key] && getAttributeSignature(el, attribute) !== fingerprint[key]) return false;
|
|
179
191
|
}
|
|
180
|
-
if (fingerprint.
|
|
192
|
+
if (fingerprint.id && el.id !== fingerprint.id) return false;
|
|
193
|
+
if (fingerprint.testId && !["data-testid", "data-test", "data-cy"].some(name => getAttributeSignature(el, name) === fingerprint.testId)) return false;
|
|
194
|
+
const stableIdentity = (fingerprint.id && el.id === fingerprint.id) ||
|
|
195
|
+
(fingerprint.testId && ["data-testid", "data-test", "data-cy"].some(name => getAttributeSignature(el, name) === fingerprint.testId));
|
|
196
|
+
if (fingerprint.ownText && getElementOwnTextSignature(el) !== fingerprint.ownText && !(allowTextChanges && stableIdentity)) return false;
|
|
181
197
|
// Container text is useful for matching remounted drawers and dialogs.
|
|
182
|
-
if (!fingerprint.ownText && fingerprint.text && getElementTextSignature(el) !== fingerprint.text)
|
|
198
|
+
if (!ignoreDescendantText && !fingerprint.ownText && fingerprint.text && getElementTextSignature(el) !== fingerprint.text && !(allowTextChanges && stableIdentity)) {
|
|
199
|
+
if (!allowTextChanges || !el.children.length || !similarText(getElementTextSignature(el), fingerprint.text)) return false;
|
|
200
|
+
}
|
|
183
201
|
return true;
|
|
184
202
|
}
|
|
185
203
|
|
|
186
204
|
function matchesAnchorContext(el, context) {
|
|
187
205
|
if (!context) return true;
|
|
188
|
-
|
|
206
|
+
// A unique page landmark keeps its identity when a child is edited.
|
|
207
|
+
const pageContainer = !context.layer && (context.target?.tag === "main" || context.target?.role === "main") &&
|
|
208
|
+
[...document.querySelectorAll('main, [role="main"]')].filter(node => !isComtyNode(node)).length === 1;
|
|
209
|
+
if (!matchesAnchorIdentity(el, context.target, pageContainer)) return false;
|
|
189
210
|
const parentFingerprint = context.ancestors?.[0];
|
|
190
211
|
if (parentFingerprint?.text && parentFingerprint.text !== context.target.text &&
|
|
191
|
-
!matchesAnchorIdentity(el.parentElement, parentFingerprint)) return false;
|
|
212
|
+
!matchesAnchorIdentity(el.parentElement, parentFingerprint, pageContainer)) return false;
|
|
192
213
|
const layer = getPresentationLayer(el);
|
|
193
|
-
if (context.layer && (!layer || !matchesAnchorIdentity(layer, context.layer))) return false;
|
|
214
|
+
if (context.layer && (!layer || !matchesAnchorIdentity(layer, context.layer, false, false))) return false;
|
|
194
215
|
if (context.version >= 2 && !context.layer && layer) return false;
|
|
195
216
|
return scoreAnchorCandidate(el, context) >= 50;
|
|
196
217
|
}
|
|
197
218
|
|
|
198
|
-
function
|
|
199
|
-
if (!context?.target) return
|
|
219
|
+
function resolveAnchor(context, exact = null) {
|
|
220
|
+
if (!context?.target) return { element: exact, status: exact ? "matched" : "missing" };
|
|
200
221
|
let candidates;
|
|
201
222
|
try {
|
|
202
|
-
candidates = [...document.body.querySelectorAll(context.target.tag || "*")];
|
|
223
|
+
candidates = [...document.body.querySelectorAll(context.target.tag || "*")].filter(el => !isComtyNode(el));
|
|
203
224
|
} catch {
|
|
204
|
-
return null;
|
|
225
|
+
return { element: null, status: "missing" };
|
|
205
226
|
}
|
|
206
227
|
const matches = candidates
|
|
207
|
-
.filter(el =>
|
|
208
|
-
.map(el => ({ el, score: scoreAnchorCandidate(el, context) }))
|
|
228
|
+
.filter(el => matchesAnchorContext(el, context))
|
|
229
|
+
.map(el => ({ el, score: scoreAnchorCandidate(el, context) + (context.version === 1 && el === exact ? 16 : 0) }))
|
|
209
230
|
.sort((a, b) => b.score - a.score);
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
231
|
+
if (context.candidateCount > 1) {
|
|
232
|
+
const peers = candidates.filter(el => matchesAnchorIdentity(el, context.target));
|
|
233
|
+
if (peers.length !== context.candidateCount) {
|
|
234
|
+
// A removed identical sibling must not move its comment to the survivor.
|
|
235
|
+
const distinctive = matches[0] && (context.target.id || context.target.testId);
|
|
236
|
+
if (!distinctive) return { element: null, status: "uncertain" };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (matches[0]?.score >= 58 && (!matches[1] || matches[0].score - matches[1].score >= 12)) {
|
|
240
|
+
return { element: matches[0].el, status: "matched" };
|
|
241
|
+
}
|
|
242
|
+
// A closed/unmounted overlay is not evidence that its contents were deleted.
|
|
243
|
+
if (context.layer && !candidates.some(el => {
|
|
244
|
+
const layer = getPresentationLayer(el);
|
|
245
|
+
return layer && matchesAnchorIdentity(layer, context.layer, false, false);
|
|
246
|
+
})) return { element: null, status: "hidden" };
|
|
247
|
+
const plausible = matches.length || candidates.some(el =>
|
|
248
|
+
(context.target.id && el.id === context.target.id) ||
|
|
249
|
+
(context.target.text && (getElementTextSignature(el) === context.target.text || similarText(getElementTextSignature(el), context.target.text))));
|
|
250
|
+
return { element: null, status: plausible ? "uncertain" : "missing" };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function resolveByAnchorContext(context) {
|
|
254
|
+
return resolveAnchor(context).element;
|
|
214
255
|
}
|
|
215
256
|
|
|
216
257
|
function getViewportBounds() {
|
|
@@ -382,5 +423,5 @@ export function createAnchorMatcher({
|
|
|
382
423
|
return { clientX, clientY };
|
|
383
424
|
}
|
|
384
425
|
|
|
385
|
-
return { matchesAnchorContext, resolveByAnchorContext, isElementTreeHidden, getTopActiveModal, getVisibleAnchorPoint };
|
|
426
|
+
return { matchesAnchorContext, resolveAnchor, resolveByAnchorContext, isElementTreeHidden, getTopActiveModal, getVisibleAnchorPoint };
|
|
386
427
|
}
|
package/src/browser.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export interface ComtyOptions {
|
|
2
2
|
/** Project key from your Comty dashboard. */
|
|
3
|
-
projectKey
|
|
3
|
+
projectKey?: string;
|
|
4
4
|
/** Absolute URL of your Comty API. Defaults to http://localhost:4000. */
|
|
5
5
|
apiUrl?: string;
|
|
6
|
+
/** Set automatically by comty dev; not needed in application code. */
|
|
7
|
+
agent?: { url: string; token: string };
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
/**
|