kiku-agent 1.0.4 → 1.0.6

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.
@@ -275,6 +275,15 @@ export function stripAnsi(str) {
275
275
  return str.replace(/\x1b\[[0-9;]*m/g, '');
276
276
  }
277
277
 
278
+ export function sanitizeTerminalText(value, maxLength = 2000) {
279
+ return String(value ?? '')
280
+ .replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))/g, '')
281
+ .replace(/[\x00-\x1f\x7f]/g, ' ')
282
+ .replace(/\s+/g, ' ')
283
+ .trim()
284
+ .slice(0, Math.max(0, maxLength));
285
+ }
286
+
278
287
 
279
288
  export function fitStyledText(styledText, maxWidth) {
280
289
  const safeWidth = Math.max(0, maxWidth);
@@ -311,6 +320,8 @@ export function printSolidBannerRow(styledText, targetWidth, requestedBg = C.bgP
311
320
  console.log(indent + background + C.white + fixedStyled + '\x1b[0K' + C.reset);
312
321
  }
313
322
 
323
+ let activeTuiPrompt = null;
324
+
314
325
  export function createProgressRunner({
315
326
  badge = 'RUN',
316
327
  title = 'Processing...',
@@ -330,6 +341,13 @@ export function createProgressRunner({
330
341
  const spinner = spinnerFrames[frameIdx % spinnerFrames.length];
331
342
  frameIdx++;
332
343
 
344
+ if (activeTuiPrompt && !activeTuiPrompt.isDisposed && activeTuiPrompt.screenBuffer) {
345
+ activeTuiPrompt.agentState = `${currentTitle}${currentStep ? ` ▸ ${currentStep}` : ''}`;
346
+ activeTuiPrompt.agentStateTime = startTime;
347
+ activeTuiPrompt.render();
348
+ return;
349
+ }
350
+
333
351
  const badgeStr = `${badgeBg}${C.bold} ${badge} ${C.reset}`;
334
352
  const spinStr = `${C.sakura}${spinner}${C.reset}`;
335
353
  const titleStr = `${C.bold}${C.white}${currentTitle}${C.reset}`;
@@ -360,7 +378,12 @@ export function createProgressRunner({
360
378
  clearInterval(timer);
361
379
  timer = null;
362
380
  }
363
- _rawStdoutWrite('\r\x1b[2K');
381
+ if (activeTuiPrompt && !activeTuiPrompt.isDisposed && activeTuiPrompt.screenBuffer) {
382
+ activeTuiPrompt.agentState = '';
383
+ activeTuiPrompt.render();
384
+ } else {
385
+ _rawStdoutWrite('\r\x1b[2K');
386
+ }
364
387
  },
365
388
  done(options = {}) {
366
389
  this.stop();
@@ -394,25 +417,33 @@ export function createProgressRunner({
394
417
 
395
418
  export function formatSolidPanelLines({ title, lines, width, indent = '', headerBg, bg }) {
396
419
  const w = width || getFullWidth();
420
+ const maxContentWidth = Math.max(16, w - (indent ? getStringDisplayWidth(indent) : 0) - 4);
397
421
  const rows = [];
398
422
  if (title) {
399
423
  const rawTitle = `${indent} ${C.bold}${C.roseGlow}${title}${C.reset}`;
400
- const fitted = fitStyledText(rawTitle, w);
401
- const pad = Math.max(0, w - getStringDisplayWidth(fitted));
424
+ const wrappedTitle = wrapText(rawTitle, maxContentWidth);
402
425
  const hBg = headerBg || C.bgPanelHeader;
403
- const themed = fitted.replace(/\x1b\[0m/g, C.reset + hBg + C.white);
404
- rows.push(`${hBg}${C.white}${themed}${' '.repeat(pad)}${C.reset}`);
426
+ for (const tLine of wrappedTitle) {
427
+ const fitted = fitStyledText(tLine.startsWith(indent) ? tLine : `${indent} ${tLine}`, w);
428
+ const pad = Math.max(0, w - getStringDisplayWidth(fitted));
429
+ const themed = fitted.replace(/\x1b\[0m/g, C.reset + hBg + C.white);
430
+ rows.push(`${hBg}${C.white}${themed}${' '.repeat(pad)}${C.reset}`);
431
+ }
405
432
  }
406
433
  const bodyBg = bg || C.bgPanel;
407
434
  for (const raw of (lines || [])) {
408
435
  const text = typeof raw === 'string' ? raw : (raw?.text ?? '');
409
436
  const sublines = String(text).split('\n');
410
437
  for (const subline of sublines) {
411
- const rowText = `${indent} ${subline}`;
412
- const fitted = fitStyledText(rowText, w);
413
- const pad = Math.max(0, w - getStringDisplayWidth(fitted));
414
- const themed = fitted.replace(/\x1b\[0m/g, C.reset + bodyBg + C.white);
415
- rows.push(`${bodyBg}${C.white}${themed}${' '.repeat(pad)}${C.reset}`);
438
+ const isBoxTable = /^\s*[\x1b\[0-9;]*[┌├└│]/.test(subline);
439
+ const wrappedSublines = isBoxTable ? [subline] : wrapText(subline, maxContentWidth);
440
+ for (const wrapped of (wrappedSublines.length > 0 ? wrappedSublines : [''])) {
441
+ const rowText = isBoxTable ? (wrapped.startsWith(indent) ? wrapped : `${indent}${wrapped}`) : `${indent} ${wrapped}`;
442
+ const fitted = fitStyledText(rowText, w);
443
+ const pad = Math.max(0, w - getStringDisplayWidth(fitted));
444
+ const themed = fitted.replace(/\x1b\[0m/g, C.reset + bodyBg + C.white);
445
+ rows.push(`${bodyBg}${C.white}${themed}${' '.repeat(pad)}${C.reset}`);
446
+ }
416
447
  }
417
448
  }
418
449
  return rows;
@@ -420,20 +451,284 @@ export function formatSolidPanelLines({ title, lines, width, indent = '', header
420
451
 
421
452
  export function printSolidPanel(options) {
422
453
  const rows = formatSolidPanelLines(options);
423
- for (const row of rows) {
424
- console.log(row);
454
+ console.log(`${rows.join('\n')}\n`);
455
+ }
456
+
457
+ export function formatRichBadge(text, color = C.emerald, bg = null) {
458
+ if (!text) return '';
459
+ if (bg) {
460
+ return `${bg}${color}${C.bold} ${text} ${C.reset}`;
461
+ }
462
+ return `${color}${C.bold}[${text}]${C.reset}`;
463
+ }
464
+
465
+ export function formatStatusBadge(statusType) {
466
+ const type = String(statusType || '').toLowerCase();
467
+ switch (type) {
468
+ case 'connected':
469
+ case 'ok':
470
+ case 'success':
471
+ return `${C.emerald}${C.bold}[✓ CONNECTED]${C.reset}`;
472
+ case 'offline':
473
+ case 'unreachable':
474
+ return `${C.amber}${C.bold}[! OFFLINE]${C.reset}`;
475
+ case 'autostart':
476
+ case 'starting':
477
+ return `${C.cyberCyan}${C.bold}[◆ AUTO-START]${C.reset}`;
478
+ case 'model':
479
+ case 'chat_ready':
480
+ return `${C.gold}${C.bold}[✦ MODEL READY]${C.reset}`;
481
+ case 'catalog':
482
+ case 'catalog_only':
483
+ return `${C.amber}${C.bold}[◆ CATALOG-ONLY]${C.reset}`;
484
+ case 'error':
485
+ case 'failed':
486
+ return `${C.red}${C.bold}[✕ ERROR]${C.reset}`;
487
+ case 'warning':
488
+ case 'warn':
489
+ return `${C.amber}${C.bold}[! WARNING]${C.reset}`;
490
+ case 'info':
491
+ return `${C.cyberCyan}${C.bold}[◆ INFO]${C.reset}`;
492
+ case 'mcp':
493
+ return `${C.lavender}${C.bold}[● MCP]${C.reset}`;
494
+ default:
495
+ return `${C.slate}${C.bold}[${String(statusType).toUpperCase()}]${C.reset}`;
496
+ }
497
+ }
498
+
499
+ export const STATUS_THEMES = Object.freeze({
500
+ connected: {
501
+ badgeBg: '\x1b[48;2;16;185;129m', badgeFg: '\x1b[38;2;10;25;18m',
502
+ headerBg: '\x1b[48;2;20;64;44m', headerFg: '\x1b[38;2;240;253;244m',
503
+ bodyBg: '\x1b[48;2;12;40;28m', bodyFg: '\x1b[38;2;230;250;240m',
504
+ icon: '✓', defaultBadge: 'CONNECTED',
505
+ },
506
+ ok: {
507
+ badgeBg: '\x1b[48;2;16;185;129m', badgeFg: '\x1b[38;2;10;25;18m',
508
+ headerBg: '\x1b[48;2;20;64;44m', headerFg: '\x1b[38;2;240;253;244m',
509
+ bodyBg: '\x1b[48;2;12;40;28m', bodyFg: '\x1b[38;2;230;250;240m',
510
+ icon: '✓', defaultBadge: 'READY',
511
+ },
512
+ success: {
513
+ badgeBg: '\x1b[48;2;16;185;129m', badgeFg: '\x1b[38;2;10;25;18m',
514
+ headerBg: '\x1b[48;2;20;64;44m', headerFg: '\x1b[38;2;240;253;244m',
515
+ bodyBg: '\x1b[48;2;12;40;28m', bodyFg: '\x1b[38;2;230;250;240m',
516
+ icon: '✓', defaultBadge: 'SUCCESS',
517
+ },
518
+ offline: {
519
+ badgeBg: '\x1b[48;2;245;158;11m', badgeFg: '\x1b[38;2;28;20;8m',
520
+ headerBg: '\x1b[48;2;60;40;14m', headerFg: '\x1b[38;2;254;243;199m',
521
+ bodyBg: '\x1b[48;2;42;28;10m', bodyFg: '\x1b[38;2;253;230;138m',
522
+ icon: '!', defaultBadge: 'OFFLINE',
523
+ },
524
+ unreachable: {
525
+ badgeBg: '\x1b[48;2;245;158;11m', badgeFg: '\x1b[38;2;28;20;8m',
526
+ headerBg: '\x1b[48;2;60;40;14m', headerFg: '\x1b[38;2;254;243;199m',
527
+ bodyBg: '\x1b[48;2;42;28;10m', bodyFg: '\x1b[38;2;253;230;138m',
528
+ icon: '!', defaultBadge: 'OFFLINE',
529
+ },
530
+ autostart: {
531
+ badgeBg: '\x1b[48;2;14;165;233m', badgeFg: '\x1b[38;2;8;24;36m',
532
+ headerBg: '\x1b[48;2;16;52;72m', headerFg: '\x1b[38;2;224;242;254m',
533
+ bodyBg: '\x1b[48;2;10;34;48m', bodyFg: '\x1b[38;2;186;230;253m',
534
+ icon: '◆', defaultBadge: 'AUTO-START',
535
+ },
536
+ starting: {
537
+ badgeBg: '\x1b[48;2;14;165;233m', badgeFg: '\x1b[38;2;8;24;36m',
538
+ headerBg: '\x1b[48;2;16;52;72m', headerFg: '\x1b[38;2;224;242;254m',
539
+ bodyBg: '\x1b[48;2;10;34;48m', bodyFg: '\x1b[38;2;186;230;253m',
540
+ icon: '◆', defaultBadge: 'AUTO-START',
541
+ },
542
+ model: {
543
+ badgeBg: '\x1b[48;2;234;179;8m', badgeFg: '\x1b[38;2;30;24;8m',
544
+ headerBg: '\x1b[48;2;58;44;14m', headerFg: '\x1b[38;2;254;249;195m',
545
+ bodyBg: '\x1b[48;2;38;30;10m', bodyFg: '\x1b[38;2;254;240;138m',
546
+ icon: '✦', defaultBadge: 'MODEL READY',
547
+ },
548
+ chat_ready: {
549
+ badgeBg: '\x1b[48;2;234;179;8m', badgeFg: '\x1b[38;2;30;24;8m',
550
+ headerBg: '\x1b[48;2;58;44;14m', headerFg: '\x1b[38;2;254;249;195m',
551
+ bodyBg: '\x1b[48;2;38;30;10m', bodyFg: '\x1b[38;2;254;240;138m',
552
+ icon: '✦', defaultBadge: 'MODEL READY',
553
+ },
554
+ catalog: {
555
+ badgeBg: '\x1b[48;2;234;88;12m', badgeFg: '\x1b[38;2;30;12;6m',
556
+ headerBg: '\x1b[48;2;64;28;12m', headerFg: '\x1b[38;2;255;237;213m',
557
+ bodyBg: '\x1b[48;2;42;20;10m', bodyFg: '\x1b[38;2;254;215;170m',
558
+ icon: '◆', defaultBadge: 'CATALOG-ONLY',
559
+ },
560
+ catalog_only: {
561
+ badgeBg: '\x1b[48;2;234;88;12m', badgeFg: '\x1b[38;2;30;12;6m',
562
+ headerBg: '\x1b[48;2;64;28;12m', headerFg: '\x1b[38;2;255;237;213m',
563
+ bodyBg: '\x1b[48;2;42;20;10m', bodyFg: '\x1b[38;2;254;215;170m',
564
+ icon: '◆', defaultBadge: 'CATALOG-ONLY',
565
+ },
566
+ mcp: {
567
+ badgeBg: '\x1b[48;2;168;85;247m', badgeFg: '\x1b[38;2;26;10;36m',
568
+ headerBg: '\x1b[48;2;52;22;68m', headerFg: '\x1b[38;2;243;232;255m',
569
+ bodyBg: '\x1b[48;2;34;14;46m', bodyFg: '\x1b[38;2;233;213;255m',
570
+ icon: '●', defaultBadge: 'MCP READY',
571
+ },
572
+ warning: {
573
+ badgeBg: '\x1b[48;2;245;158;11m', badgeFg: '\x1b[38;2;28;20;8m',
574
+ headerBg: '\x1b[48;2;60;40;14m', headerFg: '\x1b[38;2;254;243;199m',
575
+ bodyBg: '\x1b[48;2;42;28;10m', bodyFg: '\x1b[38;2;253;230;138m',
576
+ icon: '!', defaultBadge: 'WARNING',
577
+ },
578
+ warn: {
579
+ badgeBg: '\x1b[48;2;245;158;11m', badgeFg: '\x1b[38;2;28;20;8m',
580
+ headerBg: '\x1b[48;2;60;40;14m', headerFg: '\x1b[38;2;254;243;199m',
581
+ bodyBg: '\x1b[48;2;42;28;10m', bodyFg: '\x1b[38;2;253;230;138m',
582
+ icon: '!', defaultBadge: 'WARNING',
583
+ },
584
+ error: {
585
+ badgeBg: '\x1b[48;2;239;68;68m', badgeFg: '\x1b[38;2;255;255;255m',
586
+ headerBg: '\x1b[48;2;68;18;24m', headerFg: '\x1b[38;2;254;226;226m',
587
+ bodyBg: '\x1b[48;2;46;12;16m', bodyFg: '\x1b[38;2;254;202;202m',
588
+ icon: '✕', defaultBadge: 'ERROR',
589
+ },
590
+ failed: {
591
+ badgeBg: '\x1b[48;2;239;68;68m', badgeFg: '\x1b[38;2;255;255;255m',
592
+ headerBg: '\x1b[48;2;68;18;24m', headerFg: '\x1b[38;2;254;226;226m',
593
+ bodyBg: '\x1b[48;2;46;12;16m', bodyFg: '\x1b[38;2;254;202;202m',
594
+ icon: '✕', defaultBadge: 'ERROR',
595
+ },
596
+ info: {
597
+ badgeBg: '\x1b[48;2;56;189;248m', badgeFg: '\x1b[38;2;8;24;36m',
598
+ headerBg: '\x1b[48;2;16;50;70m', headerFg: '\x1b[38;2;224;242;254m',
599
+ bodyBg: '\x1b[48;2;10;32;46m', bodyFg: '\x1b[38;2;186;230;253m',
600
+ icon: '◆', defaultBadge: 'INFO',
601
+ },
602
+ });
603
+
604
+ export function formatRichCardLines({
605
+ title = '',
606
+ badge = '',
607
+ status = null,
608
+ lines = [],
609
+ width,
610
+ indent = ' ',
611
+ footer = '',
612
+ } = {}) {
613
+ const normStatus = String(status || 'info').toLowerCase();
614
+ const st = STATUS_THEMES[normStatus] || STATUS_THEMES.info;
615
+ const termWidth = width || getFullWidth();
616
+ const blockWidth = Math.max(30, Math.min(84, termWidth - getStringDisplayWidth(indent) - 2));
617
+ const rows = [];
618
+
619
+ const icon = st.icon;
620
+ const badgeLabel = badge || st.defaultBadge;
621
+ const badgePart = `${st.badgeBg}${st.badgeFg}${C.bold} ${icon} ${badgeLabel} ${C.reset}`;
622
+ const titlePart = title ? ` ${title}` : '';
623
+ const headerLeftVisual = getStringDisplayWidth(` ${icon} ${badgeLabel} `) + getStringDisplayWidth(titlePart);
624
+ const headerPad = Math.max(0, blockWidth - headerLeftVisual);
625
+ const headerRow = `${indent}${badgePart}${st.headerBg}${st.headerFg}${C.bold}${titlePart}${' '.repeat(headerPad)}${C.reset}`;
626
+ rows.push(headerRow);
627
+
628
+ const innerContentWidth = blockWidth - 4;
629
+ for (const raw of (lines || [])) {
630
+ const text = typeof raw === 'string' ? raw : (raw?.text ?? '');
631
+ const sublines = String(text).split('\n');
632
+ for (const subline of sublines) {
633
+ const wrapped = wrapText(subline, innerContentWidth);
634
+ for (const wLine of (wrapped.length > 0 ? wrapped : [''])) {
635
+ const fitted = fitStyledText(wLine, innerContentWidth);
636
+ const pad = Math.max(0, innerContentWidth - getStringDisplayWidth(fitted));
637
+ const lineRow = `${indent}${st.bodyBg}${st.bodyFg} ${fitted}${' '.repeat(pad)} ${C.reset}`;
638
+ rows.push(lineRow);
639
+ }
640
+ }
425
641
  }
426
- console.log('');
642
+
643
+ if (footer) {
644
+ const footerText = fitStyledText(footer, innerContentWidth);
645
+ const footerPad = Math.max(0, innerContentWidth - getStringDisplayWidth(footerText));
646
+ const footerRow = `${indent}${st.bodyBg}${C.dim}${st.bodyFg} ${footerText}${' '.repeat(footerPad)} ${C.reset}`;
647
+ rows.push(footerRow);
648
+ }
649
+
650
+ return rows;
427
651
  }
428
652
 
653
+ export function printRichCard(options) {
654
+ const rows = formatRichCardLines(options);
655
+ console.log(`${rows.join('\n')}\n`);
656
+ }
657
+
658
+ export function printRichStatusCard({ status, title, lines = [], footer = '', width } = {}) {
659
+ const rows = formatRichCardLines({
660
+ title,
661
+ status,
662
+ lines,
663
+ footer,
664
+ width,
665
+ });
666
+ console.log(`\n${rows.join('\n')}\n`);
667
+ }
668
+
669
+ export function formatRichTable(headers = [], rows = [], maxWidth = getFullWidth() - 4) {
670
+ if (!headers.length && !rows.length) return [];
671
+ const colCount = Math.max(headers.length, ...rows.map(r => r.length));
672
+ const colWidths = new Array(colCount).fill(3);
673
+
674
+ for (let c = 0; c < colCount; c++) {
675
+ const headerLen = getStringDisplayWidth(headers[c] || '');
676
+ colWidths[c] = Math.max(colWidths[c], headerLen);
677
+ for (const row of rows) {
678
+ const cellLen = getStringDisplayWidth(row[c] || '');
679
+ colWidths[c] = Math.max(colWidths[c], cellLen);
680
+ }
681
+ }
682
+
683
+ const totalVisualWidth = colWidths.reduce((sum, w) => sum + w + 3, 1);
684
+ if (totalVisualWidth > maxWidth) {
685
+ const available = Math.max(20, maxWidth - colCount * 3 - 1);
686
+ const sumWidths = colWidths.reduce((a, b) => a + b, 0);
687
+ for (let c = 0; c < colCount; c++) {
688
+ colWidths[c] = Math.max(6, Math.floor((colWidths[c] / sumWidths) * available));
689
+ }
690
+ }
691
+
692
+ const output = [];
693
+ const topBorder = ` ┌─${colWidths.map(w => '─'.repeat(w)).join('─┬─')}─┐`;
694
+ const midBorder = ` ├─${colWidths.map(w => '─'.repeat(w)).join('─┼─')}─┤`;
695
+ const botBorder = ` └─${colWidths.map(w => '─'.repeat(w)).join('─┴─')}─┘`;
696
+
697
+ output.push(`${C.slate}${topBorder}${C.reset}`);
698
+
699
+ if (headers.length > 0) {
700
+ const headerCells = [];
701
+ for (let c = 0; c < colCount; c++) {
702
+ const text = fitStyledText(headers[c] || '', colWidths[c]);
703
+ const pad = Math.max(0, colWidths[c] - getStringDisplayWidth(text));
704
+ headerCells.push(`${C.bold}${C.gold}${text}${C.reset}${' '.repeat(pad)}`);
705
+ }
706
+ output.push(` ${C.slate}│${C.reset} ${headerCells.join(` ${C.slate}│${C.reset} `)} ${C.slate}│${C.reset}`);
707
+ output.push(`${C.slate}${midBorder}${C.reset}`);
708
+ }
709
+
710
+ for (const row of rows) {
711
+ const rowCells = [];
712
+ for (let c = 0; c < colCount; c++) {
713
+ const cellRaw = row[c] || '—';
714
+ const text = fitStyledText(cellRaw, colWidths[c]);
715
+ const pad = Math.max(0, colWidths[c] - getStringDisplayWidth(text));
716
+ rowCells.push(`${C.white}${text}${C.reset}${' '.repeat(pad)}`);
717
+ }
718
+ output.push(` ${C.slate}│${C.reset} ${rowCells.join(` ${C.slate}│${C.reset} `)} ${C.slate}│${C.reset}`);
719
+ }
429
720
 
721
+ output.push(`${C.slate}${botBorder}${C.reset}`);
722
+ return output;
723
+ }
430
724
 
431
725
  export function formatTerminalMarkdown(text) {
432
726
  if (!text) return '';
433
727
  return text
434
728
  .replace(/^###\s*(.+)$/gm, `${C.bold}${C.gold}✦ $1${C.reset}`)
435
- .replace(/^##\s*(.+)$/gm, `\n${C.bold}${C.roseGlow}$1${C.reset}`)
729
+ .replace(/^##\s*(.+)$/gm, `\n${C.bold}${C.roseGlow}■ $1${C.reset}`)
436
730
  .replace(/^#\s*(.+)$/gm, `\n${C.bold}${C.sakura}━━━ $1 ━━━${C.reset}`)
731
+ .replace(/^>\s*(.+)$/gm, ` ${C.slate}│${C.reset} ${C.italic}${C.white}$1${C.reset}`)
437
732
  .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, `${C.bold}${C.cyberCyan}$1${C.reset} ➔ ${C.underline}${C.roseGlow}$2${C.reset}`)
438
733
  .replace(/\*{3}([^*]+)\*{3}/g, `${C.bold}${C.italic}${C.white}$1${C.reset}`)
439
734
  .replace(/\*\*([^*]+)\*\*/g, `${C.bold}${C.white}$1${C.reset}`)
@@ -443,7 +738,6 @@ export function formatTerminalMarkdown(text) {
443
738
  .replace(/^\s*[-*_]{3,}\s*$/gm, ` ${C.slate}${'─'.repeat(Math.max(20, getFullWidth() - 6))}${C.reset}`);
444
739
  }
445
740
 
446
-
447
741
  export function parseMarkdownTableRow(text) {
448
742
  const trimmed = String(text || '').trim();
449
743
  if (!trimmed.includes('|')) return null;
@@ -458,7 +752,6 @@ export function parseMarkdownTableRow(text) {
458
752
  return cells.length >= 2 ? cells : null;
459
753
  }
460
754
 
461
-
462
755
  export function isMarkdownTableSeparator(cells) {
463
756
  return Array.isArray(cells)
464
757
  && cells.length > 0
@@ -472,23 +765,112 @@ export function expandMarkdownLinks(text) {
472
765
  );
473
766
  }
474
767
 
768
+ export function formatMarkdownLines(text, maxWidth = getFullWidth() - 8) {
769
+ if (!text) return [];
770
+ const lines = String(text).split('\n');
771
+ const result = [];
772
+ let tableHeaders = null;
773
+ let tableRows = [];
774
+
775
+ const flushTable = () => {
776
+ if (tableHeaders) {
777
+ if (tableRows.length > 0) {
778
+ const rendered = formatRichTable(tableHeaders, tableRows, maxWidth);
779
+ result.push(...rendered);
780
+ } else {
781
+ result.push(formatTerminalMarkdown(`| ${tableHeaders.join(' | ')} |`));
782
+ }
783
+ tableHeaders = null;
784
+ tableRows = [];
785
+ }
786
+ };
787
+
788
+ let pendingHeader = null;
789
+
790
+ for (let i = 0; i < lines.length; i++) {
791
+ const rawLine = lines[i];
792
+ const cells = parseMarkdownTableRow(rawLine);
793
+
794
+ if (pendingHeader) {
795
+ if (cells && cells.length === pendingHeader.length && isMarkdownTableSeparator(cells)) {
796
+ tableHeaders = pendingHeader.map(c => c.replace(/\*\*/g, '').replace(/`/g, ''));
797
+ pendingHeader = null;
798
+ tableRows = [];
799
+ continue;
800
+ } else {
801
+ result.push(formatTerminalMarkdown(expandMarkdownLinks(`| ${pendingHeader.join(' | ')} |`)));
802
+ pendingHeader = null;
803
+ }
804
+ }
805
+
806
+ if (tableHeaders) {
807
+ if (cells && cells.length === tableHeaders.length && !isMarkdownTableSeparator(cells)) {
808
+ tableRows.push(cells.map(c => c.replace(/\*\*/g, '').replace(/`/g, '')));
809
+ continue;
810
+ } else {
811
+ flushTable();
812
+ }
813
+ }
814
+
815
+ if (cells && !isMarkdownTableSeparator(cells)) {
816
+ pendingHeader = cells;
817
+ continue;
818
+ }
819
+
820
+ const formatted = formatTerminalMarkdown(expandMarkdownLinks(rawLine));
821
+ result.push(formatted);
822
+ }
823
+
824
+ if (pendingHeader) {
825
+ result.push(formatTerminalMarkdown(expandMarkdownLinks(`| ${pendingHeader.join(' | ')} |`)));
826
+ }
827
+ flushTable();
828
+
829
+ return result;
830
+ }
831
+
475
832
  export function getWelcomeHeroLines() {
833
+ const p = C.sakura;
834
+ const r = C.roseGlow;
835
+ const g = C.gold;
836
+ const c = C.cyberCyan;
837
+ const l = C.lavender;
838
+ const e = C.emerald;
839
+ const s = C.slate;
840
+ const w = C.white;
841
+ const b = C.bold;
842
+ const d = C.dim;
843
+ const rst = C.reset;
844
+ const boxWidth = Math.min(84, Math.max(50, getFullWidth() - 4));
845
+ const headerTitle = 'Suggested Topics & Queries';
846
+ const topDashCount = Math.max(2, boxWidth - getStringDisplayWidth(headerTitle) - 6);
847
+ const innerWidth = boxWidth - 2;
848
+
849
+ const padItem = (icon, tag, text) => {
850
+ const tagWidth = 14;
851
+ const tagPadded = tag + ' '.repeat(Math.max(0, tagWidth - getStringDisplayWidth(tag)));
852
+ const visualContent = 2 + 2 + tagWidth + 2 + getStringDisplayWidth(text);
853
+ const pad = Math.max(0, innerWidth - visualContent);
854
+ return ` ${s}│${rst} ${icon} ${tag}${rst}${' '.repeat(Math.max(0, tagWidth - getStringDisplayWidth(tag)))} ${w}${text}${rst}${' '.repeat(pad)}${s}│${rst}`;
855
+ };
856
+
476
857
  return [
477
858
  '',
478
- ` ${C.sakura}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${C.reset} ${C.bold}${C.white}Kiku AI Terminal Agent${C.reset}`,
479
- ` ${C.sakura}██║ ██╔╝██║██║ ██╔╝██║ ██║ ██╔══██╗██║${C.reset} ${C.gold}v1.0.4${C.reset} ${C.slate}• Intelligent Fan Portal${C.reset}`,
480
- ` ${C.roseGlow}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${C.reset} ${C.cyberCyan}SSOT • DAG • Dynamic Tools${C.reset}`,
481
- ` ${C.roseGlow}██╔═██╗ ██║██╔═██╗ ██║ ██║ ██╔══██╗██║${C.reset} ${C.emerald}LM Studio / Cloud Providers${C.reset}`,
482
- ` ${C.sakura}██║ ╚██╗██║██║ ╚██╗╚██████╔╝ ██║ ██║██║${C.reset}`,
483
- ` ${C.sakura}╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝${C.reset}`,
859
+ ` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst} ${b}${p}◆ Ju Jingyi • 鞠婧祎 • Mìjú Terminal Portal ◆${rst}`,
860
+ ` ${p}██║ ██╔╝██║██║ ██╔╝██║ ██║ ██╔══██╗██║${rst} ${d}${s}Interactive Fan Knowledge Base & Autonomous Agent${rst}`,
861
+ ` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst} ${d}${s}Filmography • Discography • Fashion & Awards${rst}`,
862
+ ` ${r}██╔═██╗ ██║██╔═██╗ ██║ ██║ ██╔══██╗██║${rst} ${c}● Live Ready${rst} ${s}• Multilingual (CN / EN / TH)${rst}`,
863
+ ` ${p}██║ ╚██╗██║██║ ╚██╗╚██████╔╝ ██║ ██║██║${rst}`,
864
+ ` ${p}╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝${rst}`,
484
865
  '',
485
- ` ${C.bold}${C.gold}Suggested Queries & Topics:${C.reset}`,
486
- ` ${C.sakura}• 2026 Works${C.reset} ${C.white}"What are Ju Jingyi's 2026 upcoming dramas?"${C.reset}`,
487
- ` ${C.cyberCyan}• Music & OST${C.reset} ${C.white}"OSTs from In Blossom & Ruyi Pavilion, /play Ginkgo"${C.reset}`,
488
- ` ${C.emerald}• Endorsements${C.reset} ${C.white}"Versace Global Ambassador & fashion partnerships"${C.reset}`,
489
- ` ${C.lavender}• Tools / MCP${C.reset} ${C.white}"@search_works 2026, /mcp list, /stats, /help"${C.reset}`,
866
+ ` ${s}╭──${rst} ${b}${g}${headerTitle}${rst} ${s}${'─'.repeat(topDashCount)}╮${rst}`,
867
+ padItem(p + '◆', '2026 Works', '"What are Ju Jingyi\'s 2026 upcoming dramas?"'),
868
+ padItem(c + '◆', 'Music & OST', '"OSTs from In Blossom & Ruyi Pavilion, /play Ginkgo"'),
869
+ padItem(e + '◆', 'Endorsements', '"Versace Global Ambassador & fashion partnerships"'),
870
+ padItem(l + '◆', 'Tools / MCP', '"@search_works 2026, /mcp list, /stats, /help"'),
871
+ ` ${s}╰${'─'.repeat(boxWidth)}╯${rst}`,
490
872
  '',
491
- ` ${C.slate}Shortcuts: [Tab] Autocomplete • [↑/↓] History • [PgUp/PgDn] Scroll • [F2] Details • [Ctrl+C] Exit${C.reset}`,
873
+ ` ${s}Shortcuts: ${b}${w}[Tab]${rst} ${s}Autocomplete • ${b}${w}[↑/↓]${rst} ${s}History • ${b}${w}[PgUp/PgDn]${rst} ${s}Scroll • ${b}${w}[F2]${rst} ${s}Details • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
492
874
  '',
493
875
  ];
494
876
  }
@@ -502,6 +884,7 @@ export function printWelcomeHero() {
502
884
 
503
885
 
504
886
  export function setupOutputInterception(tuiPrompt, _isProcessingFn) {
887
+ activeTuiPrompt = tuiPrompt;
505
888
  let isTuiWriting = false;
506
889
  let isReplaying = false;
507
890
  let resizeTimer = null;
@@ -558,7 +941,7 @@ export function setupOutputInterception(tuiPrompt, _isProcessingFn) {
558
941
  if (tuiPrompt && tuiPrompt.screenBuffer && !tuiPrompt.isDisposed && !isTuiWriting) {
559
942
  isTuiWriting = true;
560
943
  try {
561
- const text = args.map(a => (typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a))).join(' ');
944
+ const text = args.length === 0 ? '' : args.map(a => (typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a))).join(' ');
562
945
  tuiPrompt.addViewportLines(text);
563
946
  } finally {
564
947
  isTuiWriting = false;
@@ -579,6 +962,7 @@ export function setupOutputInterception(tuiPrompt, _isProcessingFn) {
579
962
  };
580
963
 
581
964
  return () => {
965
+ activeTuiPrompt = null;
582
966
  process.stdout.removeListener('resize', onResize);
583
967
  process.stdout.write = _rawStdoutWrite;
584
968
  console.log = _rawConsoleLog;
@@ -646,16 +1030,34 @@ export function formatTaskPlanBanner(plan) {
646
1030
 
647
1031
  export function enterAlternateScreen() {
648
1032
  if (process.stdout.isTTY) {
649
- _rawStdoutWrite('\x1b[?1049h\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?25l\x1b[H\x1b[2J');
1033
+ _rawStdoutWrite('\x1b[?1049h\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?1015l\x1b[?25l\x1b[H\x1b[2J');
650
1034
  }
651
1035
  }
652
1036
 
653
1037
  export function exitAlternateScreen() {
654
1038
  if (process.stdout.isTTY) {
655
- _rawStdoutWrite('\x1b[?1006l\x1b[?1005l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1049l\x1b[?25h\x1b[0m\r\n');
1039
+ _rawStdoutWrite('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?1015l\x1b[?1049l\x1b[?25h\x1b[0m\r\n');
656
1040
  }
657
1041
  }
658
1042
 
1043
+ export function getTuiViewportLayout(termRows = 24) {
1044
+ const parsedRows = Number(termRows);
1045
+ const rows = Math.max(10, Number.isFinite(parsedRows) ? Math.floor(parsedRows) : 24);
1046
+ const viewportStartRow = 1;
1047
+ const statusBarRow = rows - 4;
1048
+ const promptRow = rows - 2;
1049
+ const separatorRow = rows - 1;
1050
+
1051
+ return {
1052
+ termRows: rows,
1053
+ viewportStartRow,
1054
+ viewportHeight: Math.max(1, statusBarRow - viewportStartRow),
1055
+ statusBarRow,
1056
+ promptRow,
1057
+ separatorRow,
1058
+ };
1059
+ }
1060
+
659
1061
  export class VirtualScreenBuffer {
660
1062
  constructor(width = 80, height = 24, writer = null) {
661
1063
  this.width = Math.max(10, width);