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.
- package/README.md +25 -2
- package/package.json +12 -3
- package/server/agent/agentCore.js +164 -55
- package/server/agent/cli.js +1300 -695
- package/server/agent/commandRegistry.js +436 -74
- package/server/agent/commands.js +629 -63
- package/server/agent/doctor.js +7 -6
- package/server/agent/mcpClient.js +436 -364
- package/server/agent/queryDetails.js +182 -0
- package/server/agent/sanitizer.js +4 -4
- package/server/agent/subagent.js +1 -0
- package/server/agent/suggestions.js +346 -0
- package/server/agent/theme.js +12 -12
- package/server/agent/tools.js +118 -7
- package/server/agent/ui.js +433 -31
- package/server/agent/updateManager.js +438 -0
- package/server/agent/version.js +10 -0
- package/server/mcp/data/brands.js +9 -4
- package/server/mcp/server.js +2 -1
- package/src/data/brandsData.js +65 -0
package/server/agent/ui.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
401
|
-
const pad = Math.max(0, w - getStringDisplayWidth(fitted));
|
|
424
|
+
const wrappedTitle = wrapText(rawTitle, maxContentWidth);
|
|
402
425
|
const hBg = headerBg || C.bgPanelHeader;
|
|
403
|
-
|
|
404
|
-
|
|
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
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
` ${
|
|
479
|
-
` ${
|
|
480
|
-
` ${
|
|
481
|
-
` ${
|
|
482
|
-
` ${
|
|
483
|
-
` ${
|
|
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
|
-
` ${
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
` ${
|
|
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[?
|
|
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);
|