omnibiofex 2.6.0 → 2.6.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/utils/display.js +276 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnibiofex",
3
- "version": "2.6.0",
3
+ "version": "2.6.3",
4
4
  "description": "OmniBioFex X - The Autonomous Research Terminal for AI-powered research missions",
5
5
  "main": "bin/obx",
6
6
  "bin": {
@@ -316,39 +316,292 @@ async function displayMissionDashboard(mission) {
316
316
  console.log(chalk.hex('#F24E1E')('\n═══════════════════════════════════════════════════════════\n'));
317
317
  }
318
318
 
319
- // ==================== TYPE AI RESPONSE ====================
319
+ // ==================== HELPERS FOR TABLE RENDERING ====================
320
+ function formatTable(rows) {
321
+ if (!rows || rows.length === 0) return '';
322
+
323
+ // Calculate column widths
324
+ const colWidths = [];
325
+ rows.forEach(row => {
326
+ row.forEach((cell, i) => {
327
+ colWidths[i] = Math.max(colWidths[i] || 0, cell.length);
328
+ });
329
+ });
330
+
331
+ // Format each row
332
+ return rows.map((row, rowIndex) => {
333
+ const isHeader = rowIndex === 0;
334
+ const formatted = row.map((cell, i) => {
335
+ const padded = cell.padEnd(colWidths[i]);
336
+ return isHeader ? chalk.white.bold(padded) : chalk.gray(padded);
337
+ }).join(' │ ');
338
+
339
+ return chalk.gray('│ ') + formatted + chalk.gray(' │');
340
+ }).join('\n');
341
+ }
342
+
343
+ function addVisualBreak() {
344
+ console.log(chalk.gray('\n' + '·'.repeat(60) + '\n'));
345
+ }
346
+
347
+ // ==================== INLINE MARKDOWN PARSER ====================
348
+
349
+ /**
350
+ * Parse a line and return array of segments with formatting
351
+ * Handles: **bold**, *italic*, `code`, and combinations
352
+ */
353
+ function parseInlineMarkdown(text) {
354
+ const segments = [];
355
+ let remaining = text;
356
+
357
+ while (remaining.length > 0) {
358
+ // Find the next markdown pattern
359
+ const boldMatch = remaining.match(/\*\*(.+?)\*\*/);
360
+ const italicMatch = remaining.match(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/);
361
+ const codeMatch = remaining.match(/`([^`]+)`/);
362
+
363
+ // Find earliest match
364
+ let earliest = null;
365
+ let earliestIndex = remaining.length;
366
+
367
+ if (boldMatch && boldMatch.index < earliestIndex) {
368
+ earliest = { type: 'bold', match: boldMatch, index: boldMatch.index };
369
+ earliestIndex = boldMatch.index;
370
+ }
371
+ if (codeMatch && codeMatch.index < earliestIndex) {
372
+ earliest = { type: 'code', match: codeMatch, index: codeMatch.index };
373
+ earliestIndex = codeMatch.index;
374
+ }
375
+ if (italicMatch && italicMatch.index < earliestIndex) {
376
+ earliest = { type: 'italic', match: italicMatch, index: italicMatch.index };
377
+ earliestIndex = italicMatch.index;
378
+ }
379
+
380
+ if (!earliest) {
381
+ // No more markdown, add remaining as plain text
382
+ if (remaining.length > 0) {
383
+ segments.push({ text: remaining, style: 'plain' });
384
+ }
385
+ break;
386
+ }
387
+
388
+ // Add text before the match
389
+ if (earliestIndex > 0) {
390
+ segments.push({
391
+ text: remaining.substring(0, earliestIndex),
392
+ style: 'plain'
393
+ });
394
+ }
395
+
396
+ // Add the formatted segment
397
+ segments.push({
398
+ text: earliest.match[1],
399
+ style: earliest.type
400
+ });
401
+
402
+ // Continue with remaining text
403
+ remaining = remaining.substring(earliestIndex + earliest.match[0].length);
404
+ }
405
+
406
+ return segments;
407
+ }
408
+
409
+ /**
410
+ * Type a line with inline markdown formatting (bold, italic, code)
411
+ */
412
+ async function typeFormattedLine(text, speed = 8) {
413
+ const segments = parseInlineMarkdown(text);
414
+
415
+ for (const segment of segments) {
416
+ let colorFn = chalk.white;
417
+
418
+ switch (segment.style) {
419
+ case 'bold':
420
+ colorFn = chalk.white.bold;
421
+ break;
422
+ case 'italic':
423
+ colorFn = chalk.white.italic;
424
+ break;
425
+ case 'code':
426
+ colorFn = chalk.bgBlack.cyan;
427
+ break;
428
+ case 'plain':
429
+ default:
430
+ colorFn = chalk.white;
431
+ }
432
+
433
+ await typeText(segment.text, speed, colorFn);
434
+ }
435
+
436
+ process.stdout.write('\n');
437
+ }
438
+
320
439
  /**
321
- * Stream the AI response with typing effect
440
+ * Type a line with inline markdown, but use gray color for plain text
322
441
  */
442
+ async function typeFormattedLineGray(text, speed = 6) {
443
+ const segments = parseInlineMarkdown(text);
444
+
445
+ for (const segment of segments) {
446
+ let colorFn = chalk.gray;
447
+
448
+ switch (segment.style) {
449
+ case 'bold':
450
+ colorFn = chalk.white.bold;
451
+ break;
452
+ case 'italic':
453
+ colorFn = chalk.white.italic;
454
+ break;
455
+ case 'code':
456
+ colorFn = chalk.bgBlack.cyan;
457
+ break;
458
+ case 'plain':
459
+ default:
460
+ colorFn = chalk.gray;
461
+ }
462
+
463
+ await typeText(segment.text, speed, colorFn);
464
+ }
465
+
466
+ process.stdout.write('\n');
467
+ }
468
+
469
+ // ==================== UPDATED AI RESPONSE RENDERER ====================
470
+
323
471
  async function typeAIResponse(response) {
324
472
  console.log(chalk.hex('#F24E1E').bold('\n📄 Research Report\n'));
325
- console.log(chalk.gray(''.repeat(60)));
473
+ console.log(chalk.gray(''.repeat(60)) + '\n');
326
474
 
327
- // Stream in chunks for natural feel (faster than char-by-char for long reports)
328
475
  const lines = response.split('\n');
476
+ let inTable = false;
329
477
 
330
- for (const line of lines) {
331
- if (line.trim().startsWith('#')) {
332
- // Headers - type slower and in orange
333
- await typeLine(line, 20, chalk.hex('#F24E1E').bold);
334
- } else if (line.trim().startsWith('- ') || line.trim().startsWith('* ')) {
335
- // List items - normal speed
336
- await typeLine(line, 8, chalk.white);
337
- } else if (line.trim().startsWith('**') && line.trim().endsWith('**')) {
338
- // Bold text
339
- const clean = line.replace(/\*\*/g, '');
340
- await typeLine(clean, 10, chalk.white.bold);
341
- } else if (line.trim() === '') {
478
+ for (let i = 0; i < lines.length; i++) {
479
+ const line = lines[i];
480
+ const trimmed = line.trim();
481
+
482
+ // Skip empty lines but add spacing
483
+ if (trimmed === '') {
484
+ if (!inTable) {
485
+ console.log('');
486
+ await sleep(80);
487
+ }
488
+ continue;
489
+ }
490
+
491
+ // Detect table rows (lines with |)
492
+ if (trimmed.includes('|') && trimmed.startsWith('|')) {
493
+ // Skip separator rows like |---|---| or | ---------- |
494
+ if (trimmed.match(/^\|[\s\-:]+\|$/)) {
495
+ continue;
496
+ }
497
+
498
+ if (!inTable) {
499
+ inTable = true;
500
+ console.log(chalk.gray('┌' + '─'.repeat(58) + '┐'));
501
+ }
502
+
503
+ // Parse table cells
504
+ const cells = trimmed.split('|').filter(c => c.trim() !== '').map(c => c.trim());
505
+
506
+ // Format table row with inline markdown support
507
+ if (cells.length > 0) {
508
+ const isHeader = i === 0 || (i > 0 && lines[i-1].trim() === '');
509
+
510
+ process.stdout.write(chalk.gray('│ '));
511
+
512
+ for (let j = 0; j < cells.length; j++) {
513
+ const cell = cells[j];
514
+ const padded = cell.padEnd(15);
515
+
516
+ if (isHeader) {
517
+ // Header cells - parse markdown and make bold
518
+ const segments = parseInlineMarkdown(padded);
519
+ for (const seg of segments) {
520
+ process.stdout.write(chalk.white.bold(seg.text));
521
+ }
522
+ } else {
523
+ // Regular cells - parse markdown
524
+ const segments = parseInlineMarkdown(padded);
525
+ for (const seg of segments) {
526
+ let colorFn = chalk.gray;
527
+ if (seg.style === 'bold') colorFn = chalk.white.bold;
528
+ else if (seg.style === 'italic') colorFn = chalk.white.italic;
529
+ else if (seg.style === 'code') colorFn = chalk.bgBlack.cyan;
530
+ process.stdout.write(colorFn(seg.text));
531
+ }
532
+ }
533
+
534
+ if (j < cells.length - 1) {
535
+ process.stdout.write(chalk.gray(' │ '));
536
+ }
537
+ }
538
+
539
+ console.log(chalk.gray(' │'));
540
+ await sleep(50);
541
+ }
542
+
543
+ continue;
544
+ } else if (inTable) {
545
+ // End of table
546
+ console.log(chalk.gray('└' + '─'.repeat(58) + '┘'));
342
547
  console.log('');
343
- await sleep(50);
344
- } else {
345
- // Regular text - fast stream
346
- await streamText(line, 4, 5);
548
+ inTable = false;
549
+ await sleep(100);
550
+ }
551
+
552
+ // Headers (## or #)
553
+ if (trimmed.startsWith('## ')) {
554
+ const header = trimmed.substring(3);
555
+ console.log('');
556
+ process.stdout.write(chalk.hex('#F24E1E').bold('▸ '));
557
+ await typeFormattedLine(header, 15);
558
+ console.log(chalk.gray('─'.repeat(60)));
559
+ await sleep(150);
560
+ continue;
561
+ }
562
+
563
+ if (trimmed.startsWith('# ')) {
564
+ const header = trimmed.substring(2);
347
565
  console.log('');
566
+ process.stdout.write(chalk.hex('#F24E1E').bold.underline(' '));
567
+ await typeFormattedLine(header, 20);
568
+ console.log('');
569
+ await sleep(200);
570
+ continue;
571
+ }
572
+
573
+ // Bullet points
574
+ if (trimmed.startsWith('• ') || trimmed.startsWith('- ') || trimmed.startsWith('* ')) {
575
+ const bullet = trimmed.substring(2);
576
+ process.stdout.write(chalk.hex('#F24E1E')(' ▸ '));
577
+ await typeFormattedLine(bullet, 8);
578
+ await sleep(60);
579
+ continue;
580
+ }
581
+
582
+ // Numbered lists
583
+ const numberedMatch = trimmed.match(/^(\d+)\.\s+(.+)/);
584
+ if (numberedMatch) {
585
+ const num = numberedMatch[1];
586
+ const text = numberedMatch[2];
587
+ process.stdout.write(chalk.hex('#F24E1E')(` ${num}. `));
588
+ await typeFormattedLine(text, 8);
589
+ await sleep(60);
590
+ continue;
348
591
  }
592
+
593
+ // Regular paragraph (with inline markdown support)
594
+ process.stdout.write(' ');
595
+ await typeFormattedLineGray(trimmed, 6);
596
+ await sleep(40);
597
+ }
598
+
599
+ // Close any open table
600
+ if (inTable) {
601
+ console.log(chalk.gray('└' + '─'.repeat(58) + '┘'));
349
602
  }
350
603
 
351
- console.log(chalk.gray(''.repeat(60)) + '\n');
604
+ console.log('\n' + chalk.gray(''.repeat(60)) + '\n');
352
605
  }
353
606
 
354
607
  // ==================== RENDER MARKDOWN (existing) ====================
@@ -405,4 +658,6 @@ module.exports = {
405
658
  renderMarkdown,
406
659
  displayReport,
407
660
  showThinking,
661
+ formatTable,
662
+ addVisualBreak,
408
663
  };