Skip to main content
Glama
formatter.util.ts5.27 kB
/** * Standardized formatting utilities for consistent output across all CLI and Tool interfaces. * These functions should be used by all formatters to ensure consistent formatting. */ import { Logger } from './logger.util.js'; import { ResponsePagination } from '../types/common.types.js'; /** * 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 */ export function formatDate(dateString?: string | Date): string { 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 heading with the specified level (markdown) * @param text - The heading text * @param level - The heading level (1-6, defaults to 1) * @returns Formatted heading */ export function formatHeading(text: string, level: number = 1): string { const hashes = '#'.repeat(Math.max(1, Math.min(6, level))); return `${hashes} ${text}`; } /** * Format pagination information in a standardized way for CLI output. * Includes separator, item counts, availability message, next page instructions, and timestamp. * @param pagination - The ResponsePagination object containing pagination details. * @returns Formatted pagination footer string for CLI. */ export function formatPagination(pagination: ResponsePagination): string { const methodLogger = Logger.forContext( 'utils/formatter.util.ts', 'formatPagination', ); const parts: string[] = [formatSeparator()]; // Start with separator const { count = 0, hasMore, nextCursor, total } = pagination; // Showing count and potentially total if (total !== undefined && total >= 0) { parts.push(`*Showing ${count} of ${total} total items.*`); } else if (count >= 0) { parts.push(`*Showing ${count} item${count !== 1 ? 's' : ''}.*`); } // More results availability if (hasMore) { parts.push('More results are available.'); } // Prompt for the next action (using cursor string for Confluence) if (hasMore && nextCursor) { parts.push(`*Use --cursor "${nextCursor}" to view more.*`); } // Add standard timestamp parts.push(`*Information retrieved at: ${formatDate(new Date())}*`); const result = parts.join('\n').trim(); // Join with newline methodLogger.debug(`Formatted pagination footer: ${result}`); return result; } /** * Format a URL as a markdown link * @param url - The URL to format * @param title - Optional title for the link * @returns Formatted URL as a markdown link */ export function formatUrl(url: string, title?: string): string { return `[${title || url}](${url})`; } /** * Format a separator line * @returns Formatted separator line */ export function formatSeparator(): string { return '---'; } /** * Format a bullet list from an object * @param items - The object to format as a bullet list * @param formatter - Optional function to format keys * @returns Formatted bullet list */ export function formatBulletList( items: Record<string, unknown>, formatter: (key: string) => string = (key) => key, ): string { const lines: string[] = []; for (const [key, value] of Object.entries(items)) { if (value === undefined || value === null) { continue; } const formattedKey = formatter(key); if (typeof value === 'object' && value !== null && 'url' in value) { const urlObj = value as { url: string; title?: string }; // Handle URL objects with title const urlTitle = urlObj.title || urlObj.url; lines.push( `- **${formattedKey}**: ${formatUrl(String(urlObj.url), String(urlTitle))}`, ); } else if ( typeof value === 'string' && (value.startsWith('http://') || value.startsWith('https://')) ) { // Handle URL strings lines.push(`- **${formattedKey}**: ${formatUrl(value)}`); } else if (value instanceof Date) { // Handle dates lines.push(`- **${formattedKey}**: ${formatDate(value)}`); } else if (Array.isArray(value)) { // Handle arrays lines.push(`- **${formattedKey}**: ${value.join(', ')}`); } else { // Handle all other types lines.push(`- **${formattedKey}**: ${String(value)}`); } } return lines.join('\n'); } /** * Format a numbered list from an array * @param items - The array of items to format * @param formatter - Function to format each item * @returns Formatted numbered list */ export function formatNumberedList<T>( items: T[], formatter: (item: T, index: number) => string, ): string { return items .map((item, index) => { const formattedItem = formatter(item, index); return `${index + 1}. ${formattedItem}`; }) .join('\n\n'); } /** * Format text as a Markdown code block * @param text Text content * @param language Optional language identifier * @returns Formatted code block string */ // Unused function - commented out // export function formatCodeBlock(text: string, language: string = ''): string { // // Trim trailing newline if present to avoid extra line in block // const trimmedText = text.replace(/$\n/, ''); // return `\`\`\`${language}\n${trimmedText}\n\`\`\``; // }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aashari/mcp-server-atlassian-confluence'

If you have feedback or need assistance with the MCP directory API, please join our Discord server