Created
June 27, 2025 19:07
-
-
Save koad/b6e31f66e5c1f57808b4b4b608efd806 to your computer and use it in GitHub Desktop.
Animated terminal prompt with rainbow spinner in nodejs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 🌈 Animated Terminal Prompt with Rainbow Spinner | |
* | |
* A beautiful, animated command-line prompt that features: | |
* - Braille pattern spinner animation | |
* - Smooth rainbow color transitions | |
* - Graceful Ctrl+C handling | |
* - Real-time prompt updates | |
* | |
* Author: koad | |
* Version: 0.0.1 | |
* License: MIT | |
*/ | |
// Import Node.js readline module for terminal input/output handling | |
const readline = require('readline'); | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 🎨 ANSI ESCAPE CODES - Terminal formatting and color control sequences | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
const RESET = '\x1b[0m'; // Reset all formatting back to default | |
const BOLD = '\x1b[1m'; // Make text bold/bright | |
const CLEAR_LINE = '\x1b[2K'; // Clear entire current line | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// ⚙️ CONFIGURATION - Timing and behavior settings | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Update interval in milliseconds (69ms ≈ 14.5 FPS for smooth animation) | |
const PROMPT_UPDATE_INTERVAL = 69; | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 🛡️ TTY VALIDATION - Ensure we're running in a proper terminal | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Check if stdin is connected to a terminal (TTY) | |
// This prevents issues when script is piped or redirected | |
if (!process.stdin.isTTY) { | |
console.error('Error: stdin is not a TTY.'); | |
console.error('This script requires a terminal environment to function properly.'); | |
process.exit(1); | |
} | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 🎮 RAW MODE SETUP - Enable character-by-character input processing | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Enable raw mode to capture individual keystrokes without waiting for Enter | |
process.stdin.setRawMode(true); | |
// Start reading from stdin | |
process.stdin.resume(); | |
// Set character encoding to UTF-8 for proper Unicode support | |
process.stdin.setEncoding('utf8'); | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 🚪 GRACEFUL EXIT HANDLER - Clean Ctrl+C handling | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Listen for raw keyboard input to handle Ctrl+C gracefully | |
process.stdin.on('data', (key) => { | |
// Check for Ctrl+C (ASCII code 3, Unicode \u0003) | |
if (key === '\u0003') { | |
console.log(""); | |
console.log("🛑 Ctrl+C detected, exiting gracefully..."); | |
console.log(""); | |
process.exit(); | |
} | |
}); | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 📝 READLINE INTERFACE - Terminal input/output management | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Create readline interface for handling line-based input | |
const rl = readline.createInterface({ | |
input: process.stdin, // Read from standard input | |
output: process.stdout // Write to standard output | |
}); | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 🌀 BRAILLE SPINNER - Unicode Braille patterns for smooth animation | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Array of Braille pattern characters that create a spinning effect | |
// These Unicode characters (U+2800 block) form a smooth circular animation | |
// Each character represents different dot patterns in a 2x4 Braille cell | |
const spinner = Array.from('⠁⠂⠠⢀⡀⠄⠐⠈⠃⠢⢠⣀⡄⠔⠘⠉⠣⢢⣠⣄⡔⠜⠙⠋⢣⣢⣤⣔⡜⠝⠛⠫⣣⣦⣴⣜⡝⠟⠻⢫⣧⣶⣼⣝⡟⠿⢻⣫⣷⣾⣽⣟⡿⢿⣻⣯⣷⣾⣽⣟⡿⢿⣻⣯⣧⣶⣼⣝⡟⠿⢻⣫⣣⣦⣴⣜⡝⠟⠻⢫⢣⣢⣤⣔⡜⠝⠛⠫⠣⢢⣠⣄⡔⠜⠙⠋⠃⠢⢠⣀⡄⠔⠘⠉⠁⠂⠠⢀⡀⠄⠐⠈'); | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 🎨 COLOR CONVERSION - HSL to RGB to ANSI 24-bit color | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
/** | |
* Converts HSL color values to ANSI 24-bit color escape sequence | |
* | |
* @param {number} h - Hue (0-360 degrees) | |
* @param {number} s - Saturation (0-100 percent, default: 100) | |
* @param {number} l - Lightness (0-100 percent, default: 69) | |
* @returns {string} ANSI escape sequence for 24-bit color | |
* | |
* The magic number 69 for lightness provides optimal visibility | |
* across different terminal backgrounds (not too dark, not too bright) | |
*/ | |
function hslToAnsi(h, s = 100, l = 69) { | |
// Convert percentages to decimal (0-1 range) | |
s /= 100; | |
l /= 100; | |
// HSL to RGB conversion algorithm | |
// k(n) calculates the hue sector for each RGB component | |
const k = n => (n + h / 30) % 12; | |
// Calculate chroma (color intensity) | |
const a = s * Math.min(l, 1 - l); | |
// RGB component calculation function | |
const f = n => | |
l - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1)); | |
// Calculate RGB values and convert to 0-255 range | |
const [r, g, b] = [f(0), f(8), f(4)].map(v => Math.round(v * 255)); | |
// Return ANSI 24-bit color escape sequence | |
// Format: \x1b[38;2;{r};{g};{b}m (38 = foreground, 2 = RGB mode) | |
return `\x1b[38;2;${r};${g};${b}m`; | |
} | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 🔄 ANIMATION ENGINE - Prompt update and rendering logic | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Global tick counter for animation timing | |
let tick = 0; | |
/** | |
* Updates the animated prompt with new spinner position and color | |
* | |
* This function: | |
* 1. Cycles through spinner characters | |
* 2. Calculates rainbow hue based on tick count | |
* 3. Generates colored ANSI escape sequences | |
* 4. Clears the current line | |
* 5. Renders the new prompt | |
*/ | |
function updatePrompt() { | |
// Get current spinner character (cycles through array) | |
const icon = spinner[tick % spinner.length]; | |
// Calculate hue for rainbow effect (multiplied by 3 for faster color cycling) | |
// Modulo 360 keeps hue in valid range (0-359 degrees) | |
const hue = (tick * 3) % 360; | |
// Convert hue to ANSI color code | |
const color = hslToAnsi(hue); | |
// Construct the animated prompt string | |
const prompt = `${BOLD}${color}${icon}${RESET} Waiting for agent ${BOLD}${color}❯${RESET} `; | |
// Set the new prompt (but don't display it yet) | |
rl.setPrompt(prompt); | |
// Clear current line and move cursor to beginning | |
// This prevents visual artifacts from previous prompt | |
readline.cursorTo(process.stdout, 0); | |
process.stdout.write(CLEAR_LINE); | |
// Display the prompt while preserving any user input | |
// Parameter 'true' preserves the current input line | |
rl.prompt(true); | |
// Increment tick counter for next animation frame | |
tick++; | |
} | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 📥 INPUT HANDLER - Process user commands | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Listen for completed input lines (when user presses Enter) | |
rl.on('line', (line) => { | |
// TODO: Add your command processing logic here | |
// console.log(`\nYou typed: ${line}`); | |
// This is where you can: | |
// - Parse user commands | |
// - Execute system operations | |
// - Send data to APIs | |
// - Process AI agent requests | |
// - Handle file operations | |
// - etc. | |
// For now, we just silently accept input and continue the animation | |
}); | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// 🚀 INITIALIZATION - Start the application | |
// ═══════════════════════════════════════════════════════════════════════════════ | |
// Clear terminal screen for clean start | |
console.clear(); | |
// Display application header | |
console.log('🤖 koad:io nodejs prompt; v0.0.1'); | |
console.log('───────────────────────────────────'); | |
console.log('💡 Type commands and press Enter'); | |
console.log('🛑 Press Ctrl+C to exit'); | |
console.log(''); | |
// Start the animation loop | |
// Updates prompt every PROMPT_UPDATE_INTERVAL milliseconds | |
setInterval(updatePrompt, PROMPT_UPDATE_INTERVAL); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment