@papicandela/mcx-core 0.2.7 → 0.2.9

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,129 @@ 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
+ * Get specific line range (1-indexed, inclusive).
6556
+ * @param file - File object with .lines array
6557
+ * @param start - Start line number (1-indexed)
6558
+ * @param end - End line number (1-indexed, inclusive)
6559
+ */
6560
+ globalThis.lines = (file, start, end) => {
6561
+ const fileLines = file?.lines;
6562
+ if (!Array.isArray(fileLines)) return [];
6563
+ if (start < 1) start = 1;
6564
+ if (end > fileLines.length) end = fileLines.length;
6565
+ return fileLines.slice(start - 1, end);
6566
+ };
6567
+
6568
+ /**
6569
+ * Extract code block containing a line (detects by indentation).
6570
+ * @param file - File object with .lines array
6571
+ * @param line - Line number (1-indexed)
6572
+ */
6573
+ globalThis.block = (file, line) => {
6574
+ const lines = file?.lines;
6575
+ if (!Array.isArray(lines)) return [];
6576
+ const idx = line - 1;
6577
+ if (idx < 0 || idx >= lines.length) return [];
6578
+
6579
+ const targetIndent = lines[idx].search(/\\S/);
6580
+ if (targetIndent < 0) return [lines[idx]];
6581
+
6582
+ // Find block start (line with less indentation)
6583
+ let start = idx;
6584
+ for (let i = idx - 1; i >= 0; i--) {
6585
+ const lineIndent = lines[i].search(/\\S/);
6586
+ if (lineIndent >= 0 && lineIndent < targetIndent) { start = i; break; }
6587
+ }
6588
+
6589
+ // Find block end
6590
+ const startIndent = lines[start].search(/\\S/);
6591
+ let end = idx;
6592
+ for (let i = idx + 1; i < lines.length; i++) {
6593
+ const lineIndent = lines[i].search(/\\S/);
6594
+ if (lineIndent >= 0 && lineIndent <= startIndent && lines[i].trim()) { end = i - 1; break; }
6595
+ end = i;
6596
+ }
6597
+
6598
+ return lines.slice(start, end + 1);
6599
+ };
6600
+
6601
+ /**
6602
+ * Grep with context lines.
6603
+ * @param file - File object with .lines array
6604
+ * @param pattern - String or regex pattern
6605
+ * @param ctx - Context lines (default 3)
6606
+ */
6607
+ globalThis.grep = (file, pattern, ctx = 3) => {
6608
+ const lines = file?.lines;
6609
+ if (!Array.isArray(lines)) return [];
6610
+ const regex = typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern;
6611
+ const results = [];
6612
+
6613
+ for (let i = 0; i < lines.length; i++) {
6614
+ if (regex.test(lines[i])) {
6615
+ results.push({
6616
+ line: i + 1,
6617
+ match: lines[i],
6618
+ context: lines.slice(Math.max(0, i - ctx), i + ctx + 1)
6619
+ });
6620
+ }
6621
+ }
6622
+ return results;
6623
+ };
6624
+
6625
+ /**
6626
+ * Extract outline (function/class signatures).
6627
+ * @param file - File object with .lines array
6628
+ */
6629
+ globalThis.outline = (file) => {
6630
+ const lines = file?.lines;
6631
+ if (!Array.isArray(lines)) return [];
6632
+ const signatures = [];
6633
+ const patterns = [
6634
+ /^(export\\s+)?(async\\s+)?function\\s+\\w+/,
6635
+ /^(export\\s+)?(const|let|var)\\s+\\w+\\s*=\\s*(async\\s+)?\\(/,
6636
+ /^(export\\s+)?(const|let|var)\\s+\\w+\\s*=\\s*(async\\s+)?function/,
6637
+ /^(export\\s+)?class\\s+\\w+/,
6638
+ /^(export\\s+)?interface\\s+\\w+/,
6639
+ /^(export\\s+)?type\\s+\\w+/,
6640
+ /^\\s+(async\\s+)?\\w+\\s*\\([^)]*\\)\\s*[:{]/,
6641
+ ];
6642
+
6643
+ for (let i = 0; i < lines.length; i++) {
6644
+ const line = lines[i];
6645
+ for (const pat of patterns) {
6646
+ if (pat.test(line)) {
6647
+ signatures.push({ line: i + 1, text: line.trim().slice(0, 80) });
6648
+ break;
6649
+ }
6650
+ }
6651
+ }
6652
+ return signatures;
6653
+ };
6654
+
6540
6655
  // SECURITY: Reserved keys that must not be overwritten by user-provided variables/globals
6541
6656
  const RESERVED_KEYS = new Set([
6542
6657
  'onmessage', 'postMessage', 'close', 'terminate', 'self',
6543
6658
  'constructor', 'prototype', '__proto__',
6544
6659
  'pendingCalls', 'callId', 'logs', 'console', 'adapters',
6545
6660
  'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',
6546
- 'pick', 'table', 'count', 'sum', 'first', 'safeStr', 'poll', 'waitFor'
6661
+ 'pick', 'table', 'count', 'sum', 'first', 'safeStr', 'poll', 'waitFor',
6662
+ 'around', 'lines', 'block', 'grep', 'outline'
6547
6663
  ]);
6548
6664
 
6549
6665
  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.9",
4
4
  "description": "MCX Core - MCP Code Execution Framework",
5
5
  "author": "papicandela",
6
6
  "license": "MIT",
@@ -334,13 +334,129 @@ 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
+ * Get specific line range (1-indexed, inclusive).
353
+ * @param file - File object with .lines array
354
+ * @param start - Start line number (1-indexed)
355
+ * @param end - End line number (1-indexed, inclusive)
356
+ */
357
+ globalThis.lines = (file, start, end) => {
358
+ const fileLines = file?.lines;
359
+ if (!Array.isArray(fileLines)) return [];
360
+ if (start < 1) start = 1;
361
+ if (end > fileLines.length) end = fileLines.length;
362
+ return fileLines.slice(start - 1, end);
363
+ };
364
+
365
+ /**
366
+ * Extract code block containing a line (detects by indentation).
367
+ * @param file - File object with .lines array
368
+ * @param line - Line number (1-indexed)
369
+ */
370
+ globalThis.block = (file, line) => {
371
+ const lines = file?.lines;
372
+ if (!Array.isArray(lines)) return [];
373
+ const idx = line - 1;
374
+ if (idx < 0 || idx >= lines.length) return [];
375
+
376
+ const targetIndent = lines[idx].search(/\\S/);
377
+ if (targetIndent < 0) return [lines[idx]];
378
+
379
+ // Find block start (line with less indentation)
380
+ let start = idx;
381
+ for (let i = idx - 1; i >= 0; i--) {
382
+ const lineIndent = lines[i].search(/\\S/);
383
+ if (lineIndent >= 0 && lineIndent < targetIndent) { start = i; break; }
384
+ }
385
+
386
+ // Find block end
387
+ const startIndent = lines[start].search(/\\S/);
388
+ let end = idx;
389
+ for (let i = idx + 1; i < lines.length; i++) {
390
+ const lineIndent = lines[i].search(/\\S/);
391
+ if (lineIndent >= 0 && lineIndent <= startIndent && lines[i].trim()) { end = i - 1; break; }
392
+ end = i;
393
+ }
394
+
395
+ return lines.slice(start, end + 1);
396
+ };
397
+
398
+ /**
399
+ * Grep with context lines.
400
+ * @param file - File object with .lines array
401
+ * @param pattern - String or regex pattern
402
+ * @param ctx - Context lines (default 3)
403
+ */
404
+ globalThis.grep = (file, pattern, ctx = 3) => {
405
+ const lines = file?.lines;
406
+ if (!Array.isArray(lines)) return [];
407
+ const regex = typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern;
408
+ const results = [];
409
+
410
+ for (let i = 0; i < lines.length; i++) {
411
+ if (regex.test(lines[i])) {
412
+ results.push({
413
+ line: i + 1,
414
+ match: lines[i],
415
+ context: lines.slice(Math.max(0, i - ctx), i + ctx + 1)
416
+ });
417
+ }
418
+ }
419
+ return results;
420
+ };
421
+
422
+ /**
423
+ * Extract outline (function/class signatures).
424
+ * @param file - File object with .lines array
425
+ */
426
+ globalThis.outline = (file) => {
427
+ const lines = file?.lines;
428
+ if (!Array.isArray(lines)) return [];
429
+ const signatures = [];
430
+ const patterns = [
431
+ /^(export\\s+)?(async\\s+)?function\\s+\\w+/,
432
+ /^(export\\s+)?(const|let|var)\\s+\\w+\\s*=\\s*(async\\s+)?\\(/,
433
+ /^(export\\s+)?(const|let|var)\\s+\\w+\\s*=\\s*(async\\s+)?function/,
434
+ /^(export\\s+)?class\\s+\\w+/,
435
+ /^(export\\s+)?interface\\s+\\w+/,
436
+ /^(export\\s+)?type\\s+\\w+/,
437
+ /^\\s+(async\\s+)?\\w+\\s*\\([^)]*\\)\\s*[:{]/,
438
+ ];
439
+
440
+ for (let i = 0; i < lines.length; i++) {
441
+ const line = lines[i];
442
+ for (const pat of patterns) {
443
+ if (pat.test(line)) {
444
+ signatures.push({ line: i + 1, text: line.trim().slice(0, 80) });
445
+ break;
446
+ }
447
+ }
448
+ }
449
+ return signatures;
450
+ };
451
+
337
452
  // SECURITY: Reserved keys that must not be overwritten by user-provided variables/globals
338
453
  const RESERVED_KEYS = new Set([
339
454
  'onmessage', 'postMessage', 'close', 'terminate', 'self',
340
455
  'constructor', 'prototype', '__proto__',
341
456
  'pendingCalls', 'callId', 'logs', 'console', 'adapters',
342
457
  'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',
343
- 'pick', 'table', 'count', 'sum', 'first', 'safeStr', 'poll', 'waitFor'
458
+ 'pick', 'table', 'count', 'sum', 'first', 'safeStr', 'poll', 'waitFor',
459
+ 'around', 'lines', 'block', 'grep', 'outline'
344
460
  ]);
345
461
 
346
462
  self.onmessage = async (event) => {