0.0.1 • Published 10 months ago

@dgtlntv/protostar v0.0.1

Weekly downloads
-
License
GPL-3.0
Repository
github
Last release
10 months ago

Proto* is a tool for creating interactive CLI prototypes using a simple JSON configuration file. The tool generates a website that emulates a terminal, with only the CLI defined in the configuration file being available. It's designed for quick and easy creation of CLI prototypes, enabling rapid iterations. The tool automatically deploys to GitHub Pages, allowing prototypes to be easily shared. This makes it suitable for user testing, as users can access the prototype through a simple link rather than having to install anything.

Prerequisites

You have to have git and node installed.

Setup and Usage

  1. Clone the newly created repository to your local computer either with an app like Github desktop or by running the following command:
git clone https://github.com/your-username/cli-prototype.git
cd cli-prototype
  1. In the cloned repository install the dependencies:
npm install
  1. Run the development server:
npm run dev
  1. Open the provided URL in your web browser to interact with your CLI prototype.

Deployment

This project is set up to deploy automatically to GitHub Pages using GitHub Actions:

Using Protostar as a Library

In addition to using Protostar as a standalone CLI prototyping tool, you can also integrate it as a library in your JavaScript projects:

Installation

npm install protostar

Usage as a library

// Import the required styles
import "@xterm/xterm/css/xterm.css"
import "./styles.css"

// Import the Terminal component
import { Terminal } from "protostar"

// Import your commands configuration
import commandsData from "./commands.json"

// Initialize the terminal when the DOM is ready
document.addEventListener("DOMContentLoaded", () => {
    const terminal = new Terminal(
        document.getElementById("terminal"),
        commandsData
    )
})

Customizing Your CLI

This prototyping tool is build in a way so that the only file you need to change to customize your CLI prototype is the src/commands.json file. The general schema of the commands.json is:

{
    // Welcome message for the CLI prototype
    welcome: "Welcome to My CLI! Type 'help' for available commands.",

    // The global variables you can read and write accross commands
    variables: {
        username: "dgtlntv",
        isLoggedIn: "false",
    },
    commands: {
        // The commands available in the CLI prototype
    },
}
  • welcome is optional. It defines a welcome message that is output when the CLI is loaded for the first time.
  • variables is optional as well and can be used to define variables that can be set and checked by commands. For example, through them we can prescribe a specific command sequence.
  • The commands of the CLI are defined in the commands object.

Commands

Command name

The command name with which the command can be called is the key of the command object. In this example we are creating a register command:

{
    commands: {
        register: {
            // Command content
        },
    },
}
Description

The description is shown in the automatically generated help message.

{
    commands: {
        register: {
            description: "This command registers a new account with the service.",
        },
    },
}
Alias

An alias allows you to call the same command with a different command name. Can either be a single string or an array of strings. In the following example the user could call the same registercommand with enrol or signup now.

{
    commands: {
        register: {
            alias: ["enrol", "signup"],
        },
    },
}
Example

The example will be used to provide example usages of a command in the automatically generated help message. Can be a single example command, description or an array of such examples.

{
    commands: {
        register: {
            example: [
                "register email@example.com",
                "Register a new account with the email@example.com email.",
            ],
        },
    },
}
Positional arguments

Positional arguments in commands can be either required or optional. Required positional arguments are denoted as <email>, while optional arguments are represented as [username].

{
    commands: {
        "register <email> [username]": {
            // Command content
        },
    },
}

The | character allows you to specify aliases for positional arguments.

{
    commands: {
        "register <email | username>": {
            // Command content
        },
    },
}

The last positional argument can optionally accept an array of values, by using the .. operator:

{
    commands: {
        "register <email> [..socialUrls]": {
            // Command content
        },
    },
}

Under positional the positional argument of a command are defined. You can use the following fields to describe the positional arguments:

FieldDescription
aliasAlternative name(s) for the positional argument.
choicesAn array of valid values for this positional argument.
defaultThe default value for this positional argument if not provided.
demandOptionMarks the argument as required. If true, the command will fail without it. If a string, it will be used as the error message.
descriptionA short description of the positional argument.
typeThe expected data type of the positional argument (boolean, number, string).
{
    commands: {
        "register <email>": {
            positional: {
                email: {
                    alias: "username",
                    // choice and default don't really make sense in this example, but is an available configuration
                    // "choices": [
                    //     "choice1",
                    //     "choice2",
                    //     "choice3"
                    // ],
                    // "default": "defaultOption",
                    demandOption: true,
                    description: "The email to register your account with",
                    type: "string",
                },
            },
        },
    },
}
Options / flags

Under options the flags (eg. --flag) of a command are defined. You can use the following fields to describe the options/flags:

FieldDescription
aliasAlternative name(s) for the positional argument.
choicesAn array of valid values for this positional argument.
defaultThe default value for this positional argument if not provided.
defaultDescriptionA description of the default value.
demandOptionMarks the argument as required. If true, the command will fail without it. If a string, it will be used as the error message.
descriptionA short description of the positional argument.
groupThe group to which this option belongs, used for grouping related options in help output.
hiddenIf true, the option will not be shown in help output.
nargsThe number of arguments to be consumed by this option.
requiresArgIf true, the option must be specified with a value.
typeThe expected data type of the positional argument (boolean, number, string).
{
    commands: {
        register: {
            options: {
                password: {
                    alias: ["pwd", "pw"],
                    // choice and default don't really make sense in this example, but is an available configuration
                    // "choices": [
                    //     "choice1",
                    //     "choice2",
                    //     "choice3"
                    // ],
                    // "default": "defaultOption",
                    // "defaultDescription": "The description for the default option",
                    demandOption: true,
                    description: "The password for your account",
                    group: "Login credentials",
                    hidden: false,
                    nargs: 1,
                    requiresArg: true,
                    type: "string",
                },
            },
        },
    },
}
Sub-commands

If you want to chain multiple commands you can nest commands under commands. The commands defined under commands allow for the exact same configuration as a root command.

{
    commands: {
        register: {
            commands: {
                user: {
                    // Command content
                },
                serviceaccount: {
                    // Command content
                },
            },
        },
    },
}
Handler

Under handler the response to a command is defined. The handler accepts the components available in the CLI prototyping tool. The available components are the following:

ComponentDescription
textPrints text to the terminal.
progressBarRenders a progress bar to the terminal.
spinnerRenders a spinner to the terminal.
tableRenders a table to the terminal.
conditionalEvaluates a condition and then executes a component based on if the condition evaluated to true or false.
variableSaves a value to a global variable.
autoCompletePrompt that auto-completes as the user types.
basicAuthPrompt for username and password authentication.
confirmPrompt to confirm or deny a statement.
formPrompt for multiple values on a single terminal screen.
inputPrompt for user input.
invisiblePrompt for user input, hiding it from the terminal.
listPrompt returning a list of values, created by splitting user input.
multiSelectPrompt allowing selection of multiple items from a list of options.
numberPrompt that takes a number as input.
passwordPrompt that takes user input and masks it in the terminal.
quizPrompt for multiple-choice quiz questions.
surveyPrompt for user feedback on a list of questions using a defined scale.
scaleCompact version of Survey prompt using a Likert Scale for quick feedback.
selectPrompt for selecting from a list of options.
sortPrompt for sorting items in a list.
snippetPrompt for replacing placeholders in a snippet of code or text.
togglePrompt for toggling between two values.

The handler accepts either a single component:

{
    commands: {
        register: {
            handler: {
                component: "text",
                output: "Registered successfully",
            },
        },
    },
}

Or an array of components:

{
    commands: {
        register: {
            handler: [
                {
                    component: "text",
                    output: "Registering in progress...",
                    duration: 5000,
                },
                {
                    component: "text",
                    output: "Registered successfully",
                },
            ],
        },
    },
}

The following section explains each available component more in depth:

Components

A component corresponds to something that can happen as a reaction to a command. Multiple components can be linked together to happen in sequence. See the handler documentation.

Text

npm.io

The simplest of the components is the text component. It simply prints text to the terminal, while optionally waiting for some time after printing the text to the terminal.

fieldrequired/optionalDescription
outputrequiredThe text that should be printed to the terminal
durationoptionalThe duration (in milliseconds) that should be waited after printing the text. Also accepts "random" which will wait for a random duration between 100ms and 3000ms.
{
    commands: {
        register: {
            handler: [
                {
                    component: "text",
                    output: "Registering in progress...",
                    duration: 2000,
                },
                {
                    component: "text",
                    output: "Registered successfully",
                },
            ],
        },
    },
}

Progress bar

npm.io

The progress bar component renders a progress bar in the terminal, showing a task's completion over time.

fieldrequired/optionalDescription
outputrequiredThe text displayed alongside the progress bar
durationrequiredThe duration (in milliseconds) for the progress bar to complete. Also accepts "random" which will use a random duration between 100ms and 3000ms.
{
    commands: {
        install: {
            handler: {
                component: "progressBar",
                output: "Installing dependencies...",
                duration: 2000,
            },
        },
    },
}

Spinner

npm.io

The spinner component displays an animated spinner in the terminal, indicating that a process is ongoing.

fieldrequired/optionalDescription
outputrequiredThe text or array of texts displayed alongside the spinner
durationrequiredThe duration (in milliseconds) for which the spinner should run. Also accepts "random" which will use a random duration between 100ms and 3000ms.
conclusionoptionalSpecifies how the spinner should conclude its animation. Can be stop, success, or fail.
{
    commands: {
        process: {
            handler: {
                component: "spinner",
                output: ["Processing", "Please wait", "Almost done"],
                duration: 2000,
                conclusion: "succeed",
            },
        },
    },
}

Table

npm.io

The table component renders a formatted table in the terminal.

fieldrequired/optionalDescription
outputrequiredA 2D array representing the table data, including headers if desired.
colWidthsoptionalAn array of numbers representing the width of each column in the table. If this is not set the table will hug its content, until the table fills the terminal size.
{
    commands: {
        list: {
            handler: {
                component: "table",
                output: [
                    ["Name", "Age", "City"],
                    ["John", "30", "New York"],
                    ["Alice", "25", "London"],
                ],
                colWidths: [10, 5, 15],
            },
        },
    },
}

Conditional

npm.io

The conditional component allows for branching logic based on a condition.

fieldrequired/optionalDescription
outputrequiredAn object containing "if", "then", and optionally "else" fields

The output object should contain the following fields.

fieldrequired/optionalDescription
ifrequiredA string representing the condition to be evaluated
thenrequiredThe component to be executed if the condition is true. You can provide another conditional component as well.
elseoptionalThe component to be executed if the condition is false (if this field is provided). You can provide another conditional component as well.
{
    commands: {
        check: {
            handler: {
                component: "conditional",
                output: {
                    if: "isLoggedIn == 'true'",
                    then: {
                        component: "text",
                        output: "Welcome back!",
                    },
                    else: {
                        component: "text",
                        output: "Please log in first.",
                    },
                },
            },
        },
    },
}

Variable

npm.io

The variable component allows setting global variables that can be used across commands. For the setting of the variable to be succesfull it needs to be initialized as a global variable in the CLIs global variables.

fieldrequired/optionalDescription
outputrequiredAn object where keys are variable names and values are strings
{
    commands: {
        login: {
            handler: [
                {
                    component: "text",
                    output: "Before setting the variables username is {{username}} and isLoggedin is {{isLoggedIn}}",
                },
                {
                    component: "variable",
                    output: {
                        username: "john_doe",
                        isLoggedIn: "true",
                    },
                },
                {
                    component: "text",
                    output: "After setting the variables username is {{username}} and isLoggedin is {{isLoggedIn}}",
                },
            ],
        },
    },
}

AutoComplete

npm.io

The autoComplete component provides a prompt that auto-completes as the user types.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the prompt's result
messagerequiredMessage to display with the prompt in the terminal
choicesrequiredList of items for user selection
limitoptionalNumber of choices to display on-screen
initialoptionalThe index of the initial selection
multipleoptionalAllows selection of multiple choices
footeroptionalOptional message in muted color providing interaction hint
{
    commands: {
        search: {
            handler: {
                component: "autoComplete",
                name: "query",
                message: "Search for a fruit:",
                choices: ["Apple", "Banana", "Cherry", "Date", "Elderberry"],
                limit: 3,
                footer: "Use arrow keys to navigate",
            },
        },
    },
}

BasicAuth

npm.io

The basicAuth component prompts for username and password authentication.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the prompt's result
messagerequiredMessage to display with the prompt in the terminal
usernamerequiredUsername to compare against
passwordrequiredPassword to compare against
showPasswordoptionalDetermines whether to hide or show the password
{
    commands: {
        login: {
            handler: {
                component: "basicAuth",
                name: "auth",
                message: "Please enter your credentials:",
                username: "admin",
                password: "secret",
                showPassword: false,
            },
        },
    },
}

Confirm

npm.io

The confirm component prompts to confirm or deny a statement.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the prompt's result
messagerequiredQuestion to be confirmed or denied
initialoptionalSet whether the initial value is true or false
{
    commands: {
        delete: {
            handler: {
                component: "confirm",
                name: "confirmDelete",
                message: "Are you sure you want to delete this item?",
                initial: false,
            },
        },
    },
}

Form

npm.io

The form component prompts for multiple values on a single terminal screen.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the form's results
messagerequiredMessage to display with the form in the terminal
choicesrequiredArray of form fields

Each choice in the choices array should have the following properties:

fieldrequired/optionalDescription
namerequiredIdentifier for the form field
messagerequiredLabel for the form field
initialoptionalInitial placeholder value for the field
{
    commands: {
        register: {
            handler: {
                component: "form",
                name: "userInfo",
                message: "Please enter your information:",
                choices: [
                    {
                        name: "username",
                        message: "Username:",
                        initial: "user123",
                    },
                    {
                        name: "email",
                        message: "Email:",
                    },
                ],
            },
        },
    },
}

Input

npm.io

The input component prompts for user input.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the input's result
messagerequiredQuestion or prompt for user input
initialoptionalInitial placeholder value
{
    commands: {
        name: {
            handler: {
                component: "input",
                name: "username",
                message: "What's your name?",
                initial: "Anonymous",
            },
        },
    },
}

Invisible

npm.io

The invisible component prompts for user input, hiding it from the terminal.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the input's result
messagerequiredQuestion or prompt for hidden user input
{
    commands: {
        password: {
            handler: {
                component: "invisible",
                name: "password",
                message: "Enter your password:",
            },
        },
    },
}

List

npm.io

The list component prompts for a list of values, created by splitting user input.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the list's result
messagerequiredQuestion or prompt for list input
{
    commands: {
        tags: {
            handler: {
                component: "list",
                name: "tags",
                message: "Enter tags (comma-separated):",
            },
        },
    },
}

MultiSelect

npm.io

The multiSelect component allows selection of multiple items from a list of options.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the selection results
messagerequiredMessage to display with the selection prompt
choicesrequiredArray of selectable options
limitoptionalNumber of choices to display on-screen

Each choice in the choices array should have the following properties:

fieldrequired/optionalDescription
namerequiredDisplay text for the choice
valuerequiredValue to be returned if selected
{
    commands: {
        features: {
            handler: {
                component: "multiSelect",
                name: "features",
                message: "Select desired features:",
                choices: [
                    { name: "Auto-save", value: "autosave" },
                    { name: "Dark mode", value: "darkmode" },
                    { name: "Notifications", value: "notifications" },
                ],
                limit: 2,
            },
        },
    },
}

Number

npm.io

The number component prompts for a numeric input.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the number input result
messagerequiredQuestion or prompt for number input
{
    commands: {
        age: {
            handler: {
                component: "number",
                name: "age",
                message: "Enter your age:",
            },
        },
    },
}

Password

npm.io

The password component prompts for a password, masking the input in the terminal.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the password input result
messagerequiredQuestion or prompt for password input
{
    commands: {
        password: {
            handler: {
                component: "password",
                name: "newPassword",
                message: "Enter new password:",
            },
        },
    },
}

Quiz

npm.io

The quiz component presents multiple-choice quiz questions.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the quiz result
messagerequiredQuiz question to display
choicesrequiredList of possible answers to the quiz question
correctChoicerequiredIndex of the correct choice from the choices array
{
    commands: {
        quiz: {
            handler: {
                component: "quiz",
                name: "capitalQuiz",
                message: "What is the capital of France?",
                choices: ["London", "Berlin", "Paris", "Madrid"],
                correctChoice: 2,
            },
        },
    },
}

Survey

npm.io

The survey component prompts for user feedback on a list of questions using a defined scale.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the survey results
messagerequiredMessage to display with the survey prompt
scalerequiredDefinition of the survey scale
choicesrequiredList of survey questions

Each item in the scale array should have:

fieldrequired/optionalDescription
namerequiredLabel for the scale point
messagerequiredExplanation text for the scale point

Each item in the choices array should have:

fieldrequired/optionalDescription
namerequiredIdentifier for the survey question
messagerequiredSurvey question text
{
    commands: {
        feedback: {
            handler: {
                component: "survey",
                name: "userSatisfaction",
                message: "Please rate your experience:",
                scale: [
                    { name: "1", message: "Strongly Disagree" },
                    { name: "3", message: "Neutral" },
                    { name: "5", message: "Strongly Agree" },
                ],
                choices: [
                    {
                        name: "easeOfUse",
                        message: "The product was easy to use",
                    },
                    {
                        name: "features",
                        message: "The product had all the features I needed",
                    },
                ],
            },
        },
    },
}

Scale

npm.io

The scale component is a compact version of the Survey prompt, using a Likert Scale for quick feedback.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the scale results
messagerequiredMessage to display with the scale prompt
scalerequiredDefinition of the scale
choicesrequiredList of scale questions

Each item in the scale array should have:

fieldrequired/optionalDescription
namerequiredLabel for the scale point
messagerequiredExplanation text for the scale point

Each item in the choices array should have:

fieldrequired/optionalDescription
namerequiredIdentifier for the question
messagerequiredQuestion text
initialoptionalIndex of the initial value
{
    commands: {
        feedback: {
            handler: {
                component: "scale",
                name: "productRating",
                message: "Rate our product:",
                scale: [
                    { name: "1", message: "Poor" },
                    { name: "3", message: "Average" },
                    { name: "5", message: "Excellent" },
                ],
                choices: [
                    {
                        name: "overall",
                        message: "Overall satisfaction",
                        initial: 3,
                    },
                    { name: "support", message: "Customer support" },
                ],
            },
        },
    },
}

Select

npm.io

The select component prompts for selecting from a list of options.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the selection result
messagerequiredMessage to display with the selection prompt
choicesrequiredList of options to select from

The choices can be either an array of strings or an array of objects with name and value properties:

fieldrequired/optionalDescription
namerequiredDisplay text for the choice
valuerequiredValue to be returned if selected
{
    commands: {
        color: {
            handler: {
                component: "select",
                name: "favoriteColor",
                message: "Choose your favorite color:",
                choices: [
                    { name: "Red", value: "red" },
                    { name: "Blue", value: "blue" },
                    { name: "Green", value: "green" },
                ],
            },
        },
    },
}

Sort

npm.io

The sort component prompts for sorting items in a list.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the sorted result
messagerequiredMessage to display with the sorting prompt
choicesrequiredList of items to be sorted
{
    commands: {
        tasks: {
            handler: {
                component: "sort",
                name: "taskOrder",
                message: "Sort these tasks by priority:",
                choices: [
                    "Fix bugs",
                    "Implement new feature",
                    "Write documentation",
                    "Refactor code",
                ],
            },
        },
    },
}

Snippet

npm.io

The snippet component prompts for replacing placeholders in a snippet of code or text.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the completed snippet
messagerequiredMessage to display with the snippet prompt
fieldsrequiredList of fields to be filled in the snippet
templaterequiredString with placeholders marked as ${name}. Linebreaks in the template need to be indicated with newline characters \n. You can use online tools to do the conversion for you. Make sure to use single quotes if your template uses double quotes and vice versa.

Each item in the fields array should have:

fieldrequired/optionalDescription
namerequiredIdentifier for the field
messagerequiredPrompt message for the field
{
    commands: {
        generate: {
            handler: {
                component: "snippet",
                name: "package",
                message: "Fill out the fields in package.json",
                fields: [
                    { name: "name", message: "Name of the package" },
                    {
                        name: "description",
                        message: "Description of the package",
                    },
                    { name: "version", message: "Version of the package" },
                    { name: "username", message: "Your username" },
                    { name: "author_name", message: "Your name" },
                    {
                        name: "license",
                        message: "The license of the package",
                    },
                ],
                template: '{\n  "name": "${name}",\n  "description": "${description}",\n  "version": "${version}",\n  "homepage": "https://github.com/${username}/${name}",\n  "author": "${author_name} (https://github.com/${username})",\n  "repository": "${username}/${name}",\n  "license": "${license:ISC}"\n}\n',
            },
        },
    },
}

Toggle

npm.io

The toggle component prompts for toggling between two values.

fieldrequired/optionalDescription
namerequiredIdentifier for accessing the toggle result
messagerequiredMessage to display with the toggle prompt
enabledrequiredLabel for the enabled state
disabledrequiredLabel for the disabled state
{
    commands: {
        notifications: {
            handler: {
                component: "toggle",
                name: "notificationsEnabled",
                message: "Enable notifications?",
                enabled: "Yes",
                disabled: "No",
            },
        },
    },
}