crawlio-browser 1.3.0 → 1.4.0

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.
@@ -1,1540 +1,16 @@
1
1
  import {
2
- __commonJS,
3
- __toESM
4
- } from "./chunk-JSBRDJBE.js";
5
-
6
- // node_modules/ip-address/dist/common.js
7
- var require_common = __commonJS({
8
- "node_modules/ip-address/dist/common.js"(exports) {
9
- "use strict";
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.isInSubnet = isInSubnet;
12
- exports.isCorrect = isCorrect;
13
- exports.numberToPaddedHex = numberToPaddedHex;
14
- exports.stringToPaddedHex = stringToPaddedHex;
15
- exports.testBit = testBit;
16
- function isInSubnet(address) {
17
- if (this.subnetMask < address.subnetMask) {
18
- return false;
19
- }
20
- if (this.mask(address.subnetMask) === address.mask()) {
21
- return true;
22
- }
23
- return false;
24
- }
25
- function isCorrect(defaultBits) {
26
- return function() {
27
- if (this.addressMinusSuffix !== this.correctForm()) {
28
- return false;
29
- }
30
- if (this.subnetMask === defaultBits && !this.parsedSubnet) {
31
- return true;
32
- }
33
- return this.parsedSubnet === String(this.subnetMask);
34
- };
35
- }
36
- function numberToPaddedHex(number) {
37
- return number.toString(16).padStart(2, "0");
38
- }
39
- function stringToPaddedHex(numberString) {
40
- return numberToPaddedHex(parseInt(numberString, 10));
41
- }
42
- function testBit(binaryValue, position) {
43
- const { length } = binaryValue;
44
- if (position > length) {
45
- return false;
46
- }
47
- const positionInString = length - position;
48
- return binaryValue.substring(positionInString, positionInString + 1) === "1";
49
- }
50
- }
51
- });
52
-
53
- // node_modules/ip-address/dist/v4/constants.js
54
- var require_constants = __commonJS({
55
- "node_modules/ip-address/dist/v4/constants.js"(exports) {
56
- "use strict";
57
- Object.defineProperty(exports, "__esModule", { value: true });
58
- exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0;
59
- exports.BITS = 32;
60
- exports.GROUPS = 4;
61
- exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/g;
62
- exports.RE_SUBNET_STRING = /\/\d{1,2}$/;
63
- }
64
- });
65
-
66
- // node_modules/ip-address/dist/address-error.js
67
- var require_address_error = __commonJS({
68
- "node_modules/ip-address/dist/address-error.js"(exports) {
69
- "use strict";
70
- Object.defineProperty(exports, "__esModule", { value: true });
71
- exports.AddressError = void 0;
72
- var AddressError = class extends Error {
73
- constructor(message, parseMessage) {
74
- super(message);
75
- this.name = "AddressError";
76
- this.parseMessage = parseMessage;
77
- }
78
- };
79
- exports.AddressError = AddressError;
80
- }
81
- });
82
-
83
- // node_modules/ip-address/dist/ipv4.js
84
- var require_ipv4 = __commonJS({
85
- "node_modules/ip-address/dist/ipv4.js"(exports) {
86
- "use strict";
87
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
88
- if (k2 === void 0) k2 = k;
89
- var desc = Object.getOwnPropertyDescriptor(m, k);
90
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
91
- desc = { enumerable: true, get: function() {
92
- return m[k];
93
- } };
94
- }
95
- Object.defineProperty(o, k2, desc);
96
- }) : (function(o, m, k, k2) {
97
- if (k2 === void 0) k2 = k;
98
- o[k2] = m[k];
99
- }));
100
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
101
- Object.defineProperty(o, "default", { enumerable: true, value: v });
102
- }) : function(o, v) {
103
- o["default"] = v;
104
- });
105
- var __importStar = exports && exports.__importStar || function(mod) {
106
- if (mod && mod.__esModule) return mod;
107
- var result = {};
108
- if (mod != null) {
109
- for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
110
- }
111
- __setModuleDefault(result, mod);
112
- return result;
113
- };
114
- Object.defineProperty(exports, "__esModule", { value: true });
115
- exports.Address4 = void 0;
116
- var common = __importStar(require_common());
117
- var constants = __importStar(require_constants());
118
- var address_error_1 = require_address_error();
119
- var Address4 = class _Address4 {
120
- constructor(address) {
121
- this.groups = constants.GROUPS;
122
- this.parsedAddress = [];
123
- this.parsedSubnet = "";
124
- this.subnet = "/32";
125
- this.subnetMask = 32;
126
- this.v4 = true;
127
- this.isCorrect = common.isCorrect(constants.BITS);
128
- this.isInSubnet = common.isInSubnet;
129
- this.address = address;
130
- const subnet = constants.RE_SUBNET_STRING.exec(address);
131
- if (subnet) {
132
- this.parsedSubnet = subnet[0].replace("/", "");
133
- this.subnetMask = parseInt(this.parsedSubnet, 10);
134
- this.subnet = `/${this.subnetMask}`;
135
- if (this.subnetMask < 0 || this.subnetMask > constants.BITS) {
136
- throw new address_error_1.AddressError("Invalid subnet mask.");
137
- }
138
- address = address.replace(constants.RE_SUBNET_STRING, "");
139
- }
140
- this.addressMinusSuffix = address;
141
- this.parsedAddress = this.parse(address);
142
- }
143
- static isValid(address) {
144
- try {
145
- new _Address4(address);
146
- return true;
147
- } catch (e) {
148
- return false;
149
- }
150
- }
151
- /*
152
- * Parses a v4 address
153
- */
154
- parse(address) {
155
- const groups = address.split(".");
156
- if (!address.match(constants.RE_ADDRESS)) {
157
- throw new address_error_1.AddressError("Invalid IPv4 address.");
158
- }
159
- return groups;
160
- }
161
- /**
162
- * Returns the correct form of an address
163
- * @memberof Address4
164
- * @instance
165
- * @returns {String}
166
- */
167
- correctForm() {
168
- return this.parsedAddress.map((part) => parseInt(part, 10)).join(".");
169
- }
170
- /**
171
- * Converts a hex string to an IPv4 address object
172
- * @memberof Address4
173
- * @static
174
- * @param {string} hex - a hex string to convert
175
- * @returns {Address4}
176
- */
177
- static fromHex(hex) {
178
- const padded = hex.replace(/:/g, "").padStart(8, "0");
179
- const groups = [];
180
- let i;
181
- for (i = 0; i < 8; i += 2) {
182
- const h = padded.slice(i, i + 2);
183
- groups.push(parseInt(h, 16));
184
- }
185
- return new _Address4(groups.join("."));
186
- }
187
- /**
188
- * Converts an integer into a IPv4 address object
189
- * @memberof Address4
190
- * @static
191
- * @param {integer} integer - a number to convert
192
- * @returns {Address4}
193
- */
194
- static fromInteger(integer) {
195
- return _Address4.fromHex(integer.toString(16));
196
- }
197
- /**
198
- * Return an address from in-addr.arpa form
199
- * @memberof Address4
200
- * @static
201
- * @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address
202
- * @returns {Adress4}
203
- * @example
204
- * var address = Address4.fromArpa(42.2.0.192.in-addr.arpa.)
205
- * address.correctForm(); // '192.0.2.42'
206
- */
207
- static fromArpa(arpaFormAddress) {
208
- const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.$/, "");
209
- const address = leader.split(".").reverse().join(".");
210
- return new _Address4(address);
211
- }
212
- /**
213
- * Converts an IPv4 address object to a hex string
214
- * @memberof Address4
215
- * @instance
216
- * @returns {String}
217
- */
218
- toHex() {
219
- return this.parsedAddress.map((part) => common.stringToPaddedHex(part)).join(":");
220
- }
221
- /**
222
- * Converts an IPv4 address object to an array of bytes
223
- * @memberof Address4
224
- * @instance
225
- * @returns {Array}
226
- */
227
- toArray() {
228
- return this.parsedAddress.map((part) => parseInt(part, 10));
229
- }
230
- /**
231
- * Converts an IPv4 address object to an IPv6 address group
232
- * @memberof Address4
233
- * @instance
234
- * @returns {String}
235
- */
236
- toGroup6() {
237
- const output = [];
238
- let i;
239
- for (i = 0; i < constants.GROUPS; i += 2) {
240
- output.push(`${common.stringToPaddedHex(this.parsedAddress[i])}${common.stringToPaddedHex(this.parsedAddress[i + 1])}`);
241
- }
242
- return output.join(":");
243
- }
244
- /**
245
- * Returns the address as a `bigint`
246
- * @memberof Address4
247
- * @instance
248
- * @returns {bigint}
249
- */
250
- bigInt() {
251
- return BigInt(`0x${this.parsedAddress.map((n) => common.stringToPaddedHex(n)).join("")}`);
252
- }
253
- /**
254
- * Helper function getting start address.
255
- * @memberof Address4
256
- * @instance
257
- * @returns {bigint}
258
- */
259
- _startAddress() {
260
- return BigInt(`0b${this.mask() + "0".repeat(constants.BITS - this.subnetMask)}`);
261
- }
262
- /**
263
- * The first address in the range given by this address' subnet.
264
- * Often referred to as the Network Address.
265
- * @memberof Address4
266
- * @instance
267
- * @returns {Address4}
268
- */
269
- startAddress() {
270
- return _Address4.fromBigInt(this._startAddress());
271
- }
272
- /**
273
- * The first host address in the range given by this address's subnet ie
274
- * the first address after the Network Address
275
- * @memberof Address4
276
- * @instance
277
- * @returns {Address4}
278
- */
279
- startAddressExclusive() {
280
- const adjust = BigInt("1");
281
- return _Address4.fromBigInt(this._startAddress() + adjust);
282
- }
283
- /**
284
- * Helper function getting end address.
285
- * @memberof Address4
286
- * @instance
287
- * @returns {bigint}
288
- */
289
- _endAddress() {
290
- return BigInt(`0b${this.mask() + "1".repeat(constants.BITS - this.subnetMask)}`);
291
- }
292
- /**
293
- * The last address in the range given by this address' subnet
294
- * Often referred to as the Broadcast
295
- * @memberof Address4
296
- * @instance
297
- * @returns {Address4}
298
- */
299
- endAddress() {
300
- return _Address4.fromBigInt(this._endAddress());
301
- }
302
- /**
303
- * The last host address in the range given by this address's subnet ie
304
- * the last address prior to the Broadcast Address
305
- * @memberof Address4
306
- * @instance
307
- * @returns {Address4}
308
- */
309
- endAddressExclusive() {
310
- const adjust = BigInt("1");
311
- return _Address4.fromBigInt(this._endAddress() - adjust);
312
- }
313
- /**
314
- * Converts a BigInt to a v4 address object
315
- * @memberof Address4
316
- * @static
317
- * @param {bigint} bigInt - a BigInt to convert
318
- * @returns {Address4}
319
- */
320
- static fromBigInt(bigInt) {
321
- return _Address4.fromHex(bigInt.toString(16));
322
- }
323
- /**
324
- * Returns the first n bits of the address, defaulting to the
325
- * subnet mask
326
- * @memberof Address4
327
- * @instance
328
- * @returns {String}
329
- */
330
- mask(mask) {
331
- if (mask === void 0) {
332
- mask = this.subnetMask;
333
- }
334
- return this.getBitsBase2(0, mask);
335
- }
336
- /**
337
- * Returns the bits in the given range as a base-2 string
338
- * @memberof Address4
339
- * @instance
340
- * @returns {string}
341
- */
342
- getBitsBase2(start, end) {
343
- return this.binaryZeroPad().slice(start, end);
344
- }
345
- /**
346
- * Return the reversed ip6.arpa form of the address
347
- * @memberof Address4
348
- * @param {Object} options
349
- * @param {boolean} options.omitSuffix - omit the "in-addr.arpa" suffix
350
- * @instance
351
- * @returns {String}
352
- */
353
- reverseForm(options) {
354
- if (!options) {
355
- options = {};
356
- }
357
- const reversed = this.correctForm().split(".").reverse().join(".");
358
- if (options.omitSuffix) {
359
- return reversed;
360
- }
361
- return `${reversed}.in-addr.arpa.`;
362
- }
363
- /**
364
- * Returns true if the given address is a multicast address
365
- * @memberof Address4
366
- * @instance
367
- * @returns {boolean}
368
- */
369
- isMulticast() {
370
- return this.isInSubnet(new _Address4("224.0.0.0/4"));
371
- }
372
- /**
373
- * Returns a zero-padded base-2 string representation of the address
374
- * @memberof Address4
375
- * @instance
376
- * @returns {string}
377
- */
378
- binaryZeroPad() {
379
- return this.bigInt().toString(2).padStart(constants.BITS, "0");
380
- }
381
- /**
382
- * Groups an IPv4 address for inclusion at the end of an IPv6 address
383
- * @returns {String}
384
- */
385
- groupForV6() {
386
- const segments = this.parsedAddress;
387
- return this.address.replace(constants.RE_ADDRESS, `<span class="hover-group group-v4 group-6">${segments.slice(0, 2).join(".")}</span>.<span class="hover-group group-v4 group-7">${segments.slice(2, 4).join(".")}</span>`);
388
- }
389
- };
390
- exports.Address4 = Address4;
391
- }
392
- });
393
-
394
- // node_modules/ip-address/dist/v6/constants.js
395
- var require_constants2 = __commonJS({
396
- "node_modules/ip-address/dist/v6/constants.js"(exports) {
397
- "use strict";
398
- Object.defineProperty(exports, "__esModule", { value: true });
399
- exports.RE_URL_WITH_PORT = exports.RE_URL = exports.RE_ZONE_STRING = exports.RE_SUBNET_STRING = exports.RE_BAD_ADDRESS = exports.RE_BAD_CHARACTERS = exports.TYPES = exports.SCOPES = exports.GROUPS = exports.BITS = void 0;
400
- exports.BITS = 128;
401
- exports.GROUPS = 8;
402
- exports.SCOPES = {
403
- 0: "Reserved",
404
- 1: "Interface local",
405
- 2: "Link local",
406
- 4: "Admin local",
407
- 5: "Site local",
408
- 8: "Organization local",
409
- 14: "Global",
410
- 15: "Reserved"
411
- };
412
- exports.TYPES = {
413
- "ff01::1/128": "Multicast (All nodes on this interface)",
414
- "ff01::2/128": "Multicast (All routers on this interface)",
415
- "ff02::1/128": "Multicast (All nodes on this link)",
416
- "ff02::2/128": "Multicast (All routers on this link)",
417
- "ff05::2/128": "Multicast (All routers in this site)",
418
- "ff02::5/128": "Multicast (OSPFv3 AllSPF routers)",
419
- "ff02::6/128": "Multicast (OSPFv3 AllDR routers)",
420
- "ff02::9/128": "Multicast (RIP routers)",
421
- "ff02::a/128": "Multicast (EIGRP routers)",
422
- "ff02::d/128": "Multicast (PIM routers)",
423
- "ff02::16/128": "Multicast (MLDv2 reports)",
424
- "ff01::fb/128": "Multicast (mDNSv6)",
425
- "ff02::fb/128": "Multicast (mDNSv6)",
426
- "ff05::fb/128": "Multicast (mDNSv6)",
427
- "ff02::1:2/128": "Multicast (All DHCP servers and relay agents on this link)",
428
- "ff05::1:2/128": "Multicast (All DHCP servers and relay agents in this site)",
429
- "ff02::1:3/128": "Multicast (All DHCP servers on this link)",
430
- "ff05::1:3/128": "Multicast (All DHCP servers in this site)",
431
- "::/128": "Unspecified",
432
- "::1/128": "Loopback",
433
- "ff00::/8": "Multicast",
434
- "fe80::/10": "Link-local unicast"
435
- };
436
- exports.RE_BAD_CHARACTERS = /([^0-9a-f:/%])/gi;
437
- exports.RE_BAD_ADDRESS = /([0-9a-f]{5,}|:{3,}|[^:]:$|^:[^:]|\/$)/gi;
438
- exports.RE_SUBNET_STRING = /\/\d{1,3}(?=%|$)/;
439
- exports.RE_ZONE_STRING = /%.*$/;
440
- exports.RE_URL = /^\[{0,1}([0-9a-f:]+)\]{0,1}/;
441
- exports.RE_URL_WITH_PORT = /\[([0-9a-f:]+)\]:([0-9]{1,5})/;
442
- }
443
- });
444
-
445
- // node_modules/ip-address/dist/v6/helpers.js
446
- var require_helpers = __commonJS({
447
- "node_modules/ip-address/dist/v6/helpers.js"(exports) {
448
- "use strict";
449
- Object.defineProperty(exports, "__esModule", { value: true });
450
- exports.spanAllZeroes = spanAllZeroes;
451
- exports.spanAll = spanAll;
452
- exports.spanLeadingZeroes = spanLeadingZeroes;
453
- exports.simpleGroup = simpleGroup;
454
- function spanAllZeroes(s) {
455
- return s.replace(/(0+)/g, '<span class="zero">$1</span>');
456
- }
457
- function spanAll(s, offset = 0) {
458
- const letters = s.split("");
459
- return letters.map((n, i) => `<span class="digit value-${n} position-${i + offset}">${spanAllZeroes(n)}</span>`).join("");
460
- }
461
- function spanLeadingZeroesSimple(group) {
462
- return group.replace(/^(0+)/, '<span class="zero">$1</span>');
463
- }
464
- function spanLeadingZeroes(address) {
465
- const groups = address.split(":");
466
- return groups.map((g) => spanLeadingZeroesSimple(g)).join(":");
467
- }
468
- function simpleGroup(addressString, offset = 0) {
469
- const groups = addressString.split(":");
470
- return groups.map((g, i) => {
471
- if (/group-v4/.test(g)) {
472
- return g;
473
- }
474
- return `<span class="hover-group group-${i + offset}">${spanLeadingZeroesSimple(g)}</span>`;
475
- });
476
- }
477
- }
478
- });
479
-
480
- // node_modules/ip-address/dist/v6/regular-expressions.js
481
- var require_regular_expressions = __commonJS({
482
- "node_modules/ip-address/dist/v6/regular-expressions.js"(exports) {
483
- "use strict";
484
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
485
- if (k2 === void 0) k2 = k;
486
- var desc = Object.getOwnPropertyDescriptor(m, k);
487
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
488
- desc = { enumerable: true, get: function() {
489
- return m[k];
490
- } };
491
- }
492
- Object.defineProperty(o, k2, desc);
493
- }) : (function(o, m, k, k2) {
494
- if (k2 === void 0) k2 = k;
495
- o[k2] = m[k];
496
- }));
497
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
498
- Object.defineProperty(o, "default", { enumerable: true, value: v });
499
- }) : function(o, v) {
500
- o["default"] = v;
501
- });
502
- var __importStar = exports && exports.__importStar || function(mod) {
503
- if (mod && mod.__esModule) return mod;
504
- var result = {};
505
- if (mod != null) {
506
- for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
507
- }
508
- __setModuleDefault(result, mod);
509
- return result;
510
- };
511
- Object.defineProperty(exports, "__esModule", { value: true });
512
- exports.ADDRESS_BOUNDARY = void 0;
513
- exports.groupPossibilities = groupPossibilities;
514
- exports.padGroup = padGroup;
515
- exports.simpleRegularExpression = simpleRegularExpression;
516
- exports.possibleElisions = possibleElisions;
517
- var v6 = __importStar(require_constants2());
518
- function groupPossibilities(possibilities) {
519
- return `(${possibilities.join("|")})`;
520
- }
521
- function padGroup(group) {
522
- if (group.length < 4) {
523
- return `0{0,${4 - group.length}}${group}`;
524
- }
525
- return group;
526
- }
527
- exports.ADDRESS_BOUNDARY = "[^A-Fa-f0-9:]";
528
- function simpleRegularExpression(groups) {
529
- const zeroIndexes = [];
530
- groups.forEach((group, i) => {
531
- const groupInteger = parseInt(group, 16);
532
- if (groupInteger === 0) {
533
- zeroIndexes.push(i);
534
- }
535
- });
536
- const possibilities = zeroIndexes.map((zeroIndex) => groups.map((group, i) => {
537
- if (i === zeroIndex) {
538
- const elision = i === 0 || i === v6.GROUPS - 1 ? ":" : "";
539
- return groupPossibilities([padGroup(group), elision]);
540
- }
541
- return padGroup(group);
542
- }).join(":"));
543
- possibilities.push(groups.map(padGroup).join(":"));
544
- return groupPossibilities(possibilities);
545
- }
546
- function possibleElisions(elidedGroups, moreLeft, moreRight) {
547
- const left = moreLeft ? "" : ":";
548
- const right = moreRight ? "" : ":";
549
- const possibilities = [];
550
- if (!moreLeft && !moreRight) {
551
- possibilities.push("::");
552
- }
553
- if (moreLeft && moreRight) {
554
- possibilities.push("");
555
- }
556
- if (moreRight && !moreLeft || !moreRight && moreLeft) {
557
- possibilities.push(":");
558
- }
559
- possibilities.push(`${left}(:0{1,4}){1,${elidedGroups - 1}}`);
560
- possibilities.push(`(0{1,4}:){1,${elidedGroups - 1}}${right}`);
561
- possibilities.push(`(0{1,4}:){${elidedGroups - 1}}0{1,4}`);
562
- for (let groups = 1; groups < elidedGroups - 1; groups++) {
563
- for (let position = 1; position < elidedGroups - groups; position++) {
564
- possibilities.push(`(0{1,4}:){${position}}:(0{1,4}:){${elidedGroups - position - groups - 1}}0{1,4}`);
565
- }
566
- }
567
- return groupPossibilities(possibilities);
568
- }
569
- }
570
- });
571
-
572
- // node_modules/ip-address/dist/ipv6.js
573
- var require_ipv6 = __commonJS({
574
- "node_modules/ip-address/dist/ipv6.js"(exports) {
575
- "use strict";
576
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
577
- if (k2 === void 0) k2 = k;
578
- var desc = Object.getOwnPropertyDescriptor(m, k);
579
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
580
- desc = { enumerable: true, get: function() {
581
- return m[k];
582
- } };
583
- }
584
- Object.defineProperty(o, k2, desc);
585
- }) : (function(o, m, k, k2) {
586
- if (k2 === void 0) k2 = k;
587
- o[k2] = m[k];
588
- }));
589
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
590
- Object.defineProperty(o, "default", { enumerable: true, value: v });
591
- }) : function(o, v) {
592
- o["default"] = v;
593
- });
594
- var __importStar = exports && exports.__importStar || function(mod) {
595
- if (mod && mod.__esModule) return mod;
596
- var result = {};
597
- if (mod != null) {
598
- for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
599
- }
600
- __setModuleDefault(result, mod);
601
- return result;
602
- };
603
- Object.defineProperty(exports, "__esModule", { value: true });
604
- exports.Address6 = void 0;
605
- var common = __importStar(require_common());
606
- var constants4 = __importStar(require_constants());
607
- var constants6 = __importStar(require_constants2());
608
- var helpers = __importStar(require_helpers());
609
- var ipv4_1 = require_ipv4();
610
- var regular_expressions_1 = require_regular_expressions();
611
- var address_error_1 = require_address_error();
612
- var common_1 = require_common();
613
- function assert(condition) {
614
- if (!condition) {
615
- throw new Error("Assertion failed.");
616
- }
617
- }
618
- function addCommas(number) {
619
- const r = /(\d+)(\d{3})/;
620
- while (r.test(number)) {
621
- number = number.replace(r, "$1,$2");
622
- }
623
- return number;
624
- }
625
- function spanLeadingZeroes4(n) {
626
- n = n.replace(/^(0{1,})([1-9]+)$/, '<span class="parse-error">$1</span>$2');
627
- n = n.replace(/^(0{1,})(0)$/, '<span class="parse-error">$1</span>$2');
628
- return n;
629
- }
630
- function compact(address, slice) {
631
- const s1 = [];
632
- const s2 = [];
633
- let i;
634
- for (i = 0; i < address.length; i++) {
635
- if (i < slice[0]) {
636
- s1.push(address[i]);
637
- } else if (i > slice[1]) {
638
- s2.push(address[i]);
639
- }
640
- }
641
- return s1.concat(["compact"]).concat(s2);
642
- }
643
- function paddedHex(octet) {
644
- return parseInt(octet, 16).toString(16).padStart(4, "0");
645
- }
646
- function unsignByte(b) {
647
- return b & 255;
648
- }
649
- var Address62 = class _Address6 {
650
- constructor(address, optionalGroups) {
651
- this.addressMinusSuffix = "";
652
- this.parsedSubnet = "";
653
- this.subnet = "/128";
654
- this.subnetMask = 128;
655
- this.v4 = false;
656
- this.zone = "";
657
- this.isInSubnet = common.isInSubnet;
658
- this.isCorrect = common.isCorrect(constants6.BITS);
659
- if (optionalGroups === void 0) {
660
- this.groups = constants6.GROUPS;
661
- } else {
662
- this.groups = optionalGroups;
663
- }
664
- this.address = address;
665
- const subnet = constants6.RE_SUBNET_STRING.exec(address);
666
- if (subnet) {
667
- this.parsedSubnet = subnet[0].replace("/", "");
668
- this.subnetMask = parseInt(this.parsedSubnet, 10);
669
- this.subnet = `/${this.subnetMask}`;
670
- if (Number.isNaN(this.subnetMask) || this.subnetMask < 0 || this.subnetMask > constants6.BITS) {
671
- throw new address_error_1.AddressError("Invalid subnet mask.");
672
- }
673
- address = address.replace(constants6.RE_SUBNET_STRING, "");
674
- } else if (/\//.test(address)) {
675
- throw new address_error_1.AddressError("Invalid subnet mask.");
676
- }
677
- const zone = constants6.RE_ZONE_STRING.exec(address);
678
- if (zone) {
679
- this.zone = zone[0];
680
- address = address.replace(constants6.RE_ZONE_STRING, "");
681
- }
682
- this.addressMinusSuffix = address;
683
- this.parsedAddress = this.parse(this.addressMinusSuffix);
684
- }
685
- static isValid(address) {
686
- try {
687
- new _Address6(address);
688
- return true;
689
- } catch (e) {
690
- return false;
691
- }
692
- }
693
- /**
694
- * Convert a BigInt to a v6 address object
695
- * @memberof Address6
696
- * @static
697
- * @param {bigint} bigInt - a BigInt to convert
698
- * @returns {Address6}
699
- * @example
700
- * var bigInt = BigInt('1000000000000');
701
- * var address = Address6.fromBigInt(bigInt);
702
- * address.correctForm(); // '::e8:d4a5:1000'
703
- */
704
- static fromBigInt(bigInt) {
705
- const hex = bigInt.toString(16).padStart(32, "0");
706
- const groups = [];
707
- let i;
708
- for (i = 0; i < constants6.GROUPS; i++) {
709
- groups.push(hex.slice(i * 4, (i + 1) * 4));
710
- }
711
- return new _Address6(groups.join(":"));
712
- }
713
- /**
714
- * Convert a URL (with optional port number) to an address object
715
- * @memberof Address6
716
- * @static
717
- * @param {string} url - a URL with optional port number
718
- * @example
719
- * var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/');
720
- * addressAndPort.address.correctForm(); // 'ffff::'
721
- * addressAndPort.port; // 8080
722
- */
723
- static fromURL(url) {
724
- let host;
725
- let port = null;
726
- let result;
727
- if (url.indexOf("[") !== -1 && url.indexOf("]:") !== -1) {
728
- result = constants6.RE_URL_WITH_PORT.exec(url);
729
- if (result === null) {
730
- return {
731
- error: "failed to parse address with port",
732
- address: null,
733
- port: null
734
- };
735
- }
736
- host = result[1];
737
- port = result[2];
738
- } else if (url.indexOf("/") !== -1) {
739
- url = url.replace(/^[a-z0-9]+:\/\//, "");
740
- result = constants6.RE_URL.exec(url);
741
- if (result === null) {
742
- return {
743
- error: "failed to parse address from URL",
744
- address: null,
745
- port: null
746
- };
747
- }
748
- host = result[1];
749
- } else {
750
- host = url;
751
- }
752
- if (port) {
753
- port = parseInt(port, 10);
754
- if (port < 0 || port > 65536) {
755
- port = null;
756
- }
757
- } else {
758
- port = null;
759
- }
760
- return {
761
- address: new _Address6(host),
762
- port
763
- };
764
- }
765
- /**
766
- * Create an IPv6-mapped address given an IPv4 address
767
- * @memberof Address6
768
- * @static
769
- * @param {string} address - An IPv4 address string
770
- * @returns {Address6}
771
- * @example
772
- * var address = Address6.fromAddress4('192.168.0.1');
773
- * address.correctForm(); // '::ffff:c0a8:1'
774
- * address.to4in6(); // '::ffff:192.168.0.1'
775
- */
776
- static fromAddress4(address) {
777
- const address4 = new ipv4_1.Address4(address);
778
- const mask6 = constants6.BITS - (constants4.BITS - address4.subnetMask);
779
- return new _Address6(`::ffff:${address4.correctForm()}/${mask6}`);
780
- }
781
- /**
782
- * Return an address from ip6.arpa form
783
- * @memberof Address6
784
- * @static
785
- * @param {string} arpaFormAddress - an 'ip6.arpa' form address
786
- * @returns {Adress6}
787
- * @example
788
- * var address = Address6.fromArpa(e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa.)
789
- * address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe'
790
- */
791
- static fromArpa(arpaFormAddress) {
792
- let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, "");
793
- const semicolonAmount = 7;
794
- if (address.length !== 63) {
795
- throw new address_error_1.AddressError("Invalid 'ip6.arpa' form.");
796
- }
797
- const parts = address.split(".").reverse();
798
- for (let i = semicolonAmount; i > 0; i--) {
799
- const insertIndex = i * 4;
800
- parts.splice(insertIndex, 0, ":");
801
- }
802
- address = parts.join("");
803
- return new _Address6(address);
804
- }
805
- /**
806
- * Return the Microsoft UNC transcription of the address
807
- * @memberof Address6
808
- * @instance
809
- * @returns {String} the Microsoft UNC transcription of the address
810
- */
811
- microsoftTranscription() {
812
- return `${this.correctForm().replace(/:/g, "-")}.ipv6-literal.net`;
813
- }
814
- /**
815
- * Return the first n bits of the address, defaulting to the subnet mask
816
- * @memberof Address6
817
- * @instance
818
- * @param {number} [mask=subnet] - the number of bits to mask
819
- * @returns {String} the first n bits of the address as a string
820
- */
821
- mask(mask = this.subnetMask) {
822
- return this.getBitsBase2(0, mask);
823
- }
824
- /**
825
- * Return the number of possible subnets of a given size in the address
826
- * @memberof Address6
827
- * @instance
828
- * @param {number} [subnetSize=128] - the subnet size
829
- * @returns {String}
830
- */
831
- // TODO: probably useful to have a numeric version of this too
832
- possibleSubnets(subnetSize = 128) {
833
- const availableBits = constants6.BITS - this.subnetMask;
834
- const subnetBits = Math.abs(subnetSize - constants6.BITS);
835
- const subnetPowers = availableBits - subnetBits;
836
- if (subnetPowers < 0) {
837
- return "0";
838
- }
839
- return addCommas((BigInt("2") ** BigInt(subnetPowers)).toString(10));
840
- }
841
- /**
842
- * Helper function getting start address.
843
- * @memberof Address6
844
- * @instance
845
- * @returns {bigint}
846
- */
847
- _startAddress() {
848
- return BigInt(`0b${this.mask() + "0".repeat(constants6.BITS - this.subnetMask)}`);
849
- }
850
- /**
851
- * The first address in the range given by this address' subnet
852
- * Often referred to as the Network Address.
853
- * @memberof Address6
854
- * @instance
855
- * @returns {Address6}
856
- */
857
- startAddress() {
858
- return _Address6.fromBigInt(this._startAddress());
859
- }
860
- /**
861
- * The first host address in the range given by this address's subnet ie
862
- * the first address after the Network Address
863
- * @memberof Address6
864
- * @instance
865
- * @returns {Address6}
866
- */
867
- startAddressExclusive() {
868
- const adjust = BigInt("1");
869
- return _Address6.fromBigInt(this._startAddress() + adjust);
870
- }
871
- /**
872
- * Helper function getting end address.
873
- * @memberof Address6
874
- * @instance
875
- * @returns {bigint}
876
- */
877
- _endAddress() {
878
- return BigInt(`0b${this.mask() + "1".repeat(constants6.BITS - this.subnetMask)}`);
879
- }
880
- /**
881
- * The last address in the range given by this address' subnet
882
- * Often referred to as the Broadcast
883
- * @memberof Address6
884
- * @instance
885
- * @returns {Address6}
886
- */
887
- endAddress() {
888
- return _Address6.fromBigInt(this._endAddress());
889
- }
890
- /**
891
- * The last host address in the range given by this address's subnet ie
892
- * the last address prior to the Broadcast Address
893
- * @memberof Address6
894
- * @instance
895
- * @returns {Address6}
896
- */
897
- endAddressExclusive() {
898
- const adjust = BigInt("1");
899
- return _Address6.fromBigInt(this._endAddress() - adjust);
900
- }
901
- /**
902
- * Return the scope of the address
903
- * @memberof Address6
904
- * @instance
905
- * @returns {String}
906
- */
907
- getScope() {
908
- let scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
909
- if (this.getType() === "Global unicast" && scope !== "Link local") {
910
- scope = "Global";
911
- }
912
- return scope || "Unknown";
913
- }
914
- /**
915
- * Return the type of the address
916
- * @memberof Address6
917
- * @instance
918
- * @returns {String}
919
- */
920
- getType() {
921
- for (const subnet of Object.keys(constants6.TYPES)) {
922
- if (this.isInSubnet(new _Address6(subnet))) {
923
- return constants6.TYPES[subnet];
924
- }
925
- }
926
- return "Global unicast";
927
- }
928
- /**
929
- * Return the bits in the given range as a BigInt
930
- * @memberof Address6
931
- * @instance
932
- * @returns {bigint}
933
- */
934
- getBits(start, end) {
935
- return BigInt(`0b${this.getBitsBase2(start, end)}`);
936
- }
937
- /**
938
- * Return the bits in the given range as a base-2 string
939
- * @memberof Address6
940
- * @instance
941
- * @returns {String}
942
- */
943
- getBitsBase2(start, end) {
944
- return this.binaryZeroPad().slice(start, end);
945
- }
946
- /**
947
- * Return the bits in the given range as a base-16 string
948
- * @memberof Address6
949
- * @instance
950
- * @returns {String}
951
- */
952
- getBitsBase16(start, end) {
953
- const length = end - start;
954
- if (length % 4 !== 0) {
955
- throw new Error("Length of bits to retrieve must be divisible by four");
956
- }
957
- return this.getBits(start, end).toString(16).padStart(length / 4, "0");
958
- }
959
- /**
960
- * Return the bits that are set past the subnet mask length
961
- * @memberof Address6
962
- * @instance
963
- * @returns {String}
964
- */
965
- getBitsPastSubnet() {
966
- return this.getBitsBase2(this.subnetMask, constants6.BITS);
967
- }
968
- /**
969
- * Return the reversed ip6.arpa form of the address
970
- * @memberof Address6
971
- * @param {Object} options
972
- * @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix
973
- * @instance
974
- * @returns {String}
975
- */
976
- reverseForm(options) {
977
- if (!options) {
978
- options = {};
979
- }
980
- const characters = Math.floor(this.subnetMask / 4);
981
- const reversed = this.canonicalForm().replace(/:/g, "").split("").slice(0, characters).reverse().join(".");
982
- if (characters > 0) {
983
- if (options.omitSuffix) {
984
- return reversed;
985
- }
986
- return `${reversed}.ip6.arpa.`;
987
- }
988
- if (options.omitSuffix) {
989
- return "";
990
- }
991
- return "ip6.arpa.";
992
- }
993
- /**
994
- * Return the correct form of the address
995
- * @memberof Address6
996
- * @instance
997
- * @returns {String}
998
- */
999
- correctForm() {
1000
- let i;
1001
- let groups = [];
1002
- let zeroCounter = 0;
1003
- const zeroes = [];
1004
- for (i = 0; i < this.parsedAddress.length; i++) {
1005
- const value = parseInt(this.parsedAddress[i], 16);
1006
- if (value === 0) {
1007
- zeroCounter++;
1008
- }
1009
- if (value !== 0 && zeroCounter > 0) {
1010
- if (zeroCounter > 1) {
1011
- zeroes.push([i - zeroCounter, i - 1]);
1012
- }
1013
- zeroCounter = 0;
1014
- }
1015
- }
1016
- if (zeroCounter > 1) {
1017
- zeroes.push([this.parsedAddress.length - zeroCounter, this.parsedAddress.length - 1]);
1018
- }
1019
- const zeroLengths = zeroes.map((n) => n[1] - n[0] + 1);
1020
- if (zeroes.length > 0) {
1021
- const index = zeroLengths.indexOf(Math.max(...zeroLengths));
1022
- groups = compact(this.parsedAddress, zeroes[index]);
1023
- } else {
1024
- groups = this.parsedAddress;
1025
- }
1026
- for (i = 0; i < groups.length; i++) {
1027
- if (groups[i] !== "compact") {
1028
- groups[i] = parseInt(groups[i], 16).toString(16);
1029
- }
1030
- }
1031
- let correct = groups.join(":");
1032
- correct = correct.replace(/^compact$/, "::");
1033
- correct = correct.replace(/(^compact)|(compact$)/, ":");
1034
- correct = correct.replace(/compact/, "");
1035
- return correct;
1036
- }
1037
- /**
1038
- * Return a zero-padded base-2 string representation of the address
1039
- * @memberof Address6
1040
- * @instance
1041
- * @returns {String}
1042
- * @example
1043
- * var address = new Address6('2001:4860:4001:803::1011');
1044
- * address.binaryZeroPad();
1045
- * // '0010000000000001010010000110000001000000000000010000100000000011
1046
- * // 0000000000000000000000000000000000000000000000000001000000010001'
1047
- */
1048
- binaryZeroPad() {
1049
- return this.bigInt().toString(2).padStart(constants6.BITS, "0");
1050
- }
1051
- // TODO: Improve the semantics of this helper function
1052
- parse4in6(address) {
1053
- const groups = address.split(":");
1054
- const lastGroup = groups.slice(-1)[0];
1055
- const address4 = lastGroup.match(constants4.RE_ADDRESS);
1056
- if (address4) {
1057
- this.parsedAddress4 = address4[0];
1058
- this.address4 = new ipv4_1.Address4(this.parsedAddress4);
1059
- for (let i = 0; i < this.address4.groups; i++) {
1060
- if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) {
1061
- throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", address.replace(constants4.RE_ADDRESS, this.address4.parsedAddress.map(spanLeadingZeroes4).join(".")));
1062
- }
1063
- }
1064
- this.v4 = true;
1065
- groups[groups.length - 1] = this.address4.toGroup6();
1066
- address = groups.join(":");
1067
- }
1068
- return address;
1069
- }
1070
- // TODO: Make private?
1071
- parse(address) {
1072
- address = this.parse4in6(address);
1073
- const badCharacters = address.match(constants6.RE_BAD_CHARACTERS);
1074
- if (badCharacters) {
1075
- throw new address_error_1.AddressError(`Bad character${badCharacters.length > 1 ? "s" : ""} detected in address: ${badCharacters.join("")}`, address.replace(constants6.RE_BAD_CHARACTERS, '<span class="parse-error">$1</span>'));
1076
- }
1077
- const badAddress = address.match(constants6.RE_BAD_ADDRESS);
1078
- if (badAddress) {
1079
- throw new address_error_1.AddressError(`Address failed regex: ${badAddress.join("")}`, address.replace(constants6.RE_BAD_ADDRESS, '<span class="parse-error">$1</span>'));
1080
- }
1081
- let groups = [];
1082
- const halves = address.split("::");
1083
- if (halves.length === 2) {
1084
- let first = halves[0].split(":");
1085
- let last = halves[1].split(":");
1086
- if (first.length === 1 && first[0] === "") {
1087
- first = [];
1088
- }
1089
- if (last.length === 1 && last[0] === "") {
1090
- last = [];
1091
- }
1092
- const remaining = this.groups - (first.length + last.length);
1093
- if (!remaining) {
1094
- throw new address_error_1.AddressError("Error parsing groups");
1095
- }
1096
- this.elidedGroups = remaining;
1097
- this.elisionBegin = first.length;
1098
- this.elisionEnd = first.length + this.elidedGroups;
1099
- groups = groups.concat(first);
1100
- for (let i = 0; i < remaining; i++) {
1101
- groups.push("0");
1102
- }
1103
- groups = groups.concat(last);
1104
- } else if (halves.length === 1) {
1105
- groups = address.split(":");
1106
- this.elidedGroups = 0;
1107
- } else {
1108
- throw new address_error_1.AddressError("Too many :: groups found");
1109
- }
1110
- groups = groups.map((group) => parseInt(group, 16).toString(16));
1111
- if (groups.length !== this.groups) {
1112
- throw new address_error_1.AddressError("Incorrect number of groups found");
1113
- }
1114
- return groups;
1115
- }
1116
- /**
1117
- * Return the canonical form of the address
1118
- * @memberof Address6
1119
- * @instance
1120
- * @returns {String}
1121
- */
1122
- canonicalForm() {
1123
- return this.parsedAddress.map(paddedHex).join(":");
1124
- }
1125
- /**
1126
- * Return the decimal form of the address
1127
- * @memberof Address6
1128
- * @instance
1129
- * @returns {String}
1130
- */
1131
- decimal() {
1132
- return this.parsedAddress.map((n) => parseInt(n, 16).toString(10).padStart(5, "0")).join(":");
1133
- }
1134
- /**
1135
- * Return the address as a BigInt
1136
- * @memberof Address6
1137
- * @instance
1138
- * @returns {bigint}
1139
- */
1140
- bigInt() {
1141
- return BigInt(`0x${this.parsedAddress.map(paddedHex).join("")}`);
1142
- }
1143
- /**
1144
- * Return the last two groups of this address as an IPv4 address string
1145
- * @memberof Address6
1146
- * @instance
1147
- * @returns {Address4}
1148
- * @example
1149
- * var address = new Address6('2001:4860:4001::1825:bf11');
1150
- * address.to4().correctForm(); // '24.37.191.17'
1151
- */
1152
- to4() {
1153
- const binary = this.binaryZeroPad().split("");
1154
- return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join("")}`).toString(16));
1155
- }
1156
- /**
1157
- * Return the v4-in-v6 form of the address
1158
- * @memberof Address6
1159
- * @instance
1160
- * @returns {String}
1161
- */
1162
- to4in6() {
1163
- const address4 = this.to4();
1164
- const address6 = new _Address6(this.parsedAddress.slice(0, 6).join(":"), 6);
1165
- const correct = address6.correctForm();
1166
- let infix = "";
1167
- if (!/:$/.test(correct)) {
1168
- infix = ":";
1169
- }
1170
- return correct + infix + address4.address;
1171
- }
1172
- /**
1173
- * Return an object containing the Teredo properties of the address
1174
- * @memberof Address6
1175
- * @instance
1176
- * @returns {Object}
1177
- */
1178
- inspectTeredo() {
1179
- const prefix = this.getBitsBase16(0, 32);
1180
- const bitsForUdpPort = this.getBits(80, 96);
1181
- const udpPort = (bitsForUdpPort ^ BigInt("0xffff")).toString();
1182
- const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64));
1183
- const bitsForClient4 = this.getBits(96, 128);
1184
- const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt("0xffffffff")).toString(16));
1185
- const flagsBase2 = this.getBitsBase2(64, 80);
1186
- const coneNat = (0, common_1.testBit)(flagsBase2, 15);
1187
- const reserved = (0, common_1.testBit)(flagsBase2, 14);
1188
- const groupIndividual = (0, common_1.testBit)(flagsBase2, 8);
1189
- const universalLocal = (0, common_1.testBit)(flagsBase2, 9);
1190
- const nonce = BigInt(`0b${flagsBase2.slice(2, 6) + flagsBase2.slice(8, 16)}`).toString(10);
1191
- return {
1192
- prefix: `${prefix.slice(0, 4)}:${prefix.slice(4, 8)}`,
1193
- server4: server4.address,
1194
- client4: client4.address,
1195
- flags: flagsBase2,
1196
- coneNat,
1197
- microsoft: {
1198
- reserved,
1199
- universalLocal,
1200
- groupIndividual,
1201
- nonce
1202
- },
1203
- udpPort
1204
- };
1205
- }
1206
- /**
1207
- * Return an object containing the 6to4 properties of the address
1208
- * @memberof Address6
1209
- * @instance
1210
- * @returns {Object}
1211
- */
1212
- inspect6to4() {
1213
- const prefix = this.getBitsBase16(0, 16);
1214
- const gateway = ipv4_1.Address4.fromHex(this.getBitsBase16(16, 48));
1215
- return {
1216
- prefix: prefix.slice(0, 4),
1217
- gateway: gateway.address
1218
- };
1219
- }
1220
- /**
1221
- * Return a v6 6to4 address from a v6 v4inv6 address
1222
- * @memberof Address6
1223
- * @instance
1224
- * @returns {Address6}
1225
- */
1226
- to6to4() {
1227
- if (!this.is4()) {
1228
- return null;
1229
- }
1230
- const addr6to4 = [
1231
- "2002",
1232
- this.getBitsBase16(96, 112),
1233
- this.getBitsBase16(112, 128),
1234
- "",
1235
- "/16"
1236
- ].join(":");
1237
- return new _Address6(addr6to4);
1238
- }
1239
- /**
1240
- * Return a byte array
1241
- * @memberof Address6
1242
- * @instance
1243
- * @returns {Array}
1244
- */
1245
- toByteArray() {
1246
- const valueWithoutPadding = this.bigInt().toString(16);
1247
- const leadingPad = "0".repeat(valueWithoutPadding.length % 2);
1248
- const value = `${leadingPad}${valueWithoutPadding}`;
1249
- const bytes = [];
1250
- for (let i = 0, length = value.length; i < length; i += 2) {
1251
- bytes.push(parseInt(value.substring(i, i + 2), 16));
1252
- }
1253
- return bytes;
1254
- }
1255
- /**
1256
- * Return an unsigned byte array
1257
- * @memberof Address6
1258
- * @instance
1259
- * @returns {Array}
1260
- */
1261
- toUnsignedByteArray() {
1262
- return this.toByteArray().map(unsignByte);
1263
- }
1264
- /**
1265
- * Convert a byte array to an Address6 object
1266
- * @memberof Address6
1267
- * @static
1268
- * @returns {Address6}
1269
- */
1270
- static fromByteArray(bytes) {
1271
- return this.fromUnsignedByteArray(bytes.map(unsignByte));
1272
- }
1273
- /**
1274
- * Convert an unsigned byte array to an Address6 object
1275
- * @memberof Address6
1276
- * @static
1277
- * @returns {Address6}
1278
- */
1279
- static fromUnsignedByteArray(bytes) {
1280
- const BYTE_MAX = BigInt("256");
1281
- let result = BigInt("0");
1282
- let multiplier = BigInt("1");
1283
- for (let i = bytes.length - 1; i >= 0; i--) {
1284
- result += multiplier * BigInt(bytes[i].toString(10));
1285
- multiplier *= BYTE_MAX;
1286
- }
1287
- return _Address6.fromBigInt(result);
1288
- }
1289
- /**
1290
- * Returns true if the address is in the canonical form, false otherwise
1291
- * @memberof Address6
1292
- * @instance
1293
- * @returns {boolean}
1294
- */
1295
- isCanonical() {
1296
- return this.addressMinusSuffix === this.canonicalForm();
1297
- }
1298
- /**
1299
- * Returns true if the address is a link local address, false otherwise
1300
- * @memberof Address6
1301
- * @instance
1302
- * @returns {boolean}
1303
- */
1304
- isLinkLocal() {
1305
- if (this.getBitsBase2(0, 64) === "1111111010000000000000000000000000000000000000000000000000000000") {
1306
- return true;
1307
- }
1308
- return false;
1309
- }
1310
- /**
1311
- * Returns true if the address is a multicast address, false otherwise
1312
- * @memberof Address6
1313
- * @instance
1314
- * @returns {boolean}
1315
- */
1316
- isMulticast() {
1317
- return this.getType() === "Multicast";
1318
- }
1319
- /**
1320
- * Returns true if the address is a v4-in-v6 address, false otherwise
1321
- * @memberof Address6
1322
- * @instance
1323
- * @returns {boolean}
1324
- */
1325
- is4() {
1326
- return this.v4;
1327
- }
1328
- /**
1329
- * Returns true if the address is a Teredo address, false otherwise
1330
- * @memberof Address6
1331
- * @instance
1332
- * @returns {boolean}
1333
- */
1334
- isTeredo() {
1335
- return this.isInSubnet(new _Address6("2001::/32"));
1336
- }
1337
- /**
1338
- * Returns true if the address is a 6to4 address, false otherwise
1339
- * @memberof Address6
1340
- * @instance
1341
- * @returns {boolean}
1342
- */
1343
- is6to4() {
1344
- return this.isInSubnet(new _Address6("2002::/16"));
1345
- }
1346
- /**
1347
- * Returns true if the address is a loopback address, false otherwise
1348
- * @memberof Address6
1349
- * @instance
1350
- * @returns {boolean}
1351
- */
1352
- isLoopback() {
1353
- return this.getType() === "Loopback";
1354
- }
1355
- // #endregion
1356
- // #region HTML
1357
- /**
1358
- * @returns {String} the address in link form with a default port of 80
1359
- */
1360
- href(optionalPort) {
1361
- if (optionalPort === void 0) {
1362
- optionalPort = "";
1363
- } else {
1364
- optionalPort = `:${optionalPort}`;
1365
- }
1366
- return `http://[${this.correctForm()}]${optionalPort}/`;
1367
- }
1368
- /**
1369
- * @returns {String} a link suitable for conveying the address via a URL hash
1370
- */
1371
- link(options) {
1372
- if (!options) {
1373
- options = {};
1374
- }
1375
- if (options.className === void 0) {
1376
- options.className = "";
1377
- }
1378
- if (options.prefix === void 0) {
1379
- options.prefix = "/#address=";
1380
- }
1381
- if (options.v4 === void 0) {
1382
- options.v4 = false;
1383
- }
1384
- let formFunction = this.correctForm;
1385
- if (options.v4) {
1386
- formFunction = this.to4in6;
1387
- }
1388
- const form = formFunction.call(this);
1389
- if (options.className) {
1390
- return `<a href="${options.prefix}${form}" class="${options.className}">${form}</a>`;
1391
- }
1392
- return `<a href="${options.prefix}${form}">${form}</a>`;
1393
- }
1394
- /**
1395
- * Groups an address
1396
- * @returns {String}
1397
- */
1398
- group() {
1399
- if (this.elidedGroups === 0) {
1400
- return helpers.simpleGroup(this.address).join(":");
1401
- }
1402
- assert(typeof this.elidedGroups === "number");
1403
- assert(typeof this.elisionBegin === "number");
1404
- const output = [];
1405
- const [left, right] = this.address.split("::");
1406
- if (left.length) {
1407
- output.push(...helpers.simpleGroup(left));
1408
- } else {
1409
- output.push("");
1410
- }
1411
- const classes = ["hover-group"];
1412
- for (let i = this.elisionBegin; i < this.elisionBegin + this.elidedGroups; i++) {
1413
- classes.push(`group-${i}`);
1414
- }
1415
- output.push(`<span class="${classes.join(" ")}"></span>`);
1416
- if (right.length) {
1417
- output.push(...helpers.simpleGroup(right, this.elisionEnd));
1418
- } else {
1419
- output.push("");
1420
- }
1421
- if (this.is4()) {
1422
- assert(this.address4 instanceof ipv4_1.Address4);
1423
- output.pop();
1424
- output.push(this.address4.groupForV6());
1425
- }
1426
- return output.join(":");
1427
- }
1428
- // #endregion
1429
- // #region Regular expressions
1430
- /**
1431
- * Generate a regular expression string that can be used to find or validate
1432
- * all variations of this address
1433
- * @memberof Address6
1434
- * @instance
1435
- * @param {boolean} substringSearch
1436
- * @returns {string}
1437
- */
1438
- regularExpressionString(substringSearch = false) {
1439
- let output = [];
1440
- const address6 = new _Address6(this.correctForm());
1441
- if (address6.elidedGroups === 0) {
1442
- output.push((0, regular_expressions_1.simpleRegularExpression)(address6.parsedAddress));
1443
- } else if (address6.elidedGroups === constants6.GROUPS) {
1444
- output.push((0, regular_expressions_1.possibleElisions)(constants6.GROUPS));
1445
- } else {
1446
- const halves = address6.address.split("::");
1447
- if (halves[0].length) {
1448
- output.push((0, regular_expressions_1.simpleRegularExpression)(halves[0].split(":")));
1449
- }
1450
- assert(typeof address6.elidedGroups === "number");
1451
- output.push((0, regular_expressions_1.possibleElisions)(address6.elidedGroups, halves[0].length !== 0, halves[1].length !== 0));
1452
- if (halves[1].length) {
1453
- output.push((0, regular_expressions_1.simpleRegularExpression)(halves[1].split(":")));
1454
- }
1455
- output = [output.join(":")];
1456
- }
1457
- if (!substringSearch) {
1458
- output = [
1459
- "(?=^|",
1460
- regular_expressions_1.ADDRESS_BOUNDARY,
1461
- "|[^\\w\\:])(",
1462
- ...output,
1463
- ")(?=[^\\w\\:]|",
1464
- regular_expressions_1.ADDRESS_BOUNDARY,
1465
- "|$)"
1466
- ];
1467
- }
1468
- return output.join("");
1469
- }
1470
- /**
1471
- * Generate a regular expression that can be used to find or validate all
1472
- * variations of this address.
1473
- * @memberof Address6
1474
- * @instance
1475
- * @param {boolean} substringSearch
1476
- * @returns {RegExp}
1477
- */
1478
- regularExpression(substringSearch = false) {
1479
- return new RegExp(this.regularExpressionString(substringSearch), "i");
1480
- }
1481
- };
1482
- exports.Address6 = Address62;
1483
- }
1484
- });
1485
-
1486
- // node_modules/ip-address/dist/ip-address.js
1487
- var require_ip_address = __commonJS({
1488
- "node_modules/ip-address/dist/ip-address.js"(exports) {
1489
- "use strict";
1490
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
1491
- if (k2 === void 0) k2 = k;
1492
- var desc = Object.getOwnPropertyDescriptor(m, k);
1493
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1494
- desc = { enumerable: true, get: function() {
1495
- return m[k];
1496
- } };
1497
- }
1498
- Object.defineProperty(o, k2, desc);
1499
- }) : (function(o, m, k, k2) {
1500
- if (k2 === void 0) k2 = k;
1501
- o[k2] = m[k];
1502
- }));
1503
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
1504
- Object.defineProperty(o, "default", { enumerable: true, value: v });
1505
- }) : function(o, v) {
1506
- o["default"] = v;
1507
- });
1508
- var __importStar = exports && exports.__importStar || function(mod) {
1509
- if (mod && mod.__esModule) return mod;
1510
- var result = {};
1511
- if (mod != null) {
1512
- for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
1513
- }
1514
- __setModuleDefault(result, mod);
1515
- return result;
1516
- };
1517
- Object.defineProperty(exports, "__esModule", { value: true });
1518
- exports.v6 = exports.AddressError = exports.Address6 = exports.Address4 = void 0;
1519
- var ipv4_1 = require_ipv4();
1520
- Object.defineProperty(exports, "Address4", { enumerable: true, get: function() {
1521
- return ipv4_1.Address4;
1522
- } });
1523
- var ipv6_1 = require_ipv6();
1524
- Object.defineProperty(exports, "Address6", { enumerable: true, get: function() {
1525
- return ipv6_1.Address6;
1526
- } });
1527
- var address_error_1 = require_address_error();
1528
- Object.defineProperty(exports, "AddressError", { enumerable: true, get: function() {
1529
- return address_error_1.AddressError;
1530
- } });
1531
- var helpers = __importStar(require_helpers());
1532
- exports.v6 = { helpers };
1533
- }
1534
- });
2
+ CRAWLIO_PORT_FILE,
3
+ PKG_VERSION,
4
+ TIMEOUTS,
5
+ WS_HEARTBEAT_INTERVAL,
6
+ WS_HOST,
7
+ WS_PORT,
8
+ WS_RECONNECT_GRACE,
9
+ WS_STALE_THRESHOLD
10
+ } from "./chunk-JRZS5IP6.js";
1535
11
 
1536
12
  // src/mcp-server/index.ts
1537
- import { randomBytes } from "crypto";
13
+ import { randomBytes as randomBytes2 } from "crypto";
1538
14
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1539
15
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1540
16
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
@@ -1542,904 +18,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
1542
18
  import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
1543
19
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
1544
20
  import { ZodError } from "zod";
1545
-
1546
- // node_modules/express-rate-limit/dist/index.mjs
1547
- var import_ip_address = __toESM(require_ip_address(), 1);
1548
- import { isIPv6 } from "net";
1549
- import { isIPv6 as isIPv62 } from "net";
1550
- import { Buffer } from "buffer";
1551
- import { createHash } from "crypto";
1552
- import { isIP } from "net";
1553
- function ipKeyGenerator(ip, ipv6Subnet = 56) {
1554
- if (ipv6Subnet && isIPv6(ip)) {
1555
- return `${new import_ip_address.Address6(`${ip}/${ipv6Subnet}`).startAddress().correctForm()}/${ipv6Subnet}`;
1556
- }
1557
- return ip;
1558
- }
1559
- var MemoryStore = class {
1560
- constructor(validations2) {
1561
- this.validations = validations2;
1562
- this.previous = /* @__PURE__ */ new Map();
1563
- this.current = /* @__PURE__ */ new Map();
1564
- this.localKeys = true;
1565
- }
1566
- /**
1567
- * Method that initializes the store.
1568
- *
1569
- * @param options {Options} - The options used to setup the middleware.
1570
- */
1571
- init(options) {
1572
- this.windowMs = options.windowMs;
1573
- this.validations?.windowMs(this.windowMs);
1574
- if (this.interval) clearInterval(this.interval);
1575
- this.interval = setInterval(() => {
1576
- this.clearExpired();
1577
- }, this.windowMs);
1578
- this.interval.unref?.();
1579
- }
1580
- /**
1581
- * Method to fetch a client's hit count and reset time.
1582
- *
1583
- * @param key {string} - The identifier for a client.
1584
- *
1585
- * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
1586
- *
1587
- * @public
1588
- */
1589
- async get(key) {
1590
- return this.current.get(key) ?? this.previous.get(key);
1591
- }
1592
- /**
1593
- * Method to increment a client's hit counter.
1594
- *
1595
- * @param key {string} - The identifier for a client.
1596
- *
1597
- * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
1598
- *
1599
- * @public
1600
- */
1601
- async increment(key) {
1602
- const client = this.getClient(key);
1603
- const now = Date.now();
1604
- if (client.resetTime.getTime() <= now) {
1605
- this.resetClient(client, now);
1606
- }
1607
- client.totalHits++;
1608
- return client;
1609
- }
1610
- /**
1611
- * Method to decrement a client's hit counter.
1612
- *
1613
- * @param key {string} - The identifier for a client.
1614
- *
1615
- * @public
1616
- */
1617
- async decrement(key) {
1618
- const client = this.getClient(key);
1619
- if (client.totalHits > 0) client.totalHits--;
1620
- }
1621
- /**
1622
- * Method to reset a client's hit counter.
1623
- *
1624
- * @param key {string} - The identifier for a client.
1625
- *
1626
- * @public
1627
- */
1628
- async resetKey(key) {
1629
- this.current.delete(key);
1630
- this.previous.delete(key);
1631
- }
1632
- /**
1633
- * Method to reset everyone's hit counter.
1634
- *
1635
- * @public
1636
- */
1637
- async resetAll() {
1638
- this.current.clear();
1639
- this.previous.clear();
1640
- }
1641
- /**
1642
- * Method to stop the timer (if currently running) and prevent any memory
1643
- * leaks.
1644
- *
1645
- * @public
1646
- */
1647
- shutdown() {
1648
- clearInterval(this.interval);
1649
- void this.resetAll();
1650
- }
1651
- /**
1652
- * Recycles a client by setting its hit count to zero, and reset time to
1653
- * `windowMs` milliseconds from now.
1654
- *
1655
- * NOT to be confused with `#resetKey()`, which removes a client from both the
1656
- * `current` and `previous` maps.
1657
- *
1658
- * @param client {Client} - The client to recycle.
1659
- * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
1660
- *
1661
- * @return {Client} - The modified client that was passed in, to allow for chaining.
1662
- */
1663
- resetClient(client, now = Date.now()) {
1664
- client.totalHits = 0;
1665
- client.resetTime.setTime(now + this.windowMs);
1666
- return client;
1667
- }
1668
- /**
1669
- * Retrieves or creates a client, given a key. Also ensures that the client being
1670
- * returned is in the `current` map.
1671
- *
1672
- * @param key {string} - The key under which the client is (or is to be) stored.
1673
- *
1674
- * @returns {Client} - The requested client.
1675
- */
1676
- getClient(key) {
1677
- if (this.current.has(key)) return this.current.get(key);
1678
- let client;
1679
- if (this.previous.has(key)) {
1680
- client = this.previous.get(key);
1681
- this.previous.delete(key);
1682
- } else {
1683
- client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
1684
- this.resetClient(client);
1685
- }
1686
- this.current.set(key, client);
1687
- return client;
1688
- }
1689
- /**
1690
- * Move current clients to previous, create a new map for current.
1691
- *
1692
- * This function is called every `windowMs`.
1693
- */
1694
- clearExpired() {
1695
- this.previous = this.current;
1696
- this.current = /* @__PURE__ */ new Map();
1697
- }
1698
- };
1699
- var SUPPORTED_DRAFT_VERSIONS = [
1700
- "draft-6",
1701
- "draft-7",
1702
- "draft-8"
1703
- ];
1704
- var getResetSeconds = (windowMs, resetTime) => {
1705
- let resetSeconds;
1706
- if (resetTime) {
1707
- const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
1708
- resetSeconds = Math.max(0, deltaSeconds);
1709
- } else {
1710
- resetSeconds = Math.ceil(windowMs / 1e3);
1711
- }
1712
- return resetSeconds;
1713
- };
1714
- var getPartitionKey = (key) => {
1715
- const hash = createHash("sha256");
1716
- hash.update(key);
1717
- const partitionKey = hash.digest("hex").slice(0, 12);
1718
- return Buffer.from(partitionKey).toString("base64");
1719
- };
1720
- var setLegacyHeaders = (response, info) => {
1721
- if (response.headersSent) return;
1722
- response.setHeader("X-RateLimit-Limit", info.limit.toString());
1723
- response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
1724
- if (info.resetTime instanceof Date) {
1725
- response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
1726
- response.setHeader(
1727
- "X-RateLimit-Reset",
1728
- Math.ceil(info.resetTime.getTime() / 1e3).toString()
1729
- );
1730
- }
1731
- };
1732
- var setDraft6Headers = (response, info, windowMs) => {
1733
- if (response.headersSent) return;
1734
- const windowSeconds = Math.ceil(windowMs / 1e3);
1735
- const resetSeconds = getResetSeconds(windowMs, info.resetTime);
1736
- response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
1737
- response.setHeader("RateLimit-Limit", info.limit.toString());
1738
- response.setHeader("RateLimit-Remaining", info.remaining.toString());
1739
- if (typeof resetSeconds === "number")
1740
- response.setHeader("RateLimit-Reset", resetSeconds.toString());
1741
- };
1742
- var setDraft7Headers = (response, info, windowMs) => {
1743
- if (response.headersSent) return;
1744
- const windowSeconds = Math.ceil(windowMs / 1e3);
1745
- const resetSeconds = getResetSeconds(windowMs, info.resetTime);
1746
- response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
1747
- response.setHeader(
1748
- "RateLimit",
1749
- `limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
1750
- );
1751
- };
1752
- var setDraft8Headers = (response, info, windowMs, name, key) => {
1753
- if (response.headersSent) return;
1754
- const windowSeconds = Math.ceil(windowMs / 1e3);
1755
- const resetSeconds = getResetSeconds(windowMs, info.resetTime);
1756
- const partitionKey = getPartitionKey(key);
1757
- const header = `r=${info.remaining}; t=${resetSeconds}`;
1758
- const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
1759
- response.append("RateLimit", `"${name}"; ${header}`);
1760
- response.append("RateLimit-Policy", `"${name}"; ${policy}`);
1761
- };
1762
- var setRetryAfterHeader = (response, info, windowMs) => {
1763
- if (response.headersSent) return;
1764
- const resetSeconds = getResetSeconds(windowMs, info.resetTime);
1765
- response.setHeader("Retry-After", resetSeconds.toString());
1766
- };
1767
- var omitUndefinedProperties = (passedOptions) => {
1768
- const omittedOptions = {};
1769
- for (const k of Object.keys(passedOptions)) {
1770
- const key = k;
1771
- if (passedOptions[key] !== void 0) {
1772
- omittedOptions[key] = passedOptions[key];
1773
- }
1774
- }
1775
- return omittedOptions;
1776
- };
1777
- var ValidationError = class extends Error {
1778
- /**
1779
- * The code must be a string, in snake case and all capital, that starts with
1780
- * the substring `ERR_ERL_`.
1781
- *
1782
- * The message must be a string, starting with an uppercase character,
1783
- * describing the issue in detail.
1784
- */
1785
- constructor(code, message) {
1786
- const url = `https://express-rate-limit.github.io/${code}/`;
1787
- super(`${message} See ${url} for more information.`);
1788
- this.name = this.constructor.name;
1789
- this.code = code;
1790
- this.help = url;
1791
- }
1792
- };
1793
- var ChangeWarning = class extends ValidationError {
1794
- };
1795
- var usedStores = /* @__PURE__ */ new Set();
1796
- var singleCountKeys = /* @__PURE__ */ new WeakMap();
1797
- var validations = {
1798
- enabled: {
1799
- default: true
1800
- },
1801
- // Should be EnabledValidations type, but that's a circular reference
1802
- disable() {
1803
- for (const k of Object.keys(this.enabled)) this.enabled[k] = false;
1804
- },
1805
- /**
1806
- * Checks whether the IP address is valid, and that it does not have a port
1807
- * number in it.
1808
- *
1809
- * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
1810
- *
1811
- * @param ip {string | undefined} - The IP address provided by Express as request.ip.
1812
- *
1813
- * @returns {void}
1814
- */
1815
- ip(ip) {
1816
- if (ip === void 0) {
1817
- throw new ValidationError(
1818
- "ERR_ERL_UNDEFINED_IP_ADDRESS",
1819
- `An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
1820
- );
1821
- }
1822
- if (!isIP(ip)) {
1823
- throw new ValidationError(
1824
- "ERR_ERL_INVALID_IP_ADDRESS",
1825
- `An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
1826
- );
1827
- }
1828
- },
1829
- /**
1830
- * Makes sure the trust proxy setting is not set to `true`.
1831
- *
1832
- * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
1833
- *
1834
- * @param request {Request} - The Express request object.
1835
- *
1836
- * @returns {void}
1837
- */
1838
- trustProxy(request) {
1839
- if (request.app.get("trust proxy") === true) {
1840
- throw new ValidationError(
1841
- "ERR_ERL_PERMISSIVE_TRUST_PROXY",
1842
- `The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
1843
- );
1844
- }
1845
- },
1846
- /**
1847
- * Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
1848
- * header is present.
1849
- *
1850
- * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
1851
- *
1852
- * @param request {Request} - The Express request object.
1853
- *
1854
- * @returns {void}
1855
- */
1856
- xForwardedForHeader(request) {
1857
- if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
1858
- throw new ValidationError(
1859
- "ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
1860
- `The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
1861
- );
1862
- }
1863
- },
1864
- /**
1865
- * Alert the user if the Forwarded header is set (standardized version of X-Forwarded-For - not supported by express as of version 5.1.0)
1866
- *
1867
- * @param request {Request} - The Express request object.
1868
- *
1869
- * @returns {void}
1870
- */
1871
- forwardedHeader(request) {
1872
- if (request.headers.forwarded && request.ip === request.socket?.remoteAddress) {
1873
- throw new ValidationError(
1874
- "ERR_ERL_FORWARDED_HEADER",
1875
- `The 'Forwarded' header (standardized X-Forwarded-For) is set but currently being ignored. Add a custom keyGenerator to use a value from this header.`
1876
- );
1877
- }
1878
- },
1879
- /**
1880
- * Ensures totalHits value from store is a positive integer.
1881
- *
1882
- * @param hits {any} - The `totalHits` returned by the store.
1883
- */
1884
- positiveHits(hits) {
1885
- if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
1886
- throw new ValidationError(
1887
- "ERR_ERL_INVALID_HITS",
1888
- `The totalHits value returned from the store must be a positive integer, got ${hits}`
1889
- );
1890
- }
1891
- },
1892
- /**
1893
- * Ensures a single store instance is not used with multiple express-rate-limit instances
1894
- */
1895
- unsharedStore(store) {
1896
- if (usedStores.has(store)) {
1897
- const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";
1898
- throw new ValidationError(
1899
- "ERR_ERL_STORE_REUSE",
1900
- `A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`
1901
- );
1902
- }
1903
- usedStores.add(store);
1904
- },
1905
- /**
1906
- * Ensures a given key is incremented only once per request.
1907
- *
1908
- * @param request {Request} - The Express request object.
1909
- * @param store {Store} - The store class.
1910
- * @param key {string} - The key used to store the client's hit count.
1911
- *
1912
- * @returns {void}
1913
- */
1914
- singleCount(request, store, key) {
1915
- let storeKeys = singleCountKeys.get(request);
1916
- if (!storeKeys) {
1917
- storeKeys = /* @__PURE__ */ new Map();
1918
- singleCountKeys.set(request, storeKeys);
1919
- }
1920
- const storeKey = store.localKeys ? store : store.constructor.name;
1921
- let keys = storeKeys.get(storeKey);
1922
- if (!keys) {
1923
- keys = [];
1924
- storeKeys.set(storeKey, keys);
1925
- }
1926
- const prefixedKey = `${store.prefix ?? ""}${key}`;
1927
- if (keys.includes(prefixedKey)) {
1928
- throw new ValidationError(
1929
- "ERR_ERL_DOUBLE_COUNT",
1930
- `The hit count for ${key} was incremented more than once for a single request.`
1931
- );
1932
- }
1933
- keys.push(prefixedKey);
1934
- },
1935
- /**
1936
- * Warns the user that the behaviour for `max: 0` / `limit: 0` is
1937
- * changing in the next major release.
1938
- *
1939
- * @param limit {number} - The maximum number of hits per client.
1940
- *
1941
- * @returns {void}
1942
- */
1943
- limit(limit) {
1944
- if (limit === 0) {
1945
- throw new ChangeWarning(
1946
- "WRN_ERL_MAX_ZERO",
1947
- "Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7"
1948
- );
1949
- }
1950
- },
1951
- /**
1952
- * Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
1953
- * and will be removed in the next major release.
1954
- *
1955
- * @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
1956
- *
1957
- * @returns {void}
1958
- */
1959
- draftPolliHeaders(draft_polli_ratelimit_headers) {
1960
- if (draft_polli_ratelimit_headers) {
1961
- throw new ChangeWarning(
1962
- "WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
1963
- `The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
1964
- );
1965
- }
1966
- },
1967
- /**
1968
- * Warns the user that the `onLimitReached` option is deprecated and
1969
- * will be removed in the next major release.
1970
- *
1971
- * @param onLimitReached {any | undefined} - The maximum number of hits per client.
1972
- *
1973
- * @returns {void}
1974
- */
1975
- onLimitReached(onLimitReached) {
1976
- if (onLimitReached) {
1977
- throw new ChangeWarning(
1978
- "WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
1979
- "The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7."
1980
- );
1981
- }
1982
- },
1983
- /**
1984
- * Warns the user when an invalid/unsupported version of the draft spec is passed.
1985
- *
1986
- * @param version {any | undefined} - The version passed by the user.
1987
- *
1988
- * @returns {void}
1989
- */
1990
- headersDraftVersion(version) {
1991
- if (typeof version !== "string" || // @ts-expect-error This is fine. If version is not in the array, it will just return false.
1992
- !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
1993
- const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
1994
- throw new ValidationError(
1995
- "ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
1996
- `standardHeaders: only the following versions of the IETF draft specification are supported: ${versionString}.`
1997
- );
1998
- }
1999
- },
2000
- /**
2001
- * Warns the user when the selected headers option requires a reset time but
2002
- * the store does not provide one.
2003
- *
2004
- * @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
2005
- *
2006
- * @returns {void}
2007
- */
2008
- headersResetTime(resetTime) {
2009
- if (!resetTime) {
2010
- throw new ValidationError(
2011
- "ERR_ERL_HEADERS_NO_RESET",
2012
- `standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
2013
- );
2014
- }
2015
- },
2016
- knownOptions(passedOptions) {
2017
- if (!passedOptions) return;
2018
- const optionsMap = {
2019
- windowMs: true,
2020
- limit: true,
2021
- message: true,
2022
- statusCode: true,
2023
- legacyHeaders: true,
2024
- standardHeaders: true,
2025
- identifier: true,
2026
- requestPropertyName: true,
2027
- skipFailedRequests: true,
2028
- skipSuccessfulRequests: true,
2029
- keyGenerator: true,
2030
- ipv6Subnet: true,
2031
- handler: true,
2032
- skip: true,
2033
- requestWasSuccessful: true,
2034
- store: true,
2035
- validate: true,
2036
- headers: true,
2037
- max: true,
2038
- passOnStoreError: true
2039
- };
2040
- const validOptions = Object.keys(optionsMap).concat(
2041
- "draft_polli_ratelimit_headers",
2042
- // not a valid option anymore, but we have a more specific check for this one, so don't warn for it here
2043
- // from express-slow-down - https://github.com/express-rate-limit/express-slow-down/blob/main/source/types.ts#L65
2044
- "delayAfter",
2045
- "delayMs",
2046
- "maxDelayMs"
2047
- );
2048
- for (const key of Object.keys(passedOptions)) {
2049
- if (!validOptions.includes(key)) {
2050
- throw new ValidationError(
2051
- "ERR_ERL_UNKNOWN_OPTION",
2052
- `Unexpected configuration option: ${key}`
2053
- // todo: suggest a valid option with a short levenstein distance?
2054
- );
2055
- }
2056
- }
2057
- },
2058
- /**
2059
- * Checks the options.validate setting to ensure that only recognized
2060
- * validations are enabled or disabled.
2061
- *
2062
- * If any unrecognized values are found, an error is logged that
2063
- * includes the list of supported validations.
2064
- */
2065
- validationsConfig() {
2066
- const supportedValidations = Object.keys(this).filter(
2067
- (k) => !["enabled", "disable"].includes(k)
2068
- );
2069
- supportedValidations.push("default");
2070
- for (const key of Object.keys(this.enabled)) {
2071
- if (!supportedValidations.includes(key)) {
2072
- throw new ValidationError(
2073
- "ERR_ERL_UNKNOWN_VALIDATION",
2074
- `options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
2075
- ", "
2076
- )}.`
2077
- );
2078
- }
2079
- }
2080
- },
2081
- /**
2082
- * Checks to see if the instance was created inside of a request handler,
2083
- * which would prevent it from working correctly, with the default memory
2084
- * store (or any other store with localKeys.)
2085
- */
2086
- creationStack(store) {
2087
- const { stack } = new Error(
2088
- "express-rate-limit validation check (set options.validate.creationStack=false to disable)"
2089
- );
2090
- if (stack?.includes("Layer.handle [as handle_request]") || // express v4
2091
- stack?.includes("Layer.handleRequest")) {
2092
- if (!store.localKeys) {
2093
- throw new ValidationError(
2094
- "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
2095
- "express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
2096
- );
2097
- }
2098
- throw new ValidationError(
2099
- "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
2100
- "express-rate-limit instance should be created at app initialization, not when responding to a request."
2101
- );
2102
- }
2103
- },
2104
- ipv6Subnet(ipv6Subnet) {
2105
- if (ipv6Subnet === false) {
2106
- return;
2107
- }
2108
- if (!Number.isInteger(ipv6Subnet) || ipv6Subnet < 32 || ipv6Subnet > 64) {
2109
- throw new ValidationError(
2110
- "ERR_ERL_IPV6_SUBNET",
2111
- `Unexpected ipv6Subnet value: ${ipv6Subnet}. Expected an integer between 32 and 64 (usually 48-64).`
2112
- );
2113
- }
2114
- },
2115
- ipv6SubnetOrKeyGenerator(options) {
2116
- if (options.ipv6Subnet !== void 0 && options.keyGenerator) {
2117
- throw new ValidationError(
2118
- "ERR_ERL_IPV6SUBNET_OR_KEYGENERATOR",
2119
- `Incompatible options: the 'ipv6Subnet' option is ignored when a custom 'keyGenerator' function is also set.`
2120
- );
2121
- }
2122
- },
2123
- keyGeneratorIpFallback(keyGenerator) {
2124
- if (!keyGenerator) {
2125
- return;
2126
- }
2127
- const src = keyGenerator.toString();
2128
- if ((src.includes("req.ip") || src.includes("request.ip")) && !src.includes("ipKeyGenerator")) {
2129
- throw new ValidationError(
2130
- "ERR_ERL_KEY_GEN_IPV6",
2131
- "Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits."
2132
- );
2133
- }
2134
- },
2135
- /**
2136
- * Checks to see if the window duration is greater than 2^32 - 1. This is only
2137
- * called by the default MemoryStore, since it uses Node's setInterval method.
2138
- *
2139
- * See https://nodejs.org/api/timers.html#setintervalcallback-delay-args.
2140
- */
2141
- windowMs(windowMs) {
2142
- const SET_TIMEOUT_MAX = 2 ** 31 - 1;
2143
- if (typeof windowMs !== "number" || Number.isNaN(windowMs) || windowMs < 1 || windowMs > SET_TIMEOUT_MAX) {
2144
- throw new ValidationError(
2145
- "ERR_ERL_WINDOW_MS",
2146
- `Invalid windowMs value: ${windowMs}${typeof windowMs !== "number" ? ` (${typeof windowMs})` : ""}, must be a number between 1 and ${SET_TIMEOUT_MAX} when using the default MemoryStore`
2147
- );
2148
- }
2149
- }
2150
- };
2151
- var getValidations = (_enabled) => {
2152
- let enabled;
2153
- if (typeof _enabled === "boolean") {
2154
- enabled = {
2155
- default: _enabled
2156
- };
2157
- } else {
2158
- enabled = {
2159
- default: true,
2160
- ..._enabled
2161
- };
2162
- }
2163
- const wrappedValidations = { enabled };
2164
- for (const [name, validation] of Object.entries(validations)) {
2165
- if (typeof validation === "function")
2166
- wrappedValidations[name] = (...args) => {
2167
- if (!(enabled[name] ?? enabled.default)) {
2168
- return;
2169
- }
2170
- try {
2171
- ;
2172
- validation.apply(
2173
- wrappedValidations,
2174
- args
2175
- );
2176
- } catch (error) {
2177
- if (error instanceof ChangeWarning) console.warn(error);
2178
- else console.error(error);
2179
- }
2180
- };
2181
- }
2182
- return wrappedValidations;
2183
- };
2184
- var isLegacyStore = (store) => (
2185
- // Check that `incr` exists but `increment` does not - store authors might want
2186
- // to keep both around for backwards compatibility.
2187
- typeof store.incr === "function" && typeof store.increment !== "function"
2188
- );
2189
- var promisifyStore = (passedStore) => {
2190
- if (!isLegacyStore(passedStore)) {
2191
- return passedStore;
2192
- }
2193
- const legacyStore = passedStore;
2194
- class PromisifiedStore {
2195
- async increment(key) {
2196
- return new Promise((resolve2, reject) => {
2197
- legacyStore.incr(
2198
- key,
2199
- (error, totalHits, resetTime) => {
2200
- if (error) reject(error);
2201
- resolve2({ totalHits, resetTime });
2202
- }
2203
- );
2204
- });
2205
- }
2206
- async decrement(key) {
2207
- return legacyStore.decrement(key);
2208
- }
2209
- async resetKey(key) {
2210
- return legacyStore.resetKey(key);
2211
- }
2212
- /* istanbul ignore next */
2213
- async resetAll() {
2214
- if (typeof legacyStore.resetAll === "function")
2215
- return legacyStore.resetAll();
2216
- }
2217
- }
2218
- return new PromisifiedStore();
2219
- };
2220
- var getOptionsFromConfig = (config) => {
2221
- const { validations: validations2, ...directlyPassableEntries } = config;
2222
- return {
2223
- ...directlyPassableEntries,
2224
- validate: validations2.enabled
2225
- };
2226
- };
2227
- var parseOptions = (passedOptions) => {
2228
- const notUndefinedOptions = omitUndefinedProperties(passedOptions);
2229
- const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
2230
- validations2.validationsConfig();
2231
- validations2.knownOptions(passedOptions);
2232
- validations2.draftPolliHeaders(
2233
- // @ts-expect-error see the note above.
2234
- notUndefinedOptions.draft_polli_ratelimit_headers
2235
- );
2236
- validations2.onLimitReached(notUndefinedOptions.onLimitReached);
2237
- if (notUndefinedOptions.ipv6Subnet !== void 0 && typeof notUndefinedOptions.ipv6Subnet !== "function") {
2238
- validations2.ipv6Subnet(notUndefinedOptions.ipv6Subnet);
2239
- }
2240
- validations2.keyGeneratorIpFallback(notUndefinedOptions.keyGenerator);
2241
- validations2.ipv6SubnetOrKeyGenerator(notUndefinedOptions);
2242
- let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
2243
- if (standardHeaders === true) standardHeaders = "draft-6";
2244
- const config = {
2245
- windowMs: 60 * 1e3,
2246
- limit: passedOptions.max ?? 5,
2247
- // `max` is deprecated, but support it anyways.
2248
- message: "Too many requests, please try again later.",
2249
- statusCode: 429,
2250
- legacyHeaders: passedOptions.headers ?? true,
2251
- identifier(request, _response) {
2252
- let duration = "";
2253
- const property = config.requestPropertyName;
2254
- const { limit } = request[property];
2255
- const seconds = config.windowMs / 1e3;
2256
- const minutes = config.windowMs / (1e3 * 60);
2257
- const hours = config.windowMs / (1e3 * 60 * 60);
2258
- const days = config.windowMs / (1e3 * 60 * 60 * 24);
2259
- if (seconds < 60) duration = `${seconds}sec`;
2260
- else if (minutes < 60) duration = `${minutes}min`;
2261
- else if (hours < 24) duration = `${hours}hr${hours > 1 ? "s" : ""}`;
2262
- else duration = `${days}day${days > 1 ? "s" : ""}`;
2263
- return `${limit}-in-${duration}`;
2264
- },
2265
- requestPropertyName: "rateLimit",
2266
- skipFailedRequests: false,
2267
- skipSuccessfulRequests: false,
2268
- requestWasSuccessful: (_request, response) => response.statusCode < 400,
2269
- skip: (_request, _response) => false,
2270
- async keyGenerator(request, response) {
2271
- validations2.ip(request.ip);
2272
- validations2.trustProxy(request);
2273
- validations2.xForwardedForHeader(request);
2274
- validations2.forwardedHeader(request);
2275
- const ip = request.ip;
2276
- let subnet = 56;
2277
- if (isIPv62(ip)) {
2278
- subnet = typeof config.ipv6Subnet === "function" ? await config.ipv6Subnet(request, response) : config.ipv6Subnet;
2279
- if (typeof config.ipv6Subnet === "function")
2280
- validations2.ipv6Subnet(subnet);
2281
- }
2282
- return ipKeyGenerator(ip, subnet);
2283
- },
2284
- ipv6Subnet: 56,
2285
- async handler(request, response, _next, _optionsUsed) {
2286
- response.status(config.statusCode);
2287
- const message = typeof config.message === "function" ? await config.message(
2288
- request,
2289
- response
2290
- ) : config.message;
2291
- if (!response.writableEnded) response.send(message);
2292
- },
2293
- passOnStoreError: false,
2294
- // Allow the default options to be overridden by the passed options.
2295
- ...notUndefinedOptions,
2296
- // `standardHeaders` is resolved into a draft version above, use that.
2297
- standardHeaders,
2298
- // Note that this field is declared after the user's options are spread in,
2299
- // so that this field doesn't get overridden with an un-promisified store!
2300
- store: promisifyStore(
2301
- notUndefinedOptions.store ?? new MemoryStore(validations2)
2302
- ),
2303
- // Print an error to the console if a few known misconfigurations are detected.
2304
- validations: validations2
2305
- };
2306
- if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
2307
- throw new TypeError(
2308
- "An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
2309
- );
2310
- }
2311
- return config;
2312
- };
2313
- var handleAsyncErrors = (fn) => async (request, response, next) => {
2314
- try {
2315
- await Promise.resolve(fn(request, response, next)).catch(next);
2316
- } catch (error) {
2317
- next(error);
2318
- }
2319
- };
2320
- var rateLimit = (passedOptions) => {
2321
- const config = parseOptions(passedOptions ?? {});
2322
- const options = getOptionsFromConfig(config);
2323
- config.validations.creationStack(config.store);
2324
- config.validations.unsharedStore(config.store);
2325
- if (typeof config.store.init === "function") config.store.init(options);
2326
- const middleware = handleAsyncErrors(
2327
- async (request, response, next) => {
2328
- const skip = await config.skip(request, response);
2329
- if (skip) {
2330
- next();
2331
- return;
2332
- }
2333
- const augmentedRequest = request;
2334
- const key = await config.keyGenerator(request, response);
2335
- let totalHits = 0;
2336
- let resetTime;
2337
- try {
2338
- const incrementResult = await config.store.increment(key);
2339
- totalHits = incrementResult.totalHits;
2340
- resetTime = incrementResult.resetTime;
2341
- } catch (error) {
2342
- if (config.passOnStoreError) {
2343
- console.error(
2344
- "express-rate-limit: error from store, allowing request without rate-limiting.",
2345
- error
2346
- );
2347
- next();
2348
- return;
2349
- }
2350
- throw error;
2351
- }
2352
- config.validations.positiveHits(totalHits);
2353
- config.validations.singleCount(request, config.store, key);
2354
- const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
2355
- const limit = await retrieveLimit;
2356
- config.validations.limit(limit);
2357
- const info = {
2358
- limit,
2359
- used: totalHits,
2360
- remaining: Math.max(limit - totalHits, 0),
2361
- resetTime,
2362
- key
2363
- };
2364
- Object.defineProperty(info, "current", {
2365
- configurable: false,
2366
- enumerable: false,
2367
- value: totalHits
2368
- });
2369
- augmentedRequest[config.requestPropertyName] = info;
2370
- if (config.legacyHeaders && !response.headersSent) {
2371
- setLegacyHeaders(response, info);
2372
- }
2373
- if (config.standardHeaders && !response.headersSent) {
2374
- switch (config.standardHeaders) {
2375
- case "draft-6": {
2376
- setDraft6Headers(response, info, config.windowMs);
2377
- break;
2378
- }
2379
- case "draft-7": {
2380
- config.validations.headersResetTime(info.resetTime);
2381
- setDraft7Headers(response, info, config.windowMs);
2382
- break;
2383
- }
2384
- case "draft-8": {
2385
- const retrieveName = typeof config.identifier === "function" ? config.identifier(request, response) : config.identifier;
2386
- const name = await retrieveName;
2387
- config.validations.headersResetTime(info.resetTime);
2388
- setDraft8Headers(response, info, config.windowMs, name, key);
2389
- break;
2390
- }
2391
- default: {
2392
- config.validations.headersDraftVersion(config.standardHeaders);
2393
- break;
2394
- }
2395
- }
2396
- }
2397
- if (config.skipFailedRequests || config.skipSuccessfulRequests) {
2398
- let decremented = false;
2399
- const decrementKey = async () => {
2400
- if (!decremented) {
2401
- await config.store.decrement(key);
2402
- decremented = true;
2403
- }
2404
- };
2405
- if (config.skipFailedRequests) {
2406
- response.on("finish", async () => {
2407
- if (!await config.requestWasSuccessful(request, response))
2408
- await decrementKey();
2409
- });
2410
- response.on("close", async () => {
2411
- if (!response.writableEnded) await decrementKey();
2412
- });
2413
- response.on("error", async () => {
2414
- await decrementKey();
2415
- });
2416
- }
2417
- if (config.skipSuccessfulRequests) {
2418
- response.on("finish", async () => {
2419
- if (await config.requestWasSuccessful(request, response))
2420
- await decrementKey();
2421
- });
2422
- }
2423
- }
2424
- config.validations.disable();
2425
- if (totalHits > limit) {
2426
- if (config.legacyHeaders || config.standardHeaders) {
2427
- setRetryAfterHeader(response, info, config.windowMs);
2428
- }
2429
- config.handler(request, response, next, options);
2430
- return;
2431
- }
2432
- next();
2433
- }
2434
- );
2435
- const getThrowFn = () => {
2436
- throw new Error("The current store does not support the get/getKey method");
2437
- };
2438
- middleware.resetKey = config.store.resetKey.bind(config.store);
2439
- middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
2440
- return middleware;
2441
- };
2442
- var rate_limit_default = rateLimit;
21
+ import rateLimit from "express-rate-limit";
2443
22
 
2444
23
  // src/mcp-server/websocket-bridge.ts
2445
24
  import { WebSocketServer, WebSocket } from "ws";
@@ -2447,33 +26,6 @@ import { createServer } from "http";
2447
26
  import { randomUUID } from "crypto";
2448
27
  import { fileURLToPath } from "url";
2449
28
  import { dirname, resolve } from "path";
2450
-
2451
- // src/shared/constants.ts
2452
- import { homedir } from "os";
2453
- import { join } from "path";
2454
- var WS_PORT = 9333;
2455
- var WS_HOST = "127.0.0.1";
2456
- var CRAWLIO_PORT_FILE = join(
2457
- homedir(),
2458
- "Library",
2459
- "Logs",
2460
- "Crawlio",
2461
- "control.port"
2462
- );
2463
- var TIMEOUTS = {
2464
- WS_COMMAND: 3e4,
2465
- // 30s for most commands
2466
- NETWORK_CAPTURE: 12e4,
2467
- // 2min for network capture
2468
- SCREENSHOT: 1e4,
2469
- // 10s
2470
- RECONNECT: 3e3,
2471
- // 3s reconnect delay
2472
- CODE_EXECUTE: 12e4
2473
- // 2min for code-mode execute()
2474
- };
2475
-
2476
- // src/mcp-server/websocket-bridge.ts
2477
29
  function resolveIndexPath() {
2478
30
  if (process.argv[1] && process.argv[1].includes("dist")) {
2479
31
  return process.argv[1];
@@ -2665,8 +217,8 @@ var MessageQueue = class {
2665
217
  this.queue = [];
2666
218
  }
2667
219
  };
2668
- var HEARTBEAT_INTERVAL = 15e3;
2669
- var STALE_THRESHOLD = 45e3;
220
+ var HEARTBEAT_INTERVAL = WS_HEARTBEAT_INTERVAL;
221
+ var STALE_THRESHOLD = WS_STALE_THRESHOLD;
2670
222
  var WebSocketBridge = class {
2671
223
  wss = null;
2672
224
  httpServer = null;
@@ -2674,6 +226,7 @@ var WebSocketBridge = class {
2674
226
  pending = /* @__PURE__ */ new Map();
2675
227
  messageQueue = new MessageQueue();
2676
228
  heartbeatTimer = null;
229
+ reconnectGraceTimer = null;
2677
230
  lastPong = 0;
2678
231
  lastPingSent = 0;
2679
232
  latencyMs = 0;
@@ -2689,7 +242,7 @@ var WebSocketBridge = class {
2689
242
  this.lastPingSent = Date.now();
2690
243
  this.client.ping();
2691
244
  if (this.lastPong > 0 && Date.now() - this.lastPong > STALE_THRESHOLD) {
2692
- console.error("[Bridge] WebSocket stale \u2014 no pong in 45s, closing");
245
+ console.error(`[Bridge] WebSocket stale \u2014 no pong in ${STALE_THRESHOLD / 1e3}s, closing`);
2693
246
  this.client.terminate();
2694
247
  }
2695
248
  }
@@ -2742,12 +295,48 @@ var WebSocketBridge = class {
2742
295
  res.end();
2743
296
  return;
2744
297
  }
2745
- res.writeHead(426);
2746
- res.end("Upgrade Required");
298
+ res.writeHead(426);
299
+ res.end("Upgrade Required");
300
+ });
301
+ this.wss = new WebSocketServer({
302
+ server: this.httpServer,
303
+ maxPayload: 10 * 1024 * 1024,
304
+ // 10 MB — prevents memory exhaustion
305
+ verifyClient: (info, callback) => {
306
+ const origin = info.origin;
307
+ if (!origin || origin === "null") {
308
+ callback(true);
309
+ return;
310
+ }
311
+ if (origin.startsWith("chrome-extension://")) {
312
+ callback(true);
313
+ return;
314
+ }
315
+ try {
316
+ const url = new URL(origin);
317
+ if (url.hostname === "127.0.0.1" || url.hostname === "localhost") {
318
+ callback(true);
319
+ return;
320
+ }
321
+ } catch {
322
+ }
323
+ console.error(`[Bridge] WebSocket origin rejected: ${origin}`);
324
+ callback(false, 403, "Forbidden");
325
+ }
2747
326
  });
2748
- this.wss = new WebSocketServer({ server: this.httpServer });
2749
327
  this.wss.on("connection", (ws) => {
328
+ if (this.client && this.client.readyState === WebSocket.OPEN) {
329
+ console.error("[Bridge] Evicting stale client \u2014 new connection");
330
+ this.client.close(1e3, "Replaced by new connection");
331
+ }
2750
332
  console.error(`[Bridge] Extension connected`);
333
+ if (this.reconnectGraceTimer) {
334
+ clearTimeout(this.reconnectGraceTimer);
335
+ this.reconnectGraceTimer = null;
336
+ if (this.pending.size > 0) {
337
+ console.error(`[Bridge] Reconnect within grace period \u2014 ${this.pending.size} pending commands preserved`);
338
+ }
339
+ }
2751
340
  this.client = ws;
2752
341
  this.reconnectCount++;
2753
342
  this.connectTime = Date.now();
@@ -2789,6 +378,11 @@ var WebSocketBridge = class {
2789
378
  }
2790
379
  ws.on("message", (raw) => {
2791
380
  try {
381
+ const len = typeof raw === "string" ? Buffer.byteLength(raw) : raw.length;
382
+ if (len > 5 * 1024 * 1024) {
383
+ console.error(`[Bridge] Oversized message dropped: ${(len / 1024 / 1024).toFixed(1)} MB`);
384
+ return;
385
+ }
2792
386
  const msg = JSON.parse(raw.toString());
2793
387
  if (msg.type === "refresh_port") {
2794
388
  this.onPortRefreshRequested?.();
@@ -2800,20 +394,36 @@ var WebSocketBridge = class {
2800
394
  }
2801
395
  });
2802
396
  ws.on("error", (err) => {
2803
- console.error("[Bridge] WebSocket error:", err.message);
397
+ if (this.client === ws) {
398
+ console.error("[Bridge] WebSocket error:", err.message);
399
+ }
2804
400
  });
2805
401
  ws.on("close", () => {
402
+ if (this.client !== ws) {
403
+ console.error("[Bridge] Stale client closed (already replaced)");
404
+ return;
405
+ }
2806
406
  console.error("[Bridge] Extension disconnected");
2807
407
  this.stopHeartbeat();
2808
408
  this.client = null;
2809
409
  this.connectTime = 0;
2810
- for (const [id, req] of this.pending) {
2811
- clearTimeout(req.timer);
2812
- req.reject(new Error("Extension disconnected"));
2813
- this.pending.delete(id);
410
+ if (this.pending.size > 0) {
411
+ console.error(`[Bridge] ${this.pending.size} pending commands \u2014 waiting ${WS_RECONNECT_GRACE / 1e3}s for reconnect`);
412
+ this.reconnectGraceTimer = setTimeout(() => {
413
+ this.reconnectGraceTimer = null;
414
+ for (const [id, req] of this.pending) {
415
+ clearTimeout(req.timer);
416
+ req.reject(new Error("Extension disconnected"));
417
+ this.pending.delete(id);
418
+ }
419
+ }, WS_RECONNECT_GRACE);
2814
420
  }
2815
421
  });
2816
422
  });
423
+ this.wss.on("wsClientError", (err, socket) => {
424
+ console.error("[Bridge] WebSocket handshake error:", err.message);
425
+ socket.destroy();
426
+ });
2817
427
  try {
2818
428
  const probe = await fetch(`http://${WS_HOST}:${WS_PORT}/health`, { signal: AbortSignal.timeout(500) });
2819
429
  const body = await probe.json();
@@ -2855,7 +465,8 @@ var WebSocketBridge = class {
2855
465
  const serialized = JSON.stringify(fullCommand);
2856
466
  if (!this.isConnected) {
2857
467
  console.error(`[Bridge] Queuing command (offline): ${command.type} (queue depth: ${this.messageQueue.depth + 1})`);
2858
- return this.messageQueue.enqueue(serialized, timeout);
468
+ const queueTimeout = Math.max(timeout, 45e3);
469
+ return this.messageQueue.enqueue(serialized, queueTimeout);
2859
470
  }
2860
471
  return new Promise((resolve2, reject) => {
2861
472
  const timer = setTimeout(() => {
@@ -2874,8 +485,8 @@ var WebSocketBridge = class {
2874
485
  }
2875
486
  handleMessage(msg) {
2876
487
  if (msg.type === "open_crawlio_app") {
2877
- import("child_process").then(({ execFile }) => {
2878
- execFile("open", ["-a", "Crawlio"], () => {
488
+ import("child_process").then(({ execFile: execFile2 }) => {
489
+ execFile2("open", ["-a", "Crawlio"], () => {
2879
490
  });
2880
491
  });
2881
492
  return;
@@ -2908,6 +519,10 @@ var WebSocketBridge = class {
2908
519
  }
2909
520
  async stop() {
2910
521
  this.stopHeartbeat();
522
+ if (this.reconnectGraceTimer) {
523
+ clearTimeout(this.reconnectGraceTimer);
524
+ this.reconnectGraceTimer = null;
525
+ }
2911
526
  this.messageQueue.clear();
2912
527
  this.client?.close();
2913
528
  this.wss?.close();
@@ -3083,6 +698,476 @@ var CrawlioClient = class {
3083
698
 
3084
699
  // src/mcp-server/tools.ts
3085
700
  import { z } from "zod";
701
+ import { execFile } from "child_process";
702
+ import { writeFile, unlink } from "fs/promises";
703
+ import { tmpdir } from "os";
704
+ import { join as join2, dirname as dirname3 } from "path";
705
+ import { randomBytes } from "crypto";
706
+ import { fileURLToPath as fileURLToPath3 } from "url";
707
+ import { promisify } from "util";
708
+
709
+ // src/mcp-server/response-shapers.ts
710
+ function truncateUrl(url, max = 120) {
711
+ if (url.length <= max) return url;
712
+ return url.slice(0, max - 3) + "...";
713
+ }
714
+ function shapeListTabs(data) {
715
+ const raw = Array.isArray(data) ? { tabs: data, connectedTabId: null } : data;
716
+ return {
717
+ connectedTabId: raw.connectedTabId ?? null,
718
+ tabs: raw.tabs.map((t) => ({
719
+ id: t.tabId,
720
+ title: t.title,
721
+ url: truncateUrl(t.url),
722
+ ...t.active ? { active: true } : {}
723
+ }))
724
+ };
725
+ }
726
+ function shapeConnectTab(data) {
727
+ const shaped = {
728
+ tabId: data.tabId,
729
+ url: typeof data.url === "string" ? truncateUrl(data.url) : data.url,
730
+ title: data.title
731
+ };
732
+ const ds = data.domainState;
733
+ if (ds?.required) {
734
+ const failed = ds.required.filter((d) => !d.success);
735
+ if (failed.length > 0) {
736
+ shaped.ok = false;
737
+ shaped.failedDomains = failed.map((d) => d.domain);
738
+ } else {
739
+ shaped.ok = true;
740
+ }
741
+ } else {
742
+ shaped.ok = true;
743
+ }
744
+ return shaped;
745
+ }
746
+ function summarizeDom(node) {
747
+ const stats = { nodeCount: 0, forms: 0, links: 0, images: 0, inputs: 0 };
748
+ const walk = (n) => {
749
+ stats.nodeCount++;
750
+ const tag = n.tag?.toLowerCase();
751
+ if (tag === "form") stats.forms++;
752
+ else if (tag === "a") stats.links++;
753
+ else if (tag === "img") stats.images++;
754
+ else if (tag === "input" || tag === "textarea" || tag === "select") stats.inputs++;
755
+ if (n.children) n.children.forEach(walk);
756
+ };
757
+ walk(node);
758
+ return stats;
759
+ }
760
+ function shapeCapturePage(data) {
761
+ const shaped = {
762
+ url: truncateUrl(data.url),
763
+ title: data.title,
764
+ capturedAt: data.capturedAt
765
+ };
766
+ if (data.framework) {
767
+ shaped.framework = data.framework;
768
+ }
769
+ if (data.networkRequests) {
770
+ const entries = data.networkRequests;
771
+ const failed = entries.filter((e) => e.status >= 400 || e.status === 0);
772
+ const byType = {};
773
+ for (const e of entries) {
774
+ const t = e.resourceType || "other";
775
+ byType[t] = (byType[t] || 0) + 1;
776
+ }
777
+ shaped.network = {
778
+ total: entries.length,
779
+ failed: failed.length,
780
+ byType,
781
+ errors: failed.slice(0, 10).map((e) => ({
782
+ url: truncateUrl(e.url, 80),
783
+ status: e.status,
784
+ method: e.method
785
+ }))
786
+ };
787
+ }
788
+ if (data.consoleLogs) {
789
+ const entries = data.consoleLogs;
790
+ const errors = entries.filter((e) => e.level === "error");
791
+ const warnings = entries.filter((e) => e.level === "warning");
792
+ shaped.console = {
793
+ total: entries.length,
794
+ errors: errors.slice(0, 10).map((e) => ({ level: e.level, text: e.text.slice(0, 200) })),
795
+ warnings: warnings.length,
796
+ info: entries.filter((e) => e.level === "info").length,
797
+ debug: entries.filter((e) => e.level === "debug").length
798
+ };
799
+ }
800
+ if (data.cookies) {
801
+ shaped.cookies = {
802
+ total: data.cookies.length,
803
+ names: data.cookies.map((c) => c.name)
804
+ };
805
+ }
806
+ if (data.domSnapshot) {
807
+ shaped.dom = summarizeDom(data.domSnapshot);
808
+ }
809
+ return shaped;
810
+ }
811
+ function shapeConsoleLogs(entries) {
812
+ const errors = entries.filter((e) => e.level === "error");
813
+ const warnings = entries.filter((e) => e.level === "warning");
814
+ return {
815
+ total: entries.length,
816
+ errors: errors.map((e) => ({
817
+ text: e.text.slice(0, 300),
818
+ url: e.url ? truncateUrl(e.url, 80) : void 0,
819
+ lineNumber: e.lineNumber
820
+ })),
821
+ warnings: warnings.slice(0, 10).map((e) => ({
822
+ text: e.text.slice(0, 200)
823
+ })),
824
+ info: entries.filter((e) => e.level === "info").length,
825
+ debug: entries.filter((e) => e.level === "debug").length
826
+ };
827
+ }
828
+ function shapeNetworkLog(entries) {
829
+ const failed = entries.filter((e) => e.status >= 400 || e.status === 0);
830
+ const byStatus = {};
831
+ const byType = {};
832
+ for (const e of entries) {
833
+ const bucket = e.status === 0 ? "0xx" : `${Math.floor(e.status / 100)}xx`;
834
+ byStatus[bucket] = (byStatus[bucket] || 0) + 1;
835
+ const t = e.resourceType || "other";
836
+ byType[t] = (byType[t] || 0) + 1;
837
+ }
838
+ const slowest = [...entries].sort((a, b) => b.durationMs - a.durationMs).slice(0, 5).map((e) => ({
839
+ url: truncateUrl(e.url, 80),
840
+ durationMs: e.durationMs,
841
+ status: e.status
842
+ }));
843
+ return {
844
+ total: entries.length,
845
+ failed: failed.map((e) => ({
846
+ url: truncateUrl(e.url, 80),
847
+ status: e.status,
848
+ method: e.method
849
+ })),
850
+ byStatus,
851
+ byType,
852
+ slowest
853
+ };
854
+ }
855
+ function shapeCookies(data) {
856
+ return {
857
+ total: data.cookies.length,
858
+ fallbackUsed: data.fallbackUsed ?? false,
859
+ cookies: data.cookies.map((c) => ({
860
+ name: c.name,
861
+ domain: c.domain,
862
+ httpOnly: c.httpOnly,
863
+ secure: c.secure,
864
+ sameSite: c.sameSite
865
+ }))
866
+ };
867
+ }
868
+ function shapeInteraction(data) {
869
+ if (!data || typeof data !== "object") return data;
870
+ const d = data;
871
+ const shaped = {};
872
+ const keep = [
873
+ "action",
874
+ "selector",
875
+ "ref",
876
+ "url",
877
+ "title",
878
+ "text",
879
+ "key",
880
+ "message",
881
+ "snapshot",
882
+ "previousValue",
883
+ "newValue",
884
+ "filesCount",
885
+ "success",
886
+ "filled",
887
+ "submitted"
888
+ ];
889
+ for (const k of keep) {
890
+ if (d[k] !== void 0) shaped[k] = d[k];
891
+ }
892
+ if (typeof shaped.url === "string") {
893
+ shaped.url = truncateUrl(shaped.url);
894
+ }
895
+ return shaped;
896
+ }
897
+
898
+ // src/mcp-server/recording-compiler.ts
899
+ function compileRecording(session, options) {
900
+ const name = sanitizeSkillName(options.name);
901
+ const totalInteractions = session.pages.reduce((sum, p) => sum + p.interactions.length, 0);
902
+ const description = options.description ?? `Replay of a recorded browser session (${session.pages.length} pages, ${totalInteractions} interactions).`;
903
+ const lines = [];
904
+ lines.push("---");
905
+ lines.push(`name: ${name}`);
906
+ lines.push(`description: ${description}`);
907
+ lines.push("allowed-tools: mcp__crawlio-browser__search, mcp__crawlio-browser__execute, mcp__crawlio-browser__connect_tab");
908
+ lines.push("---");
909
+ lines.push("");
910
+ const title = options.name.trim() || "Unnamed Skill";
911
+ lines.push(`# ${title}`);
912
+ lines.push("");
913
+ lines.push(`Replay of a recorded browser session (${session.pages.length} pages, ${totalInteractions} interactions).`);
914
+ lines.push("");
915
+ lines.push("## Prerequisites");
916
+ lines.push("");
917
+ lines.push("Connect to a browser tab before running:");
918
+ lines.push("");
919
+ lines.push("```");
920
+ lines.push(`connect_tab({ url: "${escapeString(session.metadata.initialUrl)}" })`);
921
+ lines.push("```");
922
+ lines.push("");
923
+ for (let i = 0; i < session.pages.length; i++) {
924
+ const page = session.pages[i];
925
+ const pageLabel = page.title || page.url;
926
+ lines.push(`## Page ${i + 1}: ${pageLabel}`);
927
+ lines.push("");
928
+ lines.push("```js");
929
+ for (const interaction of page.interactions) {
930
+ lines.push(compileInteraction(interaction));
931
+ }
932
+ lines.push("```");
933
+ lines.push("");
934
+ if (i < session.pages.length - 1) {
935
+ lines.push("```js");
936
+ lines.push("await sleep(1000)");
937
+ lines.push("await smart.snapshot()");
938
+ lines.push("```");
939
+ lines.push("");
940
+ }
941
+ }
942
+ lines.push("## Session Info");
943
+ lines.push("");
944
+ lines.push(`- **Recorded**: ${session.startedAt}`);
945
+ lines.push(`- **Duration**: ${session.duration}s`);
946
+ lines.push(`- **Stop reason**: ${session.metadata.stopReason}`);
947
+ lines.push("");
948
+ return {
949
+ skillMarkdown: lines.join("\n"),
950
+ name,
951
+ pageCount: session.pages.length,
952
+ interactionCount: totalInteractions
953
+ };
954
+ }
955
+ var STRIP_KEYS = /* @__PURE__ */ new Set(["type", "id", "_internal"]);
956
+ function filterArgs(args) {
957
+ const filtered = {};
958
+ for (const [k, v] of Object.entries(args)) {
959
+ if (!STRIP_KEYS.has(k)) filtered[k] = v;
960
+ }
961
+ return filtered;
962
+ }
963
+ function resolveSelector(args) {
964
+ const sel = args.selector ?? args.ref;
965
+ return typeof sel === "string" ? sel : void 0;
966
+ }
967
+ function compileInteraction(interaction) {
968
+ const args = filterArgs(interaction.args);
969
+ const tool = interaction.tool;
970
+ switch (tool) {
971
+ case "browser_navigate": {
972
+ const url = args.url ?? "";
973
+ return `await smart.navigate(${JSON.stringify(url)})`;
974
+ }
975
+ case "browser_click": {
976
+ const sel = resolveSelector(args) ?? "";
977
+ return `await smart.click(${JSON.stringify(sel)})`;
978
+ }
979
+ case "browser_type": {
980
+ const sel = resolveSelector(args) ?? "";
981
+ const text = args.text ?? "";
982
+ if (args.clearFirst) {
983
+ return `await smart.type(${JSON.stringify(sel)}, ${JSON.stringify(text)}, { clearFirst: true })`;
984
+ }
985
+ return `await smart.type(${JSON.stringify(sel)}, ${JSON.stringify(text)})`;
986
+ }
987
+ case "browser_evaluate": {
988
+ const expr = args.expression ?? "";
989
+ return `await smart.evaluate(${JSON.stringify(expr)})`;
990
+ }
991
+ case "browser_press_key": {
992
+ const bridgeArgs = { type: "browser_press_key", ...args };
993
+ return `await bridge.send(${JSON.stringify(bridgeArgs)})`;
994
+ }
995
+ case "browser_hover": {
996
+ const sel = resolveSelector(args);
997
+ const bridgeArgs = { type: "browser_hover", ...args };
998
+ if (sel) {
999
+ return `await smart.waitFor(${JSON.stringify(sel)})
1000
+ await bridge.send(${JSON.stringify(bridgeArgs)})`;
1001
+ }
1002
+ return `await bridge.send(${JSON.stringify(bridgeArgs)})`;
1003
+ }
1004
+ case "browser_select_option": {
1005
+ const sel = resolveSelector(args);
1006
+ const bridgeArgs = { type: "browser_select_option", ...args };
1007
+ if (sel) {
1008
+ return `await smart.waitFor(${JSON.stringify(sel)})
1009
+ await bridge.send(${JSON.stringify(bridgeArgs)})`;
1010
+ }
1011
+ return `await bridge.send(${JSON.stringify(bridgeArgs)})`;
1012
+ }
1013
+ case "browser_scroll": {
1014
+ const bridgeArgs = { type: "browser_scroll", ...args };
1015
+ return `await bridge.send(${JSON.stringify(bridgeArgs)})`;
1016
+ }
1017
+ case "browser_double_click": {
1018
+ const sel = resolveSelector(args);
1019
+ const bridgeArgs = { type: "browser_double_click", ...args };
1020
+ if (sel) {
1021
+ return `await smart.waitFor(${JSON.stringify(sel)})
1022
+ await bridge.send(${JSON.stringify(bridgeArgs)})`;
1023
+ }
1024
+ return `await bridge.send(${JSON.stringify(bridgeArgs)})`;
1025
+ }
1026
+ case "browser_drag": {
1027
+ const bridgeArgs = { type: "browser_drag", ...args };
1028
+ return `await bridge.send(${JSON.stringify(bridgeArgs)})`;
1029
+ }
1030
+ case "browser_fill_form": {
1031
+ const bridgeArgs = { type: "browser_fill_form", ...args };
1032
+ return `await bridge.send(${JSON.stringify(bridgeArgs)})`;
1033
+ }
1034
+ case "browser_file_upload": {
1035
+ const sel = resolveSelector(args);
1036
+ const bridgeArgs = { type: "browser_file_upload", ...args };
1037
+ if (sel) {
1038
+ return `await smart.waitFor(${JSON.stringify(sel)})
1039
+ await bridge.send(${JSON.stringify(bridgeArgs)})`;
1040
+ }
1041
+ return `await bridge.send(${JSON.stringify(bridgeArgs)})`;
1042
+ }
1043
+ // User interaction tools (captured from manual browser events)
1044
+ case "user_click": {
1045
+ const sel = args.selector ?? "";
1046
+ return `await smart.click(${JSON.stringify(sel)})`;
1047
+ }
1048
+ case "user_type": {
1049
+ const sel = args.selector ?? "";
1050
+ const text = args.text ?? "";
1051
+ return `await smart.type(${JSON.stringify(sel)}, ${JSON.stringify(text)})`;
1052
+ }
1053
+ case "user_keypress": {
1054
+ const key = args.key ?? "";
1055
+ return `await bridge.send(${JSON.stringify({ type: "browser_press_key", key })})`;
1056
+ }
1057
+ default:
1058
+ return `// Unknown tool: ${tool}`;
1059
+ }
1060
+ }
1061
+ function sanitizeSkillName(raw) {
1062
+ let name = raw.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
1063
+ if (name.length > 50) name = name.slice(0, 50).replace(/-+$/, "");
1064
+ if (!name) name = "unnamed-skill";
1065
+ return name;
1066
+ }
1067
+ function escapeString(s) {
1068
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1069
+ }
1070
+
1071
+ // src/mcp-server/semantic-search.ts
1072
+ import { readFileSync } from "fs";
1073
+ import { join, dirname as dirname2 } from "path";
1074
+ import { fileURLToPath as fileURLToPath2 } from "url";
1075
+ function cosineSimilarity(a, b) {
1076
+ if (a.length !== b.length || a.length === 0) return 0;
1077
+ let dot = 0;
1078
+ let normA = 0;
1079
+ let normB = 0;
1080
+ for (let i = 0; i < a.length; i++) {
1081
+ dot += a[i] * b[i];
1082
+ normA += a[i] * a[i];
1083
+ normB += b[i] * b[i];
1084
+ }
1085
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
1086
+ if (denom === 0) return 0;
1087
+ return dot / denom;
1088
+ }
1089
+ function loadEmbeddings() {
1090
+ const map = /* @__PURE__ */ new Map();
1091
+ try {
1092
+ const thisDir = dirname2(fileURLToPath2(import.meta.url));
1093
+ const assetPath = join(thisDir, "tool-embeddings.json");
1094
+ const raw = readFileSync(assetPath, "utf-8");
1095
+ const asset = JSON.parse(raw);
1096
+ if (!asset.embeddings || typeof asset.embeddings !== "object") return map;
1097
+ for (const [name, vec] of Object.entries(asset.embeddings)) {
1098
+ if (Array.isArray(vec) && vec.length > 0) {
1099
+ map.set(name, new Float32Array(vec));
1100
+ }
1101
+ }
1102
+ } catch {
1103
+ }
1104
+ return map;
1105
+ }
1106
+ function semanticSearch(queryEmbedding, embeddings, limit) {
1107
+ const results = [];
1108
+ for (const [name, vec] of embeddings) {
1109
+ const score = cosineSimilarity(queryEmbedding, vec);
1110
+ results.push({ name, score });
1111
+ }
1112
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
1113
+ }
1114
+ function buildQueryEmbedding(query, catalog, embeddings) {
1115
+ const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
1116
+ if (tokens.length === 0 || embeddings.size === 0) return null;
1117
+ const matchedVectors = [];
1118
+ for (const token of tokens) {
1119
+ let bestName = "";
1120
+ let bestScore = 0;
1121
+ for (const entry of catalog) {
1122
+ if (!embeddings.has(entry.name)) continue;
1123
+ let score = 0;
1124
+ const nameLower = entry.name.toLowerCase();
1125
+ const descLower = entry.description.toLowerCase();
1126
+ if (nameLower === token) score = 10;
1127
+ else if (nameLower.includes(token)) score = 5;
1128
+ if (descLower.includes(token)) score += 2;
1129
+ if (score > bestScore) {
1130
+ bestScore = score;
1131
+ bestName = entry.name;
1132
+ }
1133
+ }
1134
+ if (bestName && bestScore > 0) {
1135
+ const vec = embeddings.get(bestName);
1136
+ if (vec) matchedVectors.push(vec);
1137
+ }
1138
+ }
1139
+ if (matchedVectors.length === 0) return null;
1140
+ const dim = matchedVectors[0].length;
1141
+ const avg = new Float32Array(dim);
1142
+ for (const vec of matchedVectors) {
1143
+ for (let i = 0; i < dim; i++) {
1144
+ avg[i] += vec[i];
1145
+ }
1146
+ }
1147
+ const count = matchedVectors.length;
1148
+ for (let i = 0; i < dim; i++) {
1149
+ avg[i] /= count;
1150
+ }
1151
+ return avg;
1152
+ }
1153
+ function normalizeScores(scores) {
1154
+ const normalized = /* @__PURE__ */ new Map();
1155
+ if (scores.size === 0) return normalized;
1156
+ let min = Infinity;
1157
+ let max = -Infinity;
1158
+ for (const s of scores.values()) {
1159
+ if (s < min) min = s;
1160
+ if (s > max) max = s;
1161
+ }
1162
+ const range = max - min;
1163
+ for (const [name, score] of scores) {
1164
+ normalized.set(name, range === 0 ? max > 0 ? 1 : 0 : (score - min) / range);
1165
+ }
1166
+ return normalized;
1167
+ }
1168
+
1169
+ // src/mcp-server/tools.ts
1170
+ var execFileAsync = promisify(execFile);
3086
1171
  var urlSchema = z.string().url().refine(
3087
1172
  (url) => {
3088
1173
  try {
@@ -3110,10 +1195,12 @@ var PERMISSION_EXEMPT_TOOLS = /* @__PURE__ */ new Set([
3110
1195
  "check_permissions",
3111
1196
  "request_permissions",
3112
1197
  "get_connection_status",
1198
+ "get_recording_status",
3113
1199
  "connect_tab",
3114
1200
  "list_tabs",
3115
1201
  "search",
3116
- "execute"
1202
+ "execute",
1203
+ "compile_recording"
3117
1204
  ]);
3118
1205
  async function ensurePermission(bridge2, toolName) {
3119
1206
  try {
@@ -3189,6 +1276,7 @@ var TOOL_TIMEOUTS = {
3189
1276
  get_dialog: 5e3,
3190
1277
  handle_dialog: 5e3,
3191
1278
  get_response_body: 5e3,
1279
+ replay_request: 3e4,
3192
1280
  set_viewport: 5e3,
3193
1281
  set_user_agent: 5e3,
3194
1282
  emulate_device: 1e4,
@@ -3236,10 +1324,15 @@ var TOOL_TIMEOUTS = {
3236
1324
  take_heap_snapshot: 3e4,
3237
1325
  highlight_element: 1e4,
3238
1326
  show_layout_shifts: 5e3,
3239
- show_paint_rects: 5e3
1327
+ show_paint_rects: 5e3,
1328
+ start_recording: 1e4,
1329
+ stop_recording: 1e4,
1330
+ get_recording_status: 5e3,
1331
+ compile_recording: 5e3,
1332
+ ocr_screenshot: 3e4
3240
1333
  };
3241
1334
  function toolSuccess(content) {
3242
- return { content: [{ type: "text", text: JSON.stringify(content ?? {}, null, 2) }], isError: false };
1335
+ return { content: [{ type: "text", text: JSON.stringify(content ?? {}) }], isError: false };
3243
1336
  }
3244
1337
  function toolError(message) {
3245
1338
  return { content: [{ type: "text", text: message }], isError: true };
@@ -3249,6 +1342,7 @@ var smartObjectCache = null;
3249
1342
  async function checkActionability(bridge2, selector) {
3250
1343
  const result = await bridge2.send({
3251
1344
  type: "browser_evaluate",
1345
+ _internal: true,
3252
1346
  expression: `(() => {
3253
1347
  const el = document.querySelector(${JSON.stringify(selector)});
3254
1348
  if (!el) return { actionable: false, reason: 'Element not found' };
@@ -3344,7 +1438,7 @@ function createTools(bridge2, crawlio2) {
3344
1438
  if (parsed.url) params.url = parsed.url;
3345
1439
  if (parsed.tabId) params.tabId = parsed.tabId;
3346
1440
  const data = await bridge2.send(params, TOOL_TIMEOUTS.connect_tab);
3347
- return toolSuccess(data);
1441
+ return toolSuccess(shapeConnectTab(data));
3348
1442
  }
3349
1443
  },
3350
1444
  {
@@ -3362,7 +1456,7 @@ function createTools(bridge2, crawlio2) {
3362
1456
  inputSchema: { type: "object", properties: {} },
3363
1457
  handler: async () => {
3364
1458
  const data = await bridge2.send({ type: "list_tabs" }, TOOL_TIMEOUTS.list_tabs);
3365
- return toolSuccess(data);
1459
+ return toolSuccess(shapeListTabs(data));
3366
1460
  }
3367
1461
  },
3368
1462
  {
@@ -3418,7 +1512,7 @@ function createTools(bridge2, crawlio2) {
3418
1512
  console.error("[Tools] Failed to send to Crawlio:", e);
3419
1513
  }
3420
1514
  }
3421
- return toolSuccess(data);
1515
+ return toolSuccess(shapeCapturePage(data));
3422
1516
  }
3423
1517
  },
3424
1518
  {
@@ -3445,7 +1539,7 @@ function createTools(bridge2, crawlio2) {
3445
1539
  inputSchema: { type: "object", properties: {} },
3446
1540
  handler: async () => {
3447
1541
  const data = await bridge2.send({ type: "stop_network_capture" }, TOOL_TIMEOUTS.stop_network_capture);
3448
- return toolSuccess(data);
1542
+ return toolSuccess(shapeNetworkLog(data));
3449
1543
  }
3450
1544
  },
3451
1545
  {
@@ -3454,7 +1548,7 @@ function createTools(bridge2, crawlio2) {
3454
1548
  inputSchema: { type: "object", properties: {} },
3455
1549
  handler: async () => {
3456
1550
  const data = await bridge2.send({ type: "get_console_logs" }, TOOL_TIMEOUTS.get_console_logs);
3457
- return toolSuccess(data);
1551
+ return toolSuccess(shapeConsoleLogs(data));
3458
1552
  }
3459
1553
  },
3460
1554
  {
@@ -3464,7 +1558,7 @@ function createTools(bridge2, crawlio2) {
3464
1558
  handler: async () => {
3465
1559
  try {
3466
1560
  const data = await bridge2.send({ type: "get_cookies" }, TOOL_TIMEOUTS.get_cookies);
3467
- return toolSuccess(data);
1561
+ return toolSuccess(shapeCookies(data));
3468
1562
  } catch (e) {
3469
1563
  const msg = e instanceof Error ? e.message : String(e);
3470
1564
  return toolError(`get_cookies failed: ${msg}. CDP domain: Network. Suggestion: use reconnect_tab to re-establish the CDP connection.`);
@@ -3538,6 +1632,11 @@ function createTools(bridge2, crawlio2) {
3538
1632
  handler: async (args) => {
3539
1633
  const schema = z.object({ url: urlSchema.optional() });
3540
1634
  const parsed = schema.parse(args);
1635
+ try {
1636
+ const bridgeData = await bridge2.send({ type: "get_enrichment", url: parsed.url }, 5e3);
1637
+ if (bridgeData) return toolSuccess(bridgeData);
1638
+ } catch {
1639
+ }
3541
1640
  const data = await crawlio2.getEnrichment(parsed.url);
3542
1641
  return toolSuccess(data);
3543
1642
  }
@@ -3683,7 +1782,7 @@ function createTools(bridge2, crawlio2) {
3683
1782
  button: parsed.button,
3684
1783
  modifiers: parsed.modifiers
3685
1784
  }, TOOL_TIMEOUTS.browser_click);
3686
- return toolSuccess(result);
1785
+ return toolSuccess(shapeInteraction(result));
3687
1786
  }
3688
1787
  )
3689
1788
  },
@@ -3736,7 +1835,7 @@ function createTools(bridge2, crawlio2) {
3736
1835
  submit: parsed.submit,
3737
1836
  modifiers: parsed.modifiers
3738
1837
  }, TOOL_TIMEOUTS.browser_type);
3739
- return toolSuccess(result);
1838
+ return toolSuccess(shapeInteraction(result));
3740
1839
  }
3741
1840
  )
3742
1841
  },
@@ -3771,7 +1870,7 @@ function createTools(bridge2, crawlio2) {
3771
1870
  key: parsed.key,
3772
1871
  modifiers: parsed.modifiers
3773
1872
  }, TOOL_TIMEOUTS.browser_press_key);
3774
- return toolSuccess(result);
1873
+ return toolSuccess(shapeInteraction(result));
3775
1874
  }
3776
1875
  },
3777
1876
  {
@@ -3807,7 +1906,7 @@ function createTools(bridge2, crawlio2) {
3807
1906
  selector: parsed.selector,
3808
1907
  modifiers: parsed.modifiers
3809
1908
  }, TOOL_TIMEOUTS.browser_hover);
3810
- return toolSuccess(result);
1909
+ return toolSuccess(shapeInteraction(result));
3811
1910
  }
3812
1911
  },
3813
1912
  {
@@ -3838,7 +1937,7 @@ function createTools(bridge2, crawlio2) {
3838
1937
  selector: parsed.selector,
3839
1938
  value: parsed.value
3840
1939
  }, TOOL_TIMEOUTS.browser_select_option);
3841
- return toolSuccess(result);
1940
+ return toolSuccess(shapeInteraction(result));
3842
1941
  }
3843
1942
  )
3844
1943
  },
@@ -3894,7 +1993,7 @@ function createTools(bridge2, crawlio2) {
3894
1993
  type: "browser_fill_form",
3895
1994
  fields: parsed.fields
3896
1995
  }, TOOL_TIMEOUTS.browser_fill_form ?? 15e3);
3897
- return toolSuccess(result);
1996
+ return toolSuccess(shapeInteraction(result));
3898
1997
  }
3899
1998
  },
3900
1999
  {
@@ -4316,6 +2415,41 @@ function createTools(bridge2, crawlio2) {
4316
2415
  return toolSuccess(data);
4317
2416
  }
4318
2417
  },
2418
+ // Network replay (PiecesOS Heist Ph3)
2419
+ {
2420
+ name: "replay_request",
2421
+ description: "Re-fire a previously captured network request with optional modifications. Requires active network capture. Specify the request by URL. Optionally override headers, body, or method. Returns the new response status, headers, and body. Useful for API testing, auth token replay, and form submission testing.",
2422
+ inputSchema: {
2423
+ type: "object",
2424
+ properties: {
2425
+ url: { type: "string", description: "URL of the captured request to replay" },
2426
+ method: { type: "string", enum: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"], description: "Override HTTP method" },
2427
+ headers: { type: "object", description: "Override or add headers (merged with original captured headers)" },
2428
+ body: { type: "string", description: "Override request body (for POST/PUT)" },
2429
+ followRedirects: { type: "boolean", description: "Follow 3xx redirects (default: true)" }
2430
+ },
2431
+ required: ["url"]
2432
+ },
2433
+ handler: async (args) => {
2434
+ const schema = z.object({
2435
+ url: z.string().url(),
2436
+ method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]).optional(),
2437
+ headers: z.record(z.string()).optional(),
2438
+ body: z.string().optional(),
2439
+ followRedirects: z.boolean().optional()
2440
+ });
2441
+ const parsed = schema.parse(args);
2442
+ const data = await bridge2.send({
2443
+ type: "replay_request",
2444
+ url: parsed.url,
2445
+ method: parsed.method,
2446
+ headers: parsed.headers,
2447
+ body: parsed.body,
2448
+ followRedirects: parsed.followRedirects
2449
+ }, TOOL_TIMEOUTS.replay_request);
2450
+ return toolSuccess(data);
2451
+ }
2452
+ },
4319
2453
  // AC-8: Viewport & device emulation
4320
2454
  {
4321
2455
  name: "set_viewport",
@@ -4460,7 +2594,7 @@ function createTools(bridge2, crawlio2) {
4460
2594
  type: "browser_scroll",
4461
2595
  ...parsed
4462
2596
  }, TOOL_TIMEOUTS.browser_scroll);
4463
- return toolSuccess(result);
2597
+ return toolSuccess(shapeInteraction(result));
4464
2598
  }
4465
2599
  },
4466
2600
  {
@@ -4484,7 +2618,7 @@ function createTools(bridge2, crawlio2) {
4484
2618
  ref: parsed.ref,
4485
2619
  selector: parsed.selector
4486
2620
  }, TOOL_TIMEOUTS.browser_double_click);
4487
- return toolSuccess(result);
2621
+ return toolSuccess(shapeInteraction(result));
4488
2622
  }
4489
2623
  },
4490
2624
  {
@@ -4517,7 +2651,7 @@ function createTools(bridge2, crawlio2) {
4517
2651
  to: parsed.to,
4518
2652
  steps: parsed.steps
4519
2653
  }, TOOL_TIMEOUTS.browser_drag);
4520
- return toolSuccess(result);
2654
+ return toolSuccess(shapeInteraction(result));
4521
2655
  }
4522
2656
  },
4523
2657
  {
@@ -4546,7 +2680,7 @@ function createTools(bridge2, crawlio2) {
4546
2680
  selector: parsed.selector,
4547
2681
  files: parsed.files
4548
2682
  }, TOOL_TIMEOUTS.browser_file_upload);
4549
- return toolSuccess(result);
2683
+ return toolSuccess(shapeInteraction(result));
4550
2684
  }
4551
2685
  },
4552
2686
  // AC-13: Accessibility tree
@@ -5306,6 +3440,190 @@ function createTools(bridge2, crawlio2) {
5306
3440
  }, TOOL_TIMEOUTS.show_paint_rects);
5307
3441
  return toolSuccess(data);
5308
3442
  }
3443
+ },
3444
+ // --- Session Recording tools ---
3445
+ {
3446
+ name: "start_recording",
3447
+ description: "Start recording a browser session. Captures all tool interactions, page navigations, network requests, and console logs organized by page. Recording auto-stops after maxDurationSec (default 600s) or maxInteractions (default 500). A tab must be connected first.",
3448
+ inputSchema: {
3449
+ type: "object",
3450
+ properties: {
3451
+ maxDurationSec: { type: "number", description: "Max recording duration in seconds (10-600, default: 600)" },
3452
+ maxInteractions: { type: "number", description: "Max interaction count before auto-stop (1-500, default: 500)" }
3453
+ }
3454
+ },
3455
+ handler: async (args) => {
3456
+ const schema = z.object({
3457
+ maxDurationSec: z.number().min(10).max(600).optional(),
3458
+ maxInteractions: z.number().min(1).max(500).optional()
3459
+ });
3460
+ const parsed = schema.parse(args);
3461
+ try {
3462
+ const data = await bridge2.send({
3463
+ type: "start_recording",
3464
+ maxDurationSec: parsed.maxDurationSec,
3465
+ maxInteractions: parsed.maxInteractions
3466
+ }, TOOL_TIMEOUTS.start_recording);
3467
+ return toolSuccess(data);
3468
+ } catch (e) {
3469
+ const msg = e instanceof Error ? e.message : String(e);
3470
+ return toolError(`start_recording failed: ${msg}. Suggestion: ensure a tab is connected via connect_tab first.`);
3471
+ }
3472
+ }
3473
+ },
3474
+ {
3475
+ name: "stop_recording",
3476
+ description: "Stop the active recording session and return the complete session data. Returns a RecordingSession JSON with all pages, interactions (tool calls with args/results/timing), network requests, and console logs partitioned by page. If the recording was auto-stopped (duration/interaction limit/tab closed), returns that session.",
3477
+ inputSchema: {
3478
+ type: "object",
3479
+ properties: {}
3480
+ },
3481
+ handler: async () => {
3482
+ try {
3483
+ const data = await bridge2.send({ type: "stop_recording" }, TOOL_TIMEOUTS.stop_recording);
3484
+ return toolSuccess(data);
3485
+ } catch (e) {
3486
+ const msg = e instanceof Error ? e.message : String(e);
3487
+ return toolError(`stop_recording failed: ${msg}`);
3488
+ }
3489
+ }
3490
+ },
3491
+ {
3492
+ name: "get_recording_status",
3493
+ description: "Check the current recording status. Returns whether a recording is active, session ID, duration, page count, interaction count, and current page URL.",
3494
+ inputSchema: {
3495
+ type: "object",
3496
+ properties: {}
3497
+ },
3498
+ handler: async () => {
3499
+ try {
3500
+ const data = await bridge2.send({ type: "get_recording_status" }, TOOL_TIMEOUTS.get_recording_status);
3501
+ return toolSuccess(data);
3502
+ } catch (e) {
3503
+ const msg = e instanceof Error ? e.message : String(e);
3504
+ return toolError(`get_recording_status failed: ${msg}`);
3505
+ }
3506
+ }
3507
+ },
3508
+ {
3509
+ name: "compile_recording",
3510
+ description: "Compile a recording session into a replayable SKILL.md markdown file. Takes the session JSON from stop_recording output and produces executable skill markdown with smart.* and bridge.send() calls. Pure computation \u2014 no browser access required.",
3511
+ inputSchema: {
3512
+ type: "object",
3513
+ properties: {
3514
+ session: {
3515
+ type: "object",
3516
+ description: "The RecordingSession object returned by stop_recording"
3517
+ },
3518
+ name: {
3519
+ type: "string",
3520
+ description: "Name for the skill (will be kebab-cased)"
3521
+ },
3522
+ description: {
3523
+ type: "string",
3524
+ description: "Optional description for the skill frontmatter"
3525
+ }
3526
+ },
3527
+ required: ["session", "name"]
3528
+ },
3529
+ handler: async (args) => {
3530
+ const schema = z.object({
3531
+ session: z.object({
3532
+ id: z.string(),
3533
+ startedAt: z.string(),
3534
+ stoppedAt: z.string().optional(),
3535
+ duration: z.number(),
3536
+ pages: z.array(z.object({
3537
+ url: z.string(),
3538
+ title: z.string().optional(),
3539
+ enteredAt: z.string(),
3540
+ interactions: z.array(z.any())
3541
+ }).passthrough()),
3542
+ metadata: z.object({
3543
+ tabId: z.number(),
3544
+ initialUrl: z.string(),
3545
+ stopReason: z.string()
3546
+ }).passthrough()
3547
+ }).passthrough(),
3548
+ name: z.string().min(1),
3549
+ description: z.string().optional()
3550
+ });
3551
+ const parsed = schema.safeParse(args);
3552
+ if (!parsed.success) {
3553
+ return toolError(`Invalid arguments: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
3554
+ }
3555
+ try {
3556
+ const result = compileRecording(
3557
+ parsed.data.session,
3558
+ { name: parsed.data.name, description: parsed.data.description }
3559
+ );
3560
+ return toolSuccess(result);
3561
+ } catch (e) {
3562
+ const msg = e instanceof Error ? e.message : String(e);
3563
+ return toolError(`compile_recording failed: ${msg}`);
3564
+ }
3565
+ }
3566
+ },
3567
+ {
3568
+ name: "ocr_screenshot",
3569
+ description: "Extract text from the current page using macOS Vision.framework OCR. Takes a screenshot via CDP, runs native text recognition (VNRecognizeTextRequest), and returns recognized text with confidence scores and bounding regions. Works on canvas elements, images, anti-scraping sites, and any visual content invisible to DOM extraction.",
3570
+ inputSchema: {
3571
+ type: "object",
3572
+ properties: {
3573
+ fullPage: { type: "boolean", description: "Capture full scrollable page (default: viewport only)" },
3574
+ selector: { type: "string", description: "CSS selector \u2014 screenshot only this element" }
3575
+ }
3576
+ },
3577
+ handler: async (args) => {
3578
+ if (process.platform !== "darwin") {
3579
+ return toolError("ocr_screenshot requires macOS (Vision.framework). On other platforms, use browser_evaluate with a JS OCR library.");
3580
+ }
3581
+ const schema = z.object({
3582
+ fullPage: z.boolean().default(false),
3583
+ selector: selectorSchema.optional()
3584
+ });
3585
+ const parsed = schema.parse(args);
3586
+ const screenshotParams = { type: "take_screenshot" };
3587
+ if (parsed.fullPage) screenshotParams.fullPage = true;
3588
+ if (parsed.selector) screenshotParams.selector = parsed.selector;
3589
+ let tmpPath = "";
3590
+ try {
3591
+ const data = await bridge2.send(screenshotParams, TOOL_TIMEOUTS.ocr_screenshot);
3592
+ if (!data?.data) {
3593
+ return toolError("Screenshot capture failed \u2014 no image data returned");
3594
+ }
3595
+ tmpPath = join2(tmpdir(), `crawlio-ocr-${randomBytes(6).toString("hex")}.png`);
3596
+ await writeFile(tmpPath, Buffer.from(data.data, "base64"));
3597
+ const shimPath = join2(dirname3(fileURLToPath3(import.meta.url)), "..", "..", "bin", "ocr-shim.swift");
3598
+ const { stdout, stderr } = await execFileAsync("swift", [shimPath, tmpPath], { timeout: 25e3 });
3599
+ if (!stdout.trim()) {
3600
+ return toolError(`OCR produced no output${stderr ? `: ${stderr.trim()}` : ""}`);
3601
+ }
3602
+ const result = JSON.parse(stdout.trim());
3603
+ if (result.error) {
3604
+ return toolError(`OCR failed: ${result.error}`);
3605
+ }
3606
+ if (result.regions && result.regions.length > 20) {
3607
+ result.regions = result.regions.sort((a, b) => b.confidence - a.confidence).slice(0, 20);
3608
+ result.regionsLimited = true;
3609
+ }
3610
+ return toolSuccess(result);
3611
+ } catch (e) {
3612
+ const execErr = e;
3613
+ if (execErr?.stderr) {
3614
+ try {
3615
+ const parsed2 = JSON.parse(execErr.stderr.trim());
3616
+ if (parsed2.error) return toolError(`OCR failed: ${parsed2.error}`);
3617
+ } catch {
3618
+ }
3619
+ }
3620
+ const msg = e instanceof Error ? e.message : String(e);
3621
+ return toolError(`OCR failed: ${msg}`);
3622
+ } finally {
3623
+ if (tmpPath) unlink(tmpPath).catch(() => {
3624
+ });
3625
+ }
3626
+ }
5309
3627
  }
5310
3628
  ];
5311
3629
  }
@@ -5514,17 +3832,36 @@ function buildCatalog(tools2) {
5514
3832
  }));
5515
3833
  return [...browserCatalog, ...crawlioHTTPCatalog];
5516
3834
  }
5517
- function searchCatalog(catalog, query, limit) {
3835
+ function searchCatalog(catalog, query, limit, embeddings) {
5518
3836
  const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
5519
3837
  if (tokens.length === 0) return catalog.slice(0, limit);
5520
- const scored = catalog.map((entry) => {
5521
- const haystack = `${entry.name} ${entry.description}`.toLowerCase();
3838
+ const keywordScores = /* @__PURE__ */ new Map();
3839
+ for (const entry of catalog) {
5522
3840
  let score = 0;
5523
3841
  for (const token of tokens) {
5524
3842
  if (entry.name.toLowerCase() === token) score += 10;
5525
3843
  else if (entry.name.toLowerCase().includes(token)) score += 5;
5526
3844
  if (entry.description.toLowerCase().includes(token)) score += 2;
5527
3845
  }
3846
+ keywordScores.set(entry.name, score);
3847
+ }
3848
+ const semanticScores = /* @__PURE__ */ new Map();
3849
+ if (embeddings && embeddings.size > 0) {
3850
+ const queryVec = buildQueryEmbedding(query, catalog, embeddings);
3851
+ if (queryVec) {
3852
+ const results = semanticSearch(queryVec, embeddings, embeddings.size);
3853
+ for (const r of results) {
3854
+ semanticScores.set(r.name, r.score);
3855
+ }
3856
+ }
3857
+ }
3858
+ const hasSemanticScores = semanticScores.size > 0;
3859
+ const normalizedKeyword = normalizeScores(keywordScores);
3860
+ const normalizedSemantic = hasSemanticScores ? normalizeScores(semanticScores) : /* @__PURE__ */ new Map();
3861
+ const scored = catalog.map((entry) => {
3862
+ const kw = normalizedKeyword.get(entry.name) ?? 0;
3863
+ const sem = normalizedSemantic.get(entry.name) ?? 0;
3864
+ const score = hasSemanticScores ? 0.6 * sem + 0.4 * kw : kw;
5528
3865
  return { entry, score };
5529
3866
  });
5530
3867
  return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => s.entry);
@@ -5532,6 +3869,7 @@ function searchCatalog(catalog, query, limit) {
5532
3869
  function createCodeModeTools(bridge2, crawlio2) {
5533
3870
  const allTools = createTools(bridge2, crawlio2);
5534
3871
  const catalog = buildCatalog(allTools);
3872
+ const embeddings = loadEmbeddings();
5535
3873
  const connectTab = allTools.find((t) => t.name === "connect_tab");
5536
3874
  return [
5537
3875
  // --- search: discover available commands ---
@@ -5552,7 +3890,7 @@ function createCodeModeTools(bridge2, crawlio2) {
5552
3890
  limit: z.number().int().min(1).max(50).default(10)
5553
3891
  });
5554
3892
  const parsed = schema.parse(args);
5555
- const results = searchCatalog(catalog, parsed.query, parsed.limit);
3893
+ const results = searchCatalog(catalog, parsed.query, parsed.limit, embeddings);
5556
3894
  return toolSuccess(results);
5557
3895
  }
5558
3896
  },
@@ -5579,6 +3917,8 @@ function createCodeModeTools(bridge2, crawlio2) {
5579
3917
  "- crawlio.postEnrichment(url, data) \u2014 shortcut for POST /enrichment/bundle",
5580
3918
  "- sleep(ms) \u2014 async wait (max 30s)",
5581
3919
  "- TIMEOUTS \u2014 per-command timeout constants",
3920
+ "- compileRecording(session, { name, description? }) \u2014 compile RecordingSession to SKILL.md",
3921
+ " Returns { skillMarkdown, name, pageCount, interactionCount }",
5582
3922
  "- smart \u2014 auto-waiting wrappers and framework-specific data accessors:",
5583
3923
  " smart.evaluate(expr) \u2014 raw JS evaluation via CDP",
5584
3924
  " smart.click(selector, opts?) \u2014 poll + click + 500ms settle",
@@ -5616,12 +3956,19 @@ function createCodeModeTools(bridge2, crawlio2) {
5616
3956
  "",
5617
3957
  "Example (smart \u2014 framework data):",
5618
3958
  " const nextData = await smart.nextjs?.getData();",
5619
- " return { page: nextData?.page, buildId: nextData?.buildId };"
3959
+ " return { page: nextData?.page, buildId: nextData?.buildId };",
3960
+ "",
3961
+ "Example (session recording + compile):",
3962
+ " const s = await bridge.send({ type: 'start_recording', maxDurationSec: 120 });",
3963
+ " // ... interact with page ...",
3964
+ " const session = await bridge.send({ type: 'stop_recording' });",
3965
+ " const skill = compileRecording(session, { name: 'my-flow' });",
3966
+ " return skill;"
5620
3967
  ].join("\n"),
5621
3968
  inputSchema: {
5622
3969
  type: "object",
5623
3970
  properties: {
5624
- code: { type: "string", description: "Async JavaScript function body. Has bridge, crawlio, sleep, TIMEOUTS, smart in scope. Must return a value." }
3971
+ code: { type: "string", description: "Async JavaScript function body. Has bridge, crawlio, sleep, TIMEOUTS, smart, compileRecording in scope. Must return a value." }
5625
3972
  },
5626
3973
  required: ["code"]
5627
3974
  },
@@ -5645,7 +3992,7 @@ function createCodeModeTools(bridge2, crawlio2) {
5645
3992
  }
5646
3993
  let fn;
5647
3994
  try {
5648
- fn = new AsyncFunction("bridge", "crawlio", "sleep", "TIMEOUTS", "smart", code);
3995
+ fn = new AsyncFunction("bridge", "crawlio", "sleep", "TIMEOUTS", "smart", "compileRecording", code);
5649
3996
  } catch (syntaxError) {
5650
3997
  return toolError(`Syntax error in code: ${syntaxError instanceof Error ? syntaxError.message : String(syntaxError)}`);
5651
3998
  }
@@ -5653,7 +4000,7 @@ function createCodeModeTools(bridge2, crawlio2) {
5653
4000
  const timer = setTimeout(() => controller.abort(), 12e4);
5654
4001
  try {
5655
4002
  const result = await Promise.race([
5656
- fn(bridge2, crawlio2, sleepFn, TOOL_TIMEOUTS, smart),
4003
+ fn(bridge2, crawlio2, sleepFn, TOOL_TIMEOUTS, smart, compileRecording),
5657
4004
  new Promise((_, reject) => {
5658
4005
  controller.signal.addEventListener(
5659
4006
  "abort",
@@ -5678,7 +4025,7 @@ function createCodeModeTools(bridge2, crawlio2) {
5678
4025
  // src/mcp-server/index.ts
5679
4026
  var initMode = process.argv.includes("init") || process.argv.includes("--setup") || process.argv.includes("setup");
5680
4027
  if (initMode) {
5681
- const { runInit } = await import("./init-TRQTWLAB.js");
4028
+ const { runInit } = await import("./init-4ITWQXXA.js");
5682
4029
  await runInit(process.argv.slice(2));
5683
4030
  process.exit(0);
5684
4031
  }
@@ -5701,7 +4048,7 @@ if (!codeMode) {
5701
4048
  }
5702
4049
  function createMcpServer() {
5703
4050
  const s = new Server(
5704
- { name: "crawlio-browser", version: "1.3.0" },
4051
+ { name: "crawlio-browser", version: PKG_VERSION },
5705
4052
  { capabilities: { tools: {} } }
5706
4053
  );
5707
4054
  s.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -5712,7 +4059,7 @@ function createMcpServer() {
5712
4059
  }))
5713
4060
  }));
5714
4061
  s.setRequestHandler(CallToolRequestSchema, async (request) => {
5715
- const requestId = randomBytes(4).toString("hex");
4062
+ const requestId = randomBytes2(4).toString("hex");
5716
4063
  const toolName = request.params.name;
5717
4064
  const tool = tools.find((t) => t.name === toolName);
5718
4065
  if (!tool) {
@@ -5767,7 +4114,8 @@ async function main() {
5767
4114
  const port = await crawlio.getPort();
5768
4115
  bridge.push({ type: "set_crawlio_port", port });
5769
4116
  console.error(`[MCP] Port refresh: pushed Crawlio port ${port}`);
5770
- } catch {
4117
+ } catch (e) {
4118
+ console.error("[MCP] Port refresh failed:", e);
5771
4119
  }
5772
4120
  };
5773
4121
  if (portalMode) {
@@ -5808,7 +4156,7 @@ async function startPortalServer(port) {
5808
4156
  }
5809
4157
  next();
5810
4158
  });
5811
- app.use(rate_limit_default({
4159
+ app.use(rateLimit({
5812
4160
  windowMs: 6e4,
5813
4161
  max: 120,
5814
4162
  standardHeaders: true,
@@ -5850,7 +4198,7 @@ async function startPortalServer(port) {
5850
4198
  app.get("/health", (_req, res) => {
5851
4199
  res.json({
5852
4200
  status: "ok",
5853
- version: "1.3.0",
4201
+ version: PKG_VERSION,
5854
4202
  mode: codeMode ? "code" : "full",
5855
4203
  transport: "portal",
5856
4204
  bridgeConnected: bridge.isConnected,
@@ -5869,6 +4217,14 @@ main().catch((e) => {
5869
4217
  console.error("[MCP] Fatal:", e);
5870
4218
  process.exit(1);
5871
4219
  });
4220
+ process.on("uncaughtException", (err) => {
4221
+ console.error("[MCP] CRASH \u2014 uncaughtException:", err.stack ?? err.message);
4222
+ process.exit(1);
4223
+ });
4224
+ process.on("unhandledRejection", (reason) => {
4225
+ console.error("[MCP] CRASH \u2014 unhandledRejection:", reason instanceof Error ? reason.stack ?? reason.message : reason);
4226
+ process.exit(1);
4227
+ });
5872
4228
  for (const sig of ["SIGTERM", "SIGINT"]) {
5873
4229
  process.on(sig, async () => {
5874
4230
  console.error(`[MCP] ${sig} received, shutting down`);