UNPKG

@aashari/mcp-server-atlassian-bitbucket

Version:

Node.js/TypeScript MCP server for Atlassian Bitbucket. Enables AI systems (LLMs) to interact with workspaces, repositories, and pull requests via tools (list, get, comment, search). Connects AI directly to version control workflows through the standard MC

273 lines (272 loc) 9.12 kB
"use strict"; /** * Standardized formatting utilities for consistent output across all CLI and Tool interfaces. * These functions should be used by all formatters to ensure consistent formatting. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.formatDate = formatDate; exports.formatRelativeTime = formatRelativeTime; exports.formatUrl = formatUrl; exports.formatPagination = formatPagination; exports.formatHeading = formatHeading; exports.formatBulletList = formatBulletList; exports.formatSeparator = formatSeparator; exports.formatNumberedList = formatNumberedList; exports.formatDiff = formatDiff; /** * Format a date in a standardized way: YYYY-MM-DD HH:MM:SS UTC * @param dateString - ISO date string or Date object * @returns Formatted date string */ function formatDate(dateString) { if (!dateString) { return 'Not available'; } try { const date = typeof dateString === 'string' ? new Date(dateString) : dateString; // Format: YYYY-MM-DD HH:MM:SS UTC return date .toISOString() .replace('T', ' ') .replace(/\.\d+Z$/, ' UTC'); } catch { return 'Invalid date'; } } /** * Format a relative time (e.g., "2 days ago") * @param dateString - ISO date string or Date object * @returns Relative time string */ function formatRelativeTime(dateString) { if (!dateString) { return 'Not available'; } try { const date = typeof dateString === 'string' ? new Date(dateString) : dateString; const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffSec = Math.floor(diffMs / 1000); const diffMin = Math.floor(diffSec / 60); const diffHour = Math.floor(diffMin / 60); const diffDay = Math.floor(diffHour / 24); const diffMonth = Math.floor(diffDay / 30); const diffYear = Math.floor(diffMonth / 12); if (diffYear > 0) { return `${diffYear} year${diffYear === 1 ? '' : 's'} ago`; } else if (diffMonth > 0) { return `${diffMonth} month${diffMonth === 1 ? '' : 's'} ago`; } else if (diffDay > 0) { return `${diffDay} day${diffDay === 1 ? '' : 's'} ago`; } else if (diffHour > 0) { return `${diffHour} hour${diffHour === 1 ? '' : 's'} ago`; } else if (diffMin > 0) { return `${diffMin} minute${diffMin === 1 ? '' : 's'} ago`; } else { return `${diffSec} second${diffSec === 1 ? '' : 's'} ago`; } } catch { return 'Invalid date'; } } /** * Format a URL as a markdown link * @param url - URL to format * @param title - Link title * @returns Formatted markdown link */ function formatUrl(url, title) { if (!url) { return 'Not available'; } const linkTitle = title || url; return `[${linkTitle}](${url})`; } /** * Format pagination information in a standardized way * @param totalItems - Number of items in the current result set * @param hasMore - Whether there are more results available * @param nextCursor - Cursor for the next page of results * @returns Formatted pagination information */ function formatPagination(totalItems, hasMore, nextCursor) { if (!hasMore) { return `*Showing ${totalItems} item${totalItems === 1 ? '' : 's'}.*`; } return `*Showing ${totalItems} item${totalItems === 1 ? '' : 's'}. More results are available.*\n\nTo see more results, use --cursor "${nextCursor}"`; } /** * Format a heading with consistent style * @param text - Heading text * @param level - Heading level (1-6) * @returns Formatted heading */ function formatHeading(text, level = 1) { const validLevel = Math.min(Math.max(level, 1), 6); const prefix = '#'.repeat(validLevel); return `${prefix} ${text}`; } /** * Format a list of key-value pairs as a bullet list * @param items - Object with key-value pairs * @param keyFormatter - Optional function to format keys * @returns Formatted bullet list */ function formatBulletList(items, keyFormatter) { const lines = []; for (const [key, value] of Object.entries(items)) { if (value === undefined || value === null) { continue; } const formattedKey = keyFormatter ? keyFormatter(key) : key; const formattedValue = formatValue(value); lines.push(`- **${formattedKey}**: ${formattedValue}`); } return lines.join('\n'); } /** * Format a value based on its type * @param value - Value to format * @returns Formatted value */ function formatValue(value) { if (value === undefined || value === null) { return 'Not available'; } if (value instanceof Date) { return formatDate(value); } // Handle URL objects with url and title properties if (typeof value === 'object' && value !== null && 'url' in value) { const urlObj = value; if (typeof urlObj.url === 'string') { return formatUrl(urlObj.url, urlObj.title); } } if (typeof value === 'string') { // Check if it's a URL if (value.startsWith('http://') || value.startsWith('https://')) { return formatUrl(value); } // Check if it might be a date if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) { return formatDate(value); } return value; } if (typeof value === 'boolean') { return value ? 'Yes' : 'No'; } return String(value); } /** * Format a separator line * @returns Separator line */ function formatSeparator() { return '---'; } /** * Format a numbered list of items * @param items - Array of items to format * @param formatter - Function to format each item * @returns Formatted numbered list */ function formatNumberedList(items, formatter) { if (items.length === 0) { return 'No items.'; } return items .map((item, index) => formatter(item, index)) .join('\n\n' + formatSeparator() + '\n\n'); } /** * Format a raw diff output for display * * Parses and formats a raw unified diff string into a Markdown * formatted display with proper code block syntax highlighting. * * @param {string} rawDiff - The raw diff content from the API * @param {number} maxFiles - Maximum number of files to display in detail (optional, default: 5) * @param {number} maxLinesPerFile - Maximum number of lines to display per file (optional, default: 100) * @returns {string} Markdown formatted diff content */ function formatDiff(rawDiff, maxFiles = 5, maxLinesPerFile = 100) { if (!rawDiff || rawDiff.trim() === '') { return '*No changes found in this pull request.*'; } const lines = rawDiff.split('\n'); const formattedLines = []; let currentFile = ''; let fileCount = 0; let inFile = false; let truncated = false; let lineCount = 0; for (const line of lines) { // New file is marked by a line starting with "diff --git" if (line.startsWith('diff --git')) { if (inFile) { // Close previous file code block formattedLines.push('```'); formattedLines.push(''); } // Only process up to maxFiles fileCount++; if (fileCount > maxFiles) { truncated = true; break; } // Extract filename const filePath = line.match(/diff --git a\/(.*) b\/(.*)/); currentFile = filePath ? filePath[1] : 'unknown file'; formattedLines.push(`### ${currentFile}`); formattedLines.push(''); formattedLines.push('```diff'); inFile = true; lineCount = 0; } else if (inFile) { lineCount++; // Truncate files that are too long if (lineCount > maxLinesPerFile) { formattedLines.push('// ... more lines omitted for brevity ...'); formattedLines.push('```'); formattedLines.push(''); inFile = false; continue; } // Format diff lines with appropriate highlighting if (line.startsWith('+')) { formattedLines.push(line); } else if (line.startsWith('-')) { formattedLines.push(line); } else if (line.startsWith('@@')) { // Change section header formattedLines.push(line); } else { // Context line formattedLines.push(line); } } } // Close the last code block if necessary if (inFile) { formattedLines.push('```'); } // Add truncation notice if we limited the output if (truncated) { formattedLines.push(''); formattedLines.push(`*Output truncated. Only showing the first ${maxFiles} files.*`); } return formattedLines.join('\n'); }