UNPKG

bugger-mcp

Version:

MCP Server for managing bugs, feature requests, and improvements

312 lines 12.2 kB
import chalk from 'chalk'; // Disable colors in MCP environment since they show as codes chalk.level = 0; // Color and formatting utilities for MCP output - simplified without colors const colors = { // Status colors - no colors, just return text red: (text) => text, green: (text) => text, yellow: (text) => text, blue: (text) => text, orange: (text) => text, purple: (text) => text, // Priority colors - no colors, just return text critical: (text) => text, high: (text) => text, medium: (text) => text, low: (text) => text, // General formatting - no colors, just return text highlight: (text) => text, success: (text) => text, info: (text) => text, warning: (text) => text, error: (text) => text }; /** * Unified table formatter for consistent output across all tools */ export function formatTable(data, columns, options = {}) { if (data.length === 0) { return "No items found."; } const { includeHeaders = true, maxContentLength = 50 } = options; // Calculate column widths const columnWidths = columns.map(col => { const headerWidth = col.header.length; const contentWidth = Math.max(...data.map(item => { const value = col.formatter ? col.formatter(item[col.key]) : String(item[col.key] || ''); return Math.min(value.length, maxContentLength); })); return Math.max(headerWidth, contentWidth, col.width || 0); }); let output = ''; // Add headers if (includeHeaders) { const headerRow = columns.map((col, i) => { const padding = columnWidths[i] - col.header.length; return col.header + ' '.repeat(Math.max(0, padding)); }).join(' | '); const separator = columns.map((_, i) => '-'.repeat(columnWidths[i])).join('--|--'); output += headerRow + '\n'; output += separator + '\n'; } // Add data rows data.forEach(item => { const row = columns.map((col, i) => { let value = col.formatter ? col.formatter(item[col.key]) : String(item[col.key] || ''); // Truncate long content if (value.length > maxContentLength) { value = value.substring(0, maxContentLength - 3) + '...'; } const padding = columnWidths[i] - value.length; const align = col.align || 'left'; switch (align) { case 'right': return ' '.repeat(Math.max(0, padding)) + value; case 'center': const leftPad = Math.floor(padding / 2); const rightPad = padding - leftPad; return ' '.repeat(leftPad) + value + ' '.repeat(rightPad); default: // left return value + ' '.repeat(Math.max(0, padding)); } }).join(' | '); output += row + '\n'; }); return output; } /** * Format error message consistently */ export function formatError(message, context) { let output = colors.error(`Error: ${message}`); if (context) { output += `\n${colors.info(`Context: ${context}`)}`; } return output; } /** * Format success message consistently */ export function formatSuccess(message, details) { let output = colors.success(message); if (details) { output += `\n${colors.info(details)}`; } return output; } /** * Format metadata consistently */ export function formatMetadata(metadata) { let output = ''; if (metadata.total !== undefined) { output += `Total: ${metadata.total}`; if (metadata.showing !== undefined && metadata.showing !== metadata.total) { output += ` (showing ${metadata.showing})`; } output += '\n'; } if (metadata.offset !== undefined && metadata.offset > 0) { output += `Results ${metadata.offset + 1}-${metadata.offset + (metadata.showing || metadata.total)}\n`; } return output; } /** * Format token usage consistently */ export function formatTokenUsage(usage) { return `\nToken usage: ${usage.total} tokens (${usage.input} input, ${usage.output} output)`; } /** * Get priority formatter */ export function getPriorityFormatter() { return (priority) => { switch (priority?.toLowerCase()) { case 'critical': return colors.critical(priority); case 'high': return colors.high(priority); case 'medium': return colors.medium(priority); case 'low': return colors.low(priority); default: return priority || ''; } }; } /** * Get status formatter with colors */ export function getStatusFormatter() { return (status) => { const statusLower = status?.toLowerCase() || ''; if (statusLower.includes('open') || statusLower === 'proposed') return chalk.red(status); else if (statusLower.includes('progress') || statusLower.includes('development')) return chalk.yellow(status); else if (statusLower.includes('fixed') || statusLower.includes('completed') || statusLower === 'done') return chalk.green(status); else if (statusLower.includes('closed') || statusLower === 'rejected') return chalk.gray(status); else if (statusLower.includes('blocked')) return chalk.red.bold(status); else if (statusLower.includes('discussion') || statusLower.includes('research')) return chalk.blue(status); else if (statusLower.includes('approved')) return chalk.green.bold(status); return status; }; } export function formatBugs(bugs) { const columns = [ { key: 'id', header: 'ID', width: 8 }, { key: 'status', header: 'Status', formatter: getStatusFormatter() }, { key: 'priority', header: 'Priority', formatter: getPriorityFormatter() }, { key: 'component', header: 'Component' }, { key: 'dateReported', header: 'Date', width: 10 }, { key: 'title', header: 'Title' } ]; return formatTable(bugs, columns); } export function formatFeatureRequests(features) { const columns = [ { key: 'id', header: 'ID', width: 8 }, { key: 'status', header: 'Status', formatter: getStatusFormatter() }, { key: 'priority', header: 'Priority', formatter: getPriorityFormatter() }, { key: 'category', header: 'Category' }, { key: 'dateRequested', header: 'Date', width: 10 }, { key: 'title', header: 'Title' } ]; return formatTable(features, columns); } export function formatImprovements(improvements) { const columns = [ { key: 'id', header: 'ID', width: 8 }, { key: 'status', header: 'Status', formatter: getStatusFormatter() }, { key: 'priority', header: 'Priority', formatter: getPriorityFormatter() }, { key: 'category', header: 'Category' }, { key: 'dateRequested', header: 'Date', width: 10 }, { key: 'title', header: 'Title' } ]; return formatTable(improvements, columns); } export function formatImprovementsWithContext(improvements) { if (improvements.length === 0) { return "No improvements found."; } let output = formatImprovements(improvements); // Add code context for each improvement improvements.forEach(improvement => { if (improvement.codeContext && improvement.codeContext.length > 0) { output += `\n${colors.highlight(`Code Context for ${improvement.id} - ${improvement.title}`)}\n`; output += `${colors.info('Description:')} ${improvement.description}\n`; output += `${colors.info('Current State:')} ${improvement.currentState}\n`; output += `${colors.info('Desired State:')} ${improvement.desiredState}\n\n`; improvement.codeContext.forEach((context) => { output += `${colors.highlight(`File: ${context.file}`)}\n`; if (context.error) { output += `${colors.error(context.error)}\n\n`; } else { // Show token-optimized content display const contentLength = context.content?.length || 0; const estimatedTokens = Math.ceil(contentLength / 4); output += `${colors.info(`Content (${contentLength} chars, ~${estimatedTokens} tokens)`)}\n`; output += '```\n'; output += context.content; output += '\n```\n\n'; } }); } }); return output; } export function formatSearchResults(results, metadata) { if (results.length === 0) { return "No items found."; } let output = ''; // Add metadata header if provided if (metadata) { output += `Search Results (${metadata.showing} of ${metadata.total} total)\n`; if (metadata.offset > 0) { output += `Showing results ${metadata.offset + 1}-${metadata.offset + metadata.showing}\n`; } output += '\n'; } const formattedResults = results.map(item => { switch (item.type) { case 'bug': return formatBugs([item]); case 'feature': return formatFeatureRequests([item]); case 'improvement': return formatImprovements([item]); default: return JSON.stringify(item, null, 2); } }).join('\n\n'); return output + formattedResults; } export function formatBulkUpdateResults(results, type) { const successCount = results.filter(r => r.status === 'success').length; const errorCount = results.filter(r => r.status === 'error').length; let output = ''; if (successCount > 0) { const successfulResults = results.filter(r => r.status === 'success'); output += `${successfulResults.length} ${type} updated successfully:\n\n`; // Create table-like format const longestId = Math.max(...successfulResults.map(r => r.bugId?.length || r.featureId?.length || r.improvementId?.length || 0)); const longestStatus = Math.max(...successfulResults.map(r => r.message?.replace('Updated to ', '').length || 0)); successfulResults.forEach(r => { const itemId = r.bugId || r.featureId || r.improvementId || ''; const status = r.message?.replace('Updated to ', '') || ''; const date = r.dateCompleted || new Date().toISOString().split('T')[0]; output += `${itemId.padEnd(longestId)} | ${status.padEnd(longestStatus)} | ${date}\n`; }); if (errorCount > 0) { output += '\n'; } } if (errorCount > 0) { output += `${errorCount} ${type} failed to update:\n\n`; results.filter(r => r.status === 'error').forEach(r => { const itemId = r.bugId || r.featureId || r.improvementId || ''; output += `${itemId}: ${r.message}\n`; }); } return output; } export function formatStatistics(stats) { let output = 'Project Statistics\n\n'; if (stats.bugs) { output += `Bugs (${stats.bugs.total} total)\n`; output += formatStatusAndPriority(stats.bugs); output += '\n'; } if (stats.features) { output += `Feature Requests (${stats.features.total} total)\n`; output += formatStatusAndPriority(stats.features); output += '\n'; } if (stats.improvements) { output += `Improvements (${stats.improvements.total} total)\n`; output += formatStatusAndPriority(stats.improvements); } return output; } function formatStatusAndPriority(category) { let output = ''; if (category.byStatus) { output += 'By Status:\n'; for (const [status, count] of Object.entries(category.byStatus)) { output += ` ${status}: ${count}\n`; } } if (category.byPriority) { output += 'By Priority:\n'; for (const [priority, count] of Object.entries(category.byPriority)) { output += ` ${priority}: ${count}\n`; } } return output; } //# sourceMappingURL=format.js.map