// noinspection JSIncompatibleTypesComparison const fs = require('fs'); const os = require('os'); const chalk = require('chalk'); const YAML = require('yaml'); const path = require('path'); global.systemRoot = path.resolve(__dirname + "/.."); console.clear(); require('./banner')(); global.daemons = fs.readdirSync(systemRoot + "/LaunchDaemons").map(i => i.substring(0, i.length - 4)); global.log = require('./log'); log("Shell", "Starting up shell..."); global.internalCommands = fs.readdirSync(__dirname + "/../Components"); global.internalCommandsMetadata = internalCommands.map((i) => { return YAML.parse(fs.readFileSync(__dirname + "/../Components/" + i + "/metadata.yml").toString()); }); global.commands = [...internalCommands]; global.commandsMetadata = [...internalCommandsMetadata]; let stdin = process.stdin; global.currentInput = ""; global.inputPosition = 0; stdin.setRawMode(true); stdin.setEncoding('utf8'); function prompt() { return chalk.cyan(os.hostname() + ":") + chalk.blue(process.cwd() + "> "); } function promptNoColor() { return os.hostname() + ":" + process.cwd() + "> "; } function main() { global.currentInput = ""; global.inputPosition = 0; stdin.resume(); updateInput(); } stdin.on('data', function (key) { if (key === '\u0003') { main(); return; } if (key === '\u0004') { process.exit(); return; } if (key === "\r" || key === "\n") { replaceWithAliasIfPossible(true, () => { if (key === "\r") { process.stdout.write("\n"); } else { process.stdout.write("\r"); } stdin.pause(); let parts = currentInput.split(" "); let command = parts[0]; if (commands.includes(command)) { if (internalCommands.includes(command)) { let metadata = commandsMetadata[commands.indexOf(command)]; parts[0] = __dirname + "/../Components/" + command + "/index.js"; let arguments = { _finals: [] }; let willAddParameters = true; for (let index in parts) { if (index > 0) { let part = parts[index]; let originalPart = part; if (originalPart === "--") { willAddParameters = false; continue; } if (part.startsWith("--") && willAddParameters) part = part.substring(2); if (part.startsWith("-") && willAddParameters) part = part.substring(1); if (part.includes("=") && willAddParameters) { let value = part.substring(part.split("=")[0].length + 1); if (!isNaN(parseFloat(value))) { value = parseFloat(value); } else if (!isNaN(parseInt(value))) { value = parseInt(value); } arguments[part.split("=")[0].toLowerCase()] = value; } else { if (originalPart.startsWith("-") && willAddParameters) { arguments[part.toLowerCase()] = true; } else { if (part.trim() !== "") arguments["_finals"].push(part); } } } } log("Shell", "Invoking command: " + command); try { if (metadata.internal) { require(__dirname + "/../Components/" + command + "/index.js")(arguments); } else { stdin.setRawMode(false); require('child_process').execFileSync(process.argv[0], [ parts[0], JSON.stringify(arguments) ], { stdio: "inherit" }); stdin.setRawMode(true); } } catch (e) { stdin.setRawMode(true); log("Shell", "Error while running: " + currentInput + "\n" + e.stack); console.log("\n" + chalk.bgRed.white("") + " " + chalk.red("Error while running command") + "\n"); main(); return; } } } else { log("Shell", "Attempted to run command " + command + " but it does not exist."); console.log(chalk.bgRed.white("") + " " + chalk.red("Command not found: ") + command); } main(); }); } else if (key === "\b" || key === "\x7F") { if (global.currentInput !== "" && inputPosition > 0) { global.currentInput = global.currentInput.substring(0, inputPosition - 1) + global.currentInput.substring(inputPosition); inputPosition--; updateInput(); } } else if (key === " ") { replaceWithAliasIfPossible(false); } else if (key.startsWith("\x1B") || key === "\t") { if (key === "\x1B[D") { // Left cursorLeft(); } else if (key === "\x1B[C" || key === "\t") { // Right cursorRight(); } else if (key === "\x1B[B") { // Down } else if (key === "\x1B[A") { // Up } } else { global.currentInput = global.currentInput.substring(0, inputPosition) + key + global.currentInput.substring(inputPosition); inputPosition++; updateInput(); } }); function replaceWithAliasIfPossible(isEnter, callback) { let text = currentInput; if (text.trim() === "") return ""; let parts = text.split(" "); let command = parts[0]; if (!commands.includes(command)) { let inputLowerCase = command.toLowerCase(); let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0]; if (!match) { let alias = null; for (let selectedCommand of commands) { let metadata = commandsMetadata[commands.indexOf(selectedCommand)]; if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(command.toLowerCase())) { alias = selectedCommand; } } if (alias) { parts[0] = alias; global.currentInput = parts.join(" ") + " "; global.inputPosition = currentInput.length; global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition); global.currentInput = global.currentInput.trim() + " "; global.inputPosition = currentInput.length; if (isEnter) { process.stdout.clearLine(0, () => { process.stdout.cursorTo(0, () => { process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => { if (callback) callback(); }); }); }); } else { updateInput(); } return; } } let alias = null; for (let selectedCommand of commands) { if (selectedCommand.toLowerCase() === command.toLowerCase()) { alias = selectedCommand; } } if (alias) { parts[0] = alias; global.currentInput = parts.join(" ") + " "; global.inputPosition = currentInput.length; global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition); global.currentInput = global.currentInput.trim() + " "; global.inputPosition = currentInput.length; if (isEnter) { process.stdout.clearLine(0, () => { process.stdout.cursorTo(0, () => { process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => { if (callback) callback(); }); }); }); } else { updateInput(); } return; } } else if (commands.includes(command)) { let metadata = commandsMetadata[commands.indexOf(command)]; let arguments = { _finals: [] }; let willAddParameters = true; for (let index in parts) { if (index > 0) { let part = parts[index]; let originalPart = part; if (originalPart === "--") { willAddParameters = false; continue; } if (part.startsWith("--") && willAddParameters) part = part.substring(2); if (part.startsWith("-") && willAddParameters) part = part.substring(1); if (part.includes("=") && willAddParameters) { let value = part.substring(part.split("=")[0].length + 1); if (!isNaN(parseFloat(value))) { value = parseFloat(value); } else if (!isNaN(parseInt(value))) { value = parseInt(value); } arguments[part.split("=")[0].toLowerCase()] = value; } else { if (originalPart.startsWith("-") && willAddParameters) { arguments[part.toLowerCase()] = true; } else { if (part.trim() !== "") arguments["_finals"].push(part); } } } } if (arguments["_finals"].length > 0) { let lastFinal = arguments["_finals"][arguments["_finals"].length - 1]; if (metadata && metadata['manual']['final'] && metadata['manual']['final']['command']) { command = lastFinal; let inputLowerCase = command.toLowerCase(); let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0]; if (!match) { let alias = null; for (let selectedCommand of commands) { let metadata = commandsMetadata[commands.indexOf(selectedCommand)]; if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(command.toLowerCase())) { alias = selectedCommand; } } if (alias) { parts[parts.indexOf(lastFinal)] = alias; global.currentInput = parts.join(" ") + " "; global.inputPosition = currentInput.length; global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition); global.currentInput = global.currentInput.trim() + " "; global.inputPosition = currentInput.length; if (isEnter) { process.stdout.clearLine(0, () => { process.stdout.cursorTo(0, () => { process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => { if (callback) callback(); }); }); }); } else { updateInput(); } return; } } let alias = null; for (let selectedCommand of commands) { if (selectedCommand.toLowerCase() === command.toLowerCase()) { alias = selectedCommand; } } if (alias) { parts[parts.indexOf(lastFinal)] = alias; global.currentInput = parts.join(" ") + " "; global.inputPosition = currentInput.length; global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition); global.currentInput = global.currentInput.trim() + " "; global.inputPosition = currentInput.length; if (isEnter) { process.stdout.clearLine(0, () => { process.stdout.cursorTo(0, () => { process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => { if (callback) callback(); }); }); }); } else { updateInput(); } return; } } else if (metadata && metadata['manual']['final'] && metadata['manual']['final']['daemon']) { command = lastFinal; let alias = null; for (let selectedCommand of commands) { if (selectedCommand.toLowerCase() === command.toLowerCase()) { alias = selectedCommand; } } if (alias) { parts[parts.indexOf(lastFinal)] = alias; global.currentInput = parts.join(" ") + " "; global.inputPosition = currentInput.length; global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition); global.currentInput = global.currentInput.trim() + " "; global.inputPosition = currentInput.length; if (isEnter) { process.stdout.clearLine(0, () => { process.stdout.cursorTo(0, () => { process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => { if (callback) callback(); }); }); }); } else { updateInput(); } return; } } } } if (!isEnter) { global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition); inputPosition++; updateInput(); } else { process.stdout.clearLine(0, () => { process.stdout.cursorTo(0, () => { process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => { if (callback) callback(); }); }); }); } } function cursorLeft() { if (inputPosition > 0) { inputPosition--; } updateInput(); } function cursorRight() { if (inputPosition < currentInput.length) { inputPosition++; } else { autoCompleteIfPossible(); } updateInput(); } function syntaxHighlighting(text) { let parts = text.split(" "); let coloredParts = []; let willColorParameters = true; let cmdMetadata = null; for (let _index in parts) { let index = parseInt(_index); let part = parts[index]; if (index === 0) { cmdMetadata = commandsMetadata[commands.indexOf(part)]; if (commands.includes(part)) { coloredParts.push(chalk.green(part)); } else { if (parts.length === 1) { let alias = null; for (let selectedCommand of commands) { let metadata = commandsMetadata[commands.indexOf(selectedCommand)]; if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(part.toLowerCase())) { alias = selectedCommand; } } if (alias) { coloredParts.push(chalk.green.bgGray(part)); } else { coloredParts.push(chalk.yellow(part)); } } else { coloredParts.push(chalk.red(part)); } } } else if (part === "--" && willColorParameters) { coloredParts.push(chalk.cyan(part)); willColorParameters = false; } else if (part.startsWith("-") && willColorParameters) { if (part.includes("=")) { coloredParts.push(chalk.magentaBright.underline(part.split("=")[0]) + chalk.magenta.bgGray("=") + chalk.magenta(part.substring(part.split("=")[0].length + 1))); } else { coloredParts.push(chalk.magenta(part)); } } else if (cmdMetadata && cmdMetadata['manual']['final'] && cmdMetadata['manual']['final']['command']) { if (commands.map(i => i.toLowerCase()).includes(part.toLowerCase())) { coloredParts.push(chalk.yellow(part)); } else { let alias = null; for (let selectedCommand of commands) { let metadata = commandsMetadata[commands.indexOf(selectedCommand)]; if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(part.toLowerCase())) { alias = selectedCommand; } } if (alias) { coloredParts.push(chalk.yellow(part)); } else { coloredParts.push(chalk.red(part)); } } } else if (cmdMetadata && cmdMetadata['manual']['final'] && cmdMetadata['manual']['final']['daemon']) { if (daemons.map(i => i.toLowerCase()).includes(part.toLowerCase())) { coloredParts.push(chalk.yellow(part)); } else { coloredParts.push(chalk.red(part)); } } else { coloredParts.push(part); } } return coloredParts.join(" "); } function autoComplete(text) { if (text.trim() === "") return ""; let parts = text.split(" "); let command = parts[0]; if (commands.includes(command)) { if (parts.filter(i => i.trim() !== "").length > 1) { let metadata = commandsMetadata[commands.indexOf(command)]; let arguments = { _finals: [] }; let willAddParameters = true; for (let index in parts) { if (index > 0) { let part = parts[index]; let originalPart = part; if (originalPart === "--") { willAddParameters = false; continue; } if (part.startsWith("--") && willAddParameters) part = part.substring(2); if (part.startsWith("-") && willAddParameters) part = part.substring(1); if (part.includes("=") && willAddParameters) { let value = part.substring(part.split("=")[0].length + 1); if (!isNaN(parseFloat(value))) { value = parseFloat(value); } else if (!isNaN(parseInt(value))) { value = parseInt(value); } arguments[part.split("=")[0].toLowerCase()] = value; } else { if (originalPart.startsWith("-") && willAddParameters) { arguments[part.toLowerCase()] = true; } else { if (part.trim() !== "") arguments["_finals"].push(part); } } } } if (arguments["_finals"].length > 0) { let lastFinal = arguments["_finals"][arguments["_finals"].length - 1]; if (metadata && metadata['manual']['final'] && metadata['manual']['final']['command']) { command = lastFinal; if (commands.includes(command)) { let metadata = commandsMetadata[commands.indexOf(command)]; return " " + chalk.gray("- " + (metadata.description ?? "")); } else { let inputLowerCase = command.toLowerCase(); let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0]; if (match) { return chalk.gray(match.substring(command.length)); } else { let alias = null; for (let selectedCommand of commands) { let metadata = commandsMetadata[commands.indexOf(selectedCommand)]; if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(command.toLowerCase())) { alias = selectedCommand; } } if (alias) { let metadata = commandsMetadata[commands.indexOf(alias)]; return " " + chalk.gray(" (-> " + alias + ") - " + (metadata.description ?? "")); } else { return ""; } } } } else if (metadata && metadata['manual']['final'] && metadata['manual']['final']['daemon']) { command = lastFinal; if (daemons.includes(command)) { return ""; } else { let inputLowerCase = command.toLowerCase(); let match = daemons.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0]; if (match) { return chalk.gray(match.substring(command.length)); } else { return ""; } } } else { return ""; } } else { return ""; } } else { let metadata = commandsMetadata[commands.indexOf(command)]; return " " + chalk.gray("- " + (metadata.description ?? "")); } } else { let inputLowerCase = command.toLowerCase(); let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0]; if (match) { return chalk.gray(match.substring(command.length)); } else { let alias = null; for (let selectedCommand of commands) { let metadata = commandsMetadata[commands.indexOf(selectedCommand)]; if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(command.toLowerCase())) { alias = selectedCommand; } } if (alias) { let metadata = commandsMetadata[commands.indexOf(alias)]; return " " + chalk.gray(" (-> " + alias + ") - " + (metadata.description ?? "")); } else { return ""; } } } } function autoCompleteIfPossible() { let text = currentInput; if (text.trim() === "") return; let parts = text.split(" "); let command = parts[0]; if (!commands.includes(command)) { let inputLowerCase = command.toLowerCase(); let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0]; if (match) { global.currentInput += match.substring(command.length); global.currentInput = global.currentInput.trim() + " "; global.inputPosition = currentInput.length - 1; replaceWithAliasIfPossible(); } } else if (commands.includes(command)) { let metadata = commandsMetadata[commands.indexOf(command)]; let arguments = { _finals: [] }; let willAddParameters = true; for (let index in parts) { if (index > 0) { let part = parts[index]; let originalPart = part; if (originalPart === "--") { willAddParameters = false; continue; } if (part.startsWith("--") && willAddParameters) part = part.substring(2); if (part.startsWith("-") && willAddParameters) part = part.substring(1); if (part.includes("=") && willAddParameters) { let value = part.substring(part.split("=")[0].length + 1); if (!isNaN(parseFloat(value))) { value = parseFloat(value); } else if (!isNaN(parseInt(value))) { value = parseInt(value); } arguments[part.split("=")[0].toLowerCase()] = value; } else { if (originalPart.startsWith("-") && willAddParameters) { arguments[part.toLowerCase()] = true; } else { if (part.trim() !== "") arguments["_finals"].push(part); } } } } if (arguments["_finals"].length > 0) { let lastFinal = arguments["_finals"][arguments["_finals"].length - 1]; if (metadata && metadata['manual']['final'] && metadata['manual']['final']['command']) { command = lastFinal; if (!commands.includes(command)) { let inputLowerCase = command.toLowerCase(); let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0]; if (match) { global.currentInput += match.substring(command.length); global.currentInput = global.currentInput.trim() + " "; global.inputPosition = currentInput.length; replaceWithAliasIfPossible(); } } } } } } function updateInput() { if (global.inputPosition < global.currentInput.length) { global.currentInput = global.currentInput.trim(); } process.stdout.clearLine(0, () => { process.stdout.cursorTo(0); process.stdout.write(prompt() + syntaxHighlighting(currentInput) + autoComplete(currentInput), () => { process.stdout.cursorTo(promptNoColor().length + inputPosition); }); }); } main(); log("Shell", "Shell successfully loaded.");