import type { FileSystemNode } from "./types"; import { getCurrentDirectory, resolvePath } from "./fs"; export interface CommandContext { currentPath: string; fileSystem: { [key: string]: FileSystemNode }; setCurrentPath: (path: string) => void; setIsTrainRunning: (running: boolean) => void; setTrainPosition: (position: number) => void; } export function executeCommand(input: string, context: CommandContext): string { const trimmedInput = input.trim(); if (!trimmedInput) return ""; const [command, ...args] = trimmedInput.split(" "); switch (command.toLowerCase()) { case "help": return handleHelp(); case "ls": return handleLs(args, context); case "cd": return handleCd(args, context); case "pwd": return handlePwd(context); case "cat": return handleCat(args, context); case "tree": return handleTree(context); case "clear": return ""; case "whoami": return handleWhoami(); case "open": return handleOpen(args); case "sl": return handleSl(context); default: return `${command}: command not found. Type 'help' for available commands.`; } } function handleHelp(): string { return `Available commands: ls [path] - List directory contents cd - Change directory cat - Display file contents pwd - Show current directory clear - Clear terminal tree - Show directory structure whoami - Display user info open - Open page in browser (simulated) help - Show this help message Navigation: Use "cd .." to go up one directory Use "cd /" to go to root directory File paths can be relative or absolute Use TAB for auto-completion Examples: ls cd resume cat about.txt open /resume open /talks cat /talks/README.txt cd social cat /tech/README.txt`; } function handleLs(args: string[], context: CommandContext): string { const { currentPath, fileSystem } = context; const targetPath = args[0] ? resolvePath(currentPath, args[0]) : currentPath; const pathParts = targetPath.split("/").filter((part: string) => part !== ""); let target = fileSystem["/"]; for (const part of pathParts) { if ( target?.children && target.children[part] && target.children[part].type === "directory" ) { target = target.children[part]; } else if (pathParts.length > 0) { return `ls: cannot access '${targetPath}': No such file or directory`; } } if (!target?.children) { return `ls: cannot access '${targetPath}': Not a directory`; } const items = Object.values(target.children) .map((item) => { const color = item.type === "directory" ? "\x1b[34m" : "\x1b[0m"; const suffix = item.type === "directory" ? "/" : ""; return `${color}${item.name}${suffix}\x1b[0m`; }) .join(" "); return items || "Directory is empty"; } function handleCd(args: string[], context: CommandContext): string { const { currentPath, fileSystem, setCurrentPath } = context; const targetPath = args[0] ? resolvePath(currentPath, args[0]) : "/"; const pathParts = targetPath.split("/").filter((part: string) => part !== ""); let current = fileSystem["/"]; for (const part of pathParts) { if ( current?.children && current.children[part] && current.children[part].type === "directory" ) { current = current.children[part]; } else { return `cd: no such file or directory: ${targetPath}`; } } setCurrentPath(targetPath || "/"); return ""; } function handlePwd(context: CommandContext): string { return context.currentPath || "/"; } function handleCat(args: string[], context: CommandContext): string { const { currentPath, fileSystem } = context; if (!args[0]) { return "cat: missing file argument"; } const filePath = resolvePath(currentPath, args[0]); const pathParts = filePath.split("/").filter((part: string) => part !== ""); const fileName = pathParts.pop(); let current = fileSystem["/"]; for (const part of pathParts) { if ( current?.children && current.children[part] && current.children[part].type === "directory" ) { current = current.children[part]; } else { return `cat: ${filePath}: No such file or directory`; } } if (!fileName || !current?.children || !current.children[fileName]) { return `cat: ${filePath}: No such file or directory`; } const file = current.children[fileName]; if (file.type !== "file") { return `cat: ${filePath}: Is a directory`; } return file.content || ""; } function handleTree(context: CommandContext): string { const { fileSystem } = context; const buildTree = ( node: FileSystemNode, prefix: string = "", isLast: boolean = true, ): string => { let result = ""; if (!node.children) return result; const entries = Object.entries(node.children); entries.forEach(([name, child], index) => { const isLastChild = index === entries.length - 1; const connector = isLastChild ? "└── " : "├── "; const color = child.type === "directory" ? "\x1b[34m" : "\x1b[0m"; const suffix = child.type === "directory" ? "/" : ""; result += `${prefix}${connector}${color}${name}${suffix}\x1b[0m\n`; if (child.type === "directory") { const newPrefix = prefix + (isLastChild ? " " : "│ "); result += buildTree(child, newPrefix, isLastChild); } }); return result; }; return ".\n" + buildTree(fileSystem["/"]); } function handleWhoami(): string { return "guest@atri.dad"; } function handleOpen(args: string[]): string { const path = args[0]; if (!path) { return "open: missing path argument"; } let url = ""; if (path === "/resume" || path.startsWith("/resume")) { url = "/resume"; } else if (path === "/projects" || path.startsWith("/projects")) { url = "/projects"; } else if (path === "/posts" || path.startsWith("/posts")) { url = "/posts"; } else if (path === "/talks" || path.startsWith("/talks")) { url = "/talks"; } else if (path === "/" || path === "/about.txt") { url = "/"; } else { return `open: cannot open '${path}': No associated page`; } window.open(url, "_blank"); return `Opening ${url} in new tab...`; } function handleSl(context: CommandContext): string { const { setIsTrainRunning, setTrainPosition } = context; setIsTrainRunning(true); setTrainPosition(100); const animateTrain = () => { let position = 100; const interval = setInterval(() => { position -= 1.5; setTrainPosition(position); if (position < -50) { clearInterval(interval); setIsTrainRunning(false); } }, 60); }; setTimeout(animateTrain, 100); return ""; }