@vornrun/connector-sdk 0.7.0-beta.14 → 0.7.0-beta.16
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 +118 -2
- package/dist/{check-Hw1F1a5z.d.ts → check-Cv6UNBA4.d.ts} +218 -2
- package/dist/{chunk-PMMEWBVK.js → chunk-FHGC7LE7.js} +940 -58
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +7 -5
- package/dist/index.d.ts +49 -4
- package/dist/index.js +21 -1
- package/package.json +2 -2
|
@@ -4,6 +4,75 @@ var PATH_DATA_PATTERN = /^[MmZzLlHhVvCcSsQqTtAa0-9\s,.\-+eE]+$/;
|
|
|
4
4
|
var VIEW_BOX_PATTERN = /^-?[\d.]+\s+-?[\d.]+\s+-?[\d.]+\s+-?[\d.]+$/;
|
|
5
5
|
var DEDUPE_STRATEGIES = ["timestamp", "lastItem"];
|
|
6
6
|
var AUTH_RUNGS = ["none", "cli", "key", "oauth"];
|
|
7
|
+
var EXTENSION_PERMISSIONS = [
|
|
8
|
+
"git.read",
|
|
9
|
+
"terminal.read",
|
|
10
|
+
"terminal.selection",
|
|
11
|
+
"terminal.send",
|
|
12
|
+
"card.rename",
|
|
13
|
+
"agent.usage"
|
|
14
|
+
];
|
|
15
|
+
var HOST_PERMISSIONS = {
|
|
16
|
+
diff: "git.read",
|
|
17
|
+
status: "git.read",
|
|
18
|
+
output: "terminal.read",
|
|
19
|
+
selection: "terminal.selection",
|
|
20
|
+
send: "terminal.send",
|
|
21
|
+
rename: "card.rename",
|
|
22
|
+
usage: "agent.usage"
|
|
23
|
+
};
|
|
24
|
+
var EXTENSION_AGENTS = [
|
|
25
|
+
"claude",
|
|
26
|
+
"copilot",
|
|
27
|
+
"codex",
|
|
28
|
+
"opencode",
|
|
29
|
+
"gemini",
|
|
30
|
+
"shell"
|
|
31
|
+
];
|
|
32
|
+
var EXTENSION_PLATFORMS = ["darwin", "linux", "win32"];
|
|
33
|
+
var WEB_ENTRY_PATTERN = /^web\/[A-Za-z0-9._/-]+\.html$/;
|
|
34
|
+
var MIN_FOOTER_SECONDS = 5;
|
|
35
|
+
var MAX_PATTERN_LENGTH = 256;
|
|
36
|
+
var GROUP_OPEN = /^\((\?(:|=|!|<=|<!|<[A-Za-z_$][\w$]*>))?/;
|
|
37
|
+
function hasNestedQuantifier(pattern) {
|
|
38
|
+
const quantifierAt = (at) => {
|
|
39
|
+
const ch = pattern[at];
|
|
40
|
+
return ch !== void 0 && "*+?{".includes(ch);
|
|
41
|
+
};
|
|
42
|
+
const quantified = [];
|
|
43
|
+
let inClass = false;
|
|
44
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
45
|
+
const ch = pattern[i];
|
|
46
|
+
if (ch === "\\") {
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inClass) {
|
|
51
|
+
if (ch === "]") inClass = false;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (ch === "[") {
|
|
55
|
+
inClass = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (ch === "(") {
|
|
59
|
+
quantified.push(false);
|
|
60
|
+
i += (GROUP_OPEN.exec(pattern.slice(i))?.[0].length ?? 1) - 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (ch === ")") {
|
|
64
|
+
const heldOne = quantified.pop() ?? false;
|
|
65
|
+
const repeated = quantifierAt(i + 1);
|
|
66
|
+
if (heldOne && repeated) return true;
|
|
67
|
+
if ((heldOne || repeated) && quantified.length > 0) {
|
|
68
|
+
quantified[quantified.length - 1] = true;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (quantifierAt(i) && quantified.length > 0) quantified[quantified.length - 1] = true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
7
76
|
var ABSOLUTE_URL_PATTERN = /^https?:\/\//i;
|
|
8
77
|
var CONFIG_ROOTED_URL_PATTERN = /^\{\{\s*config\./;
|
|
9
78
|
function assertUnique(kind, keys) {
|
|
@@ -46,33 +115,38 @@ function assertAuth(definition) {
|
|
|
46
115
|
}
|
|
47
116
|
}
|
|
48
117
|
}
|
|
49
|
-
function
|
|
50
|
-
if (explicit) return explicit;
|
|
51
|
-
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
|
|
52
|
-
}
|
|
53
|
-
function defineConnector(definition) {
|
|
118
|
+
function assertIdentity(kind, definition) {
|
|
54
119
|
if (!KEY_PATTERN.test(definition.id ?? "")) {
|
|
55
|
-
throw new Error(
|
|
120
|
+
throw new Error(`${kind} id "${definition.id}" must start with a letter and be url-safe`);
|
|
56
121
|
}
|
|
57
122
|
if (!definition.name?.trim()) {
|
|
58
|
-
throw new Error(
|
|
123
|
+
throw new Error(`${kind} ${definition.id} is missing a name`);
|
|
59
124
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
throw new Error(`Connector ${definition.id} has an icon viewBox that is not four numbers`);
|
|
125
|
+
assertIcon(`${kind} ${definition.id}`, definition.icon);
|
|
126
|
+
}
|
|
127
|
+
function assertIcon(subject, icon) {
|
|
128
|
+
if (!icon) return;
|
|
129
|
+
const { viewBox, paths } = icon;
|
|
130
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
131
|
+
throw new Error(`${subject} has an icon with no paths`);
|
|
132
|
+
}
|
|
133
|
+
for (const path of paths) {
|
|
134
|
+
if (typeof path !== "string" || !PATH_DATA_PATTERN.test(path)) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`${subject} has an icon path that is not SVG path data. Only path data is accepted, not markup.`
|
|
137
|
+
);
|
|
74
138
|
}
|
|
75
139
|
}
|
|
140
|
+
if (viewBox !== void 0 && !VIEW_BOX_PATTERN.test(viewBox)) {
|
|
141
|
+
throw new Error(`${subject} has an icon viewBox that is not four numbers`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function envNameFor(key, explicit) {
|
|
145
|
+
if (explicit) return explicit;
|
|
146
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
|
|
147
|
+
}
|
|
148
|
+
function defineConnector(definition) {
|
|
149
|
+
assertIdentity("Connector", definition);
|
|
76
150
|
const triggers = definition.triggers ?? [];
|
|
77
151
|
const actions = definition.actions ?? [];
|
|
78
152
|
if (triggers.length === 0 && actions.length === 0) {
|
|
@@ -156,12 +230,191 @@ function defineConnector(definition) {
|
|
|
156
230
|
assertAuth(definition);
|
|
157
231
|
return {
|
|
158
232
|
...definition,
|
|
233
|
+
kind: "connector",
|
|
159
234
|
version: definition.version ?? "0.0.0",
|
|
160
235
|
config: definition.config ?? [],
|
|
161
236
|
triggers,
|
|
162
237
|
actions
|
|
163
238
|
};
|
|
164
239
|
}
|
|
240
|
+
function assertPredicate(id, where, predicate) {
|
|
241
|
+
if (!predicate) return;
|
|
242
|
+
const lists = [
|
|
243
|
+
["workspaceContains", predicate.workspaceContains],
|
|
244
|
+
["remoteHost", predicate.remoteHost],
|
|
245
|
+
["agent", predicate.agent],
|
|
246
|
+
["platform", predicate.platform]
|
|
247
|
+
];
|
|
248
|
+
for (const [field, value] of lists) {
|
|
249
|
+
if (value === void 0) continue;
|
|
250
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
251
|
+
throw new Error(`Extension ${id} ${where} declares "${field}" with nothing in it`);
|
|
252
|
+
}
|
|
253
|
+
for (const entry of value) {
|
|
254
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
255
|
+
throw new Error(`Extension ${id} ${where} declares an empty "${field}" value`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const glob of predicate.workspaceContains ?? []) {
|
|
260
|
+
if (glob.startsWith("/") || glob.split("/").includes("..")) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`Extension ${id} ${where} looks for "${glob}", which is not inside the worktree`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (const agent of predicate.agent ?? []) {
|
|
267
|
+
if (!EXTENSION_AGENTS.includes(agent)) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Extension ${id} ${where} names unknown agent ${JSON.stringify(agent)}; expected ${EXTENSION_AGENTS.join(", ")}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
for (const platform of predicate.platform ?? []) {
|
|
274
|
+
if (!EXTENSION_PLATFORMS.includes(platform)) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
`Extension ${id} ${where} names unknown platform ${JSON.stringify(platform)}; expected ${EXTENSION_PLATFORMS.join(", ")}`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function assertPane(id, pane) {
|
|
282
|
+
assertIcon(`Extension ${id} pane ${pane.id}`, pane.icon);
|
|
283
|
+
const loose = pane;
|
|
284
|
+
const page2 = loose.web !== void 0;
|
|
285
|
+
const program = loose.command !== void 0;
|
|
286
|
+
if (page2 && program) {
|
|
287
|
+
throw new Error(`Extension ${id} pane ${pane.id} declares both a web page and a command`);
|
|
288
|
+
}
|
|
289
|
+
if (!page2 && !program) {
|
|
290
|
+
throw new Error(`Extension ${id} pane ${pane.id} declares neither a web page nor a command`);
|
|
291
|
+
}
|
|
292
|
+
if (page2) {
|
|
293
|
+
const web = loose.web;
|
|
294
|
+
if (typeof web !== "string" || !WEB_ENTRY_PATTERN.test(web) || web.split("/").includes("..")) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`Extension ${id} pane ${pane.id} declares the page ${JSON.stringify(web)}; a page is an .html file under web/ in the package`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const command = loose.command;
|
|
302
|
+
if (!Array.isArray(command) || command.length === 0) {
|
|
303
|
+
throw new Error(`Extension ${id} pane ${pane.id} declares a command with nothing to run`);
|
|
304
|
+
}
|
|
305
|
+
for (const arg of command) {
|
|
306
|
+
if (typeof arg !== "string" || arg === "") {
|
|
307
|
+
throw new Error(`Extension ${id} pane ${pane.id} declares a command with an empty argument`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function defineExtension(definition) {
|
|
312
|
+
assertIdentity("Extension", definition);
|
|
313
|
+
const id = definition.id;
|
|
314
|
+
const panes = definition.panes ?? [];
|
|
315
|
+
const footers = definition.footers ?? [];
|
|
316
|
+
const linkHandlers = definition.linkHandlers ?? [];
|
|
317
|
+
if (panes.length === 0 && footers.length === 0 && linkHandlers.length === 0) {
|
|
318
|
+
throw new Error(`Extension ${id} contributes nothing`);
|
|
319
|
+
}
|
|
320
|
+
const permissions = definition.permissions ?? [];
|
|
321
|
+
if (!Array.isArray(permissions)) {
|
|
322
|
+
throw new Error(`Extension ${id} declares permissions that are not a list`);
|
|
323
|
+
}
|
|
324
|
+
for (const permission of permissions) {
|
|
325
|
+
if (!EXTENSION_PERMISSIONS.includes(permission)) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
`Extension ${id} asks for unknown permission ${JSON.stringify(permission)}; expected ${EXTENSION_PERMISSIONS.join(", ")}`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
assertUnique("permission", permissions);
|
|
332
|
+
const contributions = [...panes, ...footers, ...linkHandlers];
|
|
333
|
+
for (const contribution of contributions) {
|
|
334
|
+
if (!KEY_PATTERN.test(contribution.id ?? "")) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Contribution id "${contribution.id}" must start with a letter and be url-safe`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (!contribution.title?.trim()) {
|
|
340
|
+
throw new Error(`Extension ${id} contribution ${contribution.id} is missing a title`);
|
|
341
|
+
}
|
|
342
|
+
assertPredicate(id, `contribution ${contribution.id}`, contribution.when);
|
|
343
|
+
}
|
|
344
|
+
assertUnique(
|
|
345
|
+
"contribution",
|
|
346
|
+
contributions.map((contribution) => contribution.id)
|
|
347
|
+
);
|
|
348
|
+
assertPredicate(id, "activates", definition.activates);
|
|
349
|
+
for (const pane of panes) assertPane(id, pane);
|
|
350
|
+
for (const footer of footers) {
|
|
351
|
+
if (typeof footer.run !== "function") {
|
|
352
|
+
throw new Error(`Extension ${id} footer ${footer.id} is missing a run() implementation`);
|
|
353
|
+
}
|
|
354
|
+
if (!Number.isFinite(footer.every) || footer.every < MIN_FOOTER_SECONDS) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`Extension ${id} footer ${footer.id} asks to run every ${footer.every}s; ${MIN_FOOTER_SECONDS}s is the shortest interval a footer may ask for`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const handler of linkHandlers) {
|
|
361
|
+
if (typeof handler.run !== "function") {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`Extension ${id} link handler ${handler.id} is missing a run() implementation`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (typeof handler.pattern !== "string" || handler.pattern.length > MAX_PATTERN_LENGTH) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Extension ${id} link handler ${handler.id} has a pattern longer than ${MAX_PATTERN_LENGTH} characters; it is matched on every click`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (hasNestedQuantifier(handler.pattern)) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`Extension ${id} link handler ${handler.id} has a pattern that repeats a group which already repeats; matching it can take exponential time on a click`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
let matcher;
|
|
377
|
+
try {
|
|
378
|
+
matcher = new RegExp(handler.pattern);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
381
|
+
throw new Error(
|
|
382
|
+
`Extension ${id} link handler ${handler.id} has a pattern that is not a regular expression: ${reason}`,
|
|
383
|
+
{ cause: error }
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if (typeof handler.example !== "string" || handler.example.trim() === "") {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Extension ${id} link handler ${handler.id} names no example link its pattern matches`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (!matcher.test(handler.example)) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Extension ${id} link handler ${handler.id} has the example ${JSON.stringify(handler.example)}, which its own pattern ${JSON.stringify(handler.pattern)} does not match`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
id,
|
|
399
|
+
name: definition.name,
|
|
400
|
+
...definition.description !== void 0 && { description: definition.description },
|
|
401
|
+
...definition.icon !== void 0 && { icon: definition.icon },
|
|
402
|
+
kind: "extension",
|
|
403
|
+
version: definition.version ?? "0.0.0",
|
|
404
|
+
// Its credential is the host's own token, so there is nothing to sign in to.
|
|
405
|
+
auth: { rung: "none" },
|
|
406
|
+
config: [],
|
|
407
|
+
triggers: [],
|
|
408
|
+
actions: [],
|
|
409
|
+
permissions,
|
|
410
|
+
...definition.activates !== void 0 && { activates: definition.activates },
|
|
411
|
+
contributes: {
|
|
412
|
+
...panes.length > 0 && { panes },
|
|
413
|
+
...footers.length > 0 && { footers },
|
|
414
|
+
...linkHandlers.length > 0 && { linkHandlers }
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
165
418
|
function resolveConfig(connector, env = process.env) {
|
|
166
419
|
const config = {};
|
|
167
420
|
const missing = [];
|
|
@@ -186,6 +439,12 @@ function resolveConfig(connector, env = process.env) {
|
|
|
186
439
|
function pollToolName(triggerType) {
|
|
187
440
|
return `poll_${triggerType}`;
|
|
188
441
|
}
|
|
442
|
+
function footerToolName(footerId) {
|
|
443
|
+
return `vorn_footer_${footerId}`;
|
|
444
|
+
}
|
|
445
|
+
function handlerToolName(handlerId) {
|
|
446
|
+
return `vorn_handler_${handlerId}`;
|
|
447
|
+
}
|
|
189
448
|
var MANIFEST_TOOL = "vorn_connector_manifest";
|
|
190
449
|
var PREFLIGHT_TOOL = "vorn_connector_preflight";
|
|
191
450
|
var OPTIONS_TOOL = "vorn_connector_options";
|
|
@@ -216,14 +475,49 @@ function connectionSetup(connector, triggerType) {
|
|
|
216
475
|
}))
|
|
217
476
|
};
|
|
218
477
|
}
|
|
478
|
+
function manifestContributions(connector) {
|
|
479
|
+
const contributes = connector.contributes;
|
|
480
|
+
if (!contributes) return void 0;
|
|
481
|
+
const shared = (contribution) => ({
|
|
482
|
+
id: contribution.id,
|
|
483
|
+
title: contribution.title,
|
|
484
|
+
...contribution.description !== void 0 && { description: contribution.description },
|
|
485
|
+
...contribution.when !== void 0 && { when: contribution.when }
|
|
486
|
+
});
|
|
487
|
+
return {
|
|
488
|
+
...contributes.panes !== void 0 && {
|
|
489
|
+
panes: contributes.panes.map((pane) => ({
|
|
490
|
+
...shared(pane),
|
|
491
|
+
...pane.icon !== void 0 && { icon: pane.icon },
|
|
492
|
+
...pane.web !== void 0 && { web: pane.web },
|
|
493
|
+
...pane.command !== void 0 && { command: pane.command }
|
|
494
|
+
}))
|
|
495
|
+
},
|
|
496
|
+
...contributes.footers !== void 0 && {
|
|
497
|
+
footers: contributes.footers.map((footer) => ({ ...shared(footer), every: footer.every }))
|
|
498
|
+
},
|
|
499
|
+
...contributes.linkHandlers !== void 0 && {
|
|
500
|
+
linkHandlers: contributes.linkHandlers.map((handler) => ({
|
|
501
|
+
...shared(handler),
|
|
502
|
+
pattern: handler.pattern,
|
|
503
|
+
example: handler.example
|
|
504
|
+
}))
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
219
508
|
function connectorManifest(connector) {
|
|
509
|
+
const contributes = manifestContributions(connector);
|
|
220
510
|
return {
|
|
221
511
|
id: connector.id,
|
|
222
512
|
name: connector.name,
|
|
223
513
|
version: connector.version,
|
|
514
|
+
kind: connector.kind,
|
|
224
515
|
...connector.description !== void 0 && { description: connector.description },
|
|
225
516
|
...connector.icon !== void 0 && { icon: connector.icon },
|
|
226
517
|
...connector.auth !== void 0 && { auth: connector.auth },
|
|
518
|
+
...contributes !== void 0 && { contributes },
|
|
519
|
+
...connector.permissions !== void 0 && { permissions: connector.permissions },
|
|
520
|
+
...connector.activates !== void 0 && { activates: connector.activates },
|
|
227
521
|
triggers: connector.triggers.map((trigger) => ({
|
|
228
522
|
type: trigger.type,
|
|
229
523
|
label: trigger.label,
|
|
@@ -256,11 +550,12 @@ function connectorManifest(connector) {
|
|
|
256
550
|
|
|
257
551
|
// src/packaging.ts
|
|
258
552
|
import { builtinModules } from "module";
|
|
259
|
-
import { readFileSync } from "fs";
|
|
260
|
-
import { mkdtemp, writeFile } from "fs/promises";
|
|
553
|
+
import { existsSync, readFileSync } from "fs";
|
|
554
|
+
import { cp, mkdtemp, readdir, stat, writeFile } from "fs/promises";
|
|
261
555
|
import { tmpdir } from "os";
|
|
262
556
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
263
557
|
var MAX_PACK_BYTES = 8 * 1024 * 1024;
|
|
558
|
+
var MAX_UNPACKED_BYTES = 32 * 1024 * 1024;
|
|
264
559
|
var LIFECYCLE_SCRIPTS = [
|
|
265
560
|
"preinstall",
|
|
266
561
|
"install",
|
|
@@ -434,6 +729,15 @@ function readNearestPackageJson(fromDir) {
|
|
|
434
729
|
}
|
|
435
730
|
}
|
|
436
731
|
}
|
|
732
|
+
function packageRootFor(fromDir) {
|
|
733
|
+
let current = resolve(fromDir);
|
|
734
|
+
for (; ; ) {
|
|
735
|
+
if (existsSync(join(current, "package.json"))) return current;
|
|
736
|
+
const parent = dirname(current);
|
|
737
|
+
if (parent === current) return resolve(fromDir);
|
|
738
|
+
current = parent;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
437
741
|
function packEntryContents(entry, sdkModule = "@vornrun/connector-sdk") {
|
|
438
742
|
return [
|
|
439
743
|
`import { serveConnector } from ${JSON.stringify(sdkModule)}`,
|
|
@@ -444,7 +748,20 @@ function packEntryContents(entry, sdkModule = "@vornrun/connector-sdk") {
|
|
|
444
748
|
""
|
|
445
749
|
].join("\n");
|
|
446
750
|
}
|
|
447
|
-
|
|
751
|
+
var WEB_DIR = "web";
|
|
752
|
+
async function directoryBytes(dir) {
|
|
753
|
+
let total = 0;
|
|
754
|
+
for (const entry of await readdir(dir, { withFileTypes: true, recursive: true })) {
|
|
755
|
+
if (!entry.isFile()) continue;
|
|
756
|
+
total += (await stat(join(entry.parentPath, entry.name))).size;
|
|
757
|
+
}
|
|
758
|
+
return total;
|
|
759
|
+
}
|
|
760
|
+
function webDirectories(connector) {
|
|
761
|
+
const dirs = (connector.contributes?.panes ?? []).map((pane) => pane.web).filter((web) => web !== void 0).map((web) => dirname(web));
|
|
762
|
+
return [...new Set(dirs)];
|
|
763
|
+
}
|
|
764
|
+
async function stagePack(connector, code, packageRoot) {
|
|
448
765
|
const dir = await mkdtemp(join(tmpdir(), "vorn-pack-"));
|
|
449
766
|
await writeFile(join(dir, "index.js"), code, "utf8");
|
|
450
767
|
await writeFile(
|
|
@@ -453,6 +770,12 @@ async function stagePack(connector, code) {
|
|
|
453
770
|
`,
|
|
454
771
|
"utf8"
|
|
455
772
|
);
|
|
773
|
+
if (packageRoot !== void 0) {
|
|
774
|
+
for (const relative of webDirectories(connector)) {
|
|
775
|
+
const from = join(packageRoot, relative);
|
|
776
|
+
if (existsSync(from)) await cp(from, join(dir, relative), { recursive: true });
|
|
777
|
+
}
|
|
778
|
+
}
|
|
456
779
|
return dir;
|
|
457
780
|
}
|
|
458
781
|
var LAUNCH_TIMEOUT_MS = 15e3;
|
|
@@ -570,6 +893,81 @@ async function esbuildBundle(request) {
|
|
|
570
893
|
};
|
|
571
894
|
}
|
|
572
895
|
|
|
896
|
+
// src/host.ts
|
|
897
|
+
var HOST_URL_ENV = "VORN_EXTENSION_HOST";
|
|
898
|
+
var HOST_TOKEN_ENV = "VORN_EXTENSION_TOKEN";
|
|
899
|
+
var PermissionDeniedError = class extends Error {
|
|
900
|
+
constructor(method, detail) {
|
|
901
|
+
super(`The host refused ${method}: ${detail}`);
|
|
902
|
+
this.name = "PermissionDeniedError";
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
var HostReplyError = class extends Error {
|
|
906
|
+
constructor(method, detail) {
|
|
907
|
+
super(`The host answered ${method} with ${detail}`);
|
|
908
|
+
this.name = "HostReplyError";
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
var HOST_TIMEOUT_MS = 15e3;
|
|
912
|
+
var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "[::1]"];
|
|
913
|
+
function endpoint(env) {
|
|
914
|
+
const url = env[HOST_URL_ENV]?.trim();
|
|
915
|
+
const token = env[HOST_TOKEN_ENV]?.trim();
|
|
916
|
+
if (!url || !token) {
|
|
917
|
+
throw new Error(
|
|
918
|
+
`This extension was started without a host bridge; ${HOST_URL_ENV} and ${HOST_TOKEN_ENV} are set by Vorn`
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
let parsed;
|
|
922
|
+
try {
|
|
923
|
+
parsed = new URL(url);
|
|
924
|
+
} catch {
|
|
925
|
+
throw new Error(`${HOST_URL_ENV} is ${JSON.stringify(url)}, which is not a URL`);
|
|
926
|
+
}
|
|
927
|
+
if (parsed.protocol !== "http:" || !LOOPBACK_HOSTS.includes(parsed.hostname)) {
|
|
928
|
+
throw new Error(
|
|
929
|
+
`${HOST_URL_ENV} is ${JSON.stringify(url)}; the bridge is served on this machine, over http on ${LOOPBACK_HOSTS.join(", ")}`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
return { url: url.replace(/\/$/, ""), token };
|
|
933
|
+
}
|
|
934
|
+
function createExtensionHost(options) {
|
|
935
|
+
const env = options.env ?? process.env;
|
|
936
|
+
const call = options.fetchImpl ?? fetch;
|
|
937
|
+
async function ask(method, params) {
|
|
938
|
+
const { url, token } = endpoint(env);
|
|
939
|
+
const response = await call(`${url}/${method}`, {
|
|
940
|
+
method: "POST",
|
|
941
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
942
|
+
body: JSON.stringify({ sessionId: options.sessionId, ...params }),
|
|
943
|
+
signal: AbortSignal.timeout(HOST_TIMEOUT_MS)
|
|
944
|
+
});
|
|
945
|
+
const text = await response.text();
|
|
946
|
+
if (response.status === 403) throw new PermissionDeniedError(method, text || "not granted");
|
|
947
|
+
if (!response.ok) throw new Error(`The host answered ${method} with HTTP ${response.status}`);
|
|
948
|
+
if (text === "") return void 0;
|
|
949
|
+
let parsed;
|
|
950
|
+
try {
|
|
951
|
+
parsed = JSON.parse(text);
|
|
952
|
+
} catch {
|
|
953
|
+
throw new HostReplyError(method, "a body that is not JSON");
|
|
954
|
+
}
|
|
955
|
+
if (!parsed || typeof parsed !== "object" || !("result" in parsed)) {
|
|
956
|
+
throw new HostReplyError(method, "a body carrying no result");
|
|
957
|
+
}
|
|
958
|
+
return parsed.result;
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
diff: () => ask("diff", {}),
|
|
962
|
+
status: () => ask("status", {}),
|
|
963
|
+
output: (opts) => ask("output", { ...opts?.lines !== void 0 && { lines: opts.lines } }),
|
|
964
|
+
selection: () => ask("selection", {}),
|
|
965
|
+
send: (text) => ask("send", { text }),
|
|
966
|
+
rename: (name) => ask("rename", { name }),
|
|
967
|
+
usage: () => ask("usage", {})
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
|
|
573
971
|
// src/normalize.ts
|
|
574
972
|
var RESERVED_KEYS = [
|
|
575
973
|
"externalId",
|
|
@@ -1024,8 +1422,8 @@ var DEFAULT_BASE_DELAY_MS = 250;
|
|
|
1024
1422
|
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
1025
1423
|
var MAX_ATTEMPTS = 10;
|
|
1026
1424
|
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
1027
|
-
var wait = (ms) => new Promise((
|
|
1028
|
-
setTimeout(
|
|
1425
|
+
var wait = (ms) => new Promise((resolve4) => {
|
|
1426
|
+
setTimeout(resolve4, ms);
|
|
1029
1427
|
});
|
|
1030
1428
|
function retryAfterMs(header, now) {
|
|
1031
1429
|
if (!header) return void 0;
|
|
@@ -1327,9 +1725,44 @@ function createConnectorHarness(connector, harnessOptions = {}) {
|
|
|
1327
1725
|
withMockHttp
|
|
1328
1726
|
};
|
|
1329
1727
|
}
|
|
1728
|
+
var HOST_FIXTURES = {
|
|
1729
|
+
diff: async () => "diff --git a/src/index.ts b/src/index.ts\n@@ -1 +1 @@\n-const a = 1\n+const a = 2\n",
|
|
1730
|
+
status: async () => " M src/index.ts\n",
|
|
1731
|
+
output: async () => "$ yarn test\n Test Files 1 passed (1)\n",
|
|
1732
|
+
selection: async () => "",
|
|
1733
|
+
send: async () => {
|
|
1734
|
+
},
|
|
1735
|
+
rename: async () => {
|
|
1736
|
+
},
|
|
1737
|
+
usage: async () => ({
|
|
1738
|
+
contextTokens: 13e4,
|
|
1739
|
+
contextWindow: 1e6,
|
|
1740
|
+
cacheHitRate: 0.93,
|
|
1741
|
+
limits: [{ window: "5h", remaining: 0.72 }]
|
|
1742
|
+
})
|
|
1743
|
+
};
|
|
1744
|
+
function mockExtensionHost(granted, answers = {}) {
|
|
1745
|
+
const allowed = new Set(granted);
|
|
1746
|
+
const used = /* @__PURE__ */ new Set();
|
|
1747
|
+
const host = {};
|
|
1748
|
+
for (const name of Object.keys(HOST_FIXTURES)) {
|
|
1749
|
+
const permission = HOST_PERMISSIONS[name];
|
|
1750
|
+
host[name] = async (...args) => {
|
|
1751
|
+
used.add(permission);
|
|
1752
|
+
if (!allowed.has(permission)) {
|
|
1753
|
+
throw new PermissionDeniedError(name, `this extension does not ask for ${permission}`);
|
|
1754
|
+
}
|
|
1755
|
+
const answer = answers[name] ?? HOST_FIXTURES[name];
|
|
1756
|
+
return answer(...args);
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
return { host, used };
|
|
1760
|
+
}
|
|
1330
1761
|
|
|
1331
1762
|
// src/check.ts
|
|
1763
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1332
1764
|
import { rm } from "fs/promises";
|
|
1765
|
+
import { resolve as resolve2, sep } from "path";
|
|
1333
1766
|
var EXECUTABLE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1334
1767
|
var CREDENTIAL_NAME = /(secret|token|password|passphrase|api[-_]?key|credential)/i;
|
|
1335
1768
|
var INPUT_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "select", "json"]);
|
|
@@ -1429,6 +1862,10 @@ function bundles(options) {
|
|
|
1429
1862
|
function launches(options) {
|
|
1430
1863
|
return bundles(options) && options.mock === true;
|
|
1431
1864
|
}
|
|
1865
|
+
function packageRootOf(options) {
|
|
1866
|
+
if (options.packageDir === void 0) return void 0;
|
|
1867
|
+
return packageRootFor(packageDirFor(options.packageDir, options.entry));
|
|
1868
|
+
}
|
|
1432
1869
|
async function packageFindings(connector, options) {
|
|
1433
1870
|
if (options.packageDir === void 0) return [];
|
|
1434
1871
|
const pkg = readNearestPackageJson(packageDirFor(options.packageDir, options.entry));
|
|
@@ -1452,7 +1889,7 @@ async function packageFindings(connector, options) {
|
|
|
1452
1889
|
});
|
|
1453
1890
|
found.push(...bundleDependencyFindings(built.external), ...bundledRequireFindings(built.code));
|
|
1454
1891
|
if (options.mock) {
|
|
1455
|
-
const dir = await stagePack(connector, built.code);
|
|
1892
|
+
const dir = await stagePack(connector, built.code, packageRootOf(options));
|
|
1456
1893
|
try {
|
|
1457
1894
|
found.push(...await packLaunchFindings(dir));
|
|
1458
1895
|
} finally {
|
|
@@ -1523,6 +1960,153 @@ async function mockFindings(connector, options) {
|
|
|
1523
1960
|
}
|
|
1524
1961
|
return found;
|
|
1525
1962
|
}
|
|
1963
|
+
var CHECK_SESSION = {
|
|
1964
|
+
sessionId: "check",
|
|
1965
|
+
worktreePath: process.cwd(),
|
|
1966
|
+
agent: "claude"
|
|
1967
|
+
};
|
|
1968
|
+
var FOOTER_TONES = ["default", "ok", "danger"];
|
|
1969
|
+
var FOOTER_HREF_PROTOCOLS = ["http:", "https:"];
|
|
1970
|
+
function invalidItem(item) {
|
|
1971
|
+
if (!item || typeof item !== "object") return "is not an object";
|
|
1972
|
+
const reading = item;
|
|
1973
|
+
if (typeof reading.label !== "string" || reading.label === "") return "has no label";
|
|
1974
|
+
if (typeof reading.value !== "string") return "has no value";
|
|
1975
|
+
if (reading.tone !== void 0 && !FOOTER_TONES.includes(reading.tone)) {
|
|
1976
|
+
return `has unknown tone ${JSON.stringify(reading.tone)}`;
|
|
1977
|
+
}
|
|
1978
|
+
if (reading.href === void 0) return void 0;
|
|
1979
|
+
if (typeof reading.href !== "string") return "has a link that is not text";
|
|
1980
|
+
let protocol;
|
|
1981
|
+
try {
|
|
1982
|
+
protocol = new URL(reading.href).protocol;
|
|
1983
|
+
} catch {
|
|
1984
|
+
return `has the link ${JSON.stringify(reading.href)}, which is not a URL`;
|
|
1985
|
+
}
|
|
1986
|
+
if (!FOOTER_HREF_PROTOCOLS.includes(protocol)) {
|
|
1987
|
+
return `has the link ${JSON.stringify(reading.href)}; a reading links to ${FOOTER_HREF_PROTOCOLS.join(" or ")} and nothing else`;
|
|
1988
|
+
}
|
|
1989
|
+
return void 0;
|
|
1990
|
+
}
|
|
1991
|
+
async function contributionFindings(connector) {
|
|
1992
|
+
const contributes = connector.contributes;
|
|
1993
|
+
if (!contributes) return [];
|
|
1994
|
+
const found = [];
|
|
1995
|
+
const declared = connector.permissions ?? [];
|
|
1996
|
+
const spent = /* @__PURE__ */ new Set();
|
|
1997
|
+
const ran = async (kind, id, code, body) => {
|
|
1998
|
+
const { host, used } = mockExtensionHost(declared);
|
|
1999
|
+
try {
|
|
2000
|
+
return await body(host);
|
|
2001
|
+
} catch (error) {
|
|
2002
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2003
|
+
found.push(
|
|
2004
|
+
finding2(
|
|
2005
|
+
"error",
|
|
2006
|
+
error instanceof PermissionDeniedError ? "permission-undeclared" : code,
|
|
2007
|
+
`${kind} ${id}`,
|
|
2008
|
+
error instanceof PermissionDeniedError ? `asked the host for something this extension does not declare: ${reason}` : `threw: ${reason}`
|
|
2009
|
+
)
|
|
2010
|
+
);
|
|
2011
|
+
return void 0;
|
|
2012
|
+
} finally {
|
|
2013
|
+
for (const permission of used) spent.add(permission);
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
for (const footer of contributes.footers ?? []) {
|
|
2017
|
+
const items = await ran(
|
|
2018
|
+
"footer",
|
|
2019
|
+
footer.id,
|
|
2020
|
+
"footer-failed",
|
|
2021
|
+
(host) => Promise.resolve(footer.run({ ...CHECK_SESSION, host, now: () => (/* @__PURE__ */ new Date()).toISOString() }))
|
|
2022
|
+
);
|
|
2023
|
+
if (items === void 0) continue;
|
|
2024
|
+
if (!Array.isArray(items)) {
|
|
2025
|
+
found.push(
|
|
2026
|
+
finding2(
|
|
2027
|
+
"error",
|
|
2028
|
+
"footer-items-invalid",
|
|
2029
|
+
`footer ${footer.id}`,
|
|
2030
|
+
"returned something that is not a list of readings"
|
|
2031
|
+
)
|
|
2032
|
+
);
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
for (const item of items) {
|
|
2036
|
+
const wrong = invalidItem(item);
|
|
2037
|
+
if (wrong) {
|
|
2038
|
+
found.push(
|
|
2039
|
+
finding2(
|
|
2040
|
+
"error",
|
|
2041
|
+
"footer-items-invalid",
|
|
2042
|
+
`footer ${footer.id}`,
|
|
2043
|
+
`returned a reading that ${wrong}`
|
|
2044
|
+
)
|
|
2045
|
+
);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
for (const handler of contributes.linkHandlers ?? []) {
|
|
2050
|
+
await ran(
|
|
2051
|
+
"link handler",
|
|
2052
|
+
handler.id,
|
|
2053
|
+
"handler-failed",
|
|
2054
|
+
(host) => Promise.resolve(
|
|
2055
|
+
handler.run({
|
|
2056
|
+
...CHECK_SESSION,
|
|
2057
|
+
host,
|
|
2058
|
+
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
2059
|
+
url: handler.example
|
|
2060
|
+
})
|
|
2061
|
+
)
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
const observable = !(contributes.panes ?? []).some((pane) => pane.web !== void 0);
|
|
2065
|
+
for (const permission of declared) {
|
|
2066
|
+
if (observable && !spent.has(permission)) {
|
|
2067
|
+
found.push(
|
|
2068
|
+
finding2(
|
|
2069
|
+
"warn",
|
|
2070
|
+
"permission-unused",
|
|
2071
|
+
`${connector.id} permissions`,
|
|
2072
|
+
`asks for ${permission} but nothing this run exercised used it; ask only for what it spends`
|
|
2073
|
+
)
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
return found;
|
|
2078
|
+
}
|
|
2079
|
+
function paneFindings(connector, packageRoot) {
|
|
2080
|
+
if (packageRoot === void 0) return [];
|
|
2081
|
+
const root = resolve2(packageRoot);
|
|
2082
|
+
const found = [];
|
|
2083
|
+
for (const pane of connector.contributes?.panes ?? []) {
|
|
2084
|
+
if (pane.web === void 0) continue;
|
|
2085
|
+
const full = resolve2(root, pane.web);
|
|
2086
|
+
if (full !== root && !full.startsWith(`${root}${sep}`)) {
|
|
2087
|
+
found.push(
|
|
2088
|
+
finding2(
|
|
2089
|
+
"error",
|
|
2090
|
+
"web-entry-outside-package",
|
|
2091
|
+
`pane ${pane.id}`,
|
|
2092
|
+
`declares the page "${pane.web}", which resolves outside the package`
|
|
2093
|
+
)
|
|
2094
|
+
);
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
if (!existsSync2(full)) {
|
|
2098
|
+
found.push(
|
|
2099
|
+
finding2(
|
|
2100
|
+
"error",
|
|
2101
|
+
"web-entry-missing",
|
|
2102
|
+
`pane ${pane.id}`,
|
|
2103
|
+
`declares the page "${pane.web}", which the package does not carry`
|
|
2104
|
+
)
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return found;
|
|
2109
|
+
}
|
|
1526
2110
|
var AUTH_FAILURE = /\b(401|403|unauthor|unauthenticat|forbidden|invalid[- ]?(token|credential))/i;
|
|
1527
2111
|
function liveRunnable(action) {
|
|
1528
2112
|
if (action.idempotent !== true) return false;
|
|
@@ -1651,8 +2235,10 @@ async function checkConnector(connector, options = {}) {
|
|
|
1651
2235
|
)
|
|
1652
2236
|
);
|
|
1653
2237
|
}
|
|
1654
|
-
found.push(...authFindings(connector));
|
|
2238
|
+
if (connector.kind !== "extension") found.push(...authFindings(connector));
|
|
1655
2239
|
found.push(...secretFindings(connector));
|
|
2240
|
+
found.push(...paneFindings(connector, packageRootOf(options)));
|
|
2241
|
+
found.push(...await contributionFindings(connector));
|
|
1656
2242
|
found.push(...await packageFindings(connector, options));
|
|
1657
2243
|
found.push(...await mockFindings(connector, options));
|
|
1658
2244
|
found.push(...await liveFindings(connector, options));
|
|
@@ -1750,10 +2336,23 @@ var CHECK_OWNERS = {
|
|
|
1750
2336
|
"mock-not-observed": "mock",
|
|
1751
2337
|
"preflight-failed": "live",
|
|
1752
2338
|
"live-action-failed": "live",
|
|
1753
|
-
"pack-launch": "launch"
|
|
2339
|
+
"pack-launch": "launch",
|
|
2340
|
+
"web-entry-missing": "contributes",
|
|
2341
|
+
"web-entry-outside-package": "contributes",
|
|
2342
|
+
"footer-failed": "footers",
|
|
2343
|
+
"footer-items-invalid": "footers",
|
|
2344
|
+
"handler-failed": "handlers",
|
|
2345
|
+
"permission-undeclared": "permissions",
|
|
2346
|
+
"permission-unused": "permissions"
|
|
1754
2347
|
};
|
|
1755
2348
|
function checksRun(connector, options) {
|
|
1756
|
-
const names = ["manifest"
|
|
2349
|
+
const names = ["manifest"];
|
|
2350
|
+
if (connector.kind !== "extension") names.push("auth");
|
|
2351
|
+
const contributes = connector.contributes;
|
|
2352
|
+
if (contributes?.panes?.length && options.packageDir !== void 0) names.push("contributes");
|
|
2353
|
+
if (contributes?.footers?.length) names.push("footers");
|
|
2354
|
+
if (contributes?.linkHandlers?.length) names.push("handlers");
|
|
2355
|
+
if (connector.permissions !== void 0) names.push("permissions");
|
|
1757
2356
|
if (connector.config.length > 0) names.push("secrets");
|
|
1758
2357
|
if (connector.actions.length > 0) names.push("actions");
|
|
1759
2358
|
if (connector.triggers.length > 0) names.push("dedupe");
|
|
@@ -1790,8 +2389,9 @@ function formatFindings(findings) {
|
|
|
1790
2389
|
}
|
|
1791
2390
|
|
|
1792
2391
|
// src/pack.ts
|
|
1793
|
-
import {
|
|
1794
|
-
import {
|
|
2392
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2393
|
+
import { mkdir, rm as rm2, stat as stat2 } from "fs/promises";
|
|
2394
|
+
import { join as join2, resolve as resolve3 } from "path";
|
|
1795
2395
|
function finding3(code, target, message) {
|
|
1796
2396
|
return { level: "error", code, target, message };
|
|
1797
2397
|
}
|
|
@@ -1799,9 +2399,10 @@ function packFileName(connector) {
|
|
|
1799
2399
|
return `${connector.id}-${connector.version}.vorn.tgz`;
|
|
1800
2400
|
}
|
|
1801
2401
|
async function packConnector(connector, options) {
|
|
1802
|
-
const resolveDir =
|
|
2402
|
+
const resolveDir = resolve3(options.resolveDir ?? process.cwd());
|
|
1803
2403
|
const entryDir = packageDirFor(resolveDir, options.entry);
|
|
1804
|
-
const
|
|
2404
|
+
const packageRoot = packageRootFor(entryDir);
|
|
2405
|
+
const findings = await checkConnector(connector, { packageDir: packageRoot });
|
|
1805
2406
|
findings.push(...lifecycleScriptFindings(readNearestPackageJson(entryDir)));
|
|
1806
2407
|
if (findings.some((item) => item.level === "error")) return { findings };
|
|
1807
2408
|
const contents = packEntryContents(options.entry, options.sdkModule);
|
|
@@ -1809,19 +2410,37 @@ async function packConnector(connector, options) {
|
|
|
1809
2410
|
const built = await bundle({ contents, resolveDir });
|
|
1810
2411
|
findings.push(...bundleDependencyFindings(built.external), ...bundledRequireFindings(built.code));
|
|
1811
2412
|
if (findings.some((item) => item.level === "error")) return { findings };
|
|
1812
|
-
const outDir =
|
|
2413
|
+
const outDir = resolve3(options.outDir ?? process.cwd());
|
|
1813
2414
|
await mkdir(outDir, { recursive: true });
|
|
1814
2415
|
const file = join2(outDir, packFileName(connector));
|
|
1815
|
-
const staging = await stagePack(connector, built.code);
|
|
2416
|
+
const staging = await stagePack(connector, built.code, packageRoot);
|
|
1816
2417
|
try {
|
|
1817
2418
|
findings.push(...await (options.launch ?? packLaunchFindings)(staging));
|
|
1818
2419
|
if (findings.some((item) => item.level === "error")) return { findings };
|
|
2420
|
+
const unpacked = await directoryBytes(staging);
|
|
2421
|
+
const maxUnpacked = options.maxUnpackedBytes ?? MAX_UNPACKED_BYTES;
|
|
2422
|
+
if (unpacked > maxUnpacked) {
|
|
2423
|
+
return {
|
|
2424
|
+
findings: [
|
|
2425
|
+
...findings,
|
|
2426
|
+
finding3(
|
|
2427
|
+
"pack-too-large",
|
|
2428
|
+
"bundle",
|
|
2429
|
+
`The pack unpacks to ${Math.round(unpacked / 1024)} KB; Vorn unpacks at most ${Math.round(maxUnpacked / 1024)} KB`
|
|
2430
|
+
)
|
|
2431
|
+
]
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
1819
2434
|
const { create } = await import("tar");
|
|
1820
|
-
await create({ gzip: true, file, cwd: staging }, [
|
|
2435
|
+
await create({ gzip: true, file, cwd: staging }, [
|
|
2436
|
+
"manifest.json",
|
|
2437
|
+
"index.js",
|
|
2438
|
+
...existsSync3(join2(staging, WEB_DIR)) ? [WEB_DIR] : []
|
|
2439
|
+
]);
|
|
1821
2440
|
} finally {
|
|
1822
2441
|
await rm2(staging, { recursive: true, force: true });
|
|
1823
2442
|
}
|
|
1824
|
-
const bytes = (await
|
|
2443
|
+
const bytes = (await stat2(file)).size;
|
|
1825
2444
|
const maxBytes = options.maxBytes ?? MAX_PACK_BYTES;
|
|
1826
2445
|
if (bytes > maxBytes) {
|
|
1827
2446
|
await rm2(file, { force: true });
|
|
@@ -1841,7 +2460,7 @@ async function packConnector(connector, options) {
|
|
|
1841
2460
|
|
|
1842
2461
|
// src/scaffold.ts
|
|
1843
2462
|
var ID_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
1844
|
-
var SDK_DEPENDENCY_RANGE = "^0.7.0-beta.
|
|
2463
|
+
var SDK_DEPENDENCY_RANGE = "^0.7.0-beta.14";
|
|
1845
2464
|
var SCAFFOLD_VERSION = "0.1.0";
|
|
1846
2465
|
var VITEST_RANGE = "^4.1.10";
|
|
1847
2466
|
function jsonFile(value) {
|
|
@@ -1851,17 +2470,24 @@ function jsonFile(value) {
|
|
|
1851
2470
|
function titleCase(id) {
|
|
1852
2471
|
return id.split(/[-_]+/).filter((part) => part !== "").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
1853
2472
|
}
|
|
1854
|
-
function packageJson(id, description, inRepo) {
|
|
2473
|
+
function packageJson(id, description, inRepo, kind) {
|
|
2474
|
+
const scoped = kind === "extension" ? "extension" : "connector";
|
|
1855
2475
|
return jsonFile({
|
|
1856
|
-
name: inRepo ? `@vornrun
|
|
2476
|
+
name: inRepo ? `@vornrun/${scoped}-${id}` : `vorn-${scoped}-${id}`,
|
|
1857
2477
|
version: SCAFFOLD_VERSION,
|
|
1858
2478
|
description,
|
|
1859
2479
|
type: "module",
|
|
1860
2480
|
license: "MIT",
|
|
1861
|
-
bin: { [`vorn
|
|
2481
|
+
bin: { [`vorn-${scoped}-${id}`]: "dist/index.js" },
|
|
1862
2482
|
main: "./dist/index.js",
|
|
1863
2483
|
types: "./dist/index.d.ts",
|
|
1864
|
-
|
|
2484
|
+
// A pane's page ships beside the bundle, so `web` is published like `dist`.
|
|
2485
|
+
files: [
|
|
2486
|
+
"dist",
|
|
2487
|
+
"README.md",
|
|
2488
|
+
...kind === "extension" ? ["web"] : [],
|
|
2489
|
+
...inRepo ? ["CHANGELOG.md"] : []
|
|
2490
|
+
],
|
|
1865
2491
|
...inRepo && {
|
|
1866
2492
|
repository: {
|
|
1867
2493
|
type: "git",
|
|
@@ -1884,11 +2510,12 @@ function packageJson(id, description, inRepo) {
|
|
|
1884
2510
|
typescript: "^6.0.3",
|
|
1885
2511
|
vitest: VITEST_RANGE
|
|
1886
2512
|
},
|
|
1887
|
-
// Read by the catalog build: how this
|
|
2513
|
+
// Read by the catalog build: how this is filed, found, and what it asks of you.
|
|
1888
2514
|
vorn: {
|
|
1889
|
-
category: "Other",
|
|
2515
|
+
category: kind === "extension" ? "Extensions" : "Other",
|
|
1890
2516
|
keywords: [id],
|
|
1891
|
-
...
|
|
2517
|
+
...kind === "extension" && { kind: "extension" },
|
|
2518
|
+
...inRepo && kind === "connector" && { auth: "Say in one line what signing in takes." }
|
|
1892
2519
|
}
|
|
1893
2520
|
});
|
|
1894
2521
|
}
|
|
@@ -2010,11 +2637,149 @@ export const connector = defineConnector({
|
|
|
2010
2637
|
})
|
|
2011
2638
|
`;
|
|
2012
2639
|
}
|
|
2013
|
-
function
|
|
2640
|
+
function extensionSource(id, name, description) {
|
|
2641
|
+
return `import { defineExtension } from '@vornrun/connector-sdk'
|
|
2642
|
+
// Bundled at build time: a pack is one file, so a version read from disk is not there to read.
|
|
2643
|
+
import pkg from '../package.json'
|
|
2644
|
+
|
|
2645
|
+
export const connector = defineExtension({
|
|
2646
|
+
id: ${JSON.stringify(id)},
|
|
2647
|
+
name: ${JSON.stringify(name)},
|
|
2648
|
+
description: ${JSON.stringify(description)},
|
|
2649
|
+
version: pkg.version,
|
|
2650
|
+
// Only what this actually spends: the check names one it declared and never used.
|
|
2651
|
+
permissions: ['terminal.read'],
|
|
2652
|
+
// Absent where none of these hold, rather than showing a band with nothing in it.
|
|
2653
|
+
activates: { workspaceContains: ['package.json'] },
|
|
2654
|
+
footers: [
|
|
2655
|
+
{
|
|
2656
|
+
id: 'checks',
|
|
2657
|
+
title: 'Checks',
|
|
2658
|
+
description: 'What the last commands in this session said',
|
|
2659
|
+
every: 30,
|
|
2660
|
+
async run(context) {
|
|
2661
|
+
const output = await context.host.output({ lines: 200 })
|
|
2662
|
+
const failed = /\\b(FAIL|failed|error)\\b/i.test(output)
|
|
2663
|
+
return [
|
|
2664
|
+
{
|
|
2665
|
+
label: 'tests',
|
|
2666
|
+
value: failed ? 'failing' : 'passing',
|
|
2667
|
+
tone: failed ? 'danger' : 'ok'
|
|
2668
|
+
}
|
|
2669
|
+
]
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
],
|
|
2673
|
+
panes: [
|
|
2674
|
+
{
|
|
2675
|
+
id: 'report',
|
|
2676
|
+
title: 'Report',
|
|
2677
|
+
description: 'The reading, in full, beside the terminal',
|
|
2678
|
+
// Drawn beside the pane's name wherever it is offered; path data only.
|
|
2679
|
+
icon: { viewBox: '0 0 24 24', paths: ['M4 4h16v16H4z M8 9h8 M8 13h8 M8 17h5'] },
|
|
2680
|
+
// Served from the pack; everything the page needs lives under web/.
|
|
2681
|
+
web: 'web/report/index.html'
|
|
2682
|
+
}
|
|
2683
|
+
]
|
|
2684
|
+
})
|
|
2685
|
+
`;
|
|
2686
|
+
}
|
|
2687
|
+
function extensionPage(name) {
|
|
2688
|
+
return `<!doctype html>
|
|
2689
|
+
<html lang="en">
|
|
2690
|
+
<head>
|
|
2691
|
+
<meta charset="utf-8" />
|
|
2692
|
+
<title>${name}</title>
|
|
2693
|
+
<style>
|
|
2694
|
+
body {
|
|
2695
|
+
margin: 0;
|
|
2696
|
+
padding: 12px;
|
|
2697
|
+
font: 12px ui-sans-serif, system-ui, sans-serif;
|
|
2698
|
+
color: #faf9f7;
|
|
2699
|
+
background: #101012;
|
|
2700
|
+
}
|
|
2701
|
+
h1 {
|
|
2702
|
+
font-size: 13px;
|
|
2703
|
+
font-weight: 500;
|
|
2704
|
+
margin: 0 0 8px;
|
|
2705
|
+
}
|
|
2706
|
+
pre {
|
|
2707
|
+
margin: 0;
|
|
2708
|
+
white-space: pre-wrap;
|
|
2709
|
+
color: rgba(255, 255, 255, 0.55);
|
|
2710
|
+
}
|
|
2711
|
+
</style>
|
|
2712
|
+
</head>
|
|
2713
|
+
<body>
|
|
2714
|
+
<h1>${name}</h1>
|
|
2715
|
+
<pre id="output">Reading the session\u2026</pre>
|
|
2716
|
+
<script type="module">
|
|
2717
|
+
// Same origin, so the page carries no credential: Vorn knows which pane is
|
|
2718
|
+
// asking and grants exactly the permissions the manifest declared.
|
|
2719
|
+
const ask = async (method, body = {}) => {
|
|
2720
|
+
const response = await fetch('bridge/' + method, {
|
|
2721
|
+
method: 'POST',
|
|
2722
|
+
headers: { 'content-type': 'application/json' },
|
|
2723
|
+
body: JSON.stringify(body)
|
|
2724
|
+
})
|
|
2725
|
+
if (!response.ok) throw new Error(method + ' answered ' + response.status)
|
|
2726
|
+
return (await response.json()).result
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
const node = document.getElementById('output')
|
|
2730
|
+
try {
|
|
2731
|
+
node.textContent = await ask('output', { lines: 200 })
|
|
2732
|
+
} catch (error) {
|
|
2733
|
+
node.textContent = String(error)
|
|
2734
|
+
}
|
|
2735
|
+
</script>
|
|
2736
|
+
</body>
|
|
2737
|
+
</html>
|
|
2738
|
+
`;
|
|
2739
|
+
}
|
|
2740
|
+
function extensionTestSource(name) {
|
|
2741
|
+
return `import { describe, expect, it } from 'vitest'
|
|
2742
|
+
import { mockExtensionHost } from '@vornrun/connector-sdk'
|
|
2743
|
+
import { connector } from './extension'
|
|
2744
|
+
|
|
2745
|
+
/** Runs one footer the way the host will, against a stub that enforces the manifest. */
|
|
2746
|
+
async function footer(id: string, output: string) {
|
|
2747
|
+
const { host } = mockExtensionHost(connector.permissions ?? [], { output: async () => output })
|
|
2748
|
+
const declared = connector.contributes?.footers?.find((entry) => entry.id === id)
|
|
2749
|
+
if (!declared) throw new Error('no footer ' + id)
|
|
2750
|
+
return declared.run({
|
|
2751
|
+
sessionId: 'test',
|
|
2752
|
+
worktreePath: process.cwd(),
|
|
2753
|
+
agent: 'claude',
|
|
2754
|
+
host,
|
|
2755
|
+
now: () => '2026-01-01T00:00:00.000Z'
|
|
2756
|
+
})
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
describe(${JSON.stringify(name)}, () => {
|
|
2760
|
+
it('reads the session as passing when nothing failed', async () => {
|
|
2761
|
+
expect(await footer('checks', 'Test Files 1 passed (1)')).toEqual([
|
|
2762
|
+
{ label: 'tests', value: 'passing', tone: 'ok' }
|
|
2763
|
+
])
|
|
2764
|
+
})
|
|
2765
|
+
|
|
2766
|
+
it('reads it as failing when the output says so', async () => {
|
|
2767
|
+
expect(await footer('checks', 'FAIL src/index.test.ts')).toEqual([
|
|
2768
|
+
{ label: 'tests', value: 'failing', tone: 'danger' }
|
|
2769
|
+
])
|
|
2770
|
+
})
|
|
2771
|
+
|
|
2772
|
+
it('asks for nothing it did not declare', () => {
|
|
2773
|
+
expect(connector.permissions).toEqual(['terminal.read'])
|
|
2774
|
+
})
|
|
2775
|
+
})
|
|
2776
|
+
`;
|
|
2777
|
+
}
|
|
2778
|
+
function entrySource(module) {
|
|
2014
2779
|
return `import { realpathSync } from 'node:fs'
|
|
2015
2780
|
import { fileURLToPath } from 'node:url'
|
|
2016
2781
|
import { serveConnector } from '@vornrun/connector-sdk'
|
|
2017
|
-
import { connector } from '
|
|
2782
|
+
import { connector } from './${module}'
|
|
2018
2783
|
|
|
2019
2784
|
/** True when this file was run directly rather than imported. */
|
|
2020
2785
|
export function isEntryPoint(moduleUrl: string, argv = process.argv): boolean {
|
|
@@ -2082,8 +2847,8 @@ describe('the packaged connector', () => {
|
|
|
2082
2847
|
})
|
|
2083
2848
|
`;
|
|
2084
2849
|
}
|
|
2085
|
-
function indexSource() {
|
|
2086
|
-
return `import { connector } from '
|
|
2850
|
+
function indexSource(module) {
|
|
2851
|
+
return `import { connector } from './${module}'
|
|
2087
2852
|
import { serveIfEntryPoint } from './entry'
|
|
2088
2853
|
|
|
2089
2854
|
export { connector }
|
|
@@ -2188,21 +2953,67 @@ Rename the trigger, the action and the settings to whatever this connector
|
|
|
2188
2953
|
really talks to; the shapes here are a starting point, not a rule.
|
|
2189
2954
|
`;
|
|
2190
2955
|
}
|
|
2956
|
+
function extensionReadme(id, name, description) {
|
|
2957
|
+
return `# ${name}
|
|
2958
|
+
|
|
2959
|
+
${description}
|
|
2960
|
+
|
|
2961
|
+
## Build and check
|
|
2962
|
+
|
|
2963
|
+
\`\`\`sh
|
|
2964
|
+
yarn install
|
|
2965
|
+
yarn build
|
|
2966
|
+
yarn check # verifies the extension against Vorn's contract
|
|
2967
|
+
yarn test
|
|
2968
|
+
yarn pack # writes ${id}-${SCAFFOLD_VERSION}.vorn.tgz, installable in Vorn
|
|
2969
|
+
\`\`\`
|
|
2970
|
+
|
|
2971
|
+
## What it contributes
|
|
2972
|
+
|
|
2973
|
+
| Kind | Name | What it does |
|
|
2974
|
+
| --- | --- | --- |
|
|
2975
|
+
| Footer | Checks | A band under the card's status bar, recomputed every 30s |
|
|
2976
|
+
| Pane | Report | A page beside the terminal, served from \`web/report\` |
|
|
2977
|
+
|
|
2978
|
+
## What it asks for
|
|
2979
|
+
|
|
2980
|
+
| Permission | What it grants |
|
|
2981
|
+
| --- | --- |
|
|
2982
|
+
| \`terminal.read\` | The session's recent terminal output |
|
|
2983
|
+
|
|
2984
|
+
Ask for only what the extension spends: \`check\` names a permission that was
|
|
2985
|
+
declared and never used.
|
|
2986
|
+
|
|
2987
|
+
## Where it shows
|
|
2988
|
+
|
|
2989
|
+
Sessions whose worktree has a \`package.json\`. Widen or narrow that in
|
|
2990
|
+
\`activates\`, and narrow one contribution further with its own \`when\`.
|
|
2991
|
+
`;
|
|
2992
|
+
}
|
|
2191
2993
|
function scaffoldFiles(options) {
|
|
2994
|
+
const kind = options.kind ?? "connector";
|
|
2192
2995
|
if (!ID_PATTERN.test(options.id ?? "")) {
|
|
2193
|
-
throw new Error(
|
|
2996
|
+
throw new Error(
|
|
2997
|
+
`${kind === "extension" ? "Extension" : "Connector"} id "${options.id}" must start with a letter and be url-safe`
|
|
2998
|
+
);
|
|
2194
2999
|
}
|
|
2195
3000
|
const name = options.name?.trim() || titleCase(options.id);
|
|
2196
|
-
const description = options.description?.trim() || `${name}
|
|
3001
|
+
const description = options.description?.trim() || `${name} ${kind} for Vorn`;
|
|
2197
3002
|
const inRepo = options.repoConventions ?? false;
|
|
3003
|
+
const module = kind === "extension" ? "extension" : "connector";
|
|
2198
3004
|
return [
|
|
2199
|
-
{ path: "package.json", contents: packageJson(options.id, description, inRepo) },
|
|
2200
|
-
{ path: "src/connector.ts", contents: connectorSource(options.id, name, description) },
|
|
2201
|
-
{ path: "src/entry.ts", contents: entrySource() },
|
|
2202
|
-
{ path: "src/index.ts", contents: indexSource() },
|
|
2203
|
-
{ path: "src/connector.test.ts", contents: testSource(name) },
|
|
3005
|
+
{ path: "package.json", contents: packageJson(options.id, description, inRepo, kind) },
|
|
3006
|
+
kind === "extension" ? { path: "src/extension.ts", contents: extensionSource(options.id, name, description) } : { path: "src/connector.ts", contents: connectorSource(options.id, name, description) },
|
|
3007
|
+
{ path: "src/entry.ts", contents: entrySource(module) },
|
|
3008
|
+
{ path: "src/index.ts", contents: indexSource(module) },
|
|
3009
|
+
kind === "extension" ? { path: "src/extension.test.ts", contents: extensionTestSource(name) } : { path: "src/connector.test.ts", contents: testSource(name) },
|
|
2204
3010
|
{ path: "src/entry.test.ts", contents: entryTestSource() },
|
|
2205
|
-
|
|
3011
|
+
// The page a pane is drawn from, carried into the pack as it stands here.
|
|
3012
|
+
...kind === "extension" ? [{ path: "web/report/index.html", contents: extensionPage(name) }] : [],
|
|
3013
|
+
{
|
|
3014
|
+
path: "README.md",
|
|
3015
|
+
contents: kind === "extension" ? extensionReadme(options.id, name, description) : readme(options.id, name, description)
|
|
3016
|
+
},
|
|
2206
3017
|
// Everywhere: the generated source imports its package.json, which needs resolveJsonModule to compile.
|
|
2207
3018
|
{ path: "tsconfig.json", contents: tsconfig(inRepo) },
|
|
2208
3019
|
...inRepo ? [
|
|
@@ -2369,6 +3180,67 @@ function createConnectorServer(connector, options = {}) {
|
|
|
2369
3180
|
}
|
|
2370
3181
|
);
|
|
2371
3182
|
}
|
|
3183
|
+
const sessionShape = {
|
|
3184
|
+
sessionId: z.string().describe("The session this is being computed for"),
|
|
3185
|
+
worktreePath: z.string().describe("Where the session's work is"),
|
|
3186
|
+
agent: z.enum(EXTENSION_AGENTS).describe("Which agent runs in the session")
|
|
3187
|
+
};
|
|
3188
|
+
const sessionContext = (args, host) => ({
|
|
3189
|
+
sessionId: args.sessionId,
|
|
3190
|
+
worktreePath: args.worktreePath,
|
|
3191
|
+
agent: args.agent,
|
|
3192
|
+
host,
|
|
3193
|
+
now: options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
|
|
3194
|
+
});
|
|
3195
|
+
const hostFor = (sessionId) => options.host?.(sessionId) ?? createExtensionHost({ sessionId });
|
|
3196
|
+
for (const footer of connector.contributes?.footers ?? []) {
|
|
3197
|
+
server.registerTool(
|
|
3198
|
+
footerToolName(footer.id),
|
|
3199
|
+
{
|
|
3200
|
+
title: footer.title,
|
|
3201
|
+
description: footer.description ?? `Recompute ${footer.title} for one session`,
|
|
3202
|
+
inputSchema: sessionShape,
|
|
3203
|
+
outputSchema: z.looseObject({
|
|
3204
|
+
items: z.array(z.looseObject({ label: z.string(), value: z.string() })).describe("The readings to show in the band")
|
|
3205
|
+
})
|
|
3206
|
+
},
|
|
3207
|
+
async (args) => {
|
|
3208
|
+
try {
|
|
3209
|
+
const items = await footer.run(sessionContext(args, hostFor(args.sessionId)));
|
|
3210
|
+
return json({ items });
|
|
3211
|
+
} catch (error) {
|
|
3212
|
+
return failure(error);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
);
|
|
3216
|
+
}
|
|
3217
|
+
for (const handler of connector.contributes?.linkHandlers ?? []) {
|
|
3218
|
+
server.registerTool(
|
|
3219
|
+
handlerToolName(handler.id),
|
|
3220
|
+
{
|
|
3221
|
+
title: handler.title,
|
|
3222
|
+
description: handler.description ?? `Open ${handler.title} for a clicked link`,
|
|
3223
|
+
inputSchema: {
|
|
3224
|
+
...sessionShape,
|
|
3225
|
+
url: z.string().describe("The clicked text, which matched this handler")
|
|
3226
|
+
},
|
|
3227
|
+
outputSchema: z.looseObject({
|
|
3228
|
+
openPane: z.string().optional().describe("Id of one of this extension's panes to open")
|
|
3229
|
+
})
|
|
3230
|
+
},
|
|
3231
|
+
async (args) => {
|
|
3232
|
+
try {
|
|
3233
|
+
const handled = await handler.run({
|
|
3234
|
+
...sessionContext(args, hostFor(args.sessionId)),
|
|
3235
|
+
url: args.url
|
|
3236
|
+
});
|
|
3237
|
+
return json({ ...handled ?? {} });
|
|
3238
|
+
} catch (error) {
|
|
3239
|
+
return failure(error);
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
2372
3244
|
for (const action of connector.actions) {
|
|
2373
3245
|
const base = action.description ?? `${action.label} in ${connector.name}`;
|
|
2374
3246
|
const retryHint = action.idempotent === void 0 ? "" : action.idempotent ? " Safe to retry: repeating this call with the same arguments has no additional effect." : " Not idempotent: repeating this call performs the operation again.";
|
|
@@ -2403,10 +3275,15 @@ async function serveConnector(connector, options = {}) {
|
|
|
2403
3275
|
}
|
|
2404
3276
|
|
|
2405
3277
|
export {
|
|
3278
|
+
EXTENSION_PERMISSIONS,
|
|
3279
|
+
HOST_PERMISSIONS,
|
|
2406
3280
|
envNameFor,
|
|
2407
3281
|
defineConnector,
|
|
3282
|
+
defineExtension,
|
|
2408
3283
|
resolveConfig,
|
|
2409
3284
|
pollToolName,
|
|
3285
|
+
footerToolName,
|
|
3286
|
+
handlerToolName,
|
|
2410
3287
|
MANIFEST_TOOL,
|
|
2411
3288
|
PREFLIGHT_TOOL,
|
|
2412
3289
|
OPTIONS_TOOL,
|
|
@@ -2418,6 +3295,10 @@ export {
|
|
|
2418
3295
|
bundledRequireFindings,
|
|
2419
3296
|
readNearestPackageJson,
|
|
2420
3297
|
esbuildBundle,
|
|
3298
|
+
HOST_URL_ENV,
|
|
3299
|
+
HOST_TOKEN_ENV,
|
|
3300
|
+
PermissionDeniedError,
|
|
3301
|
+
createExtensionHost,
|
|
2421
3302
|
normalizeItem,
|
|
2422
3303
|
normalizeItems,
|
|
2423
3304
|
pollWithDedupe,
|
|
@@ -2441,6 +3322,7 @@ export {
|
|
|
2441
3322
|
escapedMockHttp,
|
|
2442
3323
|
withMockHttp,
|
|
2443
3324
|
createConnectorHarness,
|
|
3325
|
+
mockExtensionHost,
|
|
2444
3326
|
checkConnector,
|
|
2445
3327
|
CHECK_OWNERS,
|
|
2446
3328
|
runConformance,
|