UNPKG

clitutor

Version:

Interactive CLI learning tool for beginners

1,379 lines (1,270 loc) 38.3 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 lessons = [ { id: "intro", title: "Welcome to the Command Line!", content: async () => { clearScreen(); console.log( boxen(green("Chapter 1: Understanding Command Structure"), { padding: 1, borderStyle: "double", borderColor: "green", }) ); await printWithTypingEffect( "\nThe command line (CLI) is a text-based way to interact with your computer." ); await sleep(500); await printWithTypingEffect( "Instead of clicking buttons, you type commands to tell your computer what to do." ); await sleep(500); await printWithTypingEffect( "\nThink of it as having a conversation with your computer!" ); await sleep(1000); await printWithTypingEffect( "\nThe objective of this first chapter is to learn the basic structures you need to start talking through your terminal!" ); await sleep(500); }, }, { id: "commands_intro", title: "Commands: The Building Blocks", content: async () => { clearScreen(); console.log( boxen(green("What Are Commands?"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nCommands are like " + bold("verbs") + " in a sentence - they tell the computer what action to take." ); await sleep(700); console.log( boxen( "Examples of commands:\n\n" + green("pwd") + dim(" # Print working directory\n") + green("ls") + dim(" # List files\n") + green("clear") + dim(" # Clear the screen\n") + green("echo") + dim(" # Display text\n") + green("git") + dim(" # Version control tool"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect("\nSome commands work by themselves:"); console.log( boxen( green("pwd") + "\n" + dim("└─ Complete command, shows your location"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(700); await printWithTypingEffect("\nOthers need more information..."); await sleep(700); }, }, { id: "learning_objectives", title: "What We'll Learn", content: async () => { clearScreen(); console.log( boxen(green("Building Your Command Knowledge"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect("\nImagine you want to tell someone:"); await sleep(700); await printWithTypingEffect( "\n" + bold('"Please add this file to my project carefully"') ); await sleep(1000); console.log( boxen( "In command line, this becomes:\n\n" + green("git") + " " + yellow("add") + " " + bold("file.txt") + " " + yellow("--verbose") + "\n\n" + "Each part has a purpose:\n" + "• " + green("git") + " = The tool (command)\n" + "• " + yellow("add") + " = The action (subcommand)\n" + "• " + bold("file.txt") + " = What to add (argument)\n" + "• " + yellow("--verbose") + " = How to do it (flag)", { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\nLet's break down each part so you can build any command!" ); }, }, { id: "subcommands", title: "Subcommands: Commands Within Commands", content: async () => { clearScreen(); console.log( boxen(green("Commands Can Have Subcommands"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nSome commands are like " + bold("toolboxes") + " - they contain multiple tools inside." ); await sleep(700); await printWithTypingEffect( "\n\nLet's use " + green("git") + " as an example." ); await sleep(700); console.log( boxen( bold("What is Git?") + "\n\n" + "Git helps you save different versions of your work,\n" + "like a time machine for your files!\n\n" + "You can:\n" + "• Save snapshots of your work\n" + "• Go back to earlier versions\n" + "• Share your work with others\n\n" + dim( "(You might have heard of GitHub - that's\n" + 'the "hub" where people share their git projects!)' ), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1500); await printWithTypingEffect( "\nBut " + green("git") + " alone doesn't do anything - you need to tell it what to do:" ); await sleep(700); console.log( boxen( green("git") + dim(' # Just saying "git" - but what action?\n\n') + green("git") + " " + yellow("status") + dim(" # Check what's changed\n") + green("git") + " " + yellow("add") + dim(" # Prepare files to save\n") + green("git") + " " + yellow("commit") + dim(" # Save a snapshot\n") + green("git") + " " + yellow("push") + dim(" # Upload to the internet"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( "Structure: " + green("command") + " " + yellow("subcommand") + "\n\n" + "The subcommand tells the command " + bold("what specific action") + " to take.", { padding: 1, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( '\nFor simplicity, we will use the term "command + subcommand", but it can also be called ' + bold("program + command") + "." ); await sleep(1000); await printWithTypingEffect("\nLet's see how this works in practice!"); await sleep(1000); }, }, { id: "arguments", title: "Arguments: The Target", content: async () => { clearScreen(); console.log( boxen(green("Arguments: What to Act On"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nArguments are like " + bold("objects") + " in a sentence - they tell commands what to work with." ); await sleep(700); await printWithTypingEffect("\n\nLet's see what you're really saying:"); await sleep(700); console.log( boxen( "Commands without arguments often feel incomplete:\n\n" + green("echo") + dim(' # "Computer, echo!" ...echo what?\n') + green("echo") + " " + bold('"Hello"') + dim(' # "Computer, echo Hello!"\n\n') + green("git add") + dim(' # "Git, add!" ...add what?\n') + green("git add") + " " + bold("file.txt") + dim(' # "Git, add file.txt!"'), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("What you're saying with git commands:") + "\n\n" + green("git") + " " + yellow("add") + " " + bold(".") + "\n" + dim(' "Git, add everything in this folder"\n\n') + green("git") + " " + yellow("add") + " " + bold("*.js") + "\n" + dim(' "Git, add all JavaScript files"\n\n') + green("git") + " " + yellow("add") + " " + bold("README.md") + "\n" + dim(' "Git, add the README.md file"'), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\nArguments complete your command's meaning!" ); await sleep(1000); // Terminal says box console.log( boxen( "💻 " + bold("Terminal says:") + "\n\n" + "\"When you give me a command without arguments,\n" + "I often feel confused! It's like saying:\n\n" + "'Hey, add!' ...add what? 🤷\n" + "'Hey, delete!' ...delete what? 😰\n\n" + "Arguments help me understand what you want!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); }, }, { id: "flags", title: "Flags: Modifying Behavior", content: async () => { clearScreen(); console.log( boxen(green("Flags: How to Do It"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nFlags are like " + bold("adverbs") + " - they modify HOW a command works." ); await sleep(700); await printWithTypingEffect( "\n\nThink of them as switches or settings you can turn on." ); await sleep(700); console.log( boxen( bold("Two styles of flags:\n\n") + yellow("Short flags: ") + green("-") + " followed by a letter\n" + " Examples: -m, -a, -v\n\n" + yellow("Long flags: ") + green("--") + " followed by a word\n" + " Examples: --message, --all, --verbose", { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( "Real examples:\n\n" + green("ls") + " " + yellow("-l") + dim(" # List with details\n") + green("git status") + " " + yellow("--short") + dim(" # Shorter output\n") + green("git commit") + " " + yellow("-m") + ' "Fix"' + dim(" # Flag with value"), { padding: 1, borderColor: "gray", } ) ); await sleep(700); await printWithTypingEffect( "\nFlags answer the question: " + bold('"How?"') ); await sleep(1000); console.log( boxen(green("Where Can Flags Go?"), { padding: 1, borderStyle: "round", borderColor: "green", margin: { top: 1 }, }) ); await printWithTypingEffect( "\nFor beginners, we show flags in a standard order:" ); await sleep(700); console.log( boxen( bold("Standard order (easiest to read):\n") + green("command") + " " + yellow("[subcommand]") + " " + yellow("[flags]") + " " + bold("[arguments]") + "\n\n" + green("git") + " " + yellow("commit") + " " + yellow("-m") + " " + bold('"Message"') + "\n" + green("ls") + " " + yellow("-la") + " " + bold("/home"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\n" + bold("But here's the secret:") + " Flags are flexible!" ); await sleep(700); console.log( boxen( bold("These all work the same:\n\n") + green("ls") + " " + yellow("-l") + " " + bold("/home") + dim(" # Flag before argument\n") + green("ls") + " " + bold("/home") + " " + yellow("-l") + dim(" # Flag after argument\n") + green("git") + " " + yellow("-c color.ui=true") + " status" + dim(" # Flag before subcommand!"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\n📚 For now, stick to the standard order. As you advance, you'll use this flexibility!" ); await sleep(1000); // Terminal says box console.log( boxen( "💻 " + bold("Terminal says:") + "\n\n" + "\"Flags are like my preference settings!\n\n" + "Without flags: I do things my default way 🤖\n" + "With flags: I follow your special instructions! ✨\n\n" + "For example:\n" + green("ls") + " → 'Show files' (basic)\n" + green("ls") + " " + yellow("-la") + " → 'Show files with ALL details!' (fancy)\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); }, }, { id: "argument_types", title: "Arguments: For Commands or Flags", content: async () => { clearScreen(); console.log( boxen(green("Where Arguments Belong"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nNow that you know commands, arguments, and flags..." ); await sleep(700); await printWithTypingEffect( "\nHere's something important: Arguments can work with " + bold("different parts") + " of your command!" ); await sleep(1000); console.log( boxen( bold("Arguments answer 'what?' for different parts:\n\n") + "1️⃣ " + bold("Arguments for Commands/Subcommands") + "\n" + "These tell the command WHAT to work with:\n\n" + green("echo") + " " + bold('"Hello World"') + "\n" + dim(" └─ Tells echo WHAT to display\n\n") + green("git add") + " " + bold("README.md") + "\n" + dim(" └─ Tells git add WHAT file to add"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( "2️⃣ " + bold("Arguments for Flags") + "\n" + "Some flags need their own arguments:\n\n" + green("git commit") + " " + yellow("-m") + " " + bold('"Fix bug"') + "\n" + dim(" └─ The -m flag needs a message\n\n") + green("head") + " " + yellow("-n") + " " + bold("20") + " file.txt\n" + dim(" └─ The -n flag needs a number\n") + dim(" └─ Command argument"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\n🎯 The key insight: Arguments always answer 'what?' - but they might be answering that question for different parts of your command!" ); await sleep(1000); console.log( boxen( bold("Real example with both types:\n\n") + green("git") + " " + yellow("log") + " " + yellow("--since") + " " + bold('"2 weeks ago"') + " " + bold("main") + "\n\n" + "Breaking it down:\n" + "• " + green("git") + " = Command\n" + "• " + yellow("log") + " = Subcommand\n" + "• " + yellow("--since") + " = Flag that needs a date\n" + "• " + bold('"2 weeks ago"') + " = Argument for the flag\n" + "• " + bold("main") + " = Argument for git log\n\n" + dim("Translation: 'Git, show me the log of main branch since 2 weeks ago'"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\n💡 Pro tip: Flag arguments usually come right after their flag!" ); }, }, { id: "handling_spaces", title: "Handling Spaces in Commands", content: async () => { clearScreen(); console.log( boxen(green("The Space Challenge"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nThe command line has one quirk you need to know..." ); await sleep(700); await printWithTypingEffect( "\n" + bold("Spaces") + " are special - they separate the parts of your command!" ); await sleep(1000); console.log( boxen( "Think of spaces like commas in a list:\n\n" + green("echo") + " Hello World\n" + dim("└─ The computer sees: echo, Hello, World\n") + " (3 separate things!)\n\n" + "That's why only 'Hello' gets printed!", { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); // Command flow visualization console.log( boxen( bold("Visual: How the computer reads your command\n\n") + "YOU TYPE: COMPUTER SEES:\n" + green("echo") + " Hello World ┌──────┐ ┌───────┐ ┌───────┐\n" + " ↓ │ echo │→│ Hello │→│ World │\n" + dim(" \"one thing\" ") + "└──────┘ └───────┘ └───────┘\n" + " " + dim("\"three separate things\""), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\nSo how do we tell the computer that words belong together?" ); await sleep(700); await printWithTypingEffect( "\n" + bold("Use quotes!") + " They're like a box that keeps words together." ); await sleep(1000); console.log( boxen( bold("Without quotes = Multiple pieces:\n") + green("echo") + " Hello World" + dim(" → echo gets 'Hello' and 'World' separately\n\n") + bold("With quotes = One piece:\n") + green("echo") + ' "Hello World"' + dim(' echo gets "Hello World" as one unit'), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Real-world examples:\n\n") + "Creating a folder with spaces:\n" + green("mkdir") + " " + bold('"My Projects"') + "\n\n" + "Git commit with a sentence:\n" + green("git commit -m") + " " + bold('"Fix login bug"') + "\n\n" + "Searching for a phrase:\n" + green("grep") + " " + bold('"error occurred"') + " log.txt", { padding: 1, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\n🎯 Remember: Quotes are your friends when dealing with spaces!" ); await sleep(700); console.log( boxen( yellow("Quick tip:") + " Both work the same:\n" + ' Double quotes: "Hello World"\n' + "• Single quotes: 'Hello World'\n\n" + dim("(We'll learn the subtle differences later!)"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); }, }, { id: "complete_picture", title: "The Complete Picture", content: async () => { clearScreen(); console.log( boxen(green("Putting It All Together"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect("\nCommands can combine all these parts:"); await sleep(700); console.log( boxen( green("command") + " " + yellow("[subcommand]") + " " + yellow("[flags]") + " " + bold("[arguments]") + "\n\n" + "Like a sentence:\n" + bold("VERB") + " " + bold("ACTION") + " " + bold("HOW") + " " + bold("WHAT") + "\n\n" + dim( "(This is the standard order for clarity, but flags can be flexible!)" ), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( "Let's analyze:\n\n" + green("git") + " " + yellow("commit") + " " + yellow("-m") + " " + bold('"Initial commit"') + "\n\n" + "• " + green("git") + " = Command (the tool)\n" + "• " + yellow("commit") + " = Subcommand (save changes)\n" + "• " + yellow("-m") + " = Flag (message flag)\n" + "• " + bold('"Initial commit"') + " = Argument (the message)", { padding: 1, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\nNot all commands use every part - use only what's needed!" ); }, }, ]; // Simplified exercises focused on core concepts const exercises = [ { id: "what_are_commands", type: "multiple_choice", title: "📚 Recognizing Commands", question: "You want to check what's changed in your project. Which part is the COMMAND in: " + green("git") + " " + yellow("status") + "?", choices: [ green("git") + " - the main program you're talking to", yellow("status") + " - the action to perform", "Both are commands", "Neither is a command", ], answer: 0, explanation: green("git") + " is the command - it's like saying 'Hey Git!' to get its attention. The " + yellow("status") + " part tells Git what you want it to do.", wrongAnswerHints: { 1: "That's the subcommand - it tells the command what to do!", 2: "Only one is the main command - the other is a subcommand.", 3: "Look again - one of these is definitely a command!", }, }, { id: "what_are_arguments", type: "multiple_choice", title: "🎯 Understanding Arguments", question: "You're telling Git to add a file. In: " + green("git add") + " " + bold("README.md") + "\nWhat role does " + bold("README.md") + " play?", choices: [ "It's another command", "It's a flag that modifies behavior", "It's the target - what Git should add", "It's a subcommand", ], answer: 2, explanation: bold("README.md") + " is an argument - it answers the question 'add WHAT?' It's like telling your computer: 'Hey Git, add README.md to my project!'", wrongAnswerHints: { 0: "Commands are the main programs like 'git' - this is something else!", 1: "Flags start with - or -- and modify HOW things work.", 3: "Subcommands are actions like 'add' or 'commit' - this is a file name!", }, }, { id: "flag_types", type: "multiple_choice", title: "🚩 Recognizing Flags", question: "You see this command: " + green("ls") + " " + yellow("-la") + " " + bold("/home") + "\nWhich part is the flag that changes HOW ls works?", choices: [ green("ls") + " - the command", yellow("-la") + " - it starts with a dash", bold("/home") + " - the directory", "There is no flag in this command", ], answer: 1, explanation: yellow("-la") + " is the flag! The dash (-) is the giveaway. It's like saying: 'Hey ls, list the files, but do it with -l (long format) and -a (show all files)!'", wrongAnswerHints: { 0: "That's the command itself - flags modify how commands work!", 2: "That's an argument telling ls WHERE to look.", 3: "Look for something starting with - or -- !", }, }, { id: "handling_spaces", type: "multiple_choice", title: "💬 Talking with Spaces", question: "You type: " + green("echo") + " Hello World" + "\nBut it only prints 'Hello'. What happened to 'World'?", choices: [ "The echo command is broken", "The computer thinks you gave it TWO things to echo", "World is not a valid word", "Echo can only handle one word at a time", ], answer: 1, explanation: "The computer uses spaces to separate your instructions! It thought you said: 'Echo this: Hello' and 'Also this: World'. To keep them together, use quotes: " + green("echo") + ' "Hello World" - like putting your words in a box!', wrongAnswerHints: { 0: "Echo works fine - it's about how the computer reads your message!", 2: "All words are valid - it's about how they're grouped!", 3: "Echo can handle many words - if you tell it they belong together!", }, }, { id: "git_commit_practice", type: "type_command", title: "✍️ Practice: Git Commit", scenario: 'You\'ve fixed a login bug and want to save your work. You need to tell Git to commit with the message "Fix login bug".\n\n' + dim("Remember: You're having a conversation with Git!"), question: "Type the complete command to save your changes:", answer: 'git commit -m "Fix login bug"', alternates: ["git commit -m 'Fix login bug'"], hints: [ "Start by calling git: git commit", "Add the message flag: -m", 'Tell it WHAT message: "Fix login bug" (with quotes!)', ], explanation: "Perfect! You just had a complete conversation with Git:\n" + green("git") + " → 'Hey Git!'\n" + yellow("commit") + " → 'Save my changes'\n" + yellow("-m") + " → 'with this message:'\n" + bold('"Fix login bug"') + " → 'the actual message'\n\n" + "It's like saying: 'Hey Git, commit my changes with the message Fix login bug!'", // Add command builder before this exercise showBuilder: true, }, ]; // Simplified exercise runner async function runExercise(exercise, index, total) { clearScreen(); // Progress indicator const progress = `[${index + 1}/${total}]`; console.log( boxen(green(progress + " " + exercise.title), { padding: 1, borderStyle: "round", borderColor: "green", }) ); // Show command builder for the last exercise if (exercise.showBuilder) { console.log("\n" + bold("Let's build this command step by step:")); await sleep(1000); console.log( boxen( green("BUILD YOUR COMMAND! 🔧") + "\n\n" + dim("─────────────────────────────────") + "\n\n" + "Pick a tool: " + green("[ git ]") + "\n" + "Pick an action: " + yellow("[ commit ]") + "\n" + "Add a flag: " + yellow("[ -m ]") + "\n" + "Add details: " + bold('[ "Fix login bug" ]') + "\n\n" + dim("─────────────────────────────────") + "\n\n" + "Result: " + green("git") + " " + yellow("commit") + " " + yellow("-m") + " " + bold('"Fix login bug"'), { padding: 1, margin: { top: 1, bottom: 1 }, borderStyle: "bold", borderColor: "yellow", title: "🔧 Interactive Builder", titleAlignment: "center", } ) ); await sleep(2000); await printWithTypingEffect("\nNow you try building it yourself!"); await sleep(1000); } // Show scenario if present if (exercise.scenario) { console.log("\n" + bold("Scenario:")); await printWithTypingEffect(exercise.scenario); await sleep(1000); } // Show hint if present if (exercise.hint) { console.log("\n" + yellow("💡 Hint: ") + dim(exercise.hint)); await sleep(1000); } // Handle exercise types 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.")); // Show specific wrong answer hint if available 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 isCorrect = answer.trim() === exercise.answer || (exercise.alternates && exercise.alternates.includes(answer.trim())); 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! Practice makes perfect. 💪")); } await sleep(2000); return correct; } async function start() { clearScreen(); // Introduction 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("Quick Practice! 🗝️") + "\n\n" + "Let's test what you've learned with " + bold("5 quick exercises") + "\n\n" + dim("Don't worry about mistakes - that's how we learn!"), { padding: 1, borderStyle: "double", borderColor: "green", } ) ); await sleep(2000); // Quick review before exercises clearScreen(); console.log( boxen(green("🎯 Quick Review"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nRemember: Using the command line is like having a conversation!" ); await sleep(700); console.log( boxen( bold("The conversation pattern:\n\n") + green("command") + " → Who you're talking to\n" + yellow("subcommand") + " → What action to take\n" + yellow("flags") + " → How to do it\n" + bold("arguments") + " → What to work with\n\n" + "Example: " + green("git") + " " + yellow("add") + " " + yellow("-v") + " " + bold("file.txt") + "\n" + dim("'Hey Git, add file.txt verbosely!'"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1500); await printWithTypingEffect("\n💡 Tip: Look for patterns in the exercises!"); await sleep(1000); const { ready } = await inquirer.prompt([ { type: "confirm", name: "ready", message: "Ready to practice?", 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; } } // Completion with score clearScreen(); const percentage = Math.round((score / exercises.length) * 100); let message = ""; if (percentage === 100) { message = "🏆 PERFECT SCORE! You're a command line natural!"; } else if (percentage >= 80) { message = "🌟 Excellent work! You've mastered the basics!"; } else if (percentage >= 60) { message = "👍 Good job! Keep practicing to improve!"; } else { message = "💪 Nice effort! Review the lessons and try again!"; } console.log( boxen( green("🎉 Chapter 1 Complete! 🎉") + "\n\n" + bold(`Your Score: ${score}/${exercises.length} (${percentage}%)`) + "\n\n" + message + "\n\n" + "You learned:\n" + "✅ Commands are the building blocks\n" + "✅ Some commands have subcommands\n" + "✅ Arguments tell commands what to work with\n" + "✅ Flags modify how commands behave\n" + "✅ Arguments can work with commands OR flags\n" + "✅ Spaces separate command parts\n" + "✅ Quotes group words together\n" + "✅ Flags can be flexible in position\n\n" + dim("Ready for Chapter 2: Essential Commands? 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 };