Files
suaveplan/packages/devtools/tooling/cli/README.md
2026-05-27 23:29:53 +01:00

14 KiB

@suaveplan/cli - Zero-dependency CLI toolkit: argument parser, command router, ANSI styling, output formatting, spinner, progress bar, interactive prompts

Summary

Enterprise-grade CLI framework for Node.js. Composable fluent API with subcommand routing, option parsing, OTel tracing, and a full set of terminal UI primitives.


Documentation tree

- [app](./src/app/app.md) — _(no submodules)_- [command](./src/command/command.md) — _(no submodules)_- [command](./src/command/command.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [format](./src/output/format/format.md) — _(no submodules)_- [format](./src/output/format/format.md) — _(no submodules)_- [table](./src/output/table/table.md) — _(no submodules)_- [table](./src/output/table/table.md) — _(no submodules)_- [parser](./src/parser/parser.md) — _(no submodules)_- [parser](./src/parser/parser.md) — _(no submodules)_- [progress](./src/progress/progress.md) — _(no submodules)_- [progress](./src/progress/progress.md) — _(no submodules)_- [prompt](./src/prompt/prompt.md) — _(no submodules)_- [prompt](./src/prompt/prompt.md) — _(no submodules)_- [spinner](./src/spinner/spinner.md) — _(no submodules)_- [spinner](./src/spinner/spinner.md) — _(no submodules)_- [style](./src/style/style.md) — _(no submodules)_- [style](./src/style/style.md) — _(no submodules)_- [app](./src/app/app.md) — _(no submodules)_- [command](./src/command/command.md) — _(no submodules)_- [command](./src/command/command.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [format](./src/output/format/format.md) — _(no submodules)_- [format](./src/output/format/format.md) — _(no submodules)_- [table](./src/output/table/table.md) — _(no submodules)_- [table](./src/output/table/table.md) — _(no submodules)_- [parser](./src/parser/parser.md) — _(no submodules)_- [parser](./src/parser/parser.md) — _(no submodules)_- [progress](./src/progress/progress.md) — _(no submodules)_- [progress](./src/progress/progress.md) — _(no submodules)_- [prompt](./src/prompt/prompt.md) — _(no submodules)_- [prompt](./src/prompt/prompt.md) — _(no submodules)_- [spinner](./src/spinner/spinner.md) — _(no submodules)_- [spinner](./src/spinner/spinner.md) — _(no submodules)_- [style](./src/style/style.md) — _(no submodules)_- [style](./src/style/style.md) — _(no submodules)_- [app](./src/app/app.md) — _(no submodules)_- [command](./src/command/command.md) — _(no submodules)_- [command](./src/command/command.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [format](./src/output/format/format.md) — _(no submodules)_- [format](./src/output/format/format.md) — _(no submodules)_- [table](./src/output/table/table.md) — _(no submodules)_- [table](./src/output/table/table.md) — _(no submodules)_- [parser](./src/parser/parser.md) — _(no submodules)_- [parser](./src/parser/parser.md) — _(no submodules)_- [progress](./src/progress/progress.md) — _(no submodules)_- [progress](./src/progress/progress.md) — _(no submodules)_- [prompt](./src/prompt/prompt.md) — _(no submodules)_- [prompt](./src/prompt/prompt.md) — _(no submodules)_- [spinner](./src/spinner/spinner.md) — _(no submodules)_- [spinner](./src/spinner/spinner.md) — _(no submodules)_- [style](./src/style/style.md) — _(no submodules)_- [style](./src/style/style.md) — _(no submodules)_- [app](./src/app/app.md) — _(no submodules)_- [command](./src/command/command.md) — _(no submodules)_ - [command](./src/command/command.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_ - [errors](./src/errors/errors.md) — _(no submodules)_- [format](./src/output/format/format.md) — _(no submodules)_ - [format](./src/output/format/format.md) — _(no submodules)_- [table](./src/output/table/table.md) — _(no submodules)_ - [table](./src/output/table/table.md) — _(no submodules)_- [parser](./src/parser/parser.md) — _(no submodules)_ - [parser](./src/parser/parser.md) — _(no submodules)_- [progress](./src/progress/progress.md) — _(no submodules)_ - [progress](./src/progress/progress.md) — _(no submodules)_- [prompt](./src/prompt/prompt.md) — _(no submodules)_ - [prompt](./src/prompt/prompt.md) — _(no submodules)_- [spinner](./src/spinner/spinner.md) — _(no submodules)_ - [spinner](./src/spinner/spinner.md) — _(no submodules)_- [style](./src/style/style.md) — _(no submodules)_ - [style](./src/style/style.md) — _(no submodules)_

Modules

Module Path Description
App / cli() app/app Application entry point and root command
Command command/command Subcommand builder with fluent API
Parser parser/parser Argv parser with options, positionals, and short flags
Errors errors/errors Stable error codes and CliError class
Style style/style ANSI color and formatting with chainable proxy styler
Format output/format/format Text formatters: key-value, list, tree, columns, heading
Table output/table/table ASCII/Unicode bordered table renderer
Spinner spinner/spinner TTY-aware animated spinner with AbortSignal
Progress progress/progress ASCII progress bar with TTY in-place update
Prompt prompt/prompt Interactive prompts: text, password, confirm, select, multiselect

Quick Start

import { cli } from "@suaveplan/cli";

const app = cli("myapp", "1.0.0");

app.description("My enterprise CLI tool");

app
  .command("build")
  .description("Build the project")
  .option("--output <dir>, -o", "output directory", "./dist")
  .option("--watch, -w", "watch for changes")
  .action(async (ctx) => {
    const { output, watch } = ctx.options;
    console.log(`Building to ${output}${watch ? " (watch mode)" : ""}...`);
  });

app
  .command("deploy")
  .description("Deploy to production")
  .argument("<env>", "target environment")
  .action(async (ctx) => {
    const env = ctx.args["env"];
    console.log(`Deploying to ${env}...`);
  });

await app.run();

App / cli() Factory

import { App, cli } from "@suaveplan/cli";

// Factory shorthand
const app = cli("myapp", "1.0.0");

// Or full constructor
const app = new App({
  name: "myapp",
  version: "1.0.0",
  description: "My tool",
  writer: process.stdout.write.bind(process.stdout),
  onError: (err) => { console.error(err); process.exit(1); },
});

app.action(async (ctx) => { /* root action */ });
await app.run(process.argv.slice(2));

Options: enableVersion (default true) and enableHelp (default true) control whether --version and --help flags are intercepted. Pass { signal: AbortSignal } to run() to support cancellation.


Commands

const cmd = app
  .command("serve")
  .description("Start the dev server")
  .alias("s")
  .option("--port <n>, -p", "port number", "3000")
  .argument("[dir]", "root directory")
  .example("myapp serve --port 8080 ./src")
  .action(async (ctx) => {
    const port = ctx.options["port"];
    const dir = ctx.args["dir"] ?? ".";
    console.log(`Serving ${dir} on port ${port}`);
  });

// Nest subcommands arbitrarily deep
const db = app.command("db").description("Database utilities");
db.command("migrate").action(async () => { /* ... */ });
db.command("seed").hidden().action(async () => { /* ... */ });

Terminal UI

Spinner

import { createSpinner } from "@suaveplan/cli";

const sp = createSpinner({ text: "Building…" });
sp.start();
// later…
sp.succeed("Build complete");
// or
sp.fail("Build failed");

Progress Bar

import { createProgress } from "@suaveplan/cli";

const bar = createProgress({ total: 100, label: "Compiling" });
bar.update(42);
bar.complete("Done");

Interactive Prompts

import { text, password, confirm, select, multiselect } from "@suaveplan/cli";

const name = await text("Your name?");
const pass = await password({ message: "Password?", validate: (v) => v.length >= 8 || "too short" });
const ok   = await confirm("Continue?", reader);
const env  = await select({ message: "Environment:", choices: ["dev", "staging", "prod"] });
const tags = await multiselect({ message: "Tags:", choices: ["a", "b", "c"], min: 1, max: 3 });

Style & Formatting

import { style, createStyler, heading, list, table } from "@suaveplan/cli";

console.log(style.bold.green("Success!"));
console.log(heading("Deploy Report", 1));
console.log(list(["Step 1", "Step 2", "Step 3"]));
console.log(table(["Name", "Status"], [["api", "ok"], ["db", "ok"]]));

Error Codes

All errors are CliError instances with stable string codes:

Code Meaning
cli.app.aborted Run cancelled via AbortSignal
cli.app.run_failed Uncaught error in action
cli.command.no_action Command has no action defined
cli.command.unknown_subcommand No matching subcommand found
cli.parse.unknown_option Unrecognised flag
cli.parse.missing_value Flag expects a value that wasn't provided
cli.parse.missing_argument Required positional argument missing
cli.parse.invalid_value Value not in allowed choices or wrong type
cli.parse.missing_required_option Required option not provided

OTel

Every App.run() and Command.execute() call is wrapped in an OpenTelemetry span. Pass a tracer via @opentelemetry/api — no-op tracer is used by default, so no configuration is required.