UNPKG

clitutor

Version:

Interactive CLI learning tool for beginners

1,169 lines (1,066 loc) β€’ 37.1 kB
const inquirer = require("inquirer"); const chalk = require("chalk"); const boxen = require("boxen"); const ora = require("ora"); const { clearScreen, sleep, printWithTypingEffect, validateCommand, showHint, } = require("../utils/helpers"); const green = chalk.hex("#00FF00"); const dim = chalk.dim; const bold = chalk.bold; const yellow = chalk.yellow; const cyan = chalk.cyan; const magenta = chalk.magenta; const blue = chalk.blue; const lessons = [ { id: "intro", title: "Welcome to File Navigation!", content: async () => { clearScreen(); console.log( boxen(green("Chapter 3: Navigating Your Digital World"), { padding: 1, borderStyle: "double", borderColor: "green", }) ); await printWithTypingEffect( "\nIn Chapters 1 and 2, you learned to speak to your computer and navigate text. Now let's explore your computer's file system!" ); await sleep(500); await printWithTypingEffect( "\nThink of your computer as a " + bold("giant filing cabinet") + " πŸ—„οΈ" ); await sleep(500); await printWithTypingEffect( "\nEach drawer is a folder, and inside are more folders and files. The terminal is your way to open any drawer instantly!" ); await sleep(1000); console.log( boxen( bold("Why Location Matters:\n\n") + "Imagine you're in a library πŸ“š\n" + "β€’ You can only read the books in your current aisle\n" + "β€’ To read a different book, you must walk to its aisle\n" + "β€’ If you're in the wrong aisle, you won't find your book!\n\n" + dim("Same with terminals - your location determines what you can access!"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( "πŸ’» " + bold("Terminal says:") + "\n\n" + "\"I can only see what's in your current location!\n\n" + "If you ask me to run a program and I say 'not found',\n" + "it usually means you're in the wrong place.\n\n" + "It's like asking for a spoon while standing in the garage -\n" + "I need you to be in the kitchen first! πŸ₯„\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); await sleep(1000); await printWithTypingEffect( "\nLet's learn three essential commands: " + cyan("pwd") + ", " + cyan("ls") + ", and " + cyan("cd") + "!" ); }, }, { id: "pwd_command", title: "pwd: Your GPS Location", content: async () => { clearScreen(); console.log( boxen(green("Where Am I? πŸ“"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nThe " + cyan("pwd") + " command is like asking your GPS: " + bold("'What's my current address?'") ); await sleep(700); await printWithTypingEffect( "\n\n" + cyan("pwd") + " stands for " + bold("P") + "rint " + bold("W") + "orking " + bold("D") + "irectory" ); await sleep(1000); console.log( boxen( bold("Example:\n\n") + "$ " + cyan("pwd") + "\n" + green("/Users/alex/Documents/Projects") + "\n\n" + "This tells you:\n" + "β€’ You're in the " + bold("Projects") + " folder\n" + "β€’ Which is inside " + bold("Documents") + "\n" + "β€’ Which is inside " + bold("alex") + "'s home\n" + "β€’ Which is inside " + bold("Users") + "\n\n" + dim("It's like a breadcrumb trail! 🍞"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); // Visual representation console.log( boxen( bold("Visual Path Breakdown:\n\n") + "/Users/alex/Documents/Projects\n" + "↑ ↑ ↑ ↑ ↑\n" + dim("Root Users alex Documents Projects\n") + dim("(/) folder home folder folder\n\n") + "Each " + green("/") + " is like a door between folders!", { padding: 1, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\nπŸ’‘ Always use " + cyan("pwd") + " when you feel lost - it's your 'You Are Here' sign!" ); }, }, { id: "ls_command", title: "ls: Looking Around", content: async () => { clearScreen(); console.log( boxen(green("What's Here? πŸ‘€"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nThe " + cyan("ls") + " command is like " + bold("turning on the lights") + " in a room" ); await sleep(700); await printWithTypingEffect( "\nIt shows you everything in your current location!" ); await sleep(1000); console.log( boxen( bold("Basic ls:\n\n") + "$ " + cyan("ls") + "\n" + blue("Desktop/") + " " + blue("Documents/") + " " + green("notes.txt") + " " + green("todo.md") + "\n" + blue("Pictures/") + " " + blue("Downloads/") + " " + green("script.js") + "\n\n" + "β€’ " + blue("Blue") + " = Folders (you can cd into these!)\n" + "β€’ " + green("Green/White") + " = Files (you can read/edit these!)", { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Detailed View with ls -l:\n\n") + "$ " + cyan("ls -l") + "\n" + dim("total 48\n") + "drwxr-xr-x 5 alex staff 160 Jan 15 10:30 " + blue("Desktop/") + "\n" + "drwxr-xr-x 8 alex staff 256 Jan 14 09:45 " + blue("Documents/") + "\n" + "-rw-r--r-- 1 alex staff 2048 Jan 13 14:22 " + green("notes.txt") + "\n\n" + "This shows:\n" + "β€’ Permissions (who can read/write)\n" + "β€’ Owner (alex)\n" + "β€’ Size (2048 bytes)\n" + "β€’ Date modified\n" + "β€’ Name", { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Useful ls Variations:\n\n") + cyan("ls -a") + " β†’ Show " + bold("a") + "ll files (including hidden ones!)\n" + cyan("ls -la") + " β†’ Detailed view + hidden files\n" + cyan("ls Desktop/") + " β†’ Look inside a folder without going there\n\n" + dim("Hidden files start with a dot (.) like .gitignore"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\n🎯 Pro tip: I use " + cyan("ls") + " constantly to see what's available!" ); }, }, { id: "cd_command", title: "cd: Teleporting Around", content: async () => { clearScreen(); console.log( boxen(green("Moving Through Your Files πŸš€"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nThe " + cyan("cd") + " command is your " + bold("teleportation device") + "!" ); await sleep(700); await printWithTypingEffect( "\n\n" + cyan("cd") + " stands for " + bold("C") + "hange " + bold("D") + "irectory" ); await sleep(1000); console.log( boxen( bold("Basic Movement:\n\n") + cyan("cd Documents") + " β†’ Go into Documents folder\n" + cyan("cd ..") + " β†’ Go back up one level\n" + cyan("cd") + " β†’ Go straight home\n" + cyan("cd /") + " β†’ Go to root (top) of filesystem\n" + cyan("cd -") + " β†’ Go to previous location\n\n" + dim("Think of '..' as the 'back button' πŸ”™"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); // Visual file tree console.log( boxen( bold("Visual Journey:\n\n") + "You are here β†’ πŸ“ /Users/alex\n" + " β”œβ”€β”€ " + blue("Desktop/") + "\n" + " β”œβ”€β”€ " + blue("Documents/") + "\n" + " β”‚ β”œβ”€β”€ " + blue("Projects/") + "\n" + " β”‚ └── " + blue("Notes/") + "\n" + " └── " + blue("Downloads/") + "\n\n" + cyan("cd Documents") + " takes you to:\n" + " /Users/alex/" + bold("Documents") + " πŸ“\n\n" + cyan("cd Projects") + " then takes you to:\n" + " /Users/alex/Documents/" + bold("Projects") + " πŸ“", { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Special Shortcuts:\n\n") + cyan("cd ~") + " or just " + cyan("cd") + " β†’ Your home directory\n" + cyan("cd ~/Desktop") + " β†’ Desktop from anywhere\n" + cyan("cd ../..") + " β†’ Go up two levels\n\n" + "The " + magenta("~") + " symbol always means 'home' 🏠", { padding: 1, margin: { top: 1 }, borderColor: "magenta", } ) ); await sleep(1000); console.log( boxen( "πŸ’» " + bold("Terminal says:") + "\n\n" + "\"Navigation tip: Use Tab completion with cd!\n\n" + "Type: cd Doc[Tab]\n" + "I'll complete it to: cd Documents/\n\n" + "Also, spaces in folder names need quotes:\n" + "cd \"My Projects\" βœ“\n" + "cd My Projects βœ— (I'll get confused!)\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); }, }, { id: "paths", title: "Understanding Paths", content: async () => { clearScreen(); console.log( boxen(green("Paths: Addresses vs Directions πŸ—ΊοΈ"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nThere are two ways to specify locations:" ); await sleep(700); await printWithTypingEffect( "\n" + bold("Absolute paths") + " = Full addresses (start with /)" ); await printWithTypingEffect( "\n" + bold("Relative paths") + " = Directions from where you are" ); await sleep(1000); console.log( boxen( bold("Real World Analogy:\n\n") + magenta("Absolute") + ": \"Go to 123 Main St, New York, NY\"\n" + dim(" (Works from anywhere!)\n\n") + yellow("Relative") + ": \"Go two blocks north, then turn left\"\n" + dim(" (Only works from current location!)"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Path Examples:\n\n") + "Current location: /Users/alex/Documents\n\n" + magenta("Absolute paths:") + " (start with /)\n" + "β€’ " + magenta("/Users/alex/Desktop") + " β†’ Full address\n" + "β€’ " + magenta("/System/Library") + " β†’ From root\n\n" + yellow("Relative paths:") + " (no / at start)\n" + "β€’ " + yellow("Projects") + " β†’ Inside current folder\n" + "β€’ " + yellow("../Desktop") + " β†’ Up one, then Desktop\n" + "β€’ " + yellow("./notes.txt") + " β†’ In current folder\n\n" + dim("The ./ means 'current directory' (often optional)"), { padding: 1, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\nπŸ’‘ When in doubt, " + cyan("pwd") + " shows your absolute path!" ); }, }, { id: "file_operations", title: "Creating and Managing Files", content: async () => { clearScreen(); console.log( boxen(green("File Operations πŸ“"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nNow let's learn to " + bold("create") + " and " + bold("delete") + " files and folders!" ); await sleep(1000); console.log( boxen( bold("Creating Things:\n\n") + cyan("mkdir") + " " + yellow("folder_name") + " β†’ " + bold("M") + "a" + bold("k") + "e " + bold("dir") + "ectory\n" + cyan("touch") + " " + yellow("file.txt") + " β†’ Create empty file\n\n" + "Examples:\n" + "β€’ " + cyan("mkdir Projects") + " β†’ Creates Projects folder\n" + "β€’ " + cyan("mkdir \"My Work\"") + " β†’ Folder with space\n" + "β€’ " + cyan("touch notes.txt") + " β†’ Creates empty notes.txt\n" + "β€’ " + cyan("touch README.md") + " β†’ Creates README.md", { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Creating Multiple Things:\n\n") + cyan("mkdir") + " " + yellow("folder1 folder2 folder3") + "\n" + dim("β†’ Creates three folders at once!\n\n") + cyan("touch") + " " + yellow("file1.txt file2.txt file3.txt") + "\n" + dim("β†’ Creates three files at once!\n\n") + cyan("mkdir -p") + " " + yellow("parent/child/grandchild") + "\n" + dim("β†’ Creates nested folders (even if parent doesn't exist!)"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Removing Things ⚠️:\n\n") + cyan("rm") + " " + yellow("file.txt") + " β†’ " + bold("R") + "e" + bold("m") + "ove file\n" + cyan("rmdir") + " " + yellow("empty_folder") + " β†’ Remove empty directory\n" + cyan("rm -r") + " " + yellow("folder") + " β†’ Remove folder + contents\n\n" + yellow("⚠️ WARNING: ") + "No trash can! Deleted = GONE!\n\n" + dim("Always double-check before using rm!\n") + dim("Pro tip: Use ls first to see what you're deleting"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( "πŸ’» " + bold("Terminal says:") + "\n\n" + "\"Please be careful with " + cyan("rm") + "! 😰\n\n" + "Unlike your desktop trash can, when I delete\n" + "something, it's gone FOREVER. No undo!\n\n" + "Before using rm, always:\n" + "1. Use " + cyan("ls") + " to check what's there\n" + "2. Double-check your command\n" + "3. Consider making a backup first!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); }, }, { id: "opening_files", title: "Opening Files from Terminal", content: async () => { clearScreen(); console.log( boxen(green("Opening Files Like Magic 🎩"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nYou found a file with " + cyan("ls") + "... now what? Let's learn to " + bold("open files") + " right from the terminal!" ); await sleep(700); await printWithTypingEffect( "\nNo need to leave the terminal - you can open PDFs, images, documents, and more!" ); await sleep(1000); console.log( boxen( bold("The Magic Word: 'open' (macOS) or 'xdg-open' (Linux)\n\n") + "🍎 " + bold("macOS:\n") + cyan("open") + " " + yellow("file.pdf") + " β†’ Opens in default PDF viewer\n" + cyan("open") + " " + yellow(".") + " β†’ Opens current folder in Finder\n\n" + "🐧 " + bold("Linux:\n") + cyan("xdg-open") + " " + yellow("file.pdf") + " β†’ Opens in default app\n" + cyan("xdg-open") + " " + yellow(".") + " β†’ Opens in file manager\n\n" + "πŸͺŸ " + bold("Windows (PowerShell):\n") + cyan("start") + " " + yellow("file.pdf") + " β†’ Opens in default app\n" + cyan("explorer") + " " + yellow(".") + " β†’ Opens in File Explorer", { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Common File Types & What Happens:\n\n") + "πŸ“„ " + cyan("open report.pdf") + "\n" + " β†’ Opens in Preview/Adobe/PDF viewer\n\n" + "πŸ–ΌοΈ " + cyan("open photo.jpg") + "\n" + " β†’ Opens in image viewer\n\n" + "πŸ“ " + cyan("open notes.txt") + "\n" + " β†’ Opens in default text editor\n\n" + "🌐 " + cyan("open index.html") + "\n" + " β†’ Opens in default web browser\n\n" + "πŸ“Š " + cyan("open data.xlsx") + "\n" + " β†’ Opens in Excel/Numbers/LibreOffice", { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Pro Tips:\n\n") + "β€’ " + cyan("open .") + " β†’ Open current folder in GUI\n" + "β€’ " + cyan("open ..") + " β†’ Open parent folder in GUI\n" + "β€’ " + cyan("open -a \"Application Name\" file") + " β†’ Choose specific app\n\n" + "Example: " + cyan('open -a "Visual Studio Code" script.js') + "\n" + dim("Opens script.js in VS Code specifically"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Reading Text Files in Terminal:\n\n") + "Sometimes you want to stay in the terminal:\n\n" + cyan("cat") + " " + yellow("file.txt") + " β†’ Display entire file\n" + cyan("less") + " " + yellow("file.txt") + " β†’ Page through file (q to quit)\n" + cyan("head") + " " + yellow("file.txt") + " β†’ Show first 10 lines\n" + cyan("tail") + " " + yellow("file.txt") + " β†’ Show last 10 lines\n\n" + dim("Use 'cat' for small files, 'less' for large ones!"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( "πŸ’» " + bold("Terminal says:") + "\n\n" + "\"I can open ANY file type! Just use 'open' and I'll\n" + "figure out which app to use.\n\n" + "Fun fact: You can even open URLs!\n" + cyan("open https://google.com") + " β†’ Opens in browser\n\n" + "And if you're curious what's in a text file without\n" + "leaving me, just use 'cat' for a quick peek! πŸ‘€\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); await sleep(1000); await printWithTypingEffect( "\n🎯 Try it: Next time you see a PDF with " + cyan("ls") + ", use " + cyan("open") + " to view it!" ); }, }, { id: "summary", title: "Your Navigation Toolkit", content: async () => { clearScreen(); console.log( boxen(green("Command Reference Card πŸ“‹"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nHere's your complete file navigation toolkit:" ); await sleep(700); console.log( boxen( bold("πŸ“ Location Commands\n") + cyan("pwd") + " β†’ Print working directory (where am I?)\n" + cyan("ls") + " β†’ List contents (what's here?)\n" + cyan("ls -la") + " β†’ Detailed list with hidden files\n\n" + bold("πŸš€ Navigation\n") + cyan("cd") + " " + yellow("folder") + " β†’ Change to folder\n" + cyan("cd ..") + " β†’ Go up one level\n" + cyan("cd") + " or " + cyan("cd ~") + " β†’ Go home\n" + cyan("cd -") + " β†’ Previous location\n\n" + bold("πŸ“ File Operations\n") + cyan("mkdir") + " " + yellow("name") + " β†’ Create folder\n" + cyan("touch") + " " + yellow("name") + " β†’ Create file\n" + cyan("rm") + " " + yellow("file") + " β†’ Remove file\n" + cyan("rmdir") + " " + yellow("folder") + " β†’ Remove empty folder\n" + cyan("rm -r") + " " + yellow("folder") + " β†’ Remove folder + contents\n\n" + bold("πŸ“‚ Opening Files\n") + cyan("open") + " " + yellow("file") + " β†’ Open in default app (macOS)\n" + cyan("xdg-open") + " " + yellow("file") + " β†’ Open in default app (Linux)\n" + cyan("cat") + " " + yellow("file") + " β†’ View text file in terminal", { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Common Workflow Example:\n\n") + dim("# Check where you are\n") + "$ " + cyan("pwd") + "\n" + green("/Users/alex") + "\n\n" + dim("# Create a new project\n") + "$ " + cyan("mkdir") + " MyProject\n" + "$ " + cyan("cd") + " MyProject\n" + "$ " + cyan("touch") + " README.md index.js\n" + "$ " + cyan("ls") + "\n" + green("README.md index.js") + "\n\n" + dim("You just created a project! πŸŽ‰"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\n🎯 Remember: " + cyan("pwd") + ", " + cyan("ls") + ", and " + cyan("cd") + " are your best friends!" ); }, }, ]; // Exercises for Chapter 3 const exercises = [ { id: "location_check", type: "multiple_choice", title: "πŸ“ Finding Your Location", question: "You just opened a new terminal window. Which command shows you exactly where you are in the file system?", choices: [ cyan("ls") + " - to see what files are here", cyan("pwd") + " - to print your working directory", cyan("cd") + " - to change directories", cyan("whereami") + " - to show location", ], answer: 1, explanation: cyan("pwd") + " (Print Working Directory) is your GPS! It shows your exact location in the file system. Always use it when you feel lost!", wrongAnswerHints: { 0: "That shows what's IN your location, not WHERE you are!", 2: "That's for moving to a new location, not checking where you are!", 3: "Nice guess, but the actual command is shorter!", }, }, { id: "ls_understanding", type: "multiple_choice", title: "πŸ‘€ Reading ls Output", scenario: "You run " + cyan("ls") + " and see this:\n\n" + blue("Documents/") + " " + blue("Downloads/") + " " + green("notes.txt") + " " + green("script.py"), question: "Which of these can you " + cyan("cd") + " into?", choices: [ "notes.txt and script.py (the files)", "Documents/ and Downloads/ (the folders)", "All of them", "None of them", ], answer: 1, explanation: "You can only " + cyan("cd") + " into folders (directories), shown in blue with / at the end. Files like notes.txt can be read or edited, but not entered!", wrongAnswerHints: { 0: "You can't cd into files - only folders!", 2: "Close! But you can only enter folders, not files.", 3: "Look again - those blue items with / are folders!", }, }, { id: "navigation_challenge", type: "multiple_choice", title: "πŸš€ Navigation Challenge", scenario: "You're in /Users/alex/Documents/Projects\n" + "You need to get to /Users/alex/Desktop\n\n" + "Which command gets you there most efficiently?", choices: [ cyan("cd Desktop"), cyan("cd ../../Desktop"), cyan("cd /Users/alex/Desktop"), cyan("cd ~/Desktop"), ], answer: 1, explanation: cyan("cd ../../Desktop") + " is the most efficient! Go up twice (..) to /Users/alex, then into Desktop. Both " + cyan("cd /Users/alex/Desktop") + " and " + cyan("cd ~/Desktop") + " also work but the relative path is quicker to type!", wrongAnswerHints: { 0: "Desktop isn't inside Projects - you need to go up first!", 2: "This works but it's longer to type than needed!", 3: "This works too, but the relative path is even shorter!", }, }, { id: "path_types", type: "multiple_choice", title: "πŸ—ΊοΈ Path Understanding", question: "Your friend says: 'The file is at ./data/users.json'\n\n" + "What does the " + yellow("./") + " at the beginning mean?", choices: [ "Go to the root of the file system", "It's in your home directory", "It's in the current directory", "Go up one directory level", ], answer: 2, explanation: "The " + yellow("./") + " means 'current directory' - so the file is inside a 'data' folder in your current location. Just " + yellow("/") + " would mean root, " + yellow("~/") + " means home, and " + yellow("../") + " means parent directory!", wrongAnswerHints: { 0: "That would be just / without the dot!", 1: "That would be ~/ for home directory!", 3: "That would be ../ to go up!", }, }, { id: "file_management", type: "type_command", title: "πŸ“ Project Setup Challenge", scenario: "Let's create a simple project structure! You need to:\n" + "1. Create a folder called 'my-website'\n" + "2. Create an empty 'index.html' file inside it\n\n" + "For this exercise, type the commands separated by ' && ' (this runs them in sequence).\n\n" + dim("Hint: First make the directory, then create the file inside it"), question: "Type the complete command sequence:", answer: "mkdir my-website && touch my-website/index.html", alternates: [ "mkdir my-website && cd my-website && touch index.html", "mkdir 'my-website' && touch 'my-website/index.html'", "mkdir \"my-website\" && touch \"my-website/index.html\"", ], hints: [ "Start with mkdir to create the folder", "You can create the file directly in the folder path, or cd into it first", "Remember: && runs commands in sequence", ], explanation: "Great job! You've created a project folder and file. The " + cyan("&&") + " operator runs commands sequentially. You could also cd into the folder first, but creating the file with a path is more efficient!", showBuilder: true, }, ]; // Exercise runner functions async function runExercise(exercise, index, total) { clearScreen(); const progress = `[${index + 1}/${total}]`; console.log( boxen(green(progress + " " + exercise.title), { padding: 1, borderStyle: "round", borderColor: "green", }) ); // Show project builder for the last exercise if (exercise.showBuilder) { console.log("\n" + bold("Let's build this step by step:")); await sleep(1000); console.log( boxen( green("PROJECT BUILDER πŸ—οΈ") + "\n\n" + dim("─────────────────────────────────") + "\n\n" + "Current location: ~/\n\n" + "Step 1: Create project folder\n" + "β†’ " + cyan("mkdir") + " " + yellow("my-website") + "\n\n" + "Result: ~/\n" + " └── " + blue("my-website/") + "\n\n" + "Step 2: Create index.html inside\n" + "β†’ " + cyan("touch") + " " + yellow("my-website/index.html") + "\n\n" + "Final structure:\n" + "~/my-website/\n" + " └── " + green("index.html"), { padding: 1, margin: { top: 1, bottom: 1 }, borderStyle: "bold", borderColor: "yellow", title: "πŸ—οΈ Interactive Builder", titleAlignment: "center", } ) ); await sleep(2000); await printWithTypingEffect("\nNow combine these commands with &&!"); await sleep(1000); } if (exercise.scenario) { console.log("\n" + bold("Scenario:")); await printWithTypingEffect(exercise.scenario); await sleep(1000); } if (exercise.hint) { console.log("\n" + yellow("πŸ’‘ Hint: ") + dim(exercise.hint)); await sleep(1000); } if (exercise.type === "multiple_choice") { return await runMultipleChoice(exercise); } else if (exercise.type === "type_command") { return await runTypeCommand(exercise); } } async function runMultipleChoice(exercise) { if (exercise.command) { console.log("\n" + exercise.command); } console.log("\n" + exercise.question); const { choice } = await inquirer.prompt([ { type: "list", name: "choice", message: green(">"), prefix: "", choices: exercise.choices, }, ]); const choiceIndex = exercise.choices.indexOf(choice); const isCorrect = choiceIndex === exercise.answer; if (isCorrect) { const spinner = ora({ text: "Checking...", color: "green", }).start(); await sleep(800); spinner.succeed(green("Correct! ✨")); } else { console.log(chalk.red("\nNot quite right.")); if (exercise.wrongAnswerHints && exercise.wrongAnswerHints[choiceIndex]) { console.log(yellow("\nπŸ’‘ Hint: ") + dim(exercise.wrongAnswerHints[choiceIndex])); } } if (exercise.explanation) { console.log(dim("\n" + exercise.explanation)); } await sleep(2000); return isCorrect; } async function runTypeCommand(exercise) { console.log("\n" + exercise.question); let attempts = 0; let correct = false; while (!correct && attempts < 3) { const { answer } = await inquirer.prompt([ { type: "input", name: "answer", message: green(">"), prefix: "", validate: (input) => input.trim().length > 0 || "Please type a command", }, ]); const normalizedAnswer = answer.trim(); const isCorrect = normalizedAnswer === exercise.answer || (exercise.alternates && exercise.alternates.includes(normalizedAnswer)); if (isCorrect) { correct = true; const spinner = ora({ text: "Checking...", color: "green", }).start(); await sleep(800); spinner.succeed(green("Perfect! ✨")); if (exercise.explanation) { console.log(dim("\n" + exercise.explanation)); } } else { attempts++; if (attempts < 3) { console.log(chalk.red("Not quite. Try again!")); if (exercise.hints && exercise.hints[attempts - 1]) { showHint(exercise.hints[attempts - 1]); } } } } if (!correct) { console.log( chalk.yellow(`\nThe correct answer was: ${green(exercise.answer)}`) ); console.log(dim("Don't worry! File navigation takes practice. πŸ’ͺ")); } await sleep(2000); return correct; } async function start() { clearScreen(); // Run through lessons for (const lesson of lessons) { await lesson.content(); console.log(); // Add spacing before the prompt const { ready } = await inquirer.prompt([ { type: "confirm", name: "ready", message: "Ready to continue?", default: true, }, ]); if (!ready) { return; } } // Practice exercises intro clearScreen(); console.log( boxen( green("Navigation Practice Time! 🧭") + "\n\n" + "Let's test your file system skills with " + bold("5 exercises") + "\n\n" + dim("Remember: pwd, ls, and cd are your navigation tools!"), { padding: 1, borderStyle: "double", borderColor: "green", } ) ); await sleep(2000); // Quick command review clearScreen(); console.log( boxen(green("🎯 Quick Review"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nRemember your essential commands:" ); await sleep(700); console.log( boxen( bold("Navigation Essentials:\n\n") + "πŸ“ " + cyan("pwd") + " = Where am I?\n" + "πŸ‘€ " + cyan("ls") + " = What's here?\n" + "πŸš€ " + cyan("cd") + " = Go somewhere\n" + "πŸ“ " + cyan("mkdir") + " = Make folder\n" + "πŸ“„ " + cyan("touch") + " = Make file\n" + "πŸ—‘οΈ " + cyan("rm") + " = Remove (careful!)\n\n" + "Special places:\n" + "β€’ " + yellow("~") + " = Home\n" + "β€’ " + yellow("..") + " = Parent folder\n" + "β€’ " + yellow("/") + " = Root\n\n" + dim("Let's test your skills! πŸš€"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1500); const { ready } = await inquirer.prompt([ { type: "confirm", name: "ready", message: "Ready for the exercises?", default: true, }, ]); if (!ready) { return; } // Run exercises let score = 0; const results = []; for (let i = 0; i < exercises.length; i++) { const result = await runExercise(exercises[i], i, exercises.length); results.push({ exercise: exercises[i].title, passed: result, }); if (result) score++; if (i < exercises.length - 1) { const { ready } = await inquirer.prompt([ { type: "confirm", name: "ready", message: "Ready for the next exercise?", default: true, }, ]); if (!ready) break; } } // Chapter completion clearScreen(); const percentage = Math.round((score / exercises.length) * 100); let message = ""; if (percentage === 100) { message = "πŸ† PERFECT! You're a navigation master!"; } else if (percentage >= 80) { message = "🌟 Excellent! You can find your way anywhere!"; } else if (percentage >= 60) { message = "πŸ‘ Good work! Your navigation skills are growing!"; } else { message = "πŸ’ͺ Keep exploring! Navigation gets easier with practice!"; } console.log( boxen( green("πŸŽ‰ Chapter 3 Complete! πŸŽ‰") + "\n\n" + bold(`Your Score: ${score}/${exercises.length} (${percentage}%)`) + "\n\n" + message + "\n\n" + "File system skills unlocked:\n" + "βœ… Check your location with pwd\n" + "βœ… See what's around with ls\n" + "βœ… Navigate with cd\n" + "βœ… Understand absolute vs relative paths\n" + "βœ… Create files and folders\n" + "βœ… Remove items (carefully!)\n\n" + yellow("🎯 Practice Challenge:") + "\n" + "Create a project folder for your next idea!\n" + "Organize your Downloads folder using these commands.\n\n" + dim("Ready for Chapter 4: Working with Git? Coming soon..."), { padding: 1, borderStyle: "double", borderColor: "green", margin: 1, } ) ); // Show exercise summary console.log("\n" + bold("Exercise Summary:")); results.forEach((result) => { const icon = result.passed ? green("βœ“") : chalk.red("βœ—"); console.log(`${icon} ${result.exercise}`); }); await inquirer.prompt([ { type: "input", name: "continue", message: "\nPress Enter to return to the main menu...", prefix: "", }, ]); } module.exports = { start };