diff --git a/README.md b/README.md index 82ac018..2a0ff70 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ao-cli -A command-line interface (CLI) that helps you install, use, and configure the Autonomous Organization (AO). This package includes the command line tool `ao-cli` (alias `ao`) which makes it very easy to install and use the AO via the command line or a web browser. `ao-cli` is a Node/JavaScript CLI tool that wraps the functionality of Alchemy, AO administration, plus key AO features into one convenient interface. +A command-line interface (CLI) that helps you install, use, and configure the Autonomous Organization (AO). This package includes the command line tool `ao-cli` (alias `ao`) which makes it very easy to install and use the AO via the command line or a web browser. `ao-cli` is a Node/JavaScript CLI tool that wraps the functionality of Alchemy, AO administration, plus key AO features into one convenient interface. Command-line social networking. To run immediately: @@ -14,5 +14,5 @@ Then you can run with `ao-cli`. (Inside the menu you will find an option to add ### Version History -0.0.2 Added browsable manual (must download ao-svelte) -0.0.1 Menus prototyped \ No newline at end of file +0.0.5 Added browsable manual (must download ao-svelte) +0.0.1 Menus prototyped diff --git a/index.js b/index.js index 649ee48..4e0fdad 100644 --- a/index.js +++ b/index.js @@ -1,172 +1,58 @@ #!/usr/bin/env node -import inquirer from 'inquirer' import chalk from 'chalk' -import chalkAnimation from 'chalk-animation' -import gradient from 'gradient-string' -import figlet from 'figlet' -import { createSpinner } from 'nanospinner' +import inquirer from 'inquirer' import { execSync } from 'child_process' -import { runAllTests, testDict } from './scripts/tests.js' - -const MANUAL_PATH = '/home/deicidus/ao-svelte/static/manual' - +import { detectOS, updateSoftware, installRequired, setNodeVersion } from './scripts/system.js' +import { checkAoEnvFile, aoEnv, setAoEnv, AO_ENV_FILE_PATH } from './scripts/settings.js' +import { unicornPortal, asciiArt, clearScreen, spinnerWait } from './scripts/console.js' +import { welcome, exclaim, roger, farewell } from './scripts/welcome.js' +import { printManualPage, manualFolderAsMenu } from './scripts/manual.js' +import { sleep } from './scripts/util.js' +import { tests } from './scripts/tests.js' +import { headerStyle } from './scripts/chalkStyles.js' +import './scripts/strings.js' +import { installAoAlias } from './scripts/features.js' +import { startPublicBootstrap } from './scripts/bootstrap.js' + +// These should become .env variables that are loaded intelligently +const MANUAL_PATH = process.env.HOME + '/.ao/manual' let distro let memberName -// Chalk styles -const greenChalk = chalk.hex('#008800') -const headerStyle = chalk.blue.bold.underline -const manualTitleStyle = greenChalk.bold.underline - -// Preformatted phrases that can be used in backticked console.log strings -const theAO = `the ${greenChalk.bold('AO')}` -const theMenu = `the ${greenChalk.bold('Menu')}` - -// Different sets of messages that can be randomly selected from -const greetingMessages = ['Portaling!', 'You are a Unicorn!', "Here we go!", "Wow!", "AO Loading...", "Powering Up!", "Unicorn Portal", "Don't Panic!"] -const welcomeMessages = [ - `You turn a corner and are suddenly back in the halls of ${theAO}.`, - `You make the sign of ${theAO} and are whisked away on a gust of divine wind.`, - `You take a closer look at the folder you are in and realize it is a room.`, - `You draw an alchemical symbol and open a doorway into ${theAO}. You step through.`, - `"Ah, there you are again!" The old man greets you as you step through the portal.`, - `A line of doge-masked worshippers glide by. By the time you exit the trance, you are in ${theAO}. Doge bless.`, - `You spraypaint an ${greenChalk.bold('A')} superimposed on an ${greenChalk.bold('O')} and step through the portal.`, - `You receive a phone call. You answer it. You are in ${theAO}.`, - `You dab fiercely, and when you raise your head, you are in ${theAO}.`, - `A ship arrives and takes you out to sea. The ship sinks and you somehow wash up safely in ${theAO}.`, - `You are reading in the Library when you find a strange book. Whatever you read next in the book is ${theAO}.`, - `You plant a magic seed and it grows into a great tree. Climbing up into its branches, you know ${theAO} is here.`, - `In the late afternoon, a warm sunbeam highlights motes of dust over your book. These motes are ${theAO}.`, - `A black cat crosses your path. Most people wouldn't notice, but you know you have entered the AO.`, - `Dipping your brush in ink, you draw a perfect circle. This is ${theAO}.`, - `You are offered a choice between two pills. However, you have secretly built up an immunity to both pills, and trick your opponent into taking one. Inconceviably, you are in ${theAO}.`, - `A young man with spiky hair and golden skin appears before you in a halo of light. He guides you to ${theAO}.`, -] -const menuMessages = [ - `You see ${theMenu}:`, - `A page of aged paper wafts into your hand. On it is written ${theMenu}:`, - `A minstrel walks by playing a haunting melody, and in its harmonies you hear the refrain of ${theMenu}:`, - `You pick up an apple from a nearby table and take a bite. You suddenly recall the words of ${theMenu}:`, - `With a screeching sound, a page containing ${theMenu} slowly emerges, line-by-line, from a dot-matrix printer:`, - `In the shadows cast in the cavernous space, you see the forms of ${theMenu}:`, - `With a low hum, standing waves appear on the reflecting pool at your feet. Impossibly, they spell out ${theMenu}:`, - `You see a box of candies labeled 'Eat Me'. Why not? you think. You find ${theMenu} printed on the wrapper:`, - `You see an ornate bottle labeled 'Drink Me'. Why not? you think. You take a sip and shrink to the size of a doormouse. Now you can read ${theMenu} scratched into the wainscoating:`, - `Cretaceous jungle plants tower overhead, overgrown from the safari room. The ancient fractals of the branches prefigure the structure of ${theMenu}:`, -] -const exclamationMessages = [ - 'Woah there!', - 'Shiver me timbers!', - 'iNtErEsTiNg!', - 'Turing\'s ghost!', - 'Frack!', - 'Frell!', - 'Good grief!', - 'With great power comes great responsibility.', -] -const farewellMessages = [ - 'Goodbye!', - 'Goodbye! Goodbye! Goodbye...!', - 'The AO will always be with you.', - 'Please return soon; the AO needs your virtue.', - 'The AO will await your return.', - 'Doge bless.', - 'Please remember the AO throughout your day as a ward against evil.', - 'Remember your PrioriTEAâ„¢.', - 'Know thyself.', - 'With great power comes great responsibility.', - 'The AO is a state of mind.' -] - -// Returns a random int between min and max (inclusive) -function randomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; -} - -// Returns a random item from the given array -function selectRandom(arrayToChooseFrom) { - return arrayToChooseFrom[randomInt(0, arrayToChooseFrom.length - 1)] -} - -// Waits for the given number of milliseconds (or a brief pause by default) -const sleep = (ms = 550) => new Promise((r) => setTimeout(r, ms)) - // This does not work function exitIfRoot() { try { execSync('[ "$EUID" -eq 0 ]') - console.log(`${chalk.red.bold(selectRandom(exclamationMessages))} Seems you're running this script as a superuser.`) + console.log(`${chalk.red.bold(exclaim())} Seems you're running this script as a superuser.`) console.log('That might cause some issues with permissions and whatnot. Run this script as your default user (without sudo) and I\'ll ask you when I need superuser permissions') process.exit(1) } catch(err) {} } -// Check for an AO env file at ~/.ao/.env and returns true if it exists -function loadAoEnvFile() { - try { - execSync('[ -f "~/.ao/.env" ]') - console.log('AO .env file exists at ~/.ao/.env') - return true - } catch(err) { - console.log('AO .env file does not exist at ~/.ao/.env') - return false - } -} - -// Displays a brief randomly-selected rainbow-animated phrase -async function unicornPortal(ms) { - const randomGreetingMessage = selectRandom(greetingMessages) - const rainbowTitle = chalkAnimation.rainbow(randomGreetingMessage + '\n') - await sleep(ms) - rainbowTitle.stop() -} - -// Prints the given message to the screen in the given ASCII art style. Here is a list of decent styles: -const asciiFonts = ['Standard', 'Digital', 'Bubble', 'Script', 'Mini', 'Banner', 'Alphabet', 'Avatar', 'Chunky', 'Computer', 'Contessa', 'Gothic', 'Invita', 'Lockergnome', 'Madrid', 'Morse', 'Moscow', 'Pawp', 'Pepper', 'Pyramid', 'Rectangles', 'Shadow', 'Short', 'Slant', 'Small', 'Stampatello', 'Stop', 'Straight', 'Thick', 'Thin', 'Weird'] -async function asciiArt(message, style) { - const randomFont = selectRandom(asciiFonts) - let art = figlet.textSync(message || 'Autonomous Organization', { font: randomFont }) - art = centerLines(art) - console.log(gradient.pastel.multiline(art)) -} - -// Clears the console -function clearScreen() { - console.clear() -} - -// Prints a random RPG-style welcome message to contextualize the AO experience and the main menu -async function welcome() { - const randomWelcomeMessage = selectRandom(welcomeMessages) - const randomMenuMessage = selectRandom(menuMessages) - const welcomeMessage = (' ' + randomWelcomeMessage + ' ' + randomMenuMessage).wordWrap(process.stdout.columns - 2) - // todo: line breaks would be more accurate if there were a function to count the length of a string minus the formatting codes (regex) - // right now the invisible formatting characters are counted so lines are wrapped early - console.log('\n' + welcomeMessage) - -} - // Prints the AO Main Menu and executes the user's choice async function mainMenu() { console.log(`\n${headerStyle('AO Main Menu')}\n`) + const mainMenuChoices = [ + 'Chat', + 'Alchemy', + 'Deck', + 'Admin', + 'Tests', + 'Manual', + 'Log Out', + 'Exit', + ] const answer = await inquirer.prompt({ name: 'main_menu', type: 'list', message: 'Please choose:', - choices: [ - 'Alchemy', - 'Deck', - 'Admin', - 'Tests', - 'Manual', - 'Log Out', - 'Exit', - ] + choices: mainMenuChoices, + pageSize: mainMenuChoices.length }) switch(answer.main_menu) { + case 'Chat': + while(await chatMenu()) {} + break case 'Alchemy': while(await alchemyMenu()) {} break @@ -181,22 +67,86 @@ async function mainMenu() { break case 'Manual': await printManualPage(MANUAL_PATH) // Fencepost case - print overview page - let previousChoice + let previousChoice = 0 do { - previousChoice = await manualFolderAsMenu(MANUAL_PATH, 'AO User Manual', 'Back to Main Menu', previousChoice) + previousChoice = await manualFolderAsMenu(MANUAL_PATH, 'AO User Manual', 'Back to Main Menu', previousChoice + 1) } while(previousChoice !== false) break case 'Log Out': await spinnerWait('Logging out... (just kidding)') break case 'Exit': - console.log(chalk.yellow.bold(selectRandom(farewellMessages))) + farewell() await sleep(310) return false } return true } +// Prints a menu that allows you to join the global AO chatrooms +let publicBootstrapStarted = false +async function chatMenu() { + let answers = {} + const PUBLIC_BOOTSTRAP_ENABLED = aoEnv('PUBLIC_BOOTSTRAP_ENABLED') + if(PUBLIC_BOOTSTRAP_ENABLED) { + // They previously enabled public bootstrapping, so check to make sure it is working and then hide the option + // todo: start and then verify functioning of p2p boostrap method here + // if it's already started don't start it again + if(!publicBootstrapStarted) { + console.log("\nBootstrapping public AO swarm...") + startPublicBootstrap() + console.log("Bootstrapped (just kidding)") + publicBootstrapStarted = true + } + //answers['chat_menu'] = 'Enable p2p bootstrap' + } + const publicBootstrapMenuItem = PUBLIC_BOOTSTRAP_ENABLED ? 'Disable p2p bootstrap' : 'Enable p2p bootstrap' + const chatChoices = [ + publicBootstrapMenuItem, + 'Join public chatroom', + 'Join chatroom', + 'Address Book', + 'Back to Main Menu', + ] + console.log(`\n${headerStyle('AO Public Chatrooms')}\n`) + const answer = await inquirer.prompt({ + name: 'chat_menu', + type: 'list', + message: 'Please choose:', + choices: chatChoices + }) + switch(answer.chat_menu) { + case 'Enable p2p bootstrap': + console.log('In order to join AO public chatrooms, AO uses the hyperswarm protocol. Joining hyperswarm may expose your IP address to other users. (For high-security installations, don\'t use public bootstrap: you can still add tor addresses to your address book manually and join those chatrooms by name.)') + setAoEnv('PUBLIC_BOOTSTRAP_ENABLED', true) + //message: Type \'public\' and press Enter to enable:') + break + case 'Disable p2p bootstrap': + setAoEnv('PUBLIC_BOOTSTRAP_ENABLED', false) + console.log(roger(), 'Disabled public bootstrapping.') + if(publicBootstrapStarted) { + // stop the bootstrap thing here + publicBootstrapStarted = false + } + break + case 'Join public chatroom': + console.log('Not yet implemented') + break + case 'Join chatroom': + console.log('Not yet implemented') + break + case 'Address Book': + console.log('The point of this address book is to make it possible to type short, one-word names and have them resolve to tor addresses.') + console.log('Name a piece of data by saying name=data. For example, doge=5uc41o1...onion. Then \'doge\' will return the .onion address.') + console.log('Querying with any synonym in a chain will return the final meanings they all point to.') + console.log('Keys can have multiple values.') + break + case 'Back to Main Menu': + return false + } + return true +} + // Prints the AO Admin Menu and executes the user's choice async function adminMenu() { console.log(`\n${headerStyle('AO Admin Menu')}`) @@ -244,7 +194,7 @@ async function adminMenu() { // Prints the AO Unit Tests Menu and executes the user's choice async function testsMenu() { console.log(`\n${headerStyle('AO Unit Tests')}`) - let testChoices = Object.entries(testDict).map(([menuTitle, testFunction]) => { + let testChoices = Object.entries(tests).map(([menuTitle, testFunction]) => { return menuTitle }) testChoices.push('Back to Main Menu') @@ -257,7 +207,7 @@ async function testsMenu() { if(answer.tests_menu === 'Back to Main Menu') { return false } - const testFunction = testDict[answer.tests_menu] + const testFunction = tests[answer.tests_menu] if(testFunction) await testFunction() return true } @@ -357,336 +307,6 @@ function detectAoVersion() { } -// Detects the operating system we are running on -function detectOS() { - let distro - - try { - execSync('[ -f "/etc/debian_version" ]') - distro = 'debian' - console.log(`${greenChalk('Debian')}, Ubuntu, or Raspbian OS detected.`) - } catch(err) {} - - try { - execSync('[ -f "/etc/arch-release" ]') - distro = 'arch' - console.log(`${greenChalk('Arch or Manjaro-based')} OS detected.`) - } catch(err) {} - - try { - execSync('[ -f "/etc/fedora-release" ]') - distro = 'fedora' - console.log(`${greenChalk('Fedora')} OS detected.`) - } catch(err) {} - - try { - execSync('[ $(uname | grep -c "Darwin") -eq 1 ]') - distro = 'mac' - console.log(`${greenChalk('MacOS')} detected.`) - } catch(err) {} - - if(!distro) { - console.log("Your OS was not recognized, sorry.") - process.exit(1) - } - - return distro -} - -// Runs the correct command to update all your software for any recognized OS -function updateSoftware() { - distro = detectOS() - if(!distro) { - console.log("Your OS was not recognized, so nothing was updated, sorry.") - return false - } - - console.log('Updating your software from repositories...') - console.log(`(You may need to input your ${chalk.blue.bold("'sudo' password")} here)`) - switch(distro) { - case 'debian': - execSync('sudo apt update && sudo apt autoremove && sudo apt upgrade') - break - case 'arch': - execSync('sudo pacman -Syu --noconfirm') - break - case 'fedora': - execSync('sudo dnf update && sudo dnf upgrade') - break - case 'mac': - execSync('install && sudo brew update') - break - } - - return true -} - -// Adds a line to .bashrc to make 'ao' an alias for 'ao-cli', to simplify using the AO from the command line -function installAoAlias() { - try { - execSync('grep "ao=\'ao-cli\'" ~/.bashrc') - console.log('You can already type \'ao\' to launch ao-cli; the alias line already exists in ~/.bashrc.') - } catch(err) { - execSync('echo alias ao=\'ao-cli\' >> .bashrc') - console.log('Added alias line to ~/.bashrc. You can now type \'ao\' to launch ao-cli.') - } -} - -// Installs core dependencies required by Alchemy and the AO -function installRequired() { - distro = detectOS() - if(!distro) { - console.log("Your OS was not recognized, so nothing was installed, sorry.") - return false - } - console.log('Installing Alchemy and AO installation process core dependencies (fast if already installed)...') - console.log(`(You may need to input your ${chalk.blue.bold("'sudo' password")} here)`) - - // Install OS-specific requirements - switch(distro) { - case 'debian': - execSync('sudo apt install build-essential') - // Some of these might not be required - execSync('source ~/Alchemy/ingredients/lead && install_if_needed sqlite3 zlib1g-dev libtool-bin autoconf autoconf-archive automake autotools-dev libgmp-dev libsqlite3-dev python python3 python3-mako libsodium-dev build-essential pkg-config libev-dev libcurl4-gnutls-dev libssl-dev fakeroot devscripts') - break - case 'arch': - try { - execSync('[[ ! $(pacman -Qg base-devel) ]]') - execSync('sudo pacman -S base-devel --noconfirm') - } catch(err) {} - execSync('source ~/Alchemy/ingredients/lead && install_if_needed python gmp sqlite3 autoconf-archive pkgconf libev python-mako python-pip net-tools zlib libsodium gettext nginx') - break - case 'fedora': - execSync('source ~/Alchemy/ingredients/lead && install_if_needed sqlite3 autoconf autoconf-archive automake python python3 python3-mako pkg-config fakeroot devscripts') - break - } - - // Install on every OS - execSync('source ~/Alchemy/ingredients/lead && install_if_needed git wget make') - try { - execSync('[ -z $NVM_DIR ]') - execSync('source ingredients/iron && install_nvm') - console.log(`Installed nvm.`) - } catch(err) {} - - return true -} - -// Sets node to the current version used by the AO -function setNodeVersion() { - execSync('source ingredients/iron && set_node_to v16.13.0') -} - -// Displays a spinner for 1.2 secconds with the given messages during and after the timer completes -async function spinnerWait(waitingMessage, doneMessage, ms = 1200) { - const spinner = createSpinner(waitingMessage || 'Please wait...').start() - await sleep(ms) - spinner.success({ text: doneMessage || 'Done.' }) -} - -// Loads the text of a file -import fs from 'fs' -import readYamlAndMarkdown from 'yaml-head-loader' -async function loadTextFile(path) { - return new Promise((resolve, reject) => { - fs.readFile(path, 'utf8', function(err, data) { - if (err) { - console.log('Reading file failed:', err) - return null - } - resolve(data) - }) - }) -} - -// Loads the given text file and returns a dictionary of its contents parsed into a .header dict and .tail markdown content -async function loadYamlMarkdownFile(path) { - let text = await loadTextFile(path) - if(!text) { - return null - } - let dict = readYamlAndMarkdown(text) - return dict -} - -// Loads and returns the list of contents of a folder as an array -function lsFolder(path) { - try { - return fs.readdirSync(path) - } catch(err) { - return null - } -} - -// Returns true if the path is a folder -function isFolder(path) { - return Array.isArray(lsFolder(path)) -} - -// Adds a .toTitleCase() function to every string -String.prototype.toTitleCase = function () { - return this.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()}) -} - -String.prototype.wordWrap = function (width = 80) { - return this.replace(new RegExp(`(?:\\S(?:.{0,${width}}\\S)?(?:\\s+|-|$)|(?:\\S{${width}}))`, 'g'), s => `${s}\n`).slice(0, -1) -} - -const repeatString = (str, n) => { - return new Array(1 + (n || 0)).join(str) -} - -String.prototype.centerInConsole = function (width = 80) { - const consoleWidth = process.stdout.columns - const padding = Math.floor((consoleWidth - width) / 2) - const lines = this.split('\n') - const centered = lines.map(line => repeatString(" ", padding) + line) - return centered.join('\n') -} - -// Centers a one-line string within the given number of characters by adding padding to the left -String.prototype.centerInLine = function (lineWidth, width = 80) { - const padding = Math.floor((width - lineWidth) / 2) - return repeatString(" ", padding) + this -} - -const centerLines = (str) => { - const lines = str.split('\n') - const centered = lines.map(line => line.centerInLine(line.length, process.stdout.columns)) - return centered.join('\n') -} - -// Removes numbered prefix such as 12_ and .md suffix, replaces underscores with spaces, and adds titlecase -function formatManualTitleString(title) { - // Remove .md suffix - if(/\.md$/.exec(title)) { - title = title.substring(0, title.length - 3) - } - // Remove numbered prefix e.g., 12_ - if(/^\d*_/.exec(title)) { - title = title.split('_').slice(1).join('_') - } - // Replace underscores with spaces - title = title.replaceAll('_', ' ') - - return title.toTitleCase() -} - -import { basename } from 'path' -import mdlogBuilder from 'mdlog' - -// Prints the specified manual page to the screen -async function printManualPage(path) { - if(isFolder(path)) { - path += '/index.md' - } - const dict = await loadYamlMarkdownFile(path) - const title = dict?.meta?.title || formatManualTitleString(basename(path)) - const formattedTitle = manualTitleStyle(title).centerInLine(title.length).centerInConsole() - console.log('\n' + formattedTitle + '\n') - const renderedMarkdown = mdlogBuilder.convert(dict?.tail.wordWrap()).join('\n').centerInConsole() - console.log(renderedMarkdown) -} - -// Given a path and file/folder name, it returns the appropriate manual title -// If it's a folder and there is an index.js inside that has a title: field, that overrides the title -// Otherwise it's the filename or foldername, minus anything before the first underscore (_), in titlecase -async function loadManualTitle(path, fileOrFolder) { - // If it's a .md file, check inside for a title: field - if(/\.md$/.exec(fileOrFolder)) { - const indexTitle = (await loadYamlMarkdownFile(path + fileOrFolder))?.meta?.title - if(indexTitle) { - return indexTitle - } - } - - // If it's a folder, check for a title: field in index.md and return if exists - if(isFolder(path + fileOrFolder)) { - const indexPath = path + fileOrFolder + '/index.md' - const indexTitle = (await loadYamlMarkdownFile(indexPath))?.meta?.title - if(indexTitle) { - return indexTitle - } - } - - // Fall back to using the file/folder name as the title - return formatManualTitleString(fileOrFolder) -} - -// Render the manual folder or a subfolder as a menu -// First the index.js is listed using the folder name or title loaded from inside the file as the menu item title -// Next, any other files not starting with a number are loaded and displayed in discovered/arbitrary order -// Next, items starting with 0_, then 1_, and so on are displayed in order. You can mix files and folders. -// Selecting a menu item renders it. For .md files it renders it and shows the same level Manual menu again. -// For folders, it goes into that folder and renders it as a manual menu folder -// This allows arbitrarily nested Manual menu folders to be explored in a standardized menu system -async function manualFolderAsMenu(path, menuTitle, backOption, previousMenuChoice = 0) { - if(!isFolder(path)) { - return false - } - if(path[path.length - 1] != '/') { - path += '/' - } - - let menuItems = [] - - const folderContents = lsFolder(path) - if(folderContents.some(fileOrFolder => fileOrFolder === 'index.md')) { - const indexTitle = await loadManualTitle(path, 'index.md') - - let indexMenuItem = {} - indexMenuItem[indexTitle] = 'index.md' - menuItems.push(indexMenuItem) - } - - let unNumberedItems = [] - let numberedItems = [] - const sortedFolderContents = folderContents.sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) - for(let i = 0; i < sortedFolderContents.length; i++) { - const fileOrFolder = sortedFolderContents[i] - if(fileOrFolder === 'index.md') { - continue - } - const potentialNumber = fileOrFolder.split('_')[0] - const initialNumber = parseInt(potentialNumber) - const title = await loadManualTitle(path, fileOrFolder) - const menuItem = {} - menuItem[title] = fileOrFolder - - if(isNaN(initialNumber)) { - unNumberedItems.push(menuItem) - } else { - numberedItems.push(menuItem) - } - } - menuItems = menuItems.concat(unNumberedItems, numberedItems) - - const menuChoices = menuItems.map(menuItem => Object.keys(menuItem)[0]) - menuChoices.push(backOption) - console.log(`\n${headerStyle(menuTitle)}`) - const answer = await inquirer.prompt({ - name: 'manual_menu', - type: 'rawlist', - message: 'Please choose:', - choices: menuChoices, - pageSize: menuChoices.length, - default: previousMenuChoice - }) - const chosenMenuIndex = menuChoices.indexOf(answer.manual_menu) - if(answer.manual_menu === backOption) { - return false - } - const chosenPath = path + Object.values(menuItems.find(menuItem => Object.keys(menuItem)[0] === answer.manual_menu))[0] - await printManualPage(chosenPath) - const newBackOption = backOption === 'Back to Main Menu' ? 'Back to Table of Contents' : 'Back to ' + menuTitle - let previousChoice - do { - previousChoice = await manualFolderAsMenu(chosenPath, answer.manual_menu, newBackOption, previousChoice) - } - while(previousChoice !== false) - return chosenMenuIndex -} - // Main entry point async function main() { // No root allowed (todo) @@ -694,7 +314,10 @@ async function main() { // Loading screen, display some quick info during the fun animation distro = detectOS() - if(!loadAoEnvFile()) { + if(checkAoEnvFile()) { + console.log('AO .env file exists at', AO_ENV_FILE_PATH) + } else { + console.log('AO .env file does not exist at', AO_ENV_FILE_PATH) } await unicornPortal(650) diff --git a/manual/index.yml b/manual/index.yml deleted file mode 100644 index 68e0c94..0000000 --- a/manual/index.yml +++ /dev/null @@ -1,7 +0,0 @@ -# file.yml -YAML: - - A human-readable data serialization language - - https://en.wikipedia.org/wiki/YAML -yaml: - - A complete JavaScript implementation - - https://www.npmjs.com/package/yaml \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6bec55d..a9d7c6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,26 +9,29 @@ "version": "0.0.5", "license": "AGPL-3.0-or-later", "dependencies": { + "ansimd": "^0.2.1", "chalk": "^5.0.1", "chalk-animation": "^2.0.2", "crypto": "^1.0.1", + "envfile": "^6.17.0", "figlet": "^1.5.2", "gradient-string": "^2.0.1", + "hash.js": "^1.1.7", "inquirer": "^8.2.4", + "marked": "^4.0.16", + "marked-terminal": "^5.1.1", "mdlog": "^1.0.3", "nanospinner": "^1.1.0", + "sha.js": "^2.4.11", "socket-io": "^1.0.0", + "socket.io-client": "^4.5.1", + "superagent": "^7.1.6", "uuid": "^8.3.2", + "wrap-ansi": "^8.0.1", "yaml-head-loader": "^1.0.2" }, "bin": { "ao-cli": "index.js" - }, - "devDependencies": { - "hash.js": "^1.1.7", - "sha.js": "^2.4.11", - "socket.io-client": "^4.5.1", - "superagent": "^7.1.6" } }, "node_modules/@babel/code-frame": { @@ -100,11 +103,19 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", - "dev": true + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, "node_modules/@types/minimist": { "version": "1.2.2", @@ -157,6 +168,31 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" + }, + "node_modules/ansimd": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansimd/-/ansimd-0.2.1.tgz", + "integrity": "sha512-l3e2CefLdNvKA2F0LEbX31aQnxisx2zotOiMAo53yHu0h9GRFjH0UbdySWZFEw124u+RDJL+LqYnrC4xss3dcw==", + "dependencies": { + "marked": "0.3.4", + "unescape-html": "~1.0.0" + }, + "bin": { + "ansimd": "cli.js" + } + }, + "node_modules/ansimd/node_modules/marked": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.4.tgz", + "integrity": "sha512-lXDsdTtXPVdWWD3yeV7Ci3bYlC2MBahwJlqggDJ3UCDG9C0LrQP7kfT/pBq9oa/W8qGldmfug+OGF0pPFjjrng==", + "bin": { + "marked": "bin/marked" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -176,14 +212,12 @@ "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -260,7 +294,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -308,6 +341,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, "node_modules/chalk": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", @@ -396,6 +441,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-table3": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -445,7 +504,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -461,8 +519,7 @@ "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -513,8 +570,7 @@ "node_modules/cookiejar": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", - "dev": true + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, "node_modules/core-util-is": { "version": "1.0.3", @@ -531,7 +587,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -595,7 +650,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -604,12 +658,16 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==", - "dev": true, "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -619,7 +677,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", - "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", @@ -632,11 +689,24 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", - "dev": true, "engines": { "node": ">=10.0.0" } }, + "node_modules/envfile": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/envfile/-/envfile-6.17.0.tgz", + "integrity": "sha512-RnhtVw3auDZeeh5VtaNrbE7s6Kq8BoRtGIzcbMpMsJ+wIpRgs5jiDG4gQjW+vfws5QPlizE57/fUU0Tj6Nrs8A==", + "bin": { + "envfile": "bin.cjs" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -681,8 +751,7 @@ "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/figlet": { "version": "1.5.2", @@ -725,7 +794,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -739,7 +807,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", - "dev": true, "dependencies": { "dezalgo": "1.0.3", "hexoid": "1.0.0", @@ -754,7 +821,6 @@ "version": "6.9.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true, "engines": { "node": ">=0.6" }, @@ -771,7 +837,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -877,7 +942,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -889,7 +953,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -907,7 +970,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true, "engines": { "node": ">=8" } @@ -1028,6 +1090,22 @@ "node": ">=8" } }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -1214,6 +1292,61 @@ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-0.4.0.tgz", "integrity": "sha512-9i/E3ZtVAoaDulRQjoPseX2X5pBNdeR8MInQb57JFvCAq4glz/w2q31eL0NHMKOntzy2D6X3plZDH4+OGuz5Fw==" }, + "node_modules/marked": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.16.tgz", + "integrity": "sha512-wahonIQ5Jnyatt2fn8KqF/nIqZM8mh3oRu2+l5EANGMhu6RFjiSG52QNE2eWzFMI94HqYSgN184NurgNG6CztA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/marked-terminal": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", + "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", + "dependencies": { + "ansi-escapes": "^5.0.0", + "cardinal": "^2.1.1", + "chalk": "^5.0.0", + "cli-table3": "^0.6.1", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.2.0" + }, + "engines": { + "node": ">=14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/marked-terminal/node_modules/ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dependencies": { + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked-terminal/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mdast": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/mdast/-/mdast-0.21.2.tgz", @@ -1362,7 +1495,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -1371,7 +1503,6 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, "bin": { "mime": "cli.js" }, @@ -1383,7 +1514,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -1392,7 +1522,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -1419,8 +1548,7 @@ "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "node_modules/minimatch": { "version": "2.0.10", @@ -1450,8 +1578,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mute-stream": { "version": "0.0.8", @@ -1466,6 +1593,14 @@ "picocolors": "^1.0.0" } }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -1484,7 +1619,6 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1493,7 +1627,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1643,7 +1776,6 @@ "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -1748,6 +1880,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dependencies": { + "esprima": "~4.0.0" + } + }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -1826,7 +1966,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -1839,7 +1978,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -1864,7 +2002,6 @@ "version": "4.5.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.1.tgz", "integrity": "sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==", - "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -1879,7 +2016,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", - "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -1971,7 +2107,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.6.tgz", "integrity": "sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==", - "dev": true, "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.3", @@ -2000,6 +2135,37 @@ "node": ">=4" } }, + "node_modules/supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2070,6 +2236,11 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "node_modules/unescape-html": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unescape-html/-/unescape-html-1.0.0.tgz", + "integrity": "sha1-dXab9Gnngi52eINNVnvytEhbJIs=" + }, "node_modules/user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", @@ -2120,21 +2291,78 @@ } }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.0.1.tgz", + "integrity": "sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrap-fn": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.5.tgz", @@ -2146,14 +2374,12 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, "engines": { "node": ">=10.0.0" }, @@ -2174,7 +2400,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -2269,11 +2494,16 @@ } } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true + }, "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", - "dev": true + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, "@types/minimist": { "version": "1.2.2", @@ -2311,6 +2541,27 @@ "color-convert": "^2.0.1" } }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" + }, + "ansimd": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansimd/-/ansimd-0.2.1.tgz", + "integrity": "sha512-l3e2CefLdNvKA2F0LEbX31aQnxisx2zotOiMAo53yHu0h9GRFjH0UbdySWZFEw124u+RDJL+LqYnrC4xss3dcw==", + "requires": { + "marked": "0.3.4", + "unescape-html": "~1.0.0" + }, + "dependencies": { + "marked": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.4.tgz", + "integrity": "sha512-lXDsdTtXPVdWWD3yeV7Ci3bYlC2MBahwJlqggDJ3UCDG9C0LrQP7kfT/pBq9oa/W8qGldmfug+OGF0pPFjjrng==" + } + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2327,14 +2578,12 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "balanced-match": { "version": "1.0.2", @@ -2383,7 +2632,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -2412,6 +2660,15 @@ } } }, + "cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, "chalk": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", @@ -2469,6 +2726,15 @@ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==" }, + "cli-table3": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -2509,7 +2775,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -2522,8 +2787,7 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "concat-map": { "version": "0.0.1", @@ -2573,8 +2837,7 @@ "cookiejar": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", - "dev": true + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, "core-util-is": { "version": "1.0.3", @@ -2590,7 +2853,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -2632,19 +2894,22 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "dezalgo": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==", - "dev": true, "requires": { "asap": "^2.0.0", "wrappy": "1" } }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2654,7 +2919,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", - "dev": true, "requires": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", @@ -2666,8 +2930,12 @@ "engine.io-parser": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", - "dev": true + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==" + }, + "envfile": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/envfile/-/envfile-6.17.0.tgz", + "integrity": "sha512-RnhtVw3auDZeeh5VtaNrbE7s6Kq8BoRtGIzcbMpMsJ+wIpRgs5jiDG4gQjW+vfws5QPlizE57/fUU0Tj6Nrs8A==" }, "error-ex": { "version": "1.3.2", @@ -2700,8 +2968,7 @@ "fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "figlet": { "version": "1.5.2", @@ -2729,7 +2996,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2740,7 +3006,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", - "dev": true, "requires": { "dezalgo": "1.0.3", "hexoid": "1.0.0", @@ -2751,8 +3016,7 @@ "qs": { "version": "6.9.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true + "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" } } }, @@ -2765,7 +3029,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -2841,14 +3104,12 @@ "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -2862,8 +3123,7 @@ "hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" }, "hosted-git-info": { "version": "4.1.0", @@ -2939,6 +3199,16 @@ "requires": { "has-flag": "^4.0.0" } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } } } }, @@ -3073,6 +3343,39 @@ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-0.4.0.tgz", "integrity": "sha512-9i/E3ZtVAoaDulRQjoPseX2X5pBNdeR8MInQb57JFvCAq4glz/w2q31eL0NHMKOntzy2D6X3plZDH4+OGuz5Fw==" }, + "marked": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.16.tgz", + "integrity": "sha512-wahonIQ5Jnyatt2fn8KqF/nIqZM8mh3oRu2+l5EANGMhu6RFjiSG52QNE2eWzFMI94HqYSgN184NurgNG6CztA==" + }, + "marked-terminal": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", + "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", + "requires": { + "ansi-escapes": "^5.0.0", + "cardinal": "^2.1.1", + "chalk": "^5.0.0", + "cli-table3": "^0.6.1", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.2.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "requires": { + "type-fest": "^1.0.2" + } + }, + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" + } + } + }, "mdast": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/mdast/-/mdast-0.21.2.tgz", @@ -3193,26 +3496,22 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "requires": { "mime-db": "1.52.0" } @@ -3230,8 +3529,7 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimatch": { "version": "2.0.10", @@ -3254,8 +3552,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mute-stream": { "version": "0.0.8", @@ -3270,6 +3567,14 @@ "picocolors": "^1.0.0" } }, + "node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "requires": { + "lodash": "^4.17.21" + } + }, "normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -3284,14 +3589,12 @@ "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -3395,7 +3698,6 @@ "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, "requires": { "side-channel": "^1.0.4" } @@ -3459,6 +3761,14 @@ "strip-indent": "^4.0.0" } }, + "redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "requires": { + "esprima": "~4.0.0" + } + }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -3508,7 +3818,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -3518,7 +3827,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -3539,7 +3847,6 @@ "version": "4.5.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.1.tgz", "integrity": "sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==", - "dev": true, "requires": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -3551,7 +3858,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", - "dev": true, "requires": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -3628,7 +3934,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.6.tgz", "integrity": "sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==", - "dev": true, "requires": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.3", @@ -3651,6 +3956,30 @@ "has-flag": "^3.0.0" } }, + "supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3703,6 +4032,11 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "unescape-html": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unescape-html/-/unescape-html-1.0.0.tgz", + "integrity": "sha1-dXab9Gnngi52eINNVnvytEhbJIs=" + }, "user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", @@ -3744,13 +4078,48 @@ } }, "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.0.1.tgz", + "integrity": "sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==", "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "requires": { + "ansi-regex": "^6.0.1" + } + } } }, "wrap-fn": { @@ -3764,21 +4133,18 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, "requires": {} }, "xmlhttprequest-ssl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", - "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", - "dev": true + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" }, "yallist": { "version": "4.0.0", diff --git a/package.json b/package.json index d9e0a18..22c95a3 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,17 @@ "author": "Coalition of Invisible Colleges", "license": "AGPL-3.0-or-later", "dependencies": { + "ansimd": "^0.2.1", "chalk": "^5.0.1", "chalk-animation": "^2.0.2", "crypto": "^1.0.1", + "envfile": "^6.17.0", "figlet": "^1.5.2", "gradient-string": "^2.0.1", "hash.js": "^1.1.7", "inquirer": "^8.2.4", + "marked": "^4.0.16", + "marked-terminal": "^5.1.1", "mdlog": "^1.0.3", "nanospinner": "^1.1.0", "sha.js": "^2.4.11", @@ -33,10 +37,10 @@ "socket.io-client": "^4.5.1", "superagent": "^7.1.6", "uuid": "^8.3.2", + "wrap-ansi": "^8.0.1", "yaml-head-loader": "^1.0.2" }, "publishConfig": { "access": "public" - }, - "devDependencies": {} + } } diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js new file mode 100644 index 0000000..40d0635 --- /dev/null +++ b/scripts/bootstrap.js @@ -0,0 +1,26 @@ +// The bootstrapping module uses the glossary in peers.json (later will use members from DB?) +// to look up tor addresses for the give shortname or SSH public key. +// We could just do all this in the AO, but the bootstrapper is for public / loose ties and the AO's explicit p2p is for close / private ties. +// The bootstrapper occasionally queries all of the tor addresses in your address book. +// If they are an AO with bootstrapping turned on, the AO server will respond with its public directory information. +// Since you have connected to them via their .onion address, it is assumed they are a known trusted party, +// so the information received will update your local directory information in your address book. +// Be careful to only connect to bootstrap servers you trust, with owners who will not add unsafe .onions to their own directory! +// An AO contacted at a tor address is considered a known party and an authority on announcing its own SSH key (if you trust the party). +// Therefore it works to receive an initial trusted .onion address, connect, get their directory, and use it to connect to others. +// You can copy the directory of each new peer, however these are marked with a hops: field to count how far away the trust gets. +// Maybe there should be a setting you announce to other nodes about whether they can share your .onion address or not (reshare) + +// Start bootstrapping in the background +export function startPublicBootstrap() { + // Go through all the address book entries in my peers.json + // For each one that has a .onion address, do a fetch on it at the /bootstrap route + // If it responds with JSON containing directory information, increment the hops: field on all of it, and merge it with my file + // Must use entire new or old record. Use whichever one has fewer hops. Only replace if timestamp is newer. + // Again we are assuming that we know the owner of the .onion address and trust them, because a .onion is not spoofable. +} + +// Kill the bootstrapping process +export function stopPublicBootstrap() { + +} diff --git a/scripts/chalkStyles.js b/scripts/chalkStyles.js new file mode 100644 index 0000000..4be5c4b --- /dev/null +++ b/scripts/chalkStyles.js @@ -0,0 +1,10 @@ +import chalk from 'chalk' + +// Chalk styles +export const greenChalk = chalk.hex('#008800') +export const headerStyle = chalk.blue.bold.underline +export const manualTitleStyle = greenChalk.bold.underline + +// Preformatted phrases that can be used in backticked console.log strings +export const theAO = `the ${greenChalk.bold('AO')}` +export const theMenu = `the ${greenChalk.bold('Menu')}` \ No newline at end of file diff --git a/scripts/console.js b/scripts/console.js new file mode 100644 index 0000000..671cf4d --- /dev/null +++ b/scripts/console.js @@ -0,0 +1,38 @@ +import chalk from 'chalk' +import chalkAnimation from 'chalk-animation' +import gradient from 'gradient-string' +import figlet from 'figlet' +import { createSpinner } from 'nanospinner' +import { sleep } from './util.js' +import { selectRandom } from './util.js' +import { centerLines } from './strings.js' + +// Displays a brief randomly-selected rainbow-animated phrase +const greetingMessages = ['Portaling!', 'You are a Unicorn!', "Here we go!", "Wow!", "AO Loading...", "Powering Up!", "Unicorn Portal", "Don't Panic!"] +export async function unicornPortal(ms) { + const randomGreetingMessage = selectRandom(greetingMessages) + const rainbowTitle = chalkAnimation.rainbow(randomGreetingMessage + '\n') + await sleep(ms) + rainbowTitle.stop() +} + +// Prints the given message to the screen in the given ASCII art style. Here is a list of decent styles: +const asciiFonts = ['Standard', 'Digital', 'Bubble', 'Script', 'Mini', 'Banner', 'Alphabet', 'Avatar', 'Chunky', 'Computer', 'Contessa', 'Gothic', 'Invita', 'Lockergnome', 'Madrid', 'Morse', 'Moscow', 'Pawp', 'Pepper', 'Pyramid', 'Rectangles', 'Shadow', 'Short', 'Slant', 'Small', 'Stampatello', 'Stop', 'Straight', 'Thick', 'Thin', 'Weird'] +export async function asciiArt(message, style) { + const randomFont = selectRandom(asciiFonts) + let art = figlet.textSync(message || 'Autonomous Organization', { font: randomFont }) + art = centerLines(art) + console.log(gradient.pastel.multiline(art)) +} + +// Clears the console +export function clearScreen() { + console.clear() +} + +// Displays a spinner for 1.2 secconds with the given messages during and after the timer completes +export async function spinnerWait(waitingMessage, doneMessage, ms = 1200) { + const spinner = createSpinner(waitingMessage || 'Please wait...').start() + await sleep(ms) + spinner.success({ text: doneMessage || 'Done.' }) +} diff --git a/scripts/features.js b/scripts/features.js new file mode 100644 index 0000000..246b8f8 --- /dev/null +++ b/scripts/features.js @@ -0,0 +1,19 @@ +// Functions to add and remove AO features +import { execSync } from 'child_process' + +// Adds a line to .bashrc to make 'ao' an alias for 'ao-cli', to simplify using the AO from the command line +export function installAoAlias() { + try { + execSync('grep "ao=\'ao-cli\'" ~/.bashrc') + console.log('You can already type \'ao\' to launch ao-cli; the alias line already exists in ~/.bashrc.') + } catch(err) { + execSync('echo alias ao=\'ao-cli\' >> .bashrc') + console.log('Added alias line to ~/.bashrc. You can now type \'ao\' to launch ao-cli.') + } +} + +// Downloads the ao-manual repo to ~/.ao/manual/ +export function downloadAoManual() { + console.log(execSync('git clone https://git.coalitionofinvisiblecolleges.org:3009/autonomousorganization/ao-manual.git')) +} + diff --git a/scripts/files.js b/scripts/files.js new file mode 100644 index 0000000..118986d --- /dev/null +++ b/scripts/files.js @@ -0,0 +1,40 @@ +// Helpers for filesystem / folder manipulations and loading files +import fs from 'fs' +import readYamlAndMarkdown from 'yaml-head-loader' + +// Loads the text of a file +async function loadTextFile(path) { + return new Promise((resolve, reject) => { + fs.readFile(path, 'utf8', function(err, data) { + if (err) { + console.log('Reading file failed:', err) + return null + } + resolve(data) + }) + }) +} + +// Loads the given text file and returns a dictionary of its contents parsed into a .header dict and .tail markdown content +export async function loadYamlMarkdownFile(path) { + let text = await loadTextFile(path) + if(!text) { + return null + } + let dict = readYamlAndMarkdown(text) + return dict +} + +// Loads and returns the list of contents of a folder as an array +export function lsFolder(path) { + try { + return fs.readdirSync(path).filter(fileOrFolderName => fileOrFolderName.length >= 1 && fileOrFolderName[0] !== '.') + } catch(err) { + return null + } +} + +// Returns true if the path is a folder +export function isFolder(path) { + return Array.isArray(lsFolder(path)) +} diff --git a/scripts/manual.js b/scripts/manual.js index 539ebea..d75e4f4 100644 --- a/scripts/manual.js +++ b/scripts/manual.js @@ -1,21 +1,148 @@ -import { parse } from 'yaml' -import fs from 'fs' +// Functions for loading the AO Manual, a hierarchy of markdown files +import chalk from 'chalk' +import inquirer from 'inquirer' +import { loadYamlMarkdownFile, lsFolder, isFolder } from './files.js' +import { repeatString, centerLines } from './strings.js' +import { headerStyle, manualTitleStyle } from './chalkStyles.js' -console.log("Parsing the manual file...") +// Removes numbered prefix such as 12_ and .md suffix, replaces underscores with spaces, and adds titlecase +function formatManualTitleString(title) { + // Remove .md suffix + if(/\.md$/.exec(title)) { + title = title.substring(0, title.length - 3) + } + // Remove numbered prefix e.g., 12_ + if(/^\d*_/.exec(title)) { + title = title.split('_').slice(1).join('_') + } + // Replace underscores with spaces + title = title.replaceAll('_', ' ') + + return title.toTitleCase() +} -parse('3.14159') -// 3.14159 +import { basename } from 'path' +import mdlogBuilder from 'mdlog' +import { marked } from 'marked' +import TerminalRenderer from 'marked-terminal' + + marked.setOptions({ + renderer: new TerminalRenderer({ + showSectionPrefix: false, + }) +}) -parse('[ true, false, maybe, null ]\n') -// [ true, false, 'maybe', null ] +// Given a path and file/folder name, it returns the appropriate manual title +// If it's a folder and there is an index.js inside that has a title: field, that overrides the title +// Otherwise it's the filename or foldername, minus anything before the first underscore (_), in titlecase +async function loadManualTitle(path, fileOrFolder) { + // If it's a .md file, check inside for a title: field + if(/\.md$/.exec(fileOrFolder)) { + const indexTitle = (await loadYamlMarkdownFile(path + fileOrFolder))?.meta?.title + if(indexTitle) { + return indexTitle + } + } + + // If it's a folder, check for a title: field in index.md and return if exists + if(isFolder(path + fileOrFolder)) { + const indexPath = path + fileOrFolder + '/index.md' + const indexTitle = (await loadYamlMarkdownFile(indexPath))?.meta?.title + if(indexTitle) { + return indexTitle + } + } + + // Fall back to using the file/folder name as the title + return formatManualTitleString(fileOrFolder) +} -const file = fs.readFileSync('./manual/index.yml', 'utf8') -const parsed = parse(file) -// { YAML: -// [ 'A human-readable data serialization language', -// 'https://en.wikipedia.org/wiki/YAML' ], -// yaml: -// [ 'A complete JavaScript implementation', -// 'https://www.npmjs.com/package/yaml' ] } +// Prints the specified manual page to the screen +export async function printManualPage(path, injectedTitle = '') { + if(isFolder(path)) { + path += '/index.md' + } + const dict = await loadYamlMarkdownFile(path) + const title = injectedTitle || dict?.meta?.title || formatManualTitleString(basename(path)) + const formattedTitle = manualTitleStyle(title).centerInLine(title.length).centerInConsole() + console.log('\n' + formattedTitle + '\n') + const renderedMarkdown = marked(dict?.tail).wordWrap().centerInConsole() + console.log(renderedMarkdown) +} -console.log(parsed) \ No newline at end of file +// Render the manual folder or a subfolder as a menu +// First the index.js is listed using the folder name or title loaded from inside the file as the menu item title +// Next, any other files not starting with a number are loaded and displayed in discovered/arbitrary order +// Next, items starting with 0_, then 1_, and so on are displayed in order. You can mix files and folders. +// Selecting a menu item renders it. For .md files it renders it and shows the same level Manual menu again. +// For folders, it goes into that folder and renders it as a manual menu folder +// This allows arbitrarily nested Manual menu folders to be explored in a standardized menu system +export async function manualFolderAsMenu(path, menuTitle, backOption, previousMenuChoice = 0) { + if(!isFolder(path)) { + return false + } + if(path[path.length - 1] != '/') { + path += '/' + } + + let menuItems = [] + + const folderContents = lsFolder(path) + if(folderContents.some(fileOrFolder => fileOrFolder === 'index.md')) { + const indexTitle = await loadManualTitle(path, 'index.md') + + let indexMenuItem = {} + indexMenuItem[indexTitle] = 'index.md' + menuItems.push(indexMenuItem) + } + + let unNumberedItems = [] + let numberedItems = [] + const sortedFolderContents = folderContents.sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) + for(let i = 0; i < sortedFolderContents.length; i++) { + const fileOrFolder = sortedFolderContents[i] + if(fileOrFolder === 'index.md') { + continue + } + const potentialNumber = fileOrFolder.split('_')[0] + const initialNumber = parseInt(potentialNumber) + const title = await loadManualTitle(path, fileOrFolder) + const menuItem = {} + menuItem[title] = fileOrFolder + + if(isNaN(initialNumber)) { + unNumberedItems.push(menuItem) + } else { + numberedItems.push(menuItem) + } + } + menuItems = menuItems.concat(unNumberedItems, numberedItems) + + const menuChoices = menuItems.map(menuItem => Object.keys(menuItem)[0]) + menuChoices.push(backOption) + if(previousMenuChoice >= menuChoices.length) { + previousMenuChoice = 0 + } + console.log(`\n${headerStyle(menuTitle)}`) + const answer = await inquirer.prompt({ + name: 'manual_menu', + type: 'rawlist', + message: 'Choose a topic:', + choices: menuChoices, + pageSize: menuChoices.length, + default: previousMenuChoice + }) + const chosenMenuIndex = menuChoices.indexOf(answer.manual_menu) + if(answer.manual_menu === backOption) { + return false + } + const chosenPath = path + Object.values(menuItems.find(menuItem => Object.keys(menuItem)[0] === answer.manual_menu))[0] + await printManualPage(chosenPath, answer.manual_menu) + const newBackOption = backOption === 'Back to Main Menu' ? 'Back to Table of Contents' : 'Back to ' + menuTitle + let previousChoice = 0 + do { + previousChoice = await manualFolderAsMenu(chosenPath, answer.manual_menu, newBackOption, previousChoice + 1) + } + while(previousChoice !== false) + return chosenMenuIndex +} diff --git a/scripts/settings.js b/scripts/settings.js new file mode 100644 index 0000000..1f82624 --- /dev/null +++ b/scripts/settings.js @@ -0,0 +1,120 @@ +import { execSync } from 'child_process' +import fs from 'fs' +import { parse, stringify } from 'envfile' + +export const AO_ENV_FILE_PATH = process.env.HOME + '/.ao/.env' + +function createAoFolderIfDoesNotExist() { + +} + +// Check for an AO env file at ~/.ao/.env and returns true if it exists +export function checkAoEnvFile() { + try { + execSync(`[ -f "${AO_ENV_FILE_PATH}" ]`) + return true + } catch(err) { + return false + } +} + +export function aoEnv(variable) { + let envFileContents = {} + try { + envFileContents = fs.readFileSync(AO_ENV_FILE_PATH) + } catch(err) { + if(err.code === 'ENOENT') { + console.log('The .env file does not exist, so the requested value', variable, 'is empty.') + } else { + console.log('Unknown error loading .env file in aoEnv, aborting.') + return null + } + } + const parsedFile = parse(envFileContents) + + if(!parsedFile.hasOwnProperty(variable)) { + return null + } + + // Convert ENV idiom to programmatic types + switch(parsedFile[variable]) { + case '1': + case 'true': + case 'TRUE': + case 'yes': + case 'YES': + return true + case '0': + case 'false': + case 'FALSE': + case 'no': + case 'NO': + return false + } + + return parsedFile[variable] +} + +// Sets and saves the given ENV=value to the global ~/.ao/.env file +// If value is null, the env variable will be deleted +// Returns true if a change was made, false if no change was made or if it failed +export function setAoEnv(variable, value) { + createAoFolderIfDoesNotExist() + if(typeof variable !== 'string') { + console.log('ENV variable name must be a string for setAoEnv') + return false + } + + // Convert types to standard ENV file idiom + switch(value) { + case true: + case 'TRUE': + case 'yes': + case 'YES': + value = '1' + break + case false: + case 'FALSE': + case 'no': + case 'NO': + value = '0' + } + + let envFileContents = {} + try { + envFileContents = fs.readFileSync(AO_ENV_FILE_PATH) + } catch(err) { + if(err.code === 'ENOENT') { + console.log('The .env file hasn\'t been created yet, creating.') + } else { + console.log('Unknown error loading .env file in setAoEnv, aborting. Error:', err) + return false + } + } + + const parsedFile = parse(envFileContents) + if(parsedFile[variable] == value) { + console.log(variable, 'is already', value, 'so no change was made.') + return false + } + + if(value === null) { + delete parsedFile.variable + } else { + parsedFile[variable] = value + } + + const stringified = stringify(parsedFile) + fs.writeFileSync(AO_ENV_FILE_PATH, stringified) + + // Confirm the variable was set in the .env file correctly + if(aoEnv(variable) != value) { + console.log('Value was not saved correctly, sorry.') + return false + } + return true +} + +function setAndSaveEnvironmentVariable(variable, value, path) { + +} diff --git a/scripts/strings.js b/scripts/strings.js new file mode 100644 index 0000000..be5dd02 --- /dev/null +++ b/scripts/strings.js @@ -0,0 +1,36 @@ +// Extends the String type and other string helper functions + +// Adds a .toTitleCase() function to every string +String.prototype.toTitleCase = function () { + return this.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()}) +} + +// Wraps the string to the console (or specified) width, ignoring any ansi formatting codes +import wrapAnsi from 'wrap-ansi' +String.prototype.wordWrap = function (width = 80) { + return wrapAnsi(this, width) +} + +export const repeatString = (str, n) => { + return new Array(1 + (n || 0)).join(str) +} + +String.prototype.centerInConsole = function (width = 80) { + const consoleWidth = process.stdout.columns + const padding = Math.floor((consoleWidth - width) / 2) + const lines = this.split('\n') + const centered = lines.map(line => repeatString(" ", padding) + line) + return centered.join('\n') +} + +// Centers a one-line string within the given number of characters by adding padding to the left +String.prototype.centerInLine = function (lineWidth, width = 80) { + const padding = Math.floor((width - lineWidth) / 2) + return repeatString(" ", padding) + this +} + +export const centerLines = (str) => { + const lines = str.split('\n') + const centered = lines.map(line => line.centerInLine(line.length, process.stdout.columns)) + return centered.join('\n') +} \ No newline at end of file diff --git a/scripts/system.js b/scripts/system.js new file mode 100644 index 0000000..45ab4cc --- /dev/null +++ b/scripts/system.js @@ -0,0 +1,111 @@ +// Functions related to OS and installing software +import { execSync } from 'child_process' + +// Detects the operating system we are running on +export function detectOS() { + let distro + + try { + execSync('[ -f "/etc/debian_version" ]') + distro = 'debian' + console.log(`${greenChalk('Debian')}, Ubuntu, or Raspbian OS detected.`) + } catch(err) {} + + try { + execSync('[ -f "/etc/arch-release" ]') + distro = 'arch' + console.log(`${greenChalk('Arch or Manjaro-based')} OS detected.`) + } catch(err) {} + + try { + execSync('[ -f "/etc/fedora-release" ]') + distro = 'fedora' + console.log(`${greenChalk('Fedora')} OS detected.`) + } catch(err) {} + + try { + execSync('[ $(uname | grep -c "Darwin") -eq 1 ]') + distro = 'mac' + console.log(`${greenChalk('MacOS')} detected.`) + } catch(err) {} + + if(!distro) { + console.log("Your OS was not recognized, sorry.") + process.exit(1) + } + + return distro +} + +// Runs the correct command to update all your software for any recognized OS +export function updateSoftware() { + distro = detectOS() + if(!distro) { + console.log("Your OS was not recognized, so nothing was updated, sorry.") + return false + } + + console.log('Updating your software from repositories...') + console.log(`(You may need to input your ${chalk.blue.bold("'sudo' password")} here)`) + switch(distro) { + case 'debian': + execSync('sudo apt update && sudo apt autoremove && sudo apt upgrade') + break + case 'arch': + execSync('sudo pacman -Syu --noconfirm') + break + case 'fedora': + execSync('sudo dnf update && sudo dnf upgrade') + break + case 'mac': + execSync('install && sudo brew update') + break + } + + return true +} + +// Installs core dependencies required by Alchemy and the AO +export function installRequired() { + distro = detectOS() + if(!distro) { + console.log("Your OS was not recognized, so nothing was installed, sorry.") + return false + } + console.log('Installing Alchemy and AO installation process core dependencies (fast if already installed)...') + console.log(`(You may need to input your ${chalk.blue.bold("'sudo' password")} here)`) + + // Install OS-specific requirements + switch(distro) { + case 'debian': + execSync('sudo apt install build-essential') + // Some of these might not be required + execSync('source ~/Alchemy/ingredients/lead && install_if_needed sqlite3 zlib1g-dev libtool-bin autoconf autoconf-archive automake autotools-dev libgmp-dev libsqlite3-dev python python3 python3-mako libsodium-dev build-essential pkg-config libev-dev libcurl4-gnutls-dev libssl-dev fakeroot devscripts') + break + case 'arch': + try { + execSync('[[ ! $(pacman -Qg base-devel) ]]') + execSync('sudo pacman -S base-devel --noconfirm') + } catch(err) {} + execSync('source ~/Alchemy/ingredients/lead && install_if_needed python gmp sqlite3 autoconf-archive pkgconf libev python-mako python-pip net-tools zlib libsodium gettext nginx') + break + case 'fedora': + execSync('source ~/Alchemy/ingredients/lead && install_if_needed sqlite3 autoconf autoconf-archive automake python python3 python3-mako pkg-config fakeroot devscripts') + break + } + + // Install on every OS + execSync('source ~/Alchemy/ingredients/lead && install_if_needed git wget make') + try { + execSync('[ -z $NVM_DIR ]') + execSync('source ingredients/iron && install_nvm') + console.log(`Installed nvm.`) + } catch(err) {} + + return true +} + +// Sets node to the current version used by the AO +export function setNodeVersion() { + execSync('source ingredients/iron && set_node_to v16.13.0') +} diff --git a/scripts/tests.js b/scripts/tests.js index 470115e..51211a1 100644 --- a/scripts/tests.js +++ b/scripts/tests.js @@ -4,7 +4,7 @@ // Maybe in the future a precompiled api.js created from api.ts can be hosted so that ao-cli does not have to compile any TypeScript import api from './api.js' -export async function testLoginAndOut() { +async function testLoginAndOut() { const username = 'ao' const password = 'ao' try { @@ -39,11 +39,11 @@ export async function testLoginAndOut() { return true } -export async function runAllTests() { +async function runAllTests() { await testLoginAndOut() } -export const testDict = { +export const tests = { "Run All Tests": runAllTests, "Test Login/Logout": testLoginAndOut } diff --git a/scripts/util.js b/scripts/util.js new file mode 100644 index 0000000..9a352e7 --- /dev/null +++ b/scripts/util.js @@ -0,0 +1,18 @@ +// General helper functions + +// Returns a random int between min and max (inclusive) +function randomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +// Returns a random item from the given array +export function selectRandom(arrayToChooseFrom) { + return arrayToChooseFrom[randomInt(0, arrayToChooseFrom.length - 1)] +} + +// Waits for the given number of milliseconds (or a brief pause by default) +export function sleep(ms = 550) { + return new Promise((r) => setTimeout(r, ms)) +} diff --git a/scripts/welcome.js b/scripts/welcome.js new file mode 100644 index 0000000..cea1270 --- /dev/null +++ b/scripts/welcome.js @@ -0,0 +1,94 @@ +import chalk from 'chalk' +import { selectRandom } from './util.js' +import { greenChalk, theAO, theMenu } from './chalkStyles.js' + +// Different sets of messages that can be randomly selected from +const welcomeMessages = [ + `You turn a corner and are suddenly back in the halls of ${theAO}.`, + `You make the sign of ${theAO} and are whisked away on a gust of divine wind.`, + `You take a closer look at the folder you are in and realize it is a room.`, + `You draw an alchemical symbol and open a doorway into ${theAO}. You step through.`, + `"Ah, there you are again!" The old man greets you as you step through the portal.`, + `A line of doge-masked worshippers glide by. By the time you exit the trance, you are in ${theAO}. Doge bless.`, + `You spraypaint an ${greenChalk.bold('A')} superimposed on an ${greenChalk.bold('O')} and step through the portal.`, + `You receive a phone call. You answer it. You are in ${theAO}.`, + `You dab fiercely, and when you raise your head, you are in ${theAO}.`, + `A ship arrives and takes you out to sea. The ship sinks and you somehow wash up safely in ${theAO}.`, + `You are reading in the Library when you find a strange book. Whatever you read next in the book is ${theAO}.`, + `You plant a magic seed and it grows into a great tree. Climbing up into its branches, you know ${theAO} is here.`, + `In the late afternoon, a warm sunbeam highlights motes of dust over your book. These motes are ${theAO}.`, + `A black cat crosses your path. Most people wouldn't notice, but you know you have entered the AO.`, + `Dipping your brush in ink, you draw a perfect circle. This is ${theAO}.`, + `You are offered a choice between two pills. However, you have secretly built up an immunity to both pills, and trick your opponent into taking one. Inconceviably, you are in ${theAO}.`, + `A young man with spiky hair and golden skin appears before you in a halo of light. He guides you to ${theAO}.`, + `You enter a gap between two hedges and, after struggling through the brush, emerge into a sunny estate garden. You've found the AO.`, + `You find a small animal burrow dug near the riverside. Crawling in, you find a network of caves that lead to the AO.` +] +const menuMessages = [ + `You see ${theMenu}:`, + `A page of aged paper wafts into your hand. On it is written ${theMenu}:`, + `A minstrel walks by playing a haunting melody, and in its harmonies you hear the refrain of ${theMenu}:`, + `You pick up an apple from a nearby table and take a bite. You suddenly recall the words of ${theMenu}:`, + `With a screeching sound, a page containing ${theMenu} slowly emerges, line-by-line, from a dot-matrix printer:`, + `In the shadows cast in the cavernous space, you see the forms of ${theMenu}:`, + `With a low hum, standing waves appear on the reflecting pool at your feet. Impossibly, they spell out ${theMenu}:`, + `You see a box of candies labeled 'Eat Me'. Why not? you think. You find ${theMenu} printed on the wrapper:`, + `You see an ornate bottle labeled 'Drink Me'. Why not? you think. You take a sip and shrink to the size of a doormouse. Now you can read ${theMenu} scratched into the wainscoating:`, + `Cretaceous jungle plants tower overhead, overgrown from the safari room. The ancient fractals of the branches prefigure the structure of ${theMenu}:`, +] +const exclamationMessages = [ + 'Woah there!', + 'Shiver me timbers!', + 'iNtErEsTiNg!', + 'Turing\'s ghost!', + 'Frack!', + 'Frell!', + 'Good grief!', + 'With great power comes great responsibility.', +] +const rogerMessages = [ + 'You\'ve got it.', + 'Aye-aye, captain!', + 'The AO provides.', + 'Roger.', + 'Yokai.' +] +const farewellMessages = [ + 'Goodbye!', + 'Goodbye! Goodbye! Goodbye...!', + 'The AO will always be with you.', + 'Please return soon; the AO needs your virtue.', + 'The AO will await your return.', + 'Doge bless.', + 'Please remember the AO throughout your day as a ward against evil.', + 'Remember your PrioriTEAâ„¢.', + 'Know thyself.', + 'With great power comes great responsibility.', + 'The AO is a state of mind.', + 'Go for the low-hanging fruit.' +] + +// Prints a random RPG-style welcome message to contextualize the AO experience and the main menu +export async function welcome() { + const randomWelcomeMessage = selectRandom(welcomeMessages) + const randomMenuMessage = selectRandom(menuMessages) + const welcomeMessage = (' ' + randomWelcomeMessage + ' ' + randomMenuMessage).wordWrap(process.stdout.columns - 2) + // todo: line breaks would be more accurate if there were a function to count the length of a string minus the formatting codes (regex) + // right now the invisible formatting characters are counted so lines are wrapped early + console.log('\n' + welcomeMessage) + +} + +// Returns a random exclamatory remark +export function exclaim() { + return selectRandom(exclamationMessages) +} + +// Returns a random obedient/affirmative remark denoting that a command was executed +export function roger() { + return selectRandom(rogerMessages) +} + +export function farewell() { + console.log(chalk.yellow.bold(selectRandom(farewellMessages))) +}