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
JavaScript
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');
}
}