Skip to content

Instantly share code, notes, and snippets.

@koad
Created June 27, 2025 19:07
Show Gist options
  • Save koad/b6e31f66e5c1f57808b4b4b608efd806 to your computer and use it in GitHub Desktop.
Save koad/b6e31f66e5c1f57808b4b4b608efd806 to your computer and use it in GitHub Desktop.
Animated terminal prompt with rainbow spinner in nodejs
/**
* 🌈 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