UNPKG

hackages

Version:

CLI tool for learning software development concepts through test-driven development

208 lines (207 loc) 8.81 kB
import fs from "fs"; import chalk from "chalk"; import { apiClient } from "../services/api.js"; import { getLearningInformation, getLatestSourceFilePath, } from "../services/file-manager.js"; import { printError, printHeader } from "../utils/console.js"; const HINT_INTERVAL = 10000; // 10 seconds between hint requests let lastContent = ""; let hasChanges = false; let hintTimer = null; let isRequestingHint = false; // Hackages brand colors const primary = chalk.rgb(34, 211, 238); // Cyan/turquoise const primaryBg = chalk.bgRgb(34, 211, 238); // Cyan background const tertiary = chalk.rgb(22, 78, 99); // Dark teal const tertiaryBg = chalk.bgRgb(22, 78, 99); // Dark teal background /** * Displays a hint in the terminal with tutor-style formatting */ function displayHint(hint) { console.log("\n"); console.log(tertiaryBg.white.bold(" ")); console.log(tertiaryBg.white.bold(" HACKAGES AI SAYS... ")); console.log(tertiaryBg.white.bold(" ")); console.log(""); // Process the hint with enhanced formatting const lines = hint.split("\n"); for (const line of lines) { // Highlight key phrases let formattedLine = line // Bold important keywords .replace(/\*\*(.*?)\*\*/g, (_, p1) => chalk.bold.white(p1)) // Inline code .replace(/`([^`]+)`/g, (_, p1) => tertiaryBg.white(` ${p1} `)) // Tips and suggestions .replace(/^(Tip:|Hint:|Suggestion:|Try:|Consider:)/gi, (match) => primary.bold(`> ${match}`)) // Warnings .replace(/^(Warning:|Careful:|Watch out:|Note:)/gi, (match) => chalk.red.bold(`! ${match}`)) // Success indicators .replace(/^(Great!|Good job!|Well done!|Correct!)/gi, (match) => chalk.green.bold(`+ ${match}`)); // Headers if (line.startsWith("###")) { console.log(primary.bold("\n" + line.replace(/^###\s*/, " -> "))); } else if (line.startsWith("##")) { console.log(primary.bold("\n" + line.replace(/^##\s*/, ">> "))); } else if (line.startsWith("#")) { console.log(primary.bold("\n" + line.replace(/^#\s*/, "# "))); } // Lists else if (line.match(/^\s*[-*]\s/)) { console.log(chalk.white(" " + line.replace(/^\s*[-*]\s/, primary(" - ")))); } else if (line.match(/^\s*\d+\.\s/)) { console.log(chalk.white(" " + line.replace(/^(\s*\d+\.)\s/, primary("$1 ")))); } // Code blocks else if (line.trim().startsWith("```")) { console.log(tertiary(" ─────────────────────────────────")); } // Empty lines else if (line.trim() === "") { console.log(""); } // Regular text else { console.log(chalk.white(" " + formattedLine)); } } console.log(""); console.log(tertiary(" ─────────────────────────────────────────────────")); console.log(tertiary(` Last updated: ${new Date().toLocaleTimeString()}`)); console.log(primary(" > ") + tertiary("Keep coding! Hackages AI will check again in 10 seconds.")); console.log(""); } /** * Displays the watching status bar */ function displayWatchingStatus(fileName) { console.log(primaryBg.black.bold(" ")); console.log(primaryBg.black.bold(" HACKAGES AI - TUTOR MODE ACTIVE ")); console.log(primaryBg.black.bold(" ")); console.log(""); console.log(primary(" [*] ") + chalk.white("Watching: ") + primary.bold(fileName)); console.log(primary(" [*] ") + chalk.white("Hint interval: ") + primary.bold("Every 10 seconds") + chalk.white(" (when code changes)")); console.log(primary(" [*] ") + chalk.white("Stop: ") + chalk.red.bold("Ctrl+C")); console.log(""); console.log(tertiary(" ─────────────────────────────────────────────────")); console.log(""); } /** * Fetches a hint from the API based on current implementation */ async function fetchHint(learningGoalId, implementation) { if (isRequestingHint) return; isRequestingHint = true; try { console.log(primary("\n [~] ") + tertiary("Analyzing your code...")); const response = await apiClient.learning.getHint(learningGoalId, implementation); if (response.hint) { displayHint(response.hint); } else { console.log(tertiary("\n [i] No specific hints at this time. Keep going!")); } } catch (error) { printError(`Failed to fetch hint: ${error}`); } finally { isRequestingHint = false; } } /** * Checks for changes and requests hint if needed */ async function checkAndFetchHint(filePath, learningGoalId) { if (!hasChanges || isRequestingHint) { return; } // Reset change flag before fetching hasChanges = false; try { const currentContent = fs.readFileSync(filePath, "utf-8"); await fetchHint(learningGoalId, currentContent); } catch (error) { printError(`Error reading file: ${error}`); } } /** * Handles file change events */ function handleFileChange(filePath) { try { const currentContent = fs.readFileSync(filePath, "utf-8"); // Only mark as changed if content actually differs if (currentContent !== lastContent) { hasChanges = true; lastContent = currentContent; console.log(tertiary(` [${new Date().toLocaleTimeString()}] `) + primary("Code change detected, will analyze soon...")); } } catch (error) { // File might be in the middle of being saved, ignore } } export async function hintCommand() { try { console.clear(); printHeader("Hackages AI - Interactive Learning Mode"); console.log(""); // Get learning information const learningInfo = getLearningInformation(); if (!learningInfo) { console.log(chalk.red.bold("\n [x] No active learning session found.")); console.log(chalk.white(" Please start with ") + primary("'hackages'") + chalk.white(" first.\n")); return; } // Get the source file path const sourceFile = getLatestSourceFilePath(); if (!sourceFile) { console.log(chalk.red.bold("\n [x] No source file found.")); console.log(chalk.white(" Please make sure you have files in the ") + primary("src/") + chalk.white(" directory.\n")); return; } const { filePath, fileName } = sourceFile; // Display watching status displayWatchingStatus(fileName); // Read initial content lastContent = fs.readFileSync(filePath, "utf-8"); // Fetch initial hint console.log(primary(" [>] ") + chalk.white("Fetching your first hint...")); await fetchHint(learningInfo.id, lastContent); // Watch for file changes const watcher = fs.watch(filePath, (eventType) => { if (eventType === "change") { handleFileChange(filePath); } }); // Set up interval to check for changes and fetch hints hintTimer = setInterval(() => { checkAndFetchHint(filePath, learningInfo.id); }, HINT_INTERVAL); // Handle process termination process.on("SIGINT", () => { console.log("\n"); console.log(tertiaryBg.white.bold(" ")); console.log(tertiaryBg.white.bold(" HACKAGES AI SESSION ENDED ")); console.log(tertiaryBg.white.bold(" ")); console.log(""); console.log(chalk.white(" Great learning session! Keep practicing and come back anytime.")); console.log(tertiary(" Run ") + primary("hackages hint") + tertiary(" to start another session.\n")); watcher.close(); if (hintTimer) { clearInterval(hintTimer); } process.exit(0); }); // Keep the process running await new Promise(() => { }); } catch (error) { printError(`Error in hint command: ${error}`); } }