UNPKG

@sbeeredd04/auto-git

Version:

AI-powered Git automation with intelligent commit decisions using Gemini function calling, smart diff optimization, push control, and enhanced interactive terminal session with persistent command history

258 lines (217 loc) 7.37 kB
import chalk from 'chalk'; /** * Markdown utility for formatting AI responses with syntax highlighting */ // ANSI color codes for markdown formatting const colors = { heading: chalk.cyan.bold, subheading: chalk.blue.bold, code: chalk.yellow, codeBlock: chalk.gray, bold: chalk.white.bold, italic: chalk.white.italic, link: chalk.blue.underline, quote: chalk.gray.italic, list: chalk.green, error: chalk.red, success: chalk.green, warning: chalk.yellow, info: chalk.blue, gray: chalk.gray }; /** * Parse and format markdown text for terminal display * @param {string} markdown - Raw markdown text * @returns {string} - Formatted text for terminal */ export function formatMarkdown(markdown) { if (!markdown || typeof markdown !== 'string') { return markdown; } let formatted = markdown; // Format headings formatted = formatted.replace(/^### (.*$)/gm, colors.subheading('### $1')); formatted = formatted.replace(/^## (.*$)/gm, colors.heading('## $1')); formatted = formatted.replace(/^# (.*$)/gm, colors.heading('# $1')); // Format code blocks formatted = formatted.replace(/```[\s\S]*?```/g, (match) => { const lines = match.split('\n'); const language = lines[0].replace('```', '').trim(); const code = lines.slice(1, -1).join('\n'); return colors.codeBlock('```' + language + '\n' + code + '\n```'); }); // Format inline code formatted = formatted.replace(/`([^`]+)`/g, colors.code('`$1`')); // Format bold text formatted = formatted.replace(/\*\*(.*?)\*\*/g, colors.bold('$1')); // Format italic text formatted = formatted.replace(/\*(.*?)\*/g, colors.italic('$1')); // Format links formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, colors.link('$1') + colors.gray(' ($2)')); // Format blockquotes formatted = formatted.replace(/^> (.*$)/gm, colors.quote('> $1')); // Format lists formatted = formatted.replace(/^[\s]*[-*+] (.*$)/gm, colors.list('• $1')); formatted = formatted.replace(/^[\s]*\d+\. (.*$)/gm, colors.list('$&')); return formatted; } /** * Format AI suggestion with proper styling and line wrapping * @param {string} suggestion - AI suggestion text * @returns {string} - Formatted suggestion */ export function formatAISuggestion(suggestion) { const formatted = formatMarkdown(suggestion); // Wrap long lines to fit terminal width const maxWidth = Math.min(process.stdout.columns || 80, 80) - 4; // Leave space for borders const wrappedLines = []; formatted.split('\n').forEach(line => { const cleanLine = stripAnsi(line); if (cleanLine.length <= maxWidth) { wrappedLines.push(line); } else { // Split long lines into multiple lines const words = line.split(' '); let currentLine = ''; let currentCleanLine = ''; for (const word of words) { const cleanWord = stripAnsi(word); const testLine = currentCleanLine + (currentCleanLine ? ' ' : '') + cleanWord; if (testLine.length <= maxWidth) { currentLine += (currentLine ? ' ' : '') + word; currentCleanLine = testLine; } else { if (currentLine) { wrappedLines.push(currentLine); } currentLine = word; currentCleanLine = cleanWord; } } if (currentLine) { wrappedLines.push(currentLine); } } }); // Create box with consistent width const boxWidth = Math.min(maxWidth + 2, 78); const border = '─'.repeat(boxWidth); const content = wrappedLines.map(line => { const cleanLine = stripAnsi(line); const padding = boxWidth - cleanLine.length - 2; return `│ ${line}${' '.repeat(Math.max(0, padding))} │`; }).join('\n'); return `\n┌${border}┐\n${content}\n└${border}┘\n`; } /** * Format command suggestions with syntax highlighting * @param {string[]} commands - Array of command suggestions * @returns {string} - Formatted commands */ export function formatCommandSuggestions(commands) { if (!Array.isArray(commands) || commands.length === 0) { return ''; } const formatted = commands.map((cmd, index) => { const number = colors.info(`${index + 1}.`); const command = colors.code(cmd); return ` ${number} ${command}`; }).join('\n'); return `\n${colors.heading('Suggested Commands:')}\n${formatted}\n`; } /** * Format error message with context * @param {string} error - Error message * @param {string} context - Additional context * @returns {string} - Formatted error */ export function formatError(error, context = '') { const formattedError = colors.error('✗ ') + error; const formattedContext = context ? colors.gray(`Context: ${context}`) : ''; return formattedContext ? `${formattedError}\n${formattedContext}` : formattedError; } /** * Format success message * @param {string} message - Success message * @returns {string} - Formatted success message */ export function formatSuccess(message) { return colors.success('✓ ') + message; } /** * Format warning message * @param {string} message - Warning message * @returns {string} - Formatted warning message */ export function formatWarning(message) { return colors.warning('⚠ ') + message; } /** * Format info message * @param {string} message - Info message * @returns {string} - Formatted info message */ export function formatInfo(message) { return colors.info('ℹ ') + message; } /** * Strip ANSI escape codes from text * @param {string} text - Text with ANSI codes * @returns {string} - Clean text */ function stripAnsi(text) { return text.replace(/\x1b\[[0-9;]*m/g, ''); } /** * Create a formatted box around text * @param {string} text - Text to box * @param {string} title - Optional title for the box * @returns {string} - Boxed text */ export function createBox(text, title = '') { const lines = text.split('\n'); const maxLength = Math.max(...lines.map(line => stripAnsi(line).length)); const width = Math.min(maxLength + 4, 80); const topBorder = title ? `┌─ ${colors.heading(title)} ${'─'.repeat(Math.max(0, width - title.length - 5))}┐` : `┌${'─'.repeat(width)}┐`; const bottomBorder = `└${'─'.repeat(width)}┘`; const content = lines.map(line => { const padding = width - stripAnsi(line).length - 2; return `│ ${line}${' '.repeat(Math.max(0, padding))} │`; }).join('\n'); return `${topBorder}\n${content}\n${bottomBorder}`; } /** * Format git command with syntax highlighting * @param {string} command - Git command * @returns {string} - Formatted command */ export function formatGitCommand(command) { if (!command.startsWith('git ')) { return colors.code(command); } const parts = command.split(' '); const git = colors.success('git'); const subcommand = colors.info(parts[1]); const args = parts.slice(2).map(arg => { if (arg.startsWith('--')) { return colors.warning(arg); } else if (arg.startsWith('-')) { return colors.yellow(arg); } return colors.code(arg); }).join(' '); return `${git} ${subcommand} ${args}`.trim(); } export default { formatMarkdown, formatAISuggestion, formatCommandSuggestions, formatError, formatSuccess, formatWarning, formatInfo, createBox, formatGitCommand };