node-comfortv2.0.0

nc.cli

What you need for command-line tools: typed arguments with a generated --help, prompts, arrow-key menus, spinners, progress bars, tables and boxes. When nobody's at the keyboard (CI, pipes), prompts read plain lines and animations print only their final state.

const { cli } = require("@ix-xs/node-comfort");
import { table } from "@ix-xs/node-comfort/cli";
const { flags } = cli.args({
  port: { type: "number", short: "p", default: 3000, description: "Port to listen on" },
});
const name = await cli.prompt("Project name?", { default: "my-app" });
const spin = cli.spinner("Installing").start();
spin.succeed("Installed");
GuideExplanations and examples for nc.cli.
Read the guide →

Functions

nc.cli.table()

table(rows: Record<string, unknown>[] | unknown[][], options?: TableOptions): string

Draws a table from an array of objects (keys become columns) or an array of arrays. Colors, emoji and CJK characters line up correctly.

Parameters

NameType
rowsArray<Record<string, unknown>> | unknown[][]
optionsoptionalTableOptions

Returns string

Example

console.log(cli.table([
  { name: "Ada", role: "admin", logins: 42 },
  { name: "Bob", role: "user", logins: 7 },
]));
// ╭──────┬───────┬────────╮
// │ name │ role  │ logins │
// ├──────┼───────┼────────┤
// │ Ada  │ admin │     42 │
// │ Bob  │ user  │      7 │
// ╰──────┴───────┴────────╯

nc.cli.box()

box(text: string, options?: BoxOptions): string

Draws a box around text.

Parameters

NameType
textstring
optionsoptionalBoxOptions

Returns string

Example

console.log(cli.box("Server ready\nhttp://localhost:3000", { title: "my-app", borderColor: "green" }));
// ╭─ my-app ──────────────────╮
// │  Server ready             │
// │  http://localhost:3000    │
// ╰───────────────────────────╯

nc.cli.spinner()

spinner(text?: string, options?: SpinnerOptions): Spinner

A spinner for work in progress. Call .start(), then finish with .succeed(), .fail(), .warn(), .info() or .stop().

Parameters

NameTypeDescription
textoptionalstringDefault: ""
optionsoptionalSpinnerOptions

Returns Spinner: Not started yet.

Example

const spin = cli.spinner("Downloading").start();
try {
  await download();
  spin.succeed("Downloaded 12 files");
} catch (error) {
  spin.fail(`Download failed: ${error.message}`);
}

nc.cli.progress()

progress(options: ProgressOptions): ProgressBar

A progress bar with percentage, ETA and speed. It redraws at most every 50 ms, so calling tick() in a tight loop is fine.

Parameters

NameType
optionsProgressOptions

Returns ProgressBar

Example

const bar = cli.progress({ total: files.length, format: "{bar} {percent}% {label}" });
for (const file of files) {
  await upload(file);
  bar.tick(1, file.name);
}
bar.stop();

nc.cli.prompt()

prompt(question: string, options?: PromptOptions): Promise<string>

Asks a question and returns the answer. With validate, it keeps asking until the answer is valid.

Parameters

NameType
questionstring
optionsoptionalPromptOptions

Returns Promise<string>: Trimmed.

Example

const name = await cli.prompt("Project name?", { default: "my-app" });
const age = await cli.prompt("Age?", {
  validate: (answer) => /^\d+$/.test(answer) || "Please enter a number",
});

nc.cli.password()

password(question: string, options?: { mask?: string; input?: ReadStream; output?: WriteStream; } | undefined): Promise<string>

Asks for a secret without showing it; each character appears as *.

Parameters

NameTypeDescription
questionstring
optionsoptional{ mask?: string, input?: NodeJS.ReadStream, output?: NodeJS.WriteStream }mask: "" shows nothing at all.

Returns Promise<string>

Example

const token = await cli.password("API token?");

nc.cli.confirm()

confirm(question: string, options?: ConfirmOptions): Promise<boolean>

Asks a yes/no question. Understands y, yes, o, oui, true, 1 and their opposites; an empty answer gives the default.

Parameters

NameType
questionstring
optionsoptionalConfirmOptions

Returns Promise<boolean>

Example

if (await cli.confirm("Delete 42 files?")) await cleanup();

nc.cli.select()

select<V = string>(question: string, choices: Choice<V>[], options?: SelectOptions): Promise<V>

Lets the user pick one choice with the arrow keys and Enter. Without an interactive terminal, it prints a numbered list and reads a number.

Parameters

NameType
questionstring
choicesArray<Choice<V>>
optionsoptionalSelectOptions

Returns Promise<V>

Example

const framework = await cli.select("Framework?", ["Express", "Fastify", "Koa"]);
const plan = await cli.select("Plan?", [
  { label: "Free", value: "free", hint: "0 €" },
  { label: "Pro", value: "pro", hint: "9 €/month" },
]);

nc.cli.multiselect()

multiselect<V = string>(question: string, choices: Choice<V>[], options?: SelectOptions): Promise<V[]>

Lets the user pick several choices: arrows to move, Space to toggle, a for all, Enter to confirm.

Parameters

NameType
questionstring
choicesArray<Choice<V>>
optionsoptionalSelectOptions

Returns Promise<V[]>: In list order.

Example

const features = await cli.multiselect("Features?", ["TypeScript", "ESLint", "Tests", "Docker"], { min: 1 });

nc.cli.args()

args<S extends Record<string, FlagSpec>>(schema: S, options?: ArgsOptions): ParsedArgs<S>

Parses command-line arguments from a schema, and types the result: flags.port is a number. Handles short aliases, defaults, required flags, allowed values, repeated flags and --no-flag, and generates --help. A typo gets a "Did you mean...?" suggestion.

Parameters

NameType
schemaS
optionsoptionalArgsOptions

Returns ParsedArgs<S>

Throws TypeError For unknown flags, bad numbers, missing required flags or values not in choices. The message is ready to show.

Example

// node server.js --port 8080 -v --tag api --tag web public/
const { flags, positionals } = cli.args({
  port: { type: "number", short: "p", default: 3000, description: "Port to listen on" },
  verbose: { type: "boolean", short: "v" },
  tag: { type: "string", multiple: true },
  env: { type: "string", choices: ["dev", "prod"], default: "dev" },
}, { description: "Start the web server", version: "1.0.0" });
// flags: { port: 8080, verbose: true, tag: ["api", "web"], env: "dev" }

nc.cli.isInteractive()

isInteractive(): boolean

Is someone at the keyboard? True when stdin and stdout are terminals and we're not in CI.

Returns boolean

Example

const name = cli.isInteractive() ? await cli.prompt("Name?") : "default";

nc.cli.size()

size(): { columns: number; rows: number; }

The terminal size, or 80x24 when it's unknown.

Returns { columns: number, rows: number }

nc.cli.clear()

clear(): void

Clears the screen. Does nothing when stdout isn't a terminal.

Types

Import any of them in TypeScript with import type { ArgsOptions } from "@ix-xs/node-comfort", or in JavaScript with import("@ix-xs/node-comfort").ArgsOptions.

ArgsOptions

Options for args().

PropertyTypeDescription
argvoptionalstring[]What to parse. Defaults to process.argv.slice(2).
nameoptionalstringProgram name in the help. Defaults to the script name.
descriptionoptionalstringShown at the top of the help.
usageoptionalstringA usage line, like "<file> [options]".
helpoptionalbooleanAnswer --help and -h by printing the help and exiting. Defaults to true.
versionoptionalstringAnswer --version with this version.
strictoptionalbooleanReject unknown flags, with a suggestion. Defaults to true.

BorderStyle

Border styles for table() and box().

type BorderStyle = "rounded" | "single" | "double" | "heavy" | "ascii" | "none"

BoxOptions

Options for box().

PropertyTypeDescription
titleoptionalstringShown in the top border.
borderoptionalBorderStyleDefaults to "rounded".
paddingoptionalnumber | { x?: number; y?: number; }Space around the text. Defaults to { x: 2, y: 0 }.
alignoptional"left" | "center" | "right"Defaults to "left".
borderColoroptionalstringA style name like "green", or a hex code.
widthoptionalnumberFixed inner width. Defaults to the longest line.
colorsoptionalbooleanAutomatic by default.

Choice

A choice for select() and multiselect(): a string, or a label with a value of any type.

type Choice = string | { label: string; value: V; hint?: string; disabled?: boolean; }

ConfirmOptions

Options for confirm().

PropertyTypeDescription
defaultoptionalbooleanThe answer when the user just presses Enter. Defaults to false.
inputoptionalReadableStreamDefaults to stdin.
outputoptionalWritableStreamDefaults to stdout.

FlagSpec

A flag for args().

PropertyTypeDescription
type"string" | "number" | "boolean"Numbers are checked. Booleans also accept --no-name.
shortoptionalstringOne-letter alias, like "p" for -p.
defaultoptionalunknownUsed when the flag is missing.
multipleoptionalbooleanCan be repeated (--tag a --tag b); you get an array.
requiredoptionalbooleanFail if it's missing.
choicesoptionalreadonly string[]Allowed values, for string flags.
descriptionoptionalstringShown in --help.

FlagValue

The type of a flag's value.

type FlagValue = F["type"] extends "number" ? number : F["type"] extends "boolean" ? boolean : F extends { choices: readonly (infer C)[]; } ? C : string

LineReader

One line reader per input stream, shared by every prompt, so piped lines that arrive early are queued instead of lost.

PropertyTypeDescription
rlInterface
linesstring[]
waiting{ resolve: (line: string) => void; reject: (error: Error) => void; }[]
closedboolean

ParsedArgs

What args() returns.

PropertyTypeDescription
flagsParsedFlags<S>
positionalsstring[]Arguments that aren't flags.
help() => stringThe generated help text.

ParsedFlags

The parsed flags, typed from the schema.

type ParsedFlags = { [K in keyof S]: S[K] extends { multiple: true; } ? Array<FlagValue<S[K]>> : S[K] extends { default: any; } | { required: true; } | { type: "boolean"; } ? FlagValue<S[K]> : FlagValue<S[K]> | undefined; }

ProgressBar

A progress bar.

PropertyTypeDescription
tick(amount?: number, label?: string) => ProgressBarAdds to the value, 1 by default.
update(value: number, label?: string) => ProgressBarSets the value.
stop() => ProgressBarDraws the final state and frees the line.
valuenumber
totalnumberYou can change it along the way.
percentnumberFrom 0 to 100.

ProgressOptions

Options for progress().

PropertyTypeDescription
totalnumberThe value that means 100%.
widthoptionalnumberBar width in characters. Defaults to 30.
formatoptionalstringCan use {bar}, {percent}, {value}, {total}, {eta}, {elapsed}, {rate} and {label}.
completeoptionalstringFilled character. Defaults to "█".
incompleteoptionalstringEmpty character. Defaults to "░".
labeloptionalstringText for {label}.
streamoptionalWriteStreamDefaults to stderr.

PromptOptions

Options for prompt().

PropertyTypeDescription
defaultoptionalstringUsed when the answer is empty.
validateoptional((answer: string) => string | true | Promise<string | true>)Return true to accept, or a message to ask again.
inputoptionalReadableStreamDefaults to stdin.
outputoptionalWritableStreamDefaults to stdout.

SelectOptions

Options for select() and multiselect().

PropertyTypeDescription
defaultoptionalnumberIndex of the choice highlighted at first. Defaults to 0.
pageSizeoptionalnumberHow many choices are visible at once. Defaults to 10.
minoptionalnumbermultiselect() only: minimum number of choices.
inputoptionalReadStreamDefaults to stdin.
outputoptionalWriteStreamDefaults to stdout.

Spinner

A spinner. Every method returns it.

PropertyTypeDescription
start(text?: string) => SpinnerStarts spinning, optionally with new text.
stop() => SpinnerStops and clears the line.
succeed(text?: string) => SpinnerStops with a green .
fail(text?: string) => SpinnerStops with a red .
warn(text?: string) => SpinnerStops with a yellow .
info(text?: string) => SpinnerStops with a blue .
textstringThe text next to the spinner. Change it any time.
isSpinningboolean

SpinnerOptions

Options for spinner().

PropertyTypeDescription
framesoptionalstring[]Animation frames. Defaults to braille dots.
intervaloptionalnumberMilliseconds per frame. Defaults to 80.
coloroptionalstringA style name or hex code. Defaults to "cyan".
streamoptionalWriteStreamDefaults to stderr, which keeps stdout clean for data.

TableColumn

A column of table().

PropertyTypeDescription
keystring | numberProperty name for object rows, index for array rows.
headeroptionalstringDefaults to the key.
alignoptional"left" | "center" | "right"Defaults to right for numbers, left otherwise.
maxWidthoptionalnumberLonger values are cut with .
formatoptional((value: unknown, row: any) => unknown)Changes the value before it's shown.

TableOptions

Options for table().

PropertyTypeDescription
columnsoptional(string | TableColumn)[]Which columns, in which order. Defaults to every key found.
borderoptionalBorderStyle | "markdown""markdown" gives a Markdown table. Defaults to "rounded".
headeroptionalbooleanShow the header row. Defaults to true.
colorsoptionalbooleanBold header and faint borders. Automatic by default.
paddingoptionalnumberSpaces around cell content. Defaults to 1.
node-comfort v2.0.0View the source