mcp-quiz-server
Version:
š§ AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.
413 lines (412 loc) ⢠21.3 kB
JavaScript
;
/**
* ASCII Animations and Graphics for MCP Quiz Server Setup
* Uses only built-in Node.js modules for maximum compatibility
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsciiAnimations = void 0;
const timers_1 = require("timers");
const process_1 = require("process");
class AsciiAnimations {
/**
* Clear all running animations
*/
static clearAllAnimations() {
this.activeAnimations.forEach(timer => (0, timers_1.clearTimeout)(timer));
this.activeAnimations.clear();
}
/**
* Apply color to text if color support is enabled
*/
static colorize(text, color, useColor = true) {
if (!useColor)
return text;
return `${color}${text}${this.COLORS.reset}`;
}
/**
* Move cursor and clear line
*/
static moveCursor(x, y) {
process_1.stdout.write(`\x1b[${y};${x}H`);
}
static clearLine() {
process_1.stdout.write('\x1b[2K');
}
static hideCursor() {
process_1.stdout.write('\x1b[?25l');
}
static showCursor() {
process_1.stdout.write('\x1b[?25h');
}
/**
* Welcome banner with ASCII art
*/
static displayWelcomeBanner(useColor = true) {
const banner = `
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā
ā āāāā āāāā āāāāāāāāāāāāāā āāāāāāā āāā āāāāāāāāāāāāāā ā
ā āāāāā āāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāā āāāāāāāāāāāāāā ā
ā āāāāāāāāāāāāāā āāāāāāāā āāā āāāāāā āāāāāā āāāāā ā
ā āāāāāāāāāāāāāā āāāāāāā āāāāā āāāāāā āāāāāā āāāāā ā
ā āāā āāā āāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā āāā āāā āāāāāāāāāā āāāāāāā āāāāāāā āāāāāāāāāāā ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāā āāā āāāāāāāāāāāāāāāāāā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāā ā
ā āāāāāāāāāāāāāā āāāāāāāāāāā āāāāāāāāā āāāāāāāā ā
ā āāāāāāāāāāāāāā āāāāāāāāāāāā āāāāāāāāāā āāāāāāāā ā
ā āāāāāāāāāāāāāāāāāāā āāā āāāāāāā āāāāāāāāāāā āāā ā
ā āāāāāāāāāāāāāāāāāāā āāā āāāāā āāāāāāāāāāā āāā ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāā ā
ā āāāāāāāāāāāāāā āāā āāā āāāāāāāāāāā ā
ā āāāāāāāāāāāāāā āāā āāā āāāāāāāāāā ā
ā āāāāāāāāāāāāāāāā āāā āāāāāāāāāāāā ā
ā āāāāāāāāāāāāāāāā āāā āāāāāāā āāā ā
ā ā
ā Welcome to the Interactive Setup Wizard! ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`;
if (useColor) {
const coloredBanner = banner
.replace(/ā/g, this.colorize('ā', this.COLORS.blue))
.replace(/ā|ā|ā|ā|ā/g, match => this.colorize(match, this.COLORS.blue))
.replace(/āāā/g, this.colorize('āāā', this.COLORS.cyan))
.replace(/Welcome to the Interactive Setup Wizard!/g, this.colorize('Welcome to the Interactive Setup Wizard!', this.COLORS.green + this.COLORS.bright));
console.log(coloredBanner);
}
else {
console.log(banner);
}
}
/**
* Loading spinner animation
*/
static startSpinner(message, options = {}) {
const frames = ['ā ', 'ā ', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ', 'ā '];
const { interval = 100, color = true } = options;
let frameIndex = 0;
this.hideCursor();
const timer = setInterval(() => {
this.clearLine();
process_1.stdout.write('\r');
const spinner = this.colorize(frames[frameIndex], this.COLORS.cyan, color);
const text = this.colorize(message, this.COLORS.white, color);
process_1.stdout.write(`${spinner} ${text}`);
frameIndex = (frameIndex + 1) % frames.length;
}, interval);
this.activeAnimations.add(timer);
return () => {
clearInterval(timer);
this.activeAnimations.delete(timer);
this.clearLine();
process_1.stdout.write('\r');
this.showCursor();
};
}
/**
* Progress bar animation
*/
static createProgressBar(total, options = {}) {
const { color = true } = options;
const width = 40;
const update = (current, message = '') => {
const percentage = Math.min(100, Math.max(0, (current / total) * 100));
const filled = Math.floor((percentage / 100) * width);
const empty = width - filled;
const filledBar = 'ā'.repeat(filled);
const emptyBar = 'ā'.repeat(empty);
this.clearLine();
process_1.stdout.write('\r');
const bar = this.colorize(filledBar, this.COLORS.green, color) +
this.colorize(emptyBar, this.COLORS.dim, color);
const percent = this.colorize(`${percentage.toFixed(1)}%`, this.COLORS.bright, color);
const text = this.colorize(message, this.COLORS.white, color);
process_1.stdout.write(`[${bar}] ${percent} ${text}`);
};
const complete = (message = 'Complete!') => {
update(total, message);
process_1.stdout.write('\n');
};
return { update, complete };
}
/**
* Step-by-step progress indicators
*/
static displayStepProgress(steps, currentStep, options = {}) {
const { color = true } = options;
console.log('\n' + this.colorize('Setup Progress:', this.COLORS.bright + this.COLORS.white, color));
console.log(this.colorize('ā'.repeat(50), this.COLORS.blue, color));
steps.forEach((step, index) => {
let icon;
let stepColor;
if (index < currentStep) {
icon = 'ā';
stepColor = this.COLORS.green;
}
else if (index === currentStep) {
icon = 'ā¶';
stepColor = this.COLORS.yellow;
}
else {
icon = 'ā';
stepColor = this.COLORS.dim;
}
const coloredIcon = this.colorize(icon, stepColor, color);
const coloredStep = this.colorize(step, stepColor, color);
console.log(` ${coloredIcon} ${coloredStep}`);
});
console.log('');
}
/**
* Password generation animation
*/
static animatePasswordGeneration(options = {}) {
return new Promise(resolve => {
const { duration = 3000, interval = 100, color = true } = options;
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
const length = 16;
let password = new Array(length).fill('?');
let revealedCount = 0;
this.hideCursor();
const timer = setInterval(() => {
// Randomly change unrevealed characters
for (let i = revealedCount; i < length; i++) {
password[i] = chars[Math.floor(Math.random() * chars.length)];
}
// Occasionally reveal a character
if (revealedCount < length && Math.random() < 0.3) {
password[revealedCount] = chars[Math.floor(Math.random() * chars.length)];
revealedCount++;
}
this.clearLine();
process_1.stdout.write('\r');
const displayPassword = password
.map((char, index) => {
if (index < revealedCount) {
return this.colorize(char, this.COLORS.green, color);
}
else {
return this.colorize(char, this.COLORS.dim, color);
}
})
.join('');
const message = this.colorize('Generating secure password: ', this.COLORS.cyan, color);
process_1.stdout.write(`${message}[${displayPassword}]`);
if (revealedCount >= length) {
clearInterval(timer);
this.activeAnimations.delete(timer);
process_1.stdout.write('\n');
this.showCursor();
resolve(password.join(''));
}
}, interval);
this.activeAnimations.add(timer);
// Force completion after duration
(0, timers_1.setTimeout)(() => {
if (revealedCount < length) {
clearInterval(timer);
this.activeAnimations.delete(timer);
const finalPassword = new Array(length)
.fill(null)
.map(() => chars[Math.floor(Math.random() * chars.length)])
.join('');
this.clearLine();
process_1.stdout.write('\r');
const message = this.colorize('Generated secure password: ', this.COLORS.green, color);
const displayPassword = this.colorize(finalPassword, this.COLORS.bright, color);
process_1.stdout.write(`${message}[${displayPassword}]\n`);
this.showCursor();
resolve(finalPassword);
}
}, duration);
});
}
/**
* Success animation
*/
static displaySuccess(message, options = {}) {
const { color = true } = options;
const successArt = `
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā
ā ā SUCCESS! ā
ā ā
ā .-~-.-~-.-~-.-~-.-~-. ā
ā ( S U C C E S S F U L ) ā
ā \`-~-^-~-^-~-^-~-^-~-^-' ā
ā ā
ā š Well done! š ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`;
if (color) {
const coloredArt = successArt
.replace(/ā|ā|ā|ā|ā|ā/g, match => this.colorize(match, this.COLORS.green))
.replace(/ā SUCCESS!/g, this.colorize('ā SUCCESS!', this.COLORS.green + this.COLORS.bright))
.replace(/š Well done! š/g, this.colorize('š Well done! š', this.COLORS.yellow + this.COLORS.bright));
console.log(coloredArt);
}
else {
console.log(successArt.replace(/š/g, '*'));
}
console.log(this.colorize(` ${message}`, this.COLORS.green + this.COLORS.bright, color));
console.log('');
}
/**
* Error animation
*/
static displayError(message, options = {}) {
const { color = true } = options;
const errorArt = `
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā
ā ā ERROR! ā
ā ā
ā .-~-.-~-.-~-.-~-.-~-. ā
ā ( S O M E T H I N G ) ā
ā ( W E N T W R O N G ) ā
ā \`-~-^-~-^-~-^-~-^-~-^-' ā
ā ā
ā ā ļø Please retry ā ļø ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`;
if (color) {
const coloredArt = errorArt
.replace(/ā|ā|ā|ā|ā|ā/g, match => this.colorize(match, this.COLORS.red))
.replace(/ā ERROR!/g, this.colorize('ā ERROR!', this.COLORS.red + this.COLORS.bright))
.replace(/ā ļø Please retry ā ļø/g, this.colorize('ā ļø Please retry ā ļø', this.COLORS.yellow + this.COLORS.bright));
console.log(coloredArt);
}
else {
console.log(errorArt.replace(/ā ļø/g, '!'));
}
console.log(this.colorize(` ${message}`, this.COLORS.red + this.COLORS.bright, color));
console.log('');
}
/**
* Fun ASCII graphics for different setup stages
*/
static displayStageGraphic(stage, options = {}) {
const { color = true } = options;
let graphic = '';
switch (stage) {
case 'database':
graphic = `
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā DATABASE SETUP ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāā ā ā
ā ā āā Tables āā Schemas āā Data ā ā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāā ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
ā Configuring data storage... ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`;
break;
case 'server':
graphic = `
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā SERVER SETUP ā
ā ā
ā ā² ā² ā² ā² ā² ā² ā² ā² ā² ā² ā
ā ā ā ā ā ā ā ā ā ā ā ā
ā āāāāāāāāāāāāāāāāāāāāāāā ā
ā ā MCP Quiz Server ā ā
ā ā Port: 3000 ā ā
ā āāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
ā Starting server configuration... ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`;
break;
case 'security':
graphic = `
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā SECURITY SETUP ā
ā ā
ā šššš”ļøššš ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā Generating keys... ā ā
ā ā Setting permissions ā ā
ā ā Configuring auth... ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
ā Securing your application... ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`;
break;
case 'final':
graphic = `
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā FINAL TOUCHES ā
ā ā
ā šØāØšÆšāšŖš ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā Applying finishing ā ā
ā ā touches and cleanup ā ā
ā ā Verifying setup... ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
ā Almost ready to launch! ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`;
break;
}
if (color) {
const coloredGraphic = graphic
.replace(/ā|ā|ā|ā|ā|ā/g, match => this.colorize(match, this.COLORS.blue))
.replace(/(DATABASE|SERVER|SECURITY|FINAL)( SETUP| TOUCHES)/g, match => this.colorize(match, this.COLORS.cyan + this.COLORS.bright))
.replace(/šššš”ļøššš|šØāØšÆšāšŖš/g, match => this.colorize(match, this.COLORS.yellow));
console.log(coloredGraphic);
}
else {
console.log(graphic.replace(/šššš”ļøššš|šØāØšÆšāšŖš/g, '***'));
}
}
/**
* Clean up and restore terminal
*/
static cleanup() {
this.clearAllAnimations();
this.showCursor();
console.log(''); // Add final newline
}
}
exports.AsciiAnimations = AsciiAnimations;
AsciiAnimations.COLORS = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
bgRed: '\x1b[41m',
bgGreen: '\x1b[42m',
bgYellow: '\x1b[43m',
bgBlue: '\x1b[44m',
};
AsciiAnimations.activeAnimations = new Set();
// Auto-cleanup on process exit
process.on('exit', () => AsciiAnimations.cleanup());
process.on('SIGINT', () => {
AsciiAnimations.cleanup();
process.exit(0);
});
process.on('SIGTERM', () => {
AsciiAnimations.cleanup();
process.exit(0);
});
exports.default = AsciiAnimations;