UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

164 lines (163 loc) 6.85 kB
import wrapAnsi from 'wrap-ansi'; import { themeManager } from '../themes.js'; export class MarkdownRenderer { static MAX_WIDTH = process.stdout.columns || 80; static CONTENT_WIDTH = Math.min(MarkdownRenderer.MAX_WIDTH - 4, 76); static render(text) { let output = text; output = this.renderHeaders(output); output = this.renderCodeBlocks(output); output = this.renderInlineCode(output); output = this.renderBold(output); output = this.renderItalic(output); output = this.renderLinks(output); output = this.renderLists(output); output = this.renderBlockquotes(output); output = this.renderHorizontalRules(output); output = this.renderTables(output); return output; } static renderHeaders(text) { const colors = themeManager.getColors(); text = text.replace(/^# (.+)$/gm, (_, content) => '\n' + colors.heading1(content.toUpperCase()) + '\n'); text = text.replace(/^## (.+)$/gm, (_, content) => '\n' + colors.heading2(content) + '\n'); text = text.replace(/^### (.+)$/gm, (_, content) => colors.heading3(content)); text = text.replace(/^#{4,6} (.+)$/gm, (_, content) => colors.bold(content)); return text; } static renderCodeBlocks(text) { const colors = themeManager.getColors(); const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g; text = text.replace(codeBlockRegex, (_, language, code) => { const lang = language ? colors.textMuted(`[${language}]`) : ''; const border = colors.border('─'.repeat(40)); const indentedCode = code .split('\n') .map((line) => ' ' + colors.code(line)) .join('\n'); return `${lang}\n${border}\n${indentedCode}\n${border}`; }); return text; } static renderInlineCode(text) { const colors = themeManager.getColors(); return text.replace(/`([^`]+)`/g, (_, code) => colors.code(` ${code} `)); } static renderBold(text) { const colors = themeManager.getColors(); text = text.replace(/\*\*([^*]+)\*\*/g, (_, content) => colors.bold(content)); text = text.replace(/__([^_]+)__/g, (_, content) => colors.bold(content)); return text; } static renderItalic(text) { const colors = themeManager.getColors(); text = text.replace(/(?<!\*)\*(?!\*)([^*]+)(?<!\*)\*(?!\*)/g, (_, content) => colors.italic(content)); text = text.replace(/(?<!_)_(?!_)([^_]+)(?<!_)_(?!_)/g, (_, content) => colors.italic(content)); return text; } static renderLinks(text) { const colors = themeManager.getColors(); text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, linkText, url) => colors.link(linkText) + colors.textMuted(` (${url})`)); text = text.replace(/(?<![[(])(https?:\/\/[^\s)]+)/g, url => colors.link(url)); return text; } static renderLists(text) { const colors = themeManager.getColors(); text = text.replace(/^[*\-+] (.+)$/gm, (_, content) => colors.listMarker(' •') + ' ' + content); text = text.replace(/^\d+\. (.+)$/gm, (match, content) => { const number = match.match(/^(\d+)/)?.[1] || '1'; return colors.listMarker(` ${number}.`) + ' ' + content; }); text = text.replace(/^ {2}[*\-+] (.+)$/gm, (_, content) => colors.textMuted(' ◦') + ' ' + content); return text; } static renderBlockquotes(text) { const lines = text.split('\n'); const processed = []; let inBlockquote = false; let blockquoteLines = []; for (const line of lines) { if (line.startsWith('>')) { inBlockquote = true; blockquoteLines.push(line.substring(1).trim()); } else if (inBlockquote && line.trim() === '') { blockquoteLines.push(''); } else { if (inBlockquote) { const colors = themeManager.getColors(); const quoted = blockquoteLines .map(l => colors.border('│ ') + colors.blockquote(l)) .join('\n'); processed.push(quoted); blockquoteLines = []; inBlockquote = false; } processed.push(line); } } if (inBlockquote && blockquoteLines.length > 0) { const colors = themeManager.getColors(); const quoted = blockquoteLines .map(l => colors.border('│ ') + colors.blockquote(l)) .join('\n'); processed.push(quoted); } return processed.join('\n'); } static renderHorizontalRules(text) { const colors = themeManager.getColors(); const hr = colors.border('─'.repeat(Math.min(50, this.CONTENT_WIDTH))); text = text.replace(/^[-*_]{3,}$/gm, hr); return text; } static renderTables(text) { const lines = text.split('\n'); const processed = []; let i = 0; while (i < lines.length) { if (i + 2 < lines.length && lines[i].includes('|') && lines[i + 1].match(/^\|?[\s\-:|]+\|?$/) && lines[i + 2].includes('|')) { const tableRows = [lines[i]]; i += 2; while (i < lines.length && lines[i].includes('|')) { tableRows.push(lines[i]); i++; } processed.push(this.renderTableRows(tableRows)); } else { processed.push(lines[i]); i++; } } return processed.join('\n'); } static renderTableRows(rows) { const colors = themeManager.getColors(); const processedRows = rows.map(row => { const cells = row .split('|') .map(cell => cell.trim()) .filter(cell => cell.length > 0); return '│ ' + cells.map(cell => colors.text(cell)).join(' │ ') + ' │'; }); const width = Math.min(70, this.CONTENT_WIDTH); const border = colors.border('─'.repeat(width)); return border + '\n' + processedRows.join('\n') + '\n' + border; } static renderWrapped(text) { const rendered = this.render(text); const lines = rendered.split('\n'); const wrapped = lines.map(line => { if (line.includes('│') || line.startsWith(' ')) { return line; } return wrapAnsi(line, this.CONTENT_WIDTH); }); return wrapped.join('\n'); } }