/** * UI Utilities - BroCode CLI */ import chalk from 'chalk'; export function printBanner() { console.clear(); console.log('\n'); // ASCII Art Logo with gradient effect const gradientColors = [ chalk.rgb(81, 141, 238), // Light blue chalk.rgb(16, 109, 224), chalk.rgb(19, 43, 165), chalk.rgb(24, 3, 129), chalk.rgb(55, 3, 118), chalk.rgb(92, 2, 140), // Purple chalk.rgb(98, 2, 122), // Pink ]; const logo = [ ' ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗', ' ██╔══██╗██╔══██╗██╔═══██╗██╔════╝██╔═══██╗██╔══██╗██╔════╝', ' ██████╔╝██████╔╝██║ ██║██║ ██║ ██║██║ ██║█████╗ ', ' ██╔══██╗██╔══██╗██║ ██║██║ ██║ ██║██║ ██║██╔══╝ ', ' ██████╔╝██║ ██║╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗', ' ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝' ]; logo.forEach((line, i) => { const colorIndex = Math.floor((i / logo.length) * gradientColors.length); console.log(gradientColors[colorIndex](line)); }); console.log('\n'); console.log(chalk.dim(' Your AI Coding Companion developed by Michael Nkomo')); console.log('\n'); // Tips section with clean icons console.log(chalk.cyan(' Tips for getting started:')); console.log(chalk.white(' 1. Ask questions, edit files, or run commands.')); console.log(chalk.white(' 2. Be specific for the best results.')); console.log(chalk.white(' 3. Create Brocode.md files to customize your interactions with BroCode.')); console.log(chalk.white(' 4. /help for more information.')); console.log('\n'); } export function printCompactBanner() { // For when returning to the CLI (no clear) console.log('\n'); const title = chalk.rgb(150, 150, 255).bold('╭─ BROCODE ─╮'); const subtitle = chalk.dim('│ AI Coding Agent │'); const bottom = chalk.rgb(150, 150, 255).bold('╰─────────────╯'); console.log(' ' + title); console.log(' ' + subtitle); console.log(' ' + bottom); console.log('\n'); console.log(chalk.gray(' Type /help for commands or start chatting!')); console.log('\n'); } export function formatResponse(text: string): string { // Basic markdown-like formatting let formatted = text; // Code blocks with language indicator formatted = formatted.replace(/```(\w+)?\n([\s\S]+?)```/g, (_match, lang, code) => { return '\n' + chalk.dim('┌─' + (lang ? `─ ${lang} ` : '') + '─'.repeat(60 - (lang?.length || 0))) + '\n' + chalk.yellow(code.trim()) + '\n' + chalk.dim('└' + '─'.repeat(65)) + '\n'; }); // Inline code formatted = formatted.replace(/`([^`]+)`/g, (_match, code) => { return chalk.bgGray.white(` ${code} `); }); // Bold formatted = formatted.replace(/\*\*([^*]+)\*\*/g, (_match, text) => { return chalk.bold.white(text); }); // Italic (using underline since terminals don't support italic well) formatted = formatted.replace(/\*([^*]+)\*/g, (_match, text) => { return chalk.dim(text); }); // Headers formatted = formatted.replace(/^### (.+)$/gm, (_match, text) => { return chalk.bold.cyan(`\n▸ ${text}`); }); formatted = formatted.replace(/^## (.+)$/gm, (_match, text) => { return chalk.bold.magenta(`\n▸▸ ${text}`); }); formatted = formatted.replace(/^# (.+)$/gm, (_match, text) => { return chalk.bold.blue(`\n▸▸▸ ${text}`); }); return chalk.white(formatted); } export function printTable(headers: string[], rows: string[][]) { const colWidths = headers.map((h, i) => { const maxContentWidth = Math.max(...rows.map(r => String(r[i]).length)); return Math.max(h.length, maxContentWidth); }); // Print top border console.log('\n' + chalk.dim('┌' + colWidths.map(w => '─'.repeat(w + 2)).join('┬') + '┐')); // Print header process.stdout.write(chalk.dim('│')); headers.forEach((h, i) => { process.stdout.write(chalk.cyan.bold(` ${h.padEnd(colWidths[i])} `) + chalk.dim('│')); }); console.log(); // Print separator console.log(chalk.dim('├' + colWidths.map(w => '─'.repeat(w + 2)).join('┼') + '┤')); // Print rows rows.forEach(row => { process.stdout.write(chalk.dim('│')); row.forEach((cell, i) => { process.stdout.write(chalk.white(` ${String(cell).padEnd(colWidths[i])} `) + chalk.dim('│')); }); console.log(); }); // Print bottom border console.log(chalk.dim('└' + colWidths.map(w => '─'.repeat(w + 2)).join('┴') + '┘') + '\n'); } export function printProgress(current: number, total: number, label = '') { const percentage = Math.floor((current / total) * 100); const filled = Math.floor(percentage / 2); const empty = 50 - filled; const bar = chalk.cyan('█'.repeat(filled)) + chalk.dim('░'.repeat(empty)); const percentText = chalk.bold(`${percentage}%`); process.stdout.write(`\r${label} ${bar} ${percentText}`); if (current === total) { console.log(); } } export function printSuccess(message: string) { console.log(chalk.green(`\n✓ ${message}\n`)); } export function printError(message: string) { console.log(chalk.red(`\n✗ ${message}\n`)); } export function printWarning(message: string) { console.log(chalk.yellow(`\n⚠ ${message}\n`)); } export function printInfo(message: string) { console.log(chalk.cyan(`\nℹ ${message}\n`)); } export function printSection(title: string) { console.log('\n' + chalk.bold.white(title)); console.log(chalk.dim('─'.repeat(title.length))); } export function printList(items: string[], color: 'white' | 'cyan' | 'green' | 'yellow' | 'red' = 'white') { items.forEach(item => { console.log(chalk[color](` • ${item}`)); }); console.log(); } interface BoxOptions { padding?: number; color?: 'white' | 'cyan' | 'green' | 'yellow' | 'red'; borderColor?: 'dim' | 'cyan' | 'green' | 'yellow' | 'red'; } export function printBox(content: string, options: BoxOptions = {}) { const { padding = 1, color = 'white', borderColor = 'dim' } = options; const lines = content.split('\n'); const maxLength = Math.max(...lines.map(l => l.length)); const width = maxLength + (padding * 2); // Top border console.log(chalk[borderColor]('\n┌' + '─'.repeat(width) + '┐')); // Content with padding lines.forEach(line => { const paddedLine = ' '.repeat(padding) + line.padEnd(maxLength) + ' '.repeat(padding); console.log(chalk[borderColor]('│') + chalk[color](paddedLine) + chalk[borderColor]('│')); }); // Bottom border console.log(chalk[borderColor]('└' + '─'.repeat(width) + '┘\n')); } interface Command { command: string; description: string; aliases?: string[]; } export function printCommandHelp(commands: Command[]) { console.log('\n'); printSection('Available Commands'); const maxCmdLength = Math.max(...commands.map(c => c.command.length)); commands.forEach(({ command, description, aliases }) => { const cmd = chalk.cyan(command.padEnd(maxCmdLength + 2)); const desc = chalk.white(description); const aliasText = aliases ? chalk.dim(` (aliases: ${aliases.join(', ')})`) : ''; console.log(` ${cmd} ${desc}${aliasText}`); }); console.log('\n'); } // Funny programming quotes const PROGRAMMING_QUOTES = [ '"It works on my machine" ¯\\_(ツ)_/¯', '99 little bugs in the code... 🐛', 'Compiling... (Time to grab coffee ☕)', 'Stackoverflow searching... just kidding! 😄', 'Turning coffee into code... ☕→💻', 'Debugging is like being a detective 🔍', 'There are only 10 types of people...', 'To err is human, to blame it on the computer is even more so', 'Code never lies, comments sometimes do 🤥', 'Programming: Expect the unexpected! 🎲', 'Real programmers count from 0 😎', 'Keep calm and clear cache 🧹', 'Have you tried turning it off and on again? 🔄', 'Semicolons: The real MVPs 🏆', 'git commit -m "fixed stuff" 🚀', ]; export function createLoadingSpinner(message = 'Loading'): NodeJS.Timeout { const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; let i = 0; let quoteIndex = Math.floor(Math.random() * PROGRAMMING_QUOTES.length); return setInterval(() => { const quote = chalk.dim.italic(PROGRAMMING_QUOTES[quoteIndex]); process.stdout.write(`\r${chalk.cyan(frames[i])} ${chalk.dim(message)} ${quote}`); i = (i + 1) % frames.length; // Change quote every 3 seconds (approximately 37 frames at 80ms) if (i % 37 === 0) { quoteIndex = (quoteIndex + 1) % PROGRAMMING_QUOTES.length; } }, 80); } export function stopLoadingSpinner(spinner: NodeJS.Timeout) { clearInterval(spinner); process.stdout.write('\r\x1B[K'); // Clear the line process.stdout.write('\r'); // Reset cursor to beginning }