@papicandela/mcx-core 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6537,13 +6537,116 @@ class BunWorkerSandbox {
6537
6537
  throw new Error('waitFor timeout after ' + timeout + 'ms');
6538
6538
  };
6539
6539
 
6540
+ /**
6541
+ * Get lines around a specific line number (1-indexed).
6542
+ * @param file - File object with .lines array
6543
+ * @param line - Line number (1-indexed)
6544
+ * @param ctx - Context lines before/after (default 10)
6545
+ */
6546
+ globalThis.around = (file, line, ctx = 10) => {
6547
+ const lines = file?.lines;
6548
+ if (!Array.isArray(lines)) return [];
6549
+ if (line < 1 || line > lines.length) return [];
6550
+ const idx = line - 1;
6551
+ return lines.slice(Math.max(0, idx - ctx), Math.min(lines.length, idx + ctx + 1));
6552
+ };
6553
+
6554
+ /**
6555
+ * Extract code block containing a line (detects by indentation).
6556
+ * @param file - File object with .lines array
6557
+ * @param line - Line number (1-indexed)
6558
+ */
6559
+ globalThis.block = (file, line) => {
6560
+ const lines = file?.lines;
6561
+ if (!Array.isArray(lines)) return [];
6562
+ const idx = line - 1;
6563
+ if (idx < 0 || idx >= lines.length) return [];
6564
+
6565
+ const targetIndent = lines[idx].search(/\\S/);
6566
+ if (targetIndent < 0) return [lines[idx]];
6567
+
6568
+ // Find block start
6569
+ let start = idx;
6570
+ for (let i = idx - 1; i >= 0; i--) {
6571
+ const lineIndent = lines[i].search(/\\S/);
6572
+ if (lineIndent >= 0 && lineIndent < targetIndent) { start = i; break; }
6573
+ if (lineIndent === 0 && lines[i].trim()) { start = i; break; }
6574
+ }
6575
+
6576
+ // Find block end
6577
+ const startIndent = lines[start].search(/\\S/);
6578
+ let end = idx;
6579
+ for (let i = idx + 1; i < lines.length; i++) {
6580
+ const lineIndent = lines[i].search(/\\S/);
6581
+ if (lineIndent >= 0 && lineIndent <= startIndent && lines[i].trim()) { end = i - 1; break; }
6582
+ end = i;
6583
+ }
6584
+
6585
+ return lines.slice(start, end + 1);
6586
+ };
6587
+
6588
+ /**
6589
+ * Grep with context lines.
6590
+ * @param file - File object with .lines array
6591
+ * @param pattern - String or regex pattern
6592
+ * @param ctx - Context lines (default 3)
6593
+ */
6594
+ globalThis.grep = (file, pattern, ctx = 3) => {
6595
+ const lines = file?.lines;
6596
+ if (!Array.isArray(lines)) return [];
6597
+ const regex = typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern;
6598
+ const results = [];
6599
+
6600
+ for (let i = 0; i < lines.length; i++) {
6601
+ if (regex.test(lines[i])) {
6602
+ results.push({
6603
+ line: i + 1,
6604
+ match: lines[i],
6605
+ context: lines.slice(Math.max(0, i - ctx), i + ctx + 1)
6606
+ });
6607
+ }
6608
+ }
6609
+ return results;
6610
+ };
6611
+
6612
+ /**
6613
+ * Extract outline (function/class signatures).
6614
+ * @param file - File object with .lines array
6615
+ */
6616
+ globalThis.outline = (file) => {
6617
+ const lines = file?.lines;
6618
+ if (!Array.isArray(lines)) return [];
6619
+ const signatures = [];
6620
+ const patterns = [
6621
+ /^(export\\s+)?(async\\s+)?function\\s+\\w+/,
6622
+ /^(export\\s+)?(const|let|var)\\s+\\w+\\s*=\\s*(async\\s+)?\\(/,
6623
+ /^(export\\s+)?(const|let|var)\\s+\\w+\\s*=\\s*(async\\s+)?function/,
6624
+ /^(export\\s+)?class\\s+\\w+/,
6625
+ /^(export\\s+)?interface\\s+\\w+/,
6626
+ /^(export\\s+)?type\\s+\\w+/,
6627
+ /^\\s+(async\\s+)?\\w+\\s*\\([^)]*\\)\\s*[:{]/,
6628
+ ];
6629
+
6630
+ for (let i = 0; i < lines.length; i++) {
6631
+ const line = lines[i];
6632
+ for (const pat of patterns) {
6633
+ if (pat.test(line)) {
6634
+ signatures.push((i + 1) + ': ' + line.trim().slice(0, 80));
6635
+ break;
6636
+ }
6637
+ }
6638
+ }
6639
+ return signatures;
6640
+ };
6641
+
6540
6642
  // SECURITY: Reserved keys that must not be overwritten by user-provided variables/globals
6541
6643
  const RESERVED_KEYS = new Set([
6542
6644
  'onmessage', 'postMessage', 'close', 'terminate', 'self',
6543
6645
  'constructor', 'prototype', '__proto__',
6544
6646
  'pendingCalls', 'callId', 'logs', 'console', 'adapters',
6545
6647
  'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',
6546
- 'pick', 'table', 'count', 'sum', 'first', 'safeStr', 'poll', 'waitFor'
6648
+ 'pick', 'table', 'count', 'sum', 'first', 'safeStr', 'poll', 'waitFor',
6649
+ 'around', 'block', 'grep', 'outline'
6547
6650
  ]);
6548
6651
 
6549
6652
  self.onmessage = async (event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papicandela/mcx-core",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "MCX Core - MCP Code Execution Framework",
5
5
  "author": "papicandela",
6
6
  "license": "MIT",
@@ -334,13 +334,116 @@ export class BunWorkerSandbox implements ISandbox {
334
334
  throw new Error('waitFor timeout after ' + timeout + 'ms');
335
335
  };
336
336
 
337
+ /**
338
+ * Get lines around a specific line number (1-indexed).
339
+ * @param file - File object with .lines array
340
+ * @param line - Line number (1-indexed)
341
+ * @param ctx - Context lines before/after (default 10)
342
+ */
343
+ globalThis.around = (file, line, ctx = 10) => {
344
+ const lines = file?.lines;
345
+ if (!Array.isArray(lines)) return [];
346
+ if (line < 1 || line > lines.length) return [];
347
+ const idx = line - 1;
348
+ return lines.slice(Math.max(0, idx - ctx), Math.min(lines.length, idx + ctx + 1));
349
+ };
350
+
351
+ /**
352
+ * Extract code block containing a line (detects by indentation).
353
+ * @param file - File object with .lines array
354
+ * @param line - Line number (1-indexed)
355
+ */
356
+ globalThis.block = (file, line) => {
357
+ const lines = file?.lines;
358
+ if (!Array.isArray(lines)) return [];
359
+ const idx = line - 1;
360
+ if (idx < 0 || idx >= lines.length) return [];
361
+
362
+ const targetIndent = lines[idx].search(/\\S/);
363
+ if (targetIndent < 0) return [lines[idx]];
364
+
365
+ // Find block start
366
+ let start = idx;
367
+ for (let i = idx - 1; i >= 0; i--) {
368
+ const lineIndent = lines[i].search(/\\S/);
369
+ if (lineIndent >= 0 && lineIndent < targetIndent) { start = i; break; }
370
+ if (lineIndent === 0 && lines[i].trim()) { start = i; break; }
371
+ }
372
+
373
+ // Find block end
374
+ const startIndent = lines[start].search(/\\S/);
375
+ let end = idx;
376
+ for (let i = idx + 1; i < lines.length; i++) {
377
+ const lineIndent = lines[i].search(/\\S/);
378
+ if (lineIndent >= 0 && lineIndent <= startIndent && lines[i].trim()) { end = i - 1; break; }
379
+ end = i;
380
+ }
381
+
382
+ return lines.slice(start, end + 1);
383
+ };
384
+
385
+ /**
386
+ * Grep with context lines.
387
+ * @param file - File object with .lines array
388
+ * @param pattern - String or regex pattern
389
+ * @param ctx - Context lines (default 3)
390
+ */
391
+ globalThis.grep = (file, pattern, ctx = 3) => {
392
+ const lines = file?.lines;
393
+ if (!Array.isArray(lines)) return [];
394
+ const regex = typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern;
395
+ const results = [];
396
+
397
+ for (let i = 0; i < lines.length; i++) {
398
+ if (regex.test(lines[i])) {
399
+ results.push({
400
+ line: i + 1,
401
+ match: lines[i],
402
+ context: lines.slice(Math.max(0, i - ctx), i + ctx + 1)
403
+ });
404
+ }
405
+ }
406
+ return results;
407
+ };
408
+
409
+ /**
410
+ * Extract outline (function/class signatures).
411
+ * @param file - File object with .lines array
412
+ */
413
+ globalThis.outline = (file) => {
414
+ const lines = file?.lines;
415
+ if (!Array.isArray(lines)) return [];
416
+ const signatures = [];
417
+ const patterns = [
418
+ /^(export\\s+)?(async\\s+)?function\\s+\\w+/,
419
+ /^(export\\s+)?(const|let|var)\\s+\\w+\\s*=\\s*(async\\s+)?\\(/,
420
+ /^(export\\s+)?(const|let|var)\\s+\\w+\\s*=\\s*(async\\s+)?function/,
421
+ /^(export\\s+)?class\\s+\\w+/,
422
+ /^(export\\s+)?interface\\s+\\w+/,
423
+ /^(export\\s+)?type\\s+\\w+/,
424
+ /^\\s+(async\\s+)?\\w+\\s*\\([^)]*\\)\\s*[:{]/,
425
+ ];
426
+
427
+ for (let i = 0; i < lines.length; i++) {
428
+ const line = lines[i];
429
+ for (const pat of patterns) {
430
+ if (pat.test(line)) {
431
+ signatures.push((i + 1) + ': ' + line.trim().slice(0, 80));
432
+ break;
433
+ }
434
+ }
435
+ }
436
+ return signatures;
437
+ };
438
+
337
439
  // SECURITY: Reserved keys that must not be overwritten by user-provided variables/globals
338
440
  const RESERVED_KEYS = new Set([
339
441
  'onmessage', 'postMessage', 'close', 'terminate', 'self',
340
442
  'constructor', 'prototype', '__proto__',
341
443
  'pendingCalls', 'callId', 'logs', 'console', 'adapters',
342
444
  'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',
343
- 'pick', 'table', 'count', 'sum', 'first', 'safeStr', 'poll', 'waitFor'
445
+ 'pick', 'table', 'count', 'sum', 'first', 'safeStr', 'poll', 'waitFor',
446
+ 'around', 'block', 'grep', 'outline'
344
447
  ]);
345
448
 
346
449
  self.onmessage = async (event) => {