clitutor
Version:
Interactive CLI learning tool for beginners
1,169 lines (1,066 loc) β’ 37.1 kB
JavaScript
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 };