A library for creating interactive command-line applications.
Warning
This library is a work-in-progress and is not yet ready for production use.
- Works in both interactive and non-interactive modes with a single configuration
- Fully typed with TypeScript, with automatic type inference for prompt results
- Support for text, select, multiselect, confirm, and async tasks
- Built-in support for flags such as
--help,--versionand--interactive
This library aims to simplify writing command line tools that can be used interactively by prompting users for input, as well as non-interactively by providing answers via CLI arguments.
Combining different tools for prompting and argument parsing leads to duplicated logic, and subtle differences which makes it hard to maintain. So the idea is to have a single API to support both use cases. The library reads CLI arguments and skips prompts for any valid answers provided via arguments, and prompts for the rest if in interactive mode.
Interactive mode can also be explicitly enabled or disabled by passing the --interactive or --no-interactive flags respectively.
npm install pigmentimport { create } from 'pigment';
const prompt = create(['<name>'], {
age: {
type: 'text',
description: 'Your age',
message: 'How old are you?',
required: true,
},
drink: {
type: 'select',
description: 'Favorite drink',
message: 'What is your favorite drink?',
choices: [
{ title: 'Coffee', value: 'coffee' },
{ title: 'Tea', value: 'tea' },
],
},
});
const answers = await prompt.show({
name: 'my-cli',
version: '1.0.0',
description: 'A CLI tool',
});
console.log(answers); // { name: string; age: string; drink: 'coffee' | 'tea' | undefined } | undefinedThe first parameter to create() allows you to define positional arguments:
const prompt = create(['<source>', '<destination>'], {
/* ... */
});The positional arguments support 2 types:
- Required:
<argument> - Optional:
[argument]
CLI usage:
my-cli source.txt dest.txtThe second parameter to create() is an object defining the questions:
const prompt = create(['<name>'], {
username: {
type: 'text',
description: 'Your username',
message: 'What is your username?',
required: true,
},
drink: {
type: 'select',
description: 'Favorite drink',
message: 'What is your favorite drink?',
choices: [
{ title: 'Coffee', value: 'coffee' },
{ title: 'Tea', value: 'tea' },
],
default: 'tea',
},
});When in interactive mode, users will be prompted for each question. In non-interactive mode, users can provide answers via CLI flags:
my-cli John --username john --drink coffeeIt's also possible to mix interactive and non-interactive modes by providing some answers via CLI flags and prompting for the rest.
The key of each question in the configuration object is used as the name. The answer will be available in the results object under a property with the same name. It's also used for the CLI flags, prefixed with -- (e.g., --username for the username question).
Each question object can have the following properties:
-
type: The type of the question (e.g.,text,select,multiselect,confirm). -
alias: A short flag alias for the question (e.g.,-ufor--username). -
description: A brief description of the question. This is used in the help text for the CLI arguments. -
message: The prompt message displayed to the user in interactive mode. -
required: Whether the question is mandatory. Iftrue, the prompt will not accept empty input in interactive mode, and in non-interactive mode, the user must provide a value via CLI arguments. Defaults tofalse. -
default: A default value for the question. It can be a static value or a function that returns a value or a promise containing a value:default: () => { const answers = prompt.read(); return answers.username?.toLowerCase(); };
It is used if no CLI argument is provided for the question, and as the initial value in interactive mode.
-
validate: Function to validate user input. It should returntrueif the input is valid,falseor a string with an error message if invalid:validate: (value) => { if (value.length < 3) { return 'Value must be at least 3 characters'; } return true; };
-
skip: A boolean or function that returns a boolean or a promise containing boolean to conditionally skip the question. It should returntrueto skip,falseotherwise:skip: async () => { const answers = prompt.read(); return answers.drink !== 'coffee'; };
If a question is skipped, the
requiredoption is ignored. If adefaultvalue is provided, it will be used as the answer.
The current answers can be read at any time using prompt.read(). This is useful for implementing dynamic logic.
const answers = prompt.read();Text questions to allow users to enter text in an input field:
{
username: {
type: 'text',
description: 'Username',
message: 'Enter your username',
}
}Usage in CLI arguments:
my-cli --username johnSingle-choice selection from a list of options.
{
drink: {
type: 'select',
description: 'Favorite drink',
message: 'Choose your drink',
choices: [
{ title: 'Coffee', value: 'coffee' },
{ title: 'Tea', value: 'tea' },
],
}
}The choices array contains objects with the following properties:
title: The display text for the option.description: (Optional) Additional description for the option.value: The value associated with the option that's used for the answer.skip: A boolean or function that returns a boolean or a promise containing boolean to conditionally hide the choice:
Usage in CLI arguments:
my-cli --drink coffeeMultiple-choice selection from a list of options.
{
fruits: {
type: 'multiselect',
description: 'Favorite fruits',
message: 'Select your favorite fruits',
choices: [
{ title: 'Apple', value: 'apple' },
{ title: 'Banana', value: 'banana' },
{ title: 'Orange', value: 'orange' },
],
}
}The choices array contains objects with the following properties:
title: The display text for the option.description: An optional description for the option.value: The value associated with the option that's used for the answer.skip: A boolean or function that returns a boolean or a promise containing boolean to conditionally hide the choice:
Usage in CLI arguments:
my-cli --fruits apple --fruits bananaAn empty array can be provided as:
my-cli --fruits=Confirmation questions for yes/no answers.
{
adult: {
type: 'confirm',
description: 'Age confirmation',
message: 'Are you over 18?',
}
}Usage in CLI arguments:
my-cli --adultThe argument can be prefixed with no- to indicate a negative response:
my-cli --no-adultAsynchronous task that performs an action. This is shown in both interactive and non-interactive modes.
It takes an async generator function which can yield a message to update the spinner text during execution.
{
data: {
type: 'task',
description: 'Load data',
message: 'Loading data...',
task: async function* () {
yield { message: 'Fetching from API...' };
const response = await fetch('https://api.example.com/data');
const data = await response.json();
yield { message: 'Processing...' };
// Process data...
return {
message: 'Data loaded successfully',
value: data,
};
},
}
}After creating the prompt with create(), you can show it using the show() method.
const result = await prompt.show({
name: 'my-cli',
description: 'A CLI tool',
version: '1.0.0',
});It takes an options object with the following properties:
name: The name of the CLI application. Used in the help text with the--helpflag.description: A brief description of the CLI application. Shown in the help text with the--helpflag.version: The version of the CLI application. Shown with the--versionflag.args: An array of strings representing the command-line arguments. Defaults toprocess.argv.slice(2).env: An object representing the environment variables to read variables such asCI,TERM, etc. Defaults toprocess.env.stdin: The readable stream to use for input. Defaults toprocess.stdin.stdout: The writable stream to use for output. Defaults toprocess.stdout.onExit: A callback function that is called when the prompt is exited (e.g., cancelled via Ctrl+C, or errored due to invalid arguments). Defaults to(code) => process.exit(code).
The result is a promise that resolves to an object containing the answers, or undefined if the prompt was not shown (e.g. when --help or --version is passed).
MIT